48

I want to modify the layout constraints when the device rotates. My UIViewController is composed of 2 UIViews, in landscape they are horizontally aligned, and in portrait they are vertically aligned.

It does work actually, in willAnimateRotationToInterfaceOrientation, I remove the desired constraints and replaced them with others to have the right layout...

But there are problems, during rotation auto layout starts breaking constraints before willAnimateRotationToInterfaceOrientation is called, so where are we meant to replace our constraints when the device reorientation occurs ?

Another issue is performance, after a few rotations the system doesn't break anymore constraints, but I have a huge performance drop, especially in portrait mode...

SkylerHill-Sky
  • 2,106
  • 2
  • 17
  • 33
vitaminwater
  • 6,934
  • 3
  • 19
  • 23
  • please add your code here – Waseem Shah Jan 28 '13 at 10:57
  • @Makleesh : yes you are right a lot of problems emerge when using auto-layout, but I find them quite handy.. anyway, looks it will be the new way of doing things, better get into it now than late. – vitaminwater Jan 29 '13 at 08:55
  • @Waseem : I don't really have code to post... as I said the constraints are broken even before my code is executed, so I don't really know what code I could post ! – vitaminwater Jan 29 '13 at 08:56
  • You should accept one of the answers - probably Rob's - as he for sure answered your question. – David H Oct 07 '13 at 17:33

3 Answers3

55

In willRotateToInterfaceOrientation:duration:, send setNeedsUpdateConstraints to any view that needs its constraints modified.

Alternatively, make a UIView subclass. In your subclass, register to receive UIApplicationWillChangeStatusBarOrientationNotification. When you receive the notification, send yourself setNeedsUpdateConstraints.

This sets the needsUpdateConstraints flag on the view. Before the system performs layout (by sending layoutSubviews messages), it sends an updateConstraints message to any view that has the needsUpdateConstraints flag set. This is where you should modify your constraints. Make a UIView subclass and override updateConstraints to update your constraints.

rob mayoff
  • 375,296
  • 67
  • 796
  • 848
  • This is very cool and works flawlessly. In my implementation I am iterating over [view constraints] and removing each one before adding the orientation-specific constraints. This is a wholesale swap and the animated change looks great. – Ben Packard Jul 26 '13 at 00:05
  • 2
    Do you have to subclass every view that will have its constraints modified, or can you somehow do this completely from the view controller? It seems that a very, very common situation would be to change some widths and heights for rotation changes, but would be quite ugly if you have to fill out your layout with multiple custom classes for many interior views. – Tenfour04 Nov 13 '13 at 18:19
  • 1
    You could make a custom class for the top-level view and give it outlets to all of the subviews that need modified constraints. Then you can set up all the constraints in that custom class's `updateConstraints` method. – rob mayoff Nov 13 '13 at 18:55
  • @robmayoff, I've created UIView subclass, and Registered Notification, Taken outlet of my view(i.e. ImageView), now when notification fired, i've setNeedsUpdateConstraints on That ImageView, and [[self ImageView] updateConstraints] set on updateConstraints method, but nothing happened... What should i do.? One thing to notice that i've given constraints in xib for Portrait and Landscape. This is working fine in iOS8 but do not work in iOS7. – Mohammad Zaid Pathan Mar 20 '15 at 11:53
  • `willRotateToInterfaceOrientation:duration:` is deprecated now? – Sebastian Roth Sep 16 '16 at 02:43
  • Have you checked the documentation to see what it is replaced with? – rob mayoff Sep 16 '16 at 02:46
  • 1
    It was replaced with `viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator)` – petrsyn Nov 06 '16 at 20:30
13

There is very good explanation of auto-layout and rotations in Matthijs Hollemans post. You can find it here: http://www.raywenderlich.com/20881/beginning-auto-layout-part-1-of-2

Usually, you need about 4 constraints to correctly position your view. If my views have constant size i prefer to pin height and width. After that you can use Leading and Top space constraints to do whatever you want. For example, you can set IBOutlets for leading and top space constraints for your views:

@interface ViewController : UIViewController {
    IBOutlet NSLayoutConstraint *_leadingSpaceConstraint;
    IBOutlet NSLayoutConstraint *_topSpaceConstraint;
}

Then control-drag from outlet to your constraint. Now you can directly change your view constraint from code:

_leadingSpaceConstraint.constant = NEW_CONSTRAINT_VALUE;

To commit your changes you need to call:

[self.view layoutIfNeeded];

And if you want to do it animated:

[UIView animateWithDuration:0.25
                 animations:^{
                     [self.view layoutIfNeeded];
                 }];

I think it will work in willAnimateRotationToInterfaceOrientation, because you don't need to break any constraints with this approach.

Some example: You have two square view in portrait orientation, one under another. Set their "leading space to superview" constraints to 20, for example. Then set "top space to superview constraint" to 20 for first view and to 120 for second. It will be our default setup.

Then, after rotation you need to recalculate your constraints. Now set both of top constraints to 20, and leading constraints to 20 and 120 respectively. Then commit changes with layoutIfNeeded.

I hope it will help.

Eugene Tartakovsky
  • 1,594
  • 17
  • 22
  • I feel like you are defeating the purpose of autolayout there, if you set a fixed width and height, and add top/left space for positionning, your doing nothing more than spring & struts. of course there are no borken constraints by doing this as you are not adding any constraints linking your views together; for example, if you have to manage more than one screen size/ratio, you'd have to make special cases, which is what autolayout tends to avoid ! – vitaminwater May 20 '13 at 19:48
  • I think it is just the special case. You need fixed size and dynamic position - you can get it. You need dynamic size - you can get it too. Autolayout allows you to implement dynamics when and where you want. Spring & struts - not. – Eugene Tartakovsky May 22 '13 at 12:04
  • There are a few special cases where you need to mix in a few hard-coded sizes that react to rotations. For example, in a scroll view that has paging turned on, where you want the inner views to be the same width as the screen so they each line up with the screen when paged to. That's not worth turning off autolayout for as long as there is a workaround like this. Autolayout is a huge time saver for supporting all the different device resolutions, and it is likely that future devices will add even more resolutions to support. – Tenfour04 Nov 13 '13 at 18:34
2

override -(void) viewWillLayoutSubviews in your UIViewController to update your constraints as below code:

-(void) viewWillLayoutSubviews {
     switch(self.interfaceorientation)
     {
          case UIInterfaceOrientationLandscapeLeft:

              break;
          case UIInterfaceOrientationLandscapeRight:

              break;

          case UIDeviceOrientationPortrait:

              break;

          case UIDeviceOrientationPortraitUpsideDown:

              break;

     }
}
MuhammadBassio
  • 1,590
  • 10
  • 13
  • layoutSubviews is an UIView method, not sure that's the way to go, but thanks ! This project has been shipped now, so I can't really test it anyway, the closest approach to what you are proposing would be to use the viewWillLayoutSubviews from the UIViewController, I think I had tried that without success.. – vitaminwater Feb 07 '13 at 08:57
  • 2
    First, you need to call `[super layoutSubviews]` if you're using autolayout. Second, you don't want to modify your constraints in `layoutSubviews`, because `layoutSubviews` is sent **after** the system has solved the constraints. Changing the constraints here will trigger another layout pass (to solve the new constraints) on the next turn of the run loop. – rob mayoff Apr 25 '13 at 07:33
  • Looks like the correct method to implement/override here would be `viewWillLayoutSubviews`. – Matt Mc Jun 12 '13 at 04:36
  • This works great. Simply just set the new constraint constants and voila! Thanks! – nortchuck Feb 08 '15 at 20:59