I am developing an application which supports portrait and landscape modes. I am using auto layout to arrange my views. As i have been reading many posts and i have realized that developers commonly use one among the following approaches.
1. First approach:
Implement the UIViewController:updateConstraints method and update constraints according to device orientation.
2. Second approach:
Implement the UIViewController:viewWillLayoutSubviews method and update constraints according to device orientation.
Could anyone please tell me what is the best approach to use ? I have been searching for a best practice to combine autorotation and auto layout and nothing yet. Thanks.
I would use this UIViewController's method:
- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration;
that is called upon rotation, and call -setNeedsUpdateConstraints from there.
If you need to do additional calculations or manage the contrainsts add
- (void)updateViewConstraints
{
[super updateViewConstraints];
// do calculations if needed, like set constants
}
The best approach is to use neither. Instead, configure your constraints correctly so that no programmatic changes are required on rotation. The Auto Layout runtime will maintain the views in position as you already have specified.
Updating constraints other than changing the value of .constant is a real performance hit and should be avoided.
Using viewWillLayoutSubviews is not necessary. Auto Layout methods are updateViewConstraints (for the view controller), and updateConstraints (in the views).
I believe that the best approach is to update the constraints in -(void)updateViewConstraints by checking the device orientation. There is no need to call setNeedsUpdateConstraints in willAnimateRotationToInterfaceOrientation because it is automatically called by the iOs when the device orientation changes. Thank you all for the great effort.
for ios8+
From the documentation for:
- (void)willTransitionToTraitCollection:(UITraitCollection *)newCollection
withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator
A standard view controller might use this method to change the
constraints on the views it manages.
Related
I am currently developing an iOS app and I was wondering how to manage the UI elements. I am using a Storyboard to place my views and Autolayout to make everything resolution independent. If it helps, here's a bit of background on how I came up with this question.
Some Backgroud
I have buttons the represent piano keys and I later in code add a subview to these that draws the actual keys on them. I did this by creating #IBOutlets on the ViewController and susbscribed to the UIDeviceOrientationDidChangeNotification. This called a method that adds the subviews based on the current button frames to get the appropriate sized keys.
This worked fine when running in iOS 8.1, but if I ran the app on iOS 7, the frames of the buttons weren't updated to the new orientation by the time I got the UIDeviceOrientationDidChangeNotification, so everything was messed up in landscape. I did a little research (UIInterfaceOrientation not yet updated when UIDeviceOrientationDidChangeNotification caught in UIView) and it seems that a good solution would be to override the layoutSubviews method. Problem is, layoutSubviewsis a UIView method, and I am managing my buttons and UI in my UIViewController.
Impulsively I wanted to subclass my UIView, override layoutSubviews and from there call a method on my Controller to add the keys to the buttons correctly, but that doesn't sound right.
The Questions
My UI is managed by the UIViewController. But I need to update my views based on a method proper of a UIView. I assume, to respect the MVC principles, that my view should't now a thing about my controller, but then this brings up a few questions:
Who should be managing the UI?
Is my strategy wrong and should the
UIView hold the #IBOutlets to the buttons so that it can later
apply the subviews I need them to have?
If you read the background, do you have a suggestion for this particular situation?
I assume the complexity of this problem is relatively small and I don't need to setup notifications in NSNotificationCenter, but I may be wrong. I would really like to hear this is not the only solution.
Thanks in advance. I hope I was clear enough, but if you want/need any additional details on my particular situation, let me know and I will gladly elaborate.
if I understood the question correctly, your ViewController should be laying out the keys within it's view. A good, but not the only, place to do this is in viewDidLayoutSubviews which a method you can override on UIViewController.
Now if your views should maintain their internal layout. i.e if you buttons/keys have any subviews, you should update those in layoutSubviews on UIView as you mentioned.
Both methods will be called in response to changes in the bounds or center of your parent view.
As far as rotation changes are concerned, in iOS 8, you should use viewWillTransitionToSize:withTransitionCoordinator: or willTransitionToTraitCollection:withTransitionCoordinator:
depending on your needs.
I highly recommend that you write your layout independent of orientation. You should just use the containing view's bounds for reference and never hard code in any frames. For example perhaps one of your keys should be 1/56 the width of it's superview instead of a magical number for portrait and landscape.
I modified programmatically one autolayout constraint. It takes effect on one certain view, but other views that are bound with other constraints to that view, do not change their positions. Is there an "updateAllConstraints" method?
Call those two methods on the view you want to be updated:
-setNeedsLayout
-layoutIfNeeded
The first one says to the layout system that this view needs to be laid out, because it has some changes and, everything should be recalculated. The second force the layout system to be run now, layout system is triggered at specific times during runtime, with this method you are saying:"do it now".
Yes There is a method on UIView called - (void)updateConstraintsIfNeeded
https://developer.apple.com/Library/ios/documentation/UIKit/Reference/UIView_Class/UIView/UIView.html#//apple_ref/occ/instm/UIView/updateConstraintsIfNeeded
However I don't think this is your problem
The problem was that constrains were not set up properly, and in this case non of the setNeedsLayout or updateConstraintsIfNeeded can help.
Those of you that come across this topic, and are looking where to find this in the menus.
Best I have found is:
Select the items that you want to "pop" back to your constraints
Top menu: Editor/Update Frames (or/) shortcut: command + option + =
Hopefully this helps
I have a custom UIView, which I have placed using Xcode (4). I need to set some default state, based on the actual bounds of the view. During awakeFromNib, bounds seems to be returning the size of the view in the storyboard layout in Xcode.
The view is in the detail side of a UISplitViewController, which in Xcode is the size of a full portrait iPad screen, but if the app loads in landscape mode then, via springs-and-struts, its size is changed, but this appears to happen after awakeFromNib.
Should I be setting this state in some other method?
It depends what sort of state you are setting - if it is dependent on the bounds then you'll need to reset it every time the device is rotated, presumably? In that case overriding setFrame: might be a better bet - be sure to call the superclass implementation before you do anything else, though.
The answer is was probably looking for, or at least the solution I have used, is to provide a public method in the UIView to be called by the parent UIViewController in viewWillAppear:
My app has very different UI structures for each orientation.
At what stage in the rotation lifecycle of an iPad should I remove, then add the orientation's UI to ensure the smoothest transition?
When handling autorotation, you have 4 main methods to care about:
shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation duration:(NSTimeInterval)duration
willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation
duration:(NSTimeInterval)duration
didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation
Now, the way I handle the rotation is, e.g.:
adjust all the frames in willAnimateRotationToInterfaceOrientation;
add/remove subviews (if needed) in willRotateToInterfaceOrientation;
restore subviews (if needed) in didRotateFromInterfaceOrientation.
More in general, I modify all properties that are animatable in willAnimateRotationToInterfaceOrientation; while I do all modifications that are not animatable in willRotateToInterfaceOrientation/didRotateFromInterfaceOrientation.
This is what the UIViewController reference says about willRotate/didRotate:
To temporarily turn off features that are not needed or might otherwise cause problems during the orientation change, you can override the willRotateToInterfaceOrientation:duration: method and perform the needed actions there. You can then override the didRotateFromInterfaceOrientation: method and use it to reenable those features once the orientation change is complete.
I am working on an iPad app that relies on a rather complex layout that seems to be beyond the abilities to the auto-resizing masks to rotate cleanly from portrait to landscape.
I can easily enough hand-tweak a layout in Interface Builder for each orientation, but I am puzzling over the most elegant and maintainable way to handle making the transition between the two different layouts.
Is there any way that this can be done with segues?
Is there a way I can easily snapshot two different layouts and use code to morph between them?
Am I better off trying to use HTML5 to do the page layout and not UILabels?
Are there other better techniques that I haven't even thought of yet?
Help is much appreciated - it seems like this shouldn't be so hard.
All you scenes may or may not need to have their own subclass (depending on inheritance). Assign the subclass to the respective scene.
You then need to set the supported rotation values in the subclass. eg.:
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
// Return YES for supported orientations
return YES;
}
EDIT:
I suggest loading a respective NIB by overriding willRotateToInterfaceOrientation:duration: and didRotateFromInterfaceOrientation:.