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:
Add a Visual Effect view to this view, and give it width and height constraints of 230 / 230:
Now, drag that Visual Effects view *out of the plain view, onto an empty space:
You will see it maintains its constraints. Then delete the original view, and add the other elements:
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:
Create your CustomView as a subclass of UIStackView as in the sample code above
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.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
How to Tell If a Node Is on the Screen Spritekit Swift
Set the Size and Position of All Windows on the Screen in Swift
Swift String Interpolation Displaying Optional
Error: Use of Unresolved Identifier 'Process'
How to Convert Between Related Types Through a Common Initializer
Division Not Working Properly in Swift
Swift Combine: How to Create a Single Publisher from a List of Publishers
Adding a Case to an Existing Enum with a Protocol
Cast a Swift Struct to Unsafemutablepointer<Void>
How to Limit the Movement of Two Anchored Lines So They Swing Continually Like a Pendulum
Background Request Not Execute Alamofire Swift
App Delegate Accessing Environment Object
Contextual Type for Closure Argument List Expects 1 Argument, But 4 Were Specified
Swiftyjson - Call Can Throw, But It Is Marked with 'Try' and the Error Is Not Handled