How To: Self-Sizing Custom-View from XIB with StackView View in iOS

前端 未结 1 1229
佛祖请我去吃肉
佛祖请我去吃肉 2021-02-05 03:22

I\'ve recently started diving into iOS development with Swift again and am now looking into a custom UIControl. For layout and flexibility this custom view is built using a Stac

1条回答
  •  醉梦人生
    2021-02-05 03:46

    Here are some answers to your questions, although in a different order:

    (5) And why do I have to add the constraints in code?

    Assuming I disable translatesAutoresizingMaskIntoConstraints then I can't set constraints like for tableview cells in IB? It'll always translate into something like label.top = top and thus the label would be stretched instead of the StackView inheriting the size/height. Also I can't really pin the StackView to it's container view in the IB for the XIB file.

    You have to add the constraints in code, in this example, because you are loading an arbitrary view from a nib, and then adding it to a your coded CustomView. The view in the nib has no pre-existing knowledge of what context or hierarchy it will be placed into, so it can't possible defined ahead of time how to constrain itself to an unknown future superview. In the case of the StackView on the storyboard, or inside a prototype cell, that context is known as well as its parent view.

    One nice alternative you can consider for this case, if you don't want to manually write constraints, is to make your CustomView subclass UIStackView instead of UIView. Since UIStackView generates appropriate constraints automatically, it will save you the manual coding. See next answer for some sample code.

    (3) Am I even loading the XIB in the "proposed" way ?

    Or is there a better way, e.g. by using UINib.instantiate? I really couldn't find a best-practice way to do that.

    I'm not aware of a "standard" or "right" way to handle loading a nib into a custom view. I've seen a variety of approaches that all work. However, I will note that you are doing more work than needed in your sample code. Here is a more minimal way of accomplishing the same outcome, but also using a UIStackView subclass instead of a UIView subclass (as mentioned above):

    class CustomView: UIStackView {
        required init(coder: NSCoder) {
            super.init(coder: coder)
            distribution = .fill
            alignment = .fill
            if let nibView = Bundle.main.loadNibNamed("CustomView", owner: self, options: nil)?.first as? UIView {
                addArrangedSubview(nibView)
            }
        }
    }
    

    Note that because CustomView is now a subclass of UIStackView, it will create the constraints needed for any subviews automatically. In this case, because distribution and alignment are both set to .fill, it will pin the edges o the subview to its own edges, as you want.

    (4) Why do I have to do this in the first place?

    Really, why? I only moved the StackView into a CustomView. So why does the StackView stop adapting itself now, why do I have to set the top and bottom constraints?

    Actually, the key issue here is your StackView's alignment property of .center. That is saying that the labels in StackView will be centered vertically, but it does not constrain them to the top or the bottom of the StackView. It's just like adding a centerY constraint to a subview inside a view. You can set the outer view to any height you want, and the subview will be centered, but it does not "push out" the top and bottom of the outer view, because center constraints only constrain centers, not top and bottom edges.

    In your storyboard version of the StackView, the view hierarchy above that StackView is known, and there are also options to generate constraints automatically from resizing masks. Either way, the final StackView heigh is known to UIKit and the labels will be centered vertically within that height, but will not actually effect it. You need to either set the alignment of your StackView to be .fill, or add additional constraints from the tops and bottoms of the labels to the top and bottom of the StackView in order for auto layout to determine how tall the StackView should be.

    (2) Or should I override intrinsicContentSize ?

    This wouldn't help much, because the intrinsic content size by itself won't push out the edges of a parent view unless the edges of the subview are also constrained to the edges of the parent view. In any event, you will get all the correct intrinsic content size behavior you need in this case automatically, so long as you address the .center alignment issue mentioned above.

    (1) Should I set constraints and disable translatesAutoresizingMaskIntoConstraints ?

    This would normally be a valid approach. If you subclass UIStackView as I describe above, though, you don't need to do either.

    In summary

    If you:

    1. Create your CustomView as a subclass of UIStackView as in the sample code above

    2. Create a view on your storyboard set to your custom class CustomView. If you want the CustomView to self-size correctly, you need to give it constraints and not use auto-generated constraints from the resizing mask.

    3. Change the StackView in your nib to either have the alignment set to .fill or to have additional constraints between the top and bottom of at least one of the labels (most likely the large center one) and the top and bottom of the stack view

    Then auto layout should work properly and your custom view with the StackView should layout as expected.

    0 讨论(0)
提交回复
热议问题