In one of the WWDC videos, Apple said that the layout is done from top down, ie from superview to subview (after constraints are calculated from bottom up). Display is also done from top down.
My questions are:
1. At what point in the viewcontroller is a view's frame (origin and size) determined? I tried to log the size of a view (defined using auto layout), but it was always 0 0 0 0, which is odd, because the view is already generated in the simulator;
For an autoresized view, when is view.frame available?
Same question, except this time it is UIImageView.frame. I tried to log to console, even though the size is fit into the constraints, the logged UIImageView frame is 0 0 width_of_original_image height_of_original_image. But for other views like labels, the frame is printed correctly on the console.
It seems like that there is a mysterious auto layout engine that performs transform, and nobody knows what is going on inside the engine, but to check what is thrown onto the simulator display to figure out how the view was rendered by this engine???
It's not mysterious. It's quite simple! Think of constraints as instructions written down on pieces of paper - the views. Every once in a while, it's layout time! The runtime collects the pieces of paper from the views in order and obeys them - and so you end up with laid out frames.
So if you check sizes of things before layout time, you get the wrong answer because it hasn't happened yet.
And when is layout time? It's whenever the runtime sends views layoutSubviews - in fact, the runtime obeys constraints and performs layout during layoutSubviews. And your view controller can hear about this before or after, with viewWillLayoutSubviews and viewDidLayoutSubviews.
I think the part that confuses beginners the most is what happens when a view controller comes into existence. viewDidLoad means it has a view, but that is all; neither the view nor its subviews are in the interface yet, so obviously there can be no layout. Considerably later, we get viewWillAppear:, the view goes into the interface, and now we get layout. So if you check sizes in, say, viewDidAppear:, they will be right.
Related
So I recently was creating what Apple calls a leaf-level view (a button) and so I followed Apple's docs to implement -intrinsicContentSize and everything worked (albeit the code felt a bit weird - I had constraints set up in my -updateConstraints method to position subviews, as well as code in the -intrinsicContentSize method to calculate what the total size should be; it felt like I was giving duplicate information to the autolayout system).
However, I also ran into a post on here claiming to, instead of using -intrinsicContentSize, use rigid constraints and then the containerview will automatically resize to fit the views it contains. I also implemented this, and achieved the same result as above, but this time I didn't feel like I was duplicating information sent (I just sent the straight constraints). Note that I see the view as described in the post mentioned above as a so called leaf-level view since it doesn't sound like any other view would be added to it.
Which implementation of resizing a container view based on the content inside of it is the proper way to go?
I'm currently leaning towards the second method, due to the fact that I don't think I should be sending duplicate information, however Apple's documentation says otherwise (then again, Apple's docs can be a bit confusing/misleading at times).
Sidenote about my specific situation, f it matters: I have two subviews in my button, one being an image, the other being a label. The image gets it's size from the label, and then the entire button from the image (so indirectly the sizing comes entirely from the label).
Your button view should have internal constraints, based on the label and the image. These should be sufficient to give it the correct size. You don't need to implement intrinsicContentSize for that.
The button's superview does not and should not know or care what is going on inside the button. You don't directly reference the intrinsic content size, the layout system does, and if your button has the right internal constraints, it isn't necessary.
Intrinsic content size is there to allow a view to express its size as something at the very bottom of the view hierarchy, based on its display contents, such as the text in a label. Everything above that is based on constraints.
You can also use it to allow a view with non-autolayout subviews to participate in autolayout, but this can lead to a lot of duplicated frame calculation code.
Auto Layout Constraints allow me to size and lay out views perfectly, without knowing beforehand what screen they will be rendered on. This works reasonably well when I'm drawing a UI on a Storyboard.
Let's move on to creating views programmatically.
My app is running, the interface has been rendered on the screen, I have all the necessary coordinates and the sizing has been done.
I would like to create a few views dynamically, for instance:
a UIImageView, that appears at the press of a button, gets animated, then disappears
a collection of custom UIView, created and laid out on the screen depending on the underlying data model (imagine a sequence of events here, arranged on a custom timeline)
In similar scenarios, I still tend to use the good old frames, e.g.:
let myView = UIView(frame: CGRectMake(x, y, w, h))
without adding any NSLayoutConstraint.
Is there any definite advantage of using constraints instead, given the added complexity, especially when you need to animate views?
The most important aspects are when you actually calculate your frame (which method), if you want universal support and if you need to support various orientations.
Inside a viewController, if you print out your self.view frame in viewDidLoad and viewWillAppear: or viewWillLayoutSubviews, it might have different values. The final frame (for example, the one for iPhone 6 plus) is not calculated in viewDidLoad. So if you make your view setup there, the calculation will be wrong.
But with constraints, it does not matter where you add them. You might need to call layoutIfNeeded after the view changes, but you don't need to worry weather the frames have their final value.
For views that are visible only for a short time, auto layout could make a difference for different orientations. By using frames, you will have to update the frame of your temporary view in the orientation change callback.
Also, if you use auto layout in the storyboard, using static frames for views created programmatically might not give you the results you are expecting. But, of course, it depends on the particularities of your project.
We're currently having a problem that only seems to affect iOS7 devices.
Within our .xib file we have two views within a container view (i.e.: not at the top level of the view hierarchy) that need to be circular on display. The views have constraints applied to their position and horizontal spacing within the container, and an aspect ratio condition requiring they are square. The views should expand in width/height on larger screen sizes, respecting the constraints described.
In our VC, we have the following in viewDidLayoutSubviews to force these views to appear circular:
- (void)viewDidLayoutSubviews {
self.progressContentContainerView.layer.cornerRadius = self.progressContentContainerView.frame.size.width/2;
}
This seems to work fine on iOS8, however on iOS7 there is a period after the view has been displayed where the constraints have not yet been applied and the size of the view/views is incorrect (see attached screenshots). This resolves itself and correctly renders a circle after half a second. This only appears to happen when the views that we intend to be circular are NOT at the top level of the VC's view hierarchy which seems to imply that viewDidLayoutSubviews is called before the subviews of subviews have also been laid out.
My guess is that we could potentially fix this issue by subclassing UIView for the nested container, adding references to the circular view within this subclass and overriding viewDidLayoutSubviews here to make the cornerRadius adjustment. This seems like a bit of a workaround though and I'm interested to see if there are other options.
Is there a cleaner/more idiomatic solution to this problem?
I know this is an old question but have you tried calling either:
[self.progressContentContainerView setNeedsUpdateConstraints];
or:
[self.progressContentContainerView layoutIfNeeded];
I was using a xib to do some simple view layouts and noticed that the size of my subviews was incorrect in relation to the size of the view itself.
I decided the last time I tried to ask this question I may have already had too complicated of a product for people to fully understand what I was asking.
Here is a simple view:
It is exactly as I would like it to be position and exactly how I would have expected to lay itself out as shown here:
If you notice at the bottom there, I did a log of the size of the views. The log is as follows in my viewDidLoad lifecycle method.
NSLog(#"%f, %f", [[UIScreen mainScreen] bounds].size.width, self.blockView.bounds.size.width);
Why is it that the view width is in no way close to the actual width reported my the application's view size? I am trying to cast a shadow under my subview but since programmatically it thinks the view is much wider, this wont work. Also positioning any subviews programmatically in the purple view shown there will not work either since the frame does not match the expect frame.
PLEASE HELP.
thank you.
EDIT: LACK OF SLEEP LEADS TO FORGETTING LIFE CYCLES....
You should log the view width in viewDidAppear.
viewDidLoad is called after the view controller has loaded its view
hierarchy into memory.
By the time viewDidLoad is called, auto layout hasn't finished calculating your view position according to the constraints of the view.
Within the pageViewController:viewControllerAfterViewController: method, just before the return statement, the view which is about to be returned as the next page has the correct view frame size.
However immediately after the pageViewController:didFinishAnimating:previousViewControllers:transitionCompleted: method is called, I check the frame size of the newly introduced view controller ([pageViewController2.viewControllers objectAtIndex:0];) and I find it resized.
Note that, I have set [self.pageViewController.view setAutoresizesSubviews:NO] and the autoresizing mask to None for the newly created ViewController.
Any ideas in which step the new ViewController is being resized?
I think the problem is inherently related to the nature of UIPageViewController. It is built from UIScrollView. I don't exactly know why there is strange resizing behavior, but it seems to be particularly pronounced when the view controllers that make up your pages use auto layout. Seemingly, locking the constraints in your page view controllers to the superview makes the elements resize after the transition because the superview is itself getting resized after said transition.
This sucks because Apple is basically pushing all of us to adopt auto layout. Auto layout is awesome, and I recommend everyone use it from now on, but it really really sucks when you use it with a UIPageViewController. They really ought to either scrap that class or build something easier for developers, something that can be dragged into a storyboard outright.
A few things to consider.
1.) Don't lock anything to the "Top Layout Guide" or the "Bottom Layout Guide". Also make sure you have "Constrain To Margins" disabled on any view intended to hug the sides of the screen.
2.) If you are using a label in your individual page / content view controllers, make sure you bind/constrain it to something other than the superview. I wanted to place a label over a UIImageView, so I aligned the label to the leading and top edges of the image view (using AutoLayout constraints only), creating an offset to give the label some margins.
3.) The following would otherwise be a good tutorial. However, it is a bit outdated. I downloaded the project and basically modified it to get a UIPageViewController implementation that works. The only problem with this project is that it doesn't use AutoLayout. I'm currently writing a blog post that more clearly discusses how to use UIPageViewController and Autolayout together.
http://www.appcoda.com/uipageviewcontroller-storyboard-tutorial/