When I'm creating a frame in iOS with CGRectMake(), be it setting the frame property or using initWithFrame:, it asks for the x, y, width and height. I supply these.
But with Auto Layout, I'm next going to say something like: "position its x in the very middle" and "set the width to half the screen", right after I set those with CGRectMake() likely differently.
What do I set them as in CGRectMake()? 0? An arbitrary value? Something close to what I want?
When using auto-layout, you shouldn't set the frame property at all. You can set the frame, but there's no purpose in doing so, and only makes your code misleading as the frame will be superseded by the dimensions it calculates from the constraints. I would suggest completely defining the dimensions of the controls using constraints and avoid setting the frame entirely to avoid confusion.
So, if you're creating views programmatically, instead of initWithFrame, just do init. Also, do not forget to set translatesAutoresizingMaskIntoConstraints to NO, and add all of the appropriate constraints such that the layout is unambiguous, but with no conflicting constraints, either.
Related
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.
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.
I'm asking this (somehow) simple question just to be finicky, because sometimes I'm worried about a misuse I might be doing of many UIView's APIs, especially when it comes to autolayout.
To make it super simple I'll go with an example, let's assume I need an UIView subclass that has an image icon and a multiline label; the behaviour I want is that the height of my view changes with the height of the label (to fit the text inside), also, I'm laying it out with Interface builder, so I have something like this:
with some constraints that give fixed width and height to the image view, and fixed width and position (relative to the image view) to the label:
Now, if I set some text to the label, I want the view to be resized in height to fit it properly, or remain with the same height it has in the xib.
Before autolayout I would have done always something like this:
In the CustoView subclass file I would have overridden sizeThatFits: like so:
- (CGSize) sizeThatFits:(CGSize)size{
//this stands for whichever method I would have used
//to calculate the height needed to display the text based on the font
CGSize labelSize = [self.titleLabel intrinsicContentSize];
//check if we're bigger than what's in ib, otherwise resize
CGFloat newHeight = (labelSize.height <= 21) ? 51: labelSize.height+20;
size.height = newHeight;
return size;
}
And than I would have called something like:
myView.titleLabel.text = #"a big text to display that should be more than a line";
[myView sizeToFit];
Now, thinking in constraints, I know that autolayout systems calls intrinsicContentSize on the view tree elements to know what their size is and make its calculations, therefore I should override intrinsicContentSize in my subview to return the exact same things it returns in the sizeThatFits: method previously shown, except for the fact that, previously, when calling sizeToFit I had my view properly resized, but now with autolayout, in combination with a xib, this is not going to happen.
Of course I might be calling sizeToFit every time I edit text in my subclass, along with an overridden intrinsicContentSize that returns the exact same size of sizeThatFits:, but somehow I don't think this is the proper way of doing it.
I was thinking about overriding needsUpdateConstraints and updateConstraints, but still makes not much sense since my view's width and height are inferred and translated from autoresizing mask from the xib.
So long, what do you think is the cleanest and most correct way to make exactly what I show here and support fully autolayout?
I don't think you need to define an intrinsicContentSize.
Here's two reasons to think that:
When the Auto Layout documentation discusses intrinsicContentSize, it refers to it as relevant to "leaf-views" like buttons or labels where a size can be computed purely based on their content. The idea is that they are the leafs in the view hierarchy tree, not branches, because they are not composed of other views.
IntrinsicContentSize is not really a "fundamental" concept in Auto Layout. The fundamental concepts are just constraints and the attributes bound by constraints. The intrinsicContentSize, the content-hugging priorities, and the compression-resistance priorities are really just conveniences to be used to generate internal constraints concerning size. The final size is just the result of those constraints interacting with all other constraints in the usual way.
So what? So if your "custom view" is really just an assembly of a couple other views, then you don't need to define an intrinsicContentSize. You can just define the constraints that create the layout you want, and those constraints will also produce the size you want.
In the particular case that you describe, I'd set a >=0 bottom space constraint from the label to the superview, another one from the image to the superview, and then also a low priority constraint of height zero for the view as a whole. The low priority constraint will try to shrink the assembly, while the other constraints stop it from shrinking so far that it clips its subviews.
If you never define the intrinsicContentSize explicitly, how do you see the size resulting from these constraints? One way is to force layout and then observe the results.
Another way is to use systemLayoutSizeFittingSize: (and in iOS8, the little-heralded systemLayoutSizeFittingSize:withHorizontalFittingPriority:verticalFittingPriority:). This is a closer cousin to sizeThatFits: than is intrinsicContentSize. It's what the system will use to calculate your view's appropriate size, taking into account all constraints it contains, including intrinsic content size constraints as well as all the others.
Unfortunately, if you have a multi-line label, you'll likely also need to configure preferredMaxLayoutWidth to get a good result, but that's another story...
I'm trying to adjust the size of a button to (it's intrinsic size + a little bit more) in order to draw a custom background. However, every time I access self.titleLabel within the button, the size and position resnaps to that of the storyboard. I don't have to do anything with the label to reproduce this, just retrieve it from the button.
I've put logging code all over my button and view controller in order to find where this is happening. It's not coming from a relaying-out of subviews or any other notification I see to get within the view controller. The line before accessing titleLabel, the position and size are correct. The line after, it has snapped back to the storyboard position. Commenting out the access prevents the size/position snapping. Can someone tell me where or why this is happening?
I have no constraints set (that I can tell), but am I fighting against auto-layout here? Should I be doing this a different way like composing controls or something?
Something similar (or the same?) has been asked before at UIButton modifying titlelabel seems to change its frame and UIButton titleLabel resizes on press?, but both were left unanswered or explained away with just "maybe a bug."
If the project has auto-layout enabled, then YES, you're fighting auto-layout. You have two choices, either subclass UIButton so that you can override the intrinsic size calculation, or modify the constraints so that the intrinsic size is not used in any constraint. If you do the latter, then you probably want to create an IBOutlet to the constraint for the width, so that you can adjust the constant property as needed.
This isn't a bug, it's a consequence of auto layout. When using auto layout, you shouldn't set any frames. Instead, you should change the size or position by modifying the constraints. What's happening, is that whenever the view needs to be redrawn, the frame reverts to the frame that's defined by the constraints.
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.