Is calling updateViewConstraints manually a good idea? - ios

I'm creating my constraints mainly in viewDidLoad. In updateViewConstraints I'm adding or removing some subviews and additionally adding and removing constraints. As a side note I'm using a container with child view controllers and I'm reusing the same view for different orientations (full in landscape, in a popover in portrait).
Because I have my complete setup in updateViewConstraints I have to call it in some situations manually to adapt the visual changes and not to brake the constraints (e.g. when presenting the same view in a popover, or on iOS 7 it is not called on rotation). I also tried to use setNeedsUpdateConstraints and so on but that always broke my constraints.
My question now is am I allowed to call updateViewConstraints manually? Does it has some negative side effects? Or is the flow of my application wrong?

According to Erica Sadun in the excellent iOS Auto Layout Demystified, 2nd Edition, it's perfectly acceptable to call updateViewConstraints directly:
When working with views, you call setNeedsUpdateConstraints
(setNeedsUpdate Constraints: on OS X) to indicate that a view needs
attention at the next layout pass. With view controllers, you call the
updateViewConstraints method directly, generally when setting up
(viewWillAppear:) and responding to rotation callbacks.

Related

Can layoutIfNeeded be used in viewDidLoad to determine a corner radius size?

Is it safe to call layoutIfNeeded inside viewDidLoad in an Xcode 8 iOS project like the code below?
- (void)viewDidLoad {
[super viewDidLoad];
[self.view layoutIfNeeded];
CGFloat frameHeight = CGRectGetHeight(self.frame);
subview.layer.cornerRadius = frameHeight * 0.5;
}
It works fine in Xcode 7 without layoutIfNeeded.
You can call it in viewDidLayoutSubviews() if you have issue.
From Apple iOS 10 Release notes is:
Sending layoutIfNeeded to a view is not expected to move the view, but in earlier releases, if the view had translatesAutoresizingMaskIntoConstraints set to NO, and if it was being positioned by constraints, layoutIfNeeded would move the view to match the layout engine before sending layout to the subtree. These changes correct this behavior, and the receiver’s position and usually its size won’t be affected by layoutIfNeeded.
Some existing code may be relying on this incorrect behavior that is now corrected. There is no behavior change for binaries linked before iOS 10, but when building on iOS 10 you may need to correct some situations by sending -layoutIfNeeded to a superview of the translatesAutoresizingMaskIntoConstraints view that was the previous receiver, or else positioning and sizing it before (or after, depending on your desired behavior) layoutIfNeeded.
Third party apps with custom UIView subclasses using Auto Layout that override layoutSubviews and dirty layout on self before calling super are at risk of triggering a layout feedback loop when they rebuild on iOS 10. When they are correctly sent subsequent layoutSubviews calls they must be sure to stop dirtying layout on self at some point (note that this call was skipped in release prior to iOS 10)."
Essentially you cannot call layoutIfNeeded on a child object of the View - now calling layoutIfNeeded has to be on the superView, and you can still call this in viewDidLayoutSubviews.
I think a better approach would be to do this either in viewWillAppear or viewDidLayoutSubviews
Those are times when the frame is actually finalized. viewDidLayoutSubviews is good because it will react to changes in the views frame.
If you have a requirement of different cornerRadius values for different size classes, I would use traitCollectionDidChange instead of viewWillLayoutSubviews or viewDidLayoutSubviews.
override func traitCollectionDidChange(previousTraitCollection: UITraitCollection?) is called only when the iOS interface environment changes. If the view is being loaded for the first time, previousTraitCollection will be nil.
That way, if your application supports multitasking, as soon as the view size changes to show two apps on your device screen at once, your views and will resize properly to fit the new size class. Using the layoutSubviews methods will also work, but might put additional processing strain on your application.

Auto Layout = ViewWillAppear has the wrong width of the view

I am using autolayout for parts of my UI. When I check the width of my view in viewWillAppear, the width has not yet updated. It gets updated after the second callback to - (void)updateViewConstraints. The issue is I set up my parts of my UI (using spring and struts for legacy reasons) based on this width in viewWillAppear. When is the good time to set up this legacy UI which takes in a width parameter ?
thanks
Layout happens after viewWillAppear returns. Do it in viewDidLayoutSubviews (in your view controller) or in layoutSubviews (in your custom view subclass). Either way, be sure to call super first.
Another way is to force layout by sending [view layoutIfNeeded]. Then you can add subviews and set their springs and struts. The system will perform layout again (at its usual time) because you added subviews.

iOS - viewDidLayoutSubviews called before auto-layout completed on iOS7

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];

Auto Layout Constraint Lifecycle

I'm trying to get a handle on the best way to apply Auto Layout constraints in code instead of IB.
I need the topLayoutGuide and bottomLayoutGuide property of my ViewController and the developer docs say to query for it in the viewDidLayoutSubviews selector. In the other ViewController lifecycle methods the top and bottom layout guides haven't been set yet.
My question is that since this can be called numerous times it feels bad to do this in viewDidLayoutSubviews multiple times:
Remove prior constraints in the view.
Set up the constraints again.
Call [self.view layoutSubviews] to apply them again.
Is there a better place to do this? For instance where I know a selector will only be called once and I apply my constraints. If any keyboard actions are called I can update them appropriately instead of destroying the others and rebuilding from scratch.

Handling interface rotation in iOS 7

Very simple question, but I'm asking as there seems to be a lot of conflicting views and I've been completely unable to find a definitive answer, let alone a modern one.
I use Auto Layout for 99% of my handling of the user changing from portrait to landscape or vice-versa in a UIViewController. Works great. But sometimes, I have to rely on frames still for one reason or another.
How would I best handle this?
You have willAnimateRotationToInterfaceOrientation, willRotateToInterfaceOrientation, the NSNotification methods with checking status bar, and I'm sure there's some others.
If I want to change the position of a view when the user rotates, which method should I be changing the frame in? And is it best to do it with a simple setFrame: or should I be using autoresizing masks?
Since iOS6, you should not be using willRotateToInterfaceOrientation: and didRotateFromInterfaceOrientation:. These are only called on the front-most presented view controller, and will not be called on others. This means if you have a pushed view controller or a presented view controller, all others will not layout correctly.
Instead, you should always use viewWillLayout and viewDidLayout to handle rotation. They are both called inside an animation block, so anything you do which is animatable, will be animated.
For positioning views, you can either set the frames yourself, or use auto-layout constraints and adjust the constraints in viewDidLayout. If you go the auto-layout route, never remove and add constraints. Use the same constraints as much as possible and just adjust their constant values.
When I'm changing the main view frame, I typically adjust the frame in willRotateToInterfaceOrientation if I need to. Then I adjust any subviews by overriding layoutSubviews for my main view.
I don't know that this is a definitive answer, though - I don't think there really is a definitive answer - it depends on how your application is structured.
wilLRotateToInterfaceOrientation and didRotateFromInterfaceOrientation are best used for stuff you need to do before and after rotation, respectively (for example, disabling user interaction before the rotation begins, and reenabling it after). Everything else should be done in layoutSubviews if possible.
Autoresizing masks are useful sometimes, but I usually lay everything out manually to avoid any surprises when things change between iOS releases (as they often do).

Resources