iOS AutoLayout - when does it Run? - ios

I have a XIB file where I specify constraints at design time.
In addition to controls with constraints, I also have controls that have no constraints in the XIB that I want to position programmatically at run time.
I do this by repositioning views manually in didRotateFromInterfaceOrientation.
When I rotate the device, my code places the controls that are manually controlled but then later it appears that auto layout messes up the placement of manual controls that have no constraints on them in the Interface builder.
Question - When exactly does Auto layout run if I rotate the device.

Auto Layout runs at the end of the run loop after setNeedsLayout is called on a view, or immediately when setNeedsLayout is followed by layoutIfNeeded. Note that there are many methods that may call setNeedsLayout in their implementation, such as addSubview:, removeFromSuperview, etc.
A couple suggestions:
Instead of updating your constraints in didRotateFromInterfaceOrientation:, try doing so in updateViewConstraints.
You may also want to add placeholder constraints in Interface Builder to the views you will position programmatically. I think IB will automatically constrain views to their x/y position if a placeholder constraint is not set, but I'm not 100% sure on that.

The important thing to know is that the results of constraint calculations are applied in layoutSubviews. So if you want to set frames, you do so after calling super in layoutSubviews or in your view controller's viewDidLayoutSubviews.
Having said that, I've only experimented with this. I've not done it in production code and can't say that you won't run into issues.
I'm actually not clear on whether it's OK to set frames manually like this. The only relevant bit of information I've come across is the following from Apple's Auto Layout documentation:
You cannot set a constraint to cross the view hierarchy if the
hierarchy includes a view that sets the frames of subviews manually in
a custom implementation for the layoutSubviews method on UIView (or
the layout method on NSView).
Here, the phrase "view that sets the frames of subviews manually" implies to me that manually setting frames is OK. I'd be interested to know if anyone has a see a more explicit discussion on this.

you must not change the frame you must connect the constraint with iboulet and in the didRotateFromInterfaceOrientation you can change the constraint like this:
(void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation{
if (UIInterfaceOrientationIsLandscape(fromInterfaceOrientation)) {
self.constraintTop.constant = 50;
}else{
self.constraintTop.constant = 100;
}
}

Related

iOS: Adding new constraints in a custom view the rely on the view's frame

I'm trying to understand when to override layoutSubviews versus updateConstraints.
I have created a custom view, and that view contains other views. I can't set the constraints for the view's subviews in the custom initializer I have as I don't know the custom view's frame yet.
Currently I have this:
-(void)layoutSubviews
{
[super layoutSubviews];
// Add new constraints
}
Then whenever I add or remove any of the subviews, I call [self setNeedsLayout];
Is this the correct way to do this? I am currently recreating the constraints whenever layoutSubviews is called. I've heard that updateConstraints might be what I want? But I'm not sure as the number of subviews does not remain constant, and thus, the number of constraints will not be constant either.
You should add constraint when you add the subviews. You may do this for each subview individually, or in a custom method if that can be generalized.
The methods layoutSubvews and updateConstraints are callbacks, and they are called at specific points in the layout cycle. Especially, they are called multiple times, and that automatically prohibits you from using constraints, because there is a danger of creating constraints more than once.
As the documentation stated, layoutSubviews should only be used when
if the autoresizing and constraint-based behaviors of the subviews do not offer the behavior you want.
If layoutSubviews is called, the current automatic layout is done, and adding, removing or modifying constraints might re-trigger the layout process. This might slow-down your app extremely.
The documentation for updateConstraints includes also a clear statement when you should overwrite it:
Override this method to optimize changes to your constraints.
Changes to constraints doesn't mean adding constraints. If you need different constraints for your views for different times, you may deactivate unused and activate needed constraints at runtime. This could be done in updateConstraints.
BTW: The (explicit) call of setNeedsLayout is usually unnecessary, especially if you have changed the view hierarchy or the layout constraints.

Autolayout vs frames

Let's say that i have a view loaded from a nib that has layout constraints changing its appearance depending on its size.
I load the view from the nib and get a reference to UIView.
I then manually change view's frame (e.g make it really big or small) and add it as a subview to already visible view.
Will the constraint system automatically figure that frame change should trigger auto layout recompilation? How does auto layout mix with manual frame/bounds changes? Where can i get detailed documentation on this?
No, if your want the autolayout to re-apply you need to call [view layoutIfNeeded], and if you set frames before the layout finished to apply, it will be override. My advice is to modify constraints instead of setting frames.

ScrollView With AutoLayout

I have a weird output with autoLayout used for following scenario.
I have 4 sub pages to scroll. (subPage or scrollPage has been designed separately with autoLayout).
MainView has a scrollView component which loads the sub pages.
Everything is fine except the starting. After first load, the sub page components are not arranged properly. As soon as it receives a first tap/touch, automatically re-scrolls/re-arranges to proper places. which looks like a bug.
The loading creates problem. I have attached 2 images for reference for the above scenarios.
FirstOne at the first loading
Second one just after tapping on scroll area
Second one is the proper one. I need to show this instead of the first.
Need help to fix this.
Thanks,
Satyaranjan
One more thing to note,
[myScroll scrollRectToVisible:CGRectMake(320*pageNumber, 0, 320 , 240) animated:NO];
this is not working As I am using dynamic width for the subPage. Because it will vary for iPhon5 and iPhone6
First, you generally don't want to set the scrollView's contentSize when using autolayout -- if your sub views are laid out correctly, it will do that automatically.
Try this:
constrain all four of scrollView's edges to its parent
make sure translatesAutoresizingMaskIntoConstraints is NO for all views
Create a view called "contentView" and parent it to the scrollView.
pin all four of contentViews's edges to scrollView
parent your subviews to contentView and arrange them with autolayout
If for some reason you need to change the size of a subview programmaticly, you need to override its intrinsicContentSize method to return the correct size after the change. After the change, you might need to call the view's sizeToFit method from its parent view (not sure about that -- it may happen automatically).
In general, when using autolayout, you should almost never explicitly set the size of anything. If it can't be avoided, you should do it by creating height/width constraints and modifying them at runtime in the updateConstraints method.
EDIT:
I made an example project which demonstrates how to set up a scrollView and a couple other things.
Take a look!
https://github.com/annabd351/AutolayoutTemplate

iOS: Getting height of views with programatically added constraints as only indicator

Hello there fellow iOS programmers. While creating an app I've ran into a problem I can't seem to find an answer to. Let's lay it out:
I'm creating a UIViewController with a UIScrollView as it's only child. Inside this view I have a UIView, and inside of this there is a list of UIViews with UILabels inside them. As you all know you need to specify a contentSize for a UIScrollView.
The problem is that the list needs to be dynamic with it's content, and I therefore have no way to know the views heights beforehand. I'm adding all views with constraints where the height is set to ">=0".
When I later try to set the height of the UIScrollView I need to either get the height of the UIView that the list is inside, or get the origin.y and height of the last view in the list. This of course needs to be ready by the time the view is displayed to the user.
I've currently tried view.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize), which returned 0; view.systemLayoutSizeFittingSize(UILayoutFittingExpandedSize), which returned 10000; and view.origin.y + view.frame.height, which also returns 0. It seems to me like the constraints haven't taken effect yet.
I've tried setting both constraints and UIScollView.contentSize from viewDidLoad(). I've also tried to set constraints in viewDidLoad and setting contentSize in viewWillAppear; this yielded the same results. Setting constraints in viewWillAppear and setting contentSize in viewDidLoad only caused a crash.
Bottom-line: When should I set up the UIScrollView.contentSize if I want to get view.height or similar methods to return a correct result, while at the same time be ready by the time the user sees the view?
Btw, I'm making this app in Swift, so answers in this language is preferred, but I'm able to translate from Objective-C to Swift; post in whatever suits you best.
Thank you! :)
You say:
As you all know you need to specify a contentSize for a UIScrollView.
No, as TN2154 says, the constraints between the scroll view and its subviews are "interpreted as the content size of the scroll view" (emphasis added). This is a boon, because you no longer have to mess around with contentSize if doing auto-layout. Just set the constraints for the scroll view's subviews and the content size takes care of itself. This leverages the labels' intrinsic size (and make sure that the label's numberOfLines to zero).
The only trick is that it sometimes cannot figure out the correct width of the labels (because the trailing constraint is to the scroll view's content size, it will sometimes make the scroll view a horizontally scrolling one). You can remedy this by either manually setting preferredMaxLayoutWidth or by setting a width constraint between the label and the scroll view's superview.
Personally, while I understand the inclination to add the UIView containers between the scroll view and the labels, I'd suggest losing them unless you need them for some other reason. IMHO, it simply complicates the constraints unnecessarily. It's hairy enough as it is. Obviously, if these containers bear other utility for you, then go ahead and keep them (and they'll work fine), but if you're doing this simply for the sake of the constraints, you might consider eliminating them.

Constraints changed when modifying UIViewController.frame

I have a UIViewController which I add to another UIViewController as a child. When the child gets added, the parent set frame on my child using childVC.frame = ...., but this fubars my constraints.
I can duplicate this issue in IB easily. Create a UIViewController, change the size to be "Freeform" and ditch the status bar (just so it looks right visually). Add in a UIButton (or anything) and constraints so so they are both the same size (see image below)
Now change the size of the top level view in the view controller and watch what happens.
It changed my constraints on me. I wanted my button to just get bigger and still hold the original constraints. Something which happens automatically if you toggle between iPhone retina 3.5/4.0 in screen or change from landscape to portrait. But why not when I resize the view? This is making a royal pain to lay things out in freeform and test different sizes.
More importantly... why does the exact same thing happen when I set the frame in code when I add the child for my VC and how do I prevent it?
Frame based calculations and Auto Layout are not compatible. Or at least, they mean stormy waters. That's why you are having issues. Having switched to Auto Layout you have to accept that you no longer manipulate frame directly.
So, you cannot do childVC.frame = whatever.
Instead, you must now use Auto Layout to update the size of childVC.frame. Do that by creating - in IB - an IBOutlet that is the width constraint of childVC. Similarly, create another IBOutlet that is the childVC height constraint.
In your view controller, update the constant value to change the size.
myWidthConstraint.constant = 123.f;
The place to do this is in the controller's updateViewConstraints method, or in the case of a view, updateConstraints.
That said, it's better to set up Auto Layout so that you don't need any programmatic updates. When properly defined, it will know how to handle things with the view's bounds change. To be honest, it should only be necessary to update sizes programmatically if you want to do something more unusual.
Can I just say however that you are better

Resources