Autolayout Contraints for a View from Xib

Is it possible to set constraints for root view in XIB?

OK, found one way to do this...

Haven't done any testing, so I'm not sure if the constraints are really set correctly or will be used by auto-layout at run-time, but this will put them there.

Create a new "View" XIB - this is how it looks to start:

Sample Image

Add a Visual Effect view to this view, and give it width and height constraints of 230 / 230:

Sample Image

Now, drag that Visual Effects view *out of the plain view, onto an empty space:

Sample Image

You will see it maintains its constraints. Then delete the original view, and add the other elements:

Sample Image

How to make autolayout work with custom UIView on XIB?

How I structured my custom UIView: My XIB's Files Owner is ItemView. ItemView has an @IBOutlet var: UIView! that's connected to the view in the XIB. Then I have:

init()
{
super.init(frame: .zero)

self.initView()
}

required init?(coder aDecoder: NSCoder)
{
super.init(coder: aDecoder)

self.initView()
}

private func initView()
{
Bundle.main.loadNibNamed("ItemView", owner: self, options: nil)
self.view.frame = CGRect(x: 0.0, y: 0.0, width: self.width, height: self.height)
self.addSubview(self.view!)
}

(I seems this extra UIView is needed when creating a custom UIView with a XIB. Would love to hear if it's not needed after all.)

To make it work I needed to call self.invalidateIntrinsicContentSize() after updating the label. And override intrinsicContentSize:

override open var intrinsicContentSize: CGSize
{
return self.view.systemLayoutSizeFitting(self.bounds.size)
}

Note that I'm calling this on self.view, not on self.

Thanks @matt for pointing me in the right direction. Was the first time I've encountered something like this.

The following things I tried we all not necessary:

override class var requiresConstraintBasedLayout: Bool
{
return true
}

Call setNeedsLayout() after setting the label text.

Call myLabel.translatesAutoresizingMaskIntoConstraints = false.

Call self.translatesAutoresizingMaskIntoConstraints = false.

And call self.setNeedsUpdateConstraints() in both init()s.

Auto-layout XIB embedded in programmatic UIView not sizing to parent

The reason it doesn't resize itself to fill its new superview is that you haven't arranged for it to do that. In short, you need to add constraints, in code, to relate the two views after adding one to the other.

Most likely, IB set the view to translate its autoresizing mask to constraints. What its autoresizing mask is can be hard to determine from the NIB when it's set to use auto layout.

Anyway, when you add it to the superview, the automatically generated constraints maintain its current frame. When the superview resizes, they resize it according to the autoresizing mask (probably allowing width and height to vary but not the distance to the superview edges).

When you're using auto layout, you should do the following:

  • Either turn off translatesAutoresizingMaskIntoConstraints in the NIB or turn it off in the code which adds it to the superview. Which of those approaches you take depends on whether you anticipate that the view will ever be added as a subview of a framework view class that manages the layout of its subviews. If you might, then you should leave it on in the NIB and let that class decide whether to turn it off.

  • After adding the view to its superview, add constraints to the superview which controls where the view should be laid out.

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

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.



Related Topics



Leave a reply



Submit