I've recently taken up trying to learn how to create iOS applications completely programatically. And due to this, one of the first roadblocks I've encountered is that translatesAutoresizingMaskIntoConstraints must usually be set to false in order to set constraints.
After doing some research, there are three things that I am pretty sure of.
AutoresizingMasks are how dynamic layouts (layouts that differ based on screen size/orientation) were achieved prior to the introduction of the auto-layout system.
AutoresizingMasks are a value that tell a view's superview how to resize it when the superview's bounds change.
translatesAutoresizingMaskIntoConstraints is a boolean value that when set to true (and is always defaulted to true for code-created UIViews), tells the UIKit framework to create constraints that replicate the behavior of the AutoresizingMask property within the auto-layout system.
What I don't understand is how exactly these constraints are implemented. Apple states in their documentation that "the system creates a set of constraints that duplicate the behavior specified by the view’s autoresizing mask. This also lets you modify the view’s size and location using the view’s frame, bounds, or center properties..."
This is the part that confuses me, and I'm not sure if it's just their wording or my misunderstanding of the topic. The AutoresizingMask is just a value, so it doesn't make sense to be able to create constraints off of it. Do they mean that the automatically generated constraints are actually based off the child view's frame (A.K.A. childView.topAnchor = superview.topAnchor)? and that the AutoresizingMask's value just determines which of these constraints based on the frame gets set?
For example: UIView with frame of (x:0, y:0, width:50, height:50) and AutoresizingMask of "FlexibleBottomMargin". Does UIKit just automatically create constraints that place the view where a frame-based layout would have, and then leave out certain constraints (in this case the bottom) to replicate the mask's behavior?
You are correct that the autoresizingMask is interpreted (along with the frame) to determine which constraints to create.
In the example you give the system would create the following constraints -
Width constraint of 50
Height constraint of 50
Leading, trailing and top constraints to the nearest neighbours, with the fixed distance to those neighbours
Greater than or equal bottom constraint to the nearest neighbour with the distance to that neighbour.
This would result in a 50x50 view that was fixed horizontally and at the top and where the space between the bottom and its neighbour can grow as required.
Related
Lets say I have set a constraint for a label during design time and does not have any reference to those constraints to modify later. Lets assume that I set x and y position constraints during design time. When this control is being rendered, what if I set a different value for x and y by using frame.origin.x and frame.origin.y. Does this override the constraint I set in design time or constraint will win over this?
I don't really get what your understanding of a "design time constraint" is but here's the general mechanism how constraints work (and the behavior is all about your view's autoresizing mask setting):
translatesAutoresizingMaskIntoConstraints == false
⇒ Setting your views frame does not have any effect as it will be overridden by your constraints in the next layout pass.
translatesAutoresizingMaskIntoConstraints == true
⇒ Setting your view's frame adds several constraints behind the scenes the enforce that the view has the frame's size and position. However, it's important to know that in this case, you should not add any other constraints to the view as they will conflict with the "frame constraints".
If you want to understand the details, check out ▶️ this talk.
I also published an article on Medium not to long ago with very granular explanations on the autoresizing mask that is probably very relevant to your question (scroll to section Deactivating Auto Layout).
Note:
There is a way to have constraints only affect your layout at design time. You simply select the constraint in Interface Builder and check the Placeholder checkbox.
But from what I can tell it's not what you are talking about.
Help me understand:
When you place any view in the interface builder, Xcode won't complain about missing constraints or ambiguous rules. Only once you set some constraints (but not all to remove every ambiguity), Xcode will notify you of missing constraints and suggest some.
For example: I set a constraint to a view, that centers it horizontally in the superview. Now Xcode complains about a missing rule for the Y position.
Why doesn't it just infer that from the current X-Postition as it does, when you don't have any constraints in place?
That's because before adding constraints to the view, Xcode will see it as if you want the view to be at the exact X,Y position. That means no matter what size the container is, the view will always be at the exact coordinate and have the same size.
However, after adding a few constraints, it means that you want the view to change its size or position according to the container size (which is what autolayout is for), so the constraints you added must provide sufficient information for Xcode to determine its frame.
For example, if you add only Horizontally in Container constraint without specifying its size or Y coordinate, Xcode can't tell where you want the view to be placed. That's why you're getting the warnings.
As for the example you mentioned, if you set the view to be centered horizontally in container, as the width of the container gets larger, Xcode can't tell which one you would prefer:
stay at the same X position and increase the width of the view.
preserve the size and increase X.
Increasing the height of the container will also face a similar problem.
Hope the explanation helps :)
Until you start using constraints, Xcode doesn't know that constraints will be used on that view so it won't show any error. But once a constraint has been added it knows to start applying autoLayout. Xcode now needs to know the width, height, x and y position of that particular view from the constraints. It can either infer them based on the constraints applied on the other views or you can explicitly define them.
Additionally you can use an option that allows Xcode to apply the constraints that it think should be present. But they aren't always what you want. Look in the image below. (The add missing constraints button)
In your example, you applied a constraint specifying where it should be in terms of X-axis but not the Y-Axis. To infer constraints in this case you need to have other views that will have constraints applied on them and this view should have other constraints with respect to these other views that allow it infer its position. In your case, this happens as there is no relation between the X-Axis and Y-Axis. Simply specifying the X coordinate cannot let autoLayout figure out the Y-Coordinate.
Also, for a UIView even if you center it horizontally and vertically, even then it will show an error telling you to define a width and height provided their is no content inside the view
In reading through the Apple documentation, I find references to autoresizing, AutoLayout, and constraints. What's the difference between using all of these in code? What is the correct way to apply these techniques programmatically in iOS 9?
There are really just two things here:
Autoresizing
AutoLayout
Autoresizing is basically a collective term for the old way Apple introduced in order to enable developers to build dynamic layouts. The number one usecase to address here was screen rotation. Since when a screen would be rotated (or otherwise resized), the subviews in the screen would most likely hold an incorrect frame (position and size) in the newly sized superview. To address this, Apple introduced a series of enumerable properties (called Autoresizing Masks), that tell the superview to treat a subview in a particular way. Among these are:
Flexible Width/Height, which causes a subview to expand to the fullest width/height available
Flexible Leading/Trailing/Top/Bottom space, which allows a specific edge to be variable, and so forth.
A view could contain any combination of these enum properties.
This was inadequate because, among other shortcomings, it lays down no rules regarding how a view should be layouted (if that's a verb) with respect to its other sibling views. It also required a lot of extra coding to manually resize views on orientation changes.
Here's where AutoLayout entered the picture. Apple built a framework which worked on the basis of constraints - rules that could be applied on views and between views, that would determine how a view would be sized in variable screen sizes. These constraints are structured in a class called NSLayoutConstraint, and each instance (constraint) of this class has the following important properties:
The item (view) on which the constraint is applied
The property of the view (height, width, leading edge, trailing edge, and so on) that the constraint is applicable to
The second item (a sibling or a child or a parent view) to which the constraint is related
The second item's attribute
The multiplier on the constraint: useful in order to specify ratio based constraints
A value (or constant) of the constraint: interestingly, the only property of a constraint that can be changed after instantiation.
A simple example of an NSLayoutConstraint, stated prosaically is: a view's width will be half the the width of its superview multiplied by 60%.
Your AutoLayout based UI would consist of many such constraints, which will all work together to express an unambiguous and non-conflicting UI Layout.
Now the AutoLayout engine, which makes it all work, interacts with views on the screen, calling AutoLayout messages such as layoutSubviews whenever needed in order automatically resize (layout) views whenever changes occur on screen, such as orientation change, a superview getting resized etc.
Constraints are most commonly added by InterfaceBuilder (.xib and .storyboard files), but adding them by code entails the same principle: create an instance of NSLayoutConstraint and add it to the highest view applicable (for eg., if there's a constraint between a child and a parent view, the constraint should be added to the parent view. If there's a constraint between two subviews, again, add it to the parent.)
Apple's AutoLayout guides, API documentation and introductory WWDC videos on AutoLayout are excellent, and those would be the best places to learn more.
In mechanical CAD software which uses concepts similar to autolayout constraints, you can often add a 'derived constraint'. This has no effect on the layout, but will allow you to directly read the value of an important dimension.
For example, consider the following layout for a view with two subviews:
32 64
|------| |------|
|-[imgOne]-[imgTwo]-|
|-------------------|
w
The width of the view is the sum of the default edge spacing on the left and right, the default spacing between image views, and the two width constraints (32 and 64) applied to the image views.
I would like to know w at runtime.
In theory, this should be view.bounds. However, it's not always safe to read that property (as the view may not have updated its layout constraints yet).
Is it possible to add a constraint which has no effect on the view size, but which will have its .constant property updated once the layout is complete?
(I have tried adding a width constraint to the view with a priority of 1, but .constant always reads the nominal value, instead of the actual value.)
Adding a "derived" constraint will put you in the same hole you began in--waiting for Auto Layout to finish laying out your views. It's all about timing. When using Auto Layout, a good place to read the final geometries of your views is in your view controller's viewDidLayoutSubviews method. In other words, read the bounds of the view in viewDidLayoutSubviews.
I cant for the love of god the the hang of this resizing superview.
I have a UIView *superview with 4 UILabels. 2 function as header for the 2 others.
The content in all 4 are dynamic coming from database.
SizeToFit vs SizeThatFits:(CGSize) vs UIView systemLayoutSizeFittingSize:, passing either UILayoutFittingCompressedSize or UILayoutFittingExpandedSize.
I use autolayout programatically and have set the superview height to be equal or greater to a dummy number.
where and how do I use these SizeToFit vs sizeThatFits:(CGSize) vs UIView systemLayoutSizeFittingSize:, passing either UILayoutFittingCompressedSize or UILayoutFittingExpandedSize. I have read a lot of tips here on stack but ended up with nothing.
DO I need to recalculate the constraints for the superview somewhere specific. Maby setting the height to be ´#property` in its controller class and remove and readd it?
Atm I have tried to put everything everywhere and then some. Still I get the same size end result with the dummy height and text floating outside. Even after setting clipsToBound on subview.
I am scratching my hair of.. help
If you're using Auto Layout, here's what you need to do:
Make sure you aren't adding fixed width and/or height constraints to any of your subviews (depending on which dimension(s) you want to dynamically size). The idea is to let the intrinsic content size of each subview determine the subview's height. UILabels come with 4 automatic implicit constraints which will (with less than Required priority) attempt to keep the label's frame at the exact size required to fit all the text inside.
Make sure that the edges of each label are connected rigidly (with Required priority constraints) to the edges of each other and their superview. You want to make sure that if you imagine one of the labels growing in size, this would force the other labels to make room for it and most importantly force the superview to expand as well.
Only add constraints to the superview to set its position, not size (at least, not for the dimension(s) you want it to size dynamically). Remember that if you set the internal constraints up correctly, its size will be determined by the sizes of all the subviews, since its edges are connected to theirs in some fashion.
That's it. You don't need to call sizeToFit or systemLayoutSizeFittingSize: to get this to work, just load your views and set the text and that should be it. The system layout engine will do the calculations for you to solve your constraints. (If anything, you might need to call setNeedsLayout on the superview...but this shouldn't be required.)
Use container views
In the following example I have a 30x30 image, and the UILabel is smaller than the containing view with the placeholder text. I needed the containing view to be at least as big as the image, but it needed to grow to contain multi-line text.
In visual format the inner container looks like this:
H:|-(15.0)-[image(30.0)]-(15.0)-[label]-(15.0)-|
V:|[image(30.0)]|
V:|[label(>=30.0)]|
Then, set the containing view to match the height of the label. Now the containing view will ride the size of the label.
As #smileyborg pointed out in his answer, connecting the content rigidly to the superview informs the layout engine that the simple container view should cause it to grow.
Yellow alignment rectangles
If you want the yellow alignment rectangles add -UIViewShowAlignmentRects YES in your scheme's list of run arguments.
This almost follows #smileyborg answer and comes with a concrete example.
Won't describe all constraints, but those related to the calculation of the height of UI objects.
[Label] Labels must not have a fixed height constraint, in this case, AutoLayout won't resize labels to fit the text, so setting edge constraints is the key. (green arrows)
[Subview] Steps 1 and 3 are very easy to follow, but this step can be misunderstood. As in the case with labels, subviews must not have height constraint set. All subviews must have top constraint set, ignoring bottom constraint, which can make you think will trigger unsatisfied constraint exception at runtime, but it won't if you set bottom constraint for the last subview. Missing to do so will blow the layout. (red arrows)
[Superview] Set all constraints the way you need, but pay big attention to the
height constraint. Assign it a random value, but make it optional, AutoLayout will set the height exactly to fit the subviews. (blue arrows)
This works perfectly, there is no need to call any additional system-layout update methods.
This was made dramatically easier with the introduction of Stack Views in iOS 9. Use a stack view inside your view to contain all your content that resizes, and then simply call
view.setNeedsUpdateConstraints()
view.updateConstraintsIfNeeded()
view.setNeedsLayout()
view.layoutIfNeeded()
after changing your content. Then you can get your new size by calling
view.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize)
if you ever need to calculate the exact size required for a view.