FlexLayout icon indicating copy to clipboard operation
FlexLayout copied to clipboard

Draft for generic support

Open foyoodo opened this issue 1 year ago • 3 comments

Hi, I wanted to ask if there are any plans to add generic support to FlexLayout. This would allow for more flexible usage, such as the following code example:

extension Flex where Base: UILabel {

    var text: String? {
        get { base?.text }
        set { base?.text = newValue; markDirty() }
    }
}

I’ve made some changes to the code, and I’m submitting this as a draft for review. Let me know if this is something that could be considered.

Thanks!

foyoodo avatar Sep 17 '24 09:09 foyoodo

You could use this to enable auto markDirty. :)

extension UIView {

    static func enableFlexAutoMarkDirty() {
        swizzle(
            classType: UIView.self,
            origin: #selector(UIView.invalidateIntrinsicContentSize),
            swizzled: #selector(UIView.flex_invalidateIntrinsicContentSize)
        )

        // UILabel().invalidateIntrinsicContentSize doesn't call super in some iOS versions
        if class_getInstanceMethod(UILabel.self, #selector(UILabel.invalidateIntrinsicContentSize))
            != class_getInstanceMethod(UIView.self, #selector(UIView.invalidateIntrinsicContentSize)) {
            swizzle(
                classType: UILabel.self,
                origin: #selector(UILabel.invalidateIntrinsicContentSize),
                swizzled: #selector(UILabel.flex_uilabel_invalidateIntrinsicContentSize)
            )
        }

        // UITextView().invalidateIntrinsicContentSize doesn't call super in some iOS versions
        if class_getInstanceMethod(UITextView.self, #selector(UITextView.invalidateIntrinsicContentSize))
            != class_getInstanceMethod(UIView.self, #selector(UIView.invalidateIntrinsicContentSize)) {
            swizzle(
                classType: UITextView.self,
                origin: #selector(UITextView.invalidateIntrinsicContentSize),
                swizzled: #selector(UITextView.flex_uitextview_invalidateIntrinsicContentSize)
            )
        }
    }
}

private extension UIView {

    @objc
    func flex_invalidateIntrinsicContentSize() {
        flex_invalidateIntrinsicContentSize()

        if isFlexEnabled {
            flex.markDirty()
        }
    }
}

private extension UILabel {

    @objc
    func flex_uilabel_invalidateIntrinsicContentSize() {
        flex_uilabel_invalidateIntrinsicContentSize()

        if isFlexEnabled {
            flex.markDirty()
        }
    }
}

private extension UITextView {

    @objc
    func flex_uitextview_invalidateIntrinsicContentSize() {
        flex_uitextview_invalidateIntrinsicContentSize()

        if isFlexEnabled {
            flex.markDirty()
        }
    }
}

nuomi1 avatar Dec 15 '24 03:12 nuomi1

You could use this to enable auto markDirty. :)

Hi @nuomi1 👋 Thanks a lot for the response and code snippet. I believe this approach is effective, but could it potentially result in unnecessary markDirty calls, triggering a view relayout? For instance, when updating a grow-width UILabel that already has enough space to accommodate the updated text?

However, I still believe that adding generic support could offer better extensibility. For example, consider the following code - and yes, it might seem unusual, but I have indeed written similar code before :)

extension Flex where Base: UIVisualEffectView {
    func addItem<Item>(_ view: Item) -> Flex<Item> {
        if let host = base {
            return host.contentView.flex.addItem(view)
        } else {
            preconditionFailure("Trying to modify deallocated host view")
        }
    }
}

foyoodo avatar Dec 15 '24 05:12 foyoodo

Great work! Thank you.

While reviewing the implementation, I was wondering whether using generics here brings significant benefits, given that FlexLayoutCompatible enforces inheritance from UIView. Since every Base is essentially a UIView, and the modified Flex object uses weak var base: Base? but ultimately consumes UIView, it seems like we could just pass UIView directly without generic.

As it stands, it feels like the current implementation mainly exists to enable generic extensions. If we need subclass-specific behavior, it looks like we can simply access the view directly and handle it there.

Your proposed structure:

extension Flex where Base: UILabel {
    var text: String? {
        get { base?.text }
        set { base?.text = newValue; markDirty() }
    }
}

label.flex.text = "hello"

What’s currently possible in our project:

class FlexLabel: UILabel {
  override var text: String? {
    get { super.text }
    set { super.text = newValue; flex.markDirty() }
  }
}

label.text = "hello"

extension UILabel {
  func setTextAndMarkDirty(_ text: String?) {
     self.text = text
     self.flex.markDirty()
  }
}

label.setTextAndMarkDirty("hello")

How about introducing the generic approach when FlexLayout supports platforms beyond UIKit, or when there’s more feedback?

heoblitz avatar Jun 23 '25 12:06 heoblitz