0
votes

I am trying to switch the Autolayout constraints of my project for each orientation and am doing so by programmatically removing and adding a portrait and landscape set of NSAutoLayoutConstraints. I have the correct auto layout constraints (I think) for the layout for both orientations. My storyboard is not using Size Classes for compatibility.

I have taken screenshots of the correct layouts. The red portion is a UIView colored red.

portrait screenshot Portrait layout

landscape screenshot Landscape layout

My viewWillLayoutSubviews: is

- (void)viewWillLayoutSubviews {
    [self.termToolbar invalidateIntrinsicContentSize];
}

and all the constraints adding and removing is in updateViewConstraints

When starting the app in portrait orientation and then rotating to landscape, I get a constraint error:

2015-07-26 10:50:42.856 reproduce[1084:23311] Unable to simultaneously satisfy constraints.
    Probably at least one of the constraints in the following list is one you don't want. Try this: (1) look at each constraint and try to figure out which you don't expect; (2) find the code that added the unwanted constraint or constraints and fix it. (Note: If you're seeing NSAutoresizingMaskLayoutConstraints that you don't understand, refer to the documentation for the UIView property translatesAutoresizingMaskIntoConstraints) 
(
    "<_UILayoutSupportConstraint:0x786436c0 V:[_UILayoutGuide:0x7864cb70(20)]>",
    "<_UILayoutSupportConstraint:0x78643680 V:|-(0)-[_UILayoutGuide:0x7864cb70]   (Names: '|':UIView:0x7864ca90 )>",
    "<NSLayoutConstraint:0x78657bc0 'UIView-Encapsulated-Layout-Width' H:[UIView:0x7864ca90(480)]>",
    "<NSLayoutConstraint:0x78657bf0 'UIView-Encapsulated-Layout-Height' V:[UIView:0x7864ca90(320)]>",
    "<NSLayoutConstraint:0x786593c0 V:[_UILayoutGuide:0x7864cb70]-(0)-[UISearchBar:0x78a21a70]>",
    "<NSLayoutConstraint:0x786593f0 V:[UISearchBar:0x78a21a70]-(0)-[UITableView:0x7982aa00]>",
    "<NSLayoutConstraint:0x78659420 V:[UITableView:0x7982aa00]-(0)-[UIView:0x78a216f0]>",
    "<NSLayoutConstraint:0x78620570 V:[UIView:0x78a216f0]-(0)-[UIToolbar:0x78b81d00]>",
    "<NSLayoutConstraint:0x786205a0 V:[UIToolbar:0x78b81d00]-(0)-|   (Names: '|':UIView:0x7864ca90 )>",
    "<NSLayoutConstraint:0x786590a0 UIView:0x78a216f0.height == 0.75*UIView:0x78a216f0.width>",
    "<NSLayoutConstraint:0x786596d0 H:|-(0)-[UIView:0x78a216f0]   (Names: '|':UIView:0x7864ca90 )>",
    "<NSLayoutConstraint:0x78659720 H:[UIView:0x78a216f0]-(0)-|   (Names: '|':UIView:0x7864ca90 )>"
)

Will attempt to recover by breaking constraint 
<NSLayoutConstraint:0x786593f0 V:[UISearchBar:0x78a21a70]-(0)-[UITableView:0x7982aa00]>

Make a symbolic breakpoint at UIViewAlertForUnsatisfiableConstraints to catch this in the debugger.
The methods in the UIConstraintBasedLayoutDebugging category on UIView listed in <UIKit/UIView.h> may also be helpful.

Note that I am rotating from portrait to landscape and the error shown are for the portrait orientation! When rotating from landscape to portrait, no debug output/error occurs. Rotating back to landscape causes the same error.

Even with these errors, the view layout appears fine for both portrait and landscape until I show a UIAlertView/Controller. Once an alert is shown, the layout fails and both portrait and landscape layouts are displayed wrong (UITableView disappears, red UIView takes up whole screen, etc). It doesn't matter if the alert is dismissed or still being presented, the layout will fail everytime after the alert.

I created a sample project to reproduce this behavior that can be found here. UI Element outlets are connected in IB. All the code is in the default ViewController class. Any idea why this behavior is occurring or is this a bug in AutoLayout for a simple layout change between orientations? Thanks.

Portrait constraints:

<NSLayoutConstraint:0x7b05cda0 V:|-(0)-[_UILayoutGuide:0x7b0648a0]   (Names: '|':UIView:0x7b064990 )>,
<NSLayoutConstraint:0x7b05cc30 V:[_UILayoutGuide:0x7b0648a0]-(0)-[UISearchBar:0x7b0e9590]>,
<NSLayoutConstraint:0x7b05cc00 V:[UISearchBar:0x7b0e9590]-(0)-[UITableView:0x7cb4d600]>,
<NSLayoutConstraint:0x7b05cbd0 V:[UITableView:0x7cb4d600]-(0)-[UIView:0x7b0e9210]>,
<NSLayoutConstraint:0x7b05cba0 V:[UIView:0x7b0e9210]-(0)-[UIToolbar:0x7b069f10]>,
<NSLayoutConstraint:0x7b05cb70 V:[UIToolbar:0x7b069f10]-(0)-|   (Names: '|':UIView:0x7b064990 )>,
<NSLayoutConstraint:0x7b05ced0 UIView:0x7b0e9210.height == 0.75*UIView:0x7b0e9210.width>,
<NSLayoutConstraint:0x7b05c990 H:|-(0)-[UISearchBar:0x7b0e9590]   (Names: '|':UIView:0x7b064990 )>,
<NSLayoutConstraint:0x7b05c940 H:[UISearchBar:0x7b0e9590]-(0)-|   (Names: '|':UIView:0x7b064990 )>,
<NSLayoutConstraint:0x7b05c8a0 H:|-(0)-[UIView:0x7b0e9210]   (Names: '|':UIView:0x7b064990 )>,
<NSLayoutConstraint:0x7b05c850 H:[UIView:0x7b0e9210]-(0)-|   (Names: '|':UIView:0x7b064990 )>,
<NSLayoutConstraint:0x7b05c780 H:|-(0)-[UITableView:0x7cb4d600]   (Names: '|':UIView:0x7b064990 )>,
<NSLayoutConstraint:0x7b05c730 H:[UITableView:0x7cb4d600]-(0)-|   (Names: '|':UIView:0x7b064990 )>,
<NSLayoutConstraint:0x7b05c6c0 H:|-(0)-[UIToolbar:0x7b069f10]   (Names: '|':UIView:0x7b064990 )>,
<NSLayoutConstraint:0x7b05c670 H:[UIToolbar:0x7b069f10]-(0)-|   (Names: '|':UIView:0x7b064990 )>

Landscape constraints:

<NSLayoutConstraint:0x7b05c530 V:|-(0)-[_UILayoutGuide:0x7b0648a0]   (Names: '|':UIView:0x7b064990 )>,
<NSLayoutConstraint:0x7b05c4e0 V:[_UILayoutGuide:0x7b0648a0]-(0)-[UISearchBar:0x7b0e9590]>,
<NSLayoutConstraint:0x7b05c4b0 V:[UISearchBar:0x7b0e9590]-(0)-[UIView:0x7b0e9210]>,
<NSLayoutConstraint:0x7b05c480 V:[UIView:0x7b0e9210]-(0)-[UIToolbar:0x7b069f10]>,
<NSLayoutConstraint:0x7b05c450 V:[UIToolbar:0x7b069f10]-(0)-|   (Names: '|':UIView:0x7b064990 )>,
<NSLayoutConstraint:0x7b05c290 V:|-(0)-[_UILayoutGuide:0x7b0648a0]   (Names: '|':UIView:0x7b064990 )>,
<NSLayoutConstraint:0x7b05c240 V:[_UILayoutGuide:0x7b0648a0]-(0)-[UISearchBar:0x7b0e9590]>,
<NSLayoutConstraint:0x7b05c210 V:[UISearchBar:0x7b0e9590]-(0)-[UITableView:0x7cb4d600]>,
<NSLayoutConstraint:0x7b05c1e0 V:[UITableView:0x7cb4d600]-(0)-[UIToolbar:0x7b069f10]>,
<NSLayoutConstraint:0x7b05c1b0 V:[UIToolbar:0x7b069f10]-(0)-|   (Names: '|':UIView:0x7b064990 )>,
<NSLayoutConstraint:0x7b05bf70 H:|-(0)-[UITableView:0x7cb4d600]   (Names: '|':UIView:0x7b064990 )>,
<NSLayoutConstraint:0x7b05bf20 H:[UITableView:0x7cb4d600]-(0)-[UIView:0x7b0e9210]>,
<NSLayoutConstraint:0x7b05bef0 H:[UIView:0x7b0e9210]-(0)-|   (Names: '|':UIView:0x7b064990 )>,
<NSLayoutConstraint:0x7b05c2f0 UIView:0x7b0e9210.height == 0.75*UIView:0x7b0e9210.width>,
<NSLayoutConstraint:0x7b05be30 H:|-(0)-[UISearchBar:0x7b0e9590]   (Names: '|':UIView:0x7b064990 )>,
<NSLayoutConstraint:0x7b05bde0 H:[UISearchBar:0x7b0e9590]-(0)-|   (Names: '|':UIView:0x7b064990 )>,
<NSLayoutConstraint:0x7b05bd70 H:|-(0)-[UIToolbar:0x7b069f10]   (Names: '|':UIView:0x7b064990 )>,
<NSLayoutConstraint:0x7b05bd20 H:[UIToolbar:0x7b069f10]-(0)-|   (Names: '|':UIView:0x7b064990 )>
1
"I have the correct auto layout constraints" No you don't! You wouldn't be getting an error if you did. The error is pretty clear. You've over-determined your constraints. You've got vertical constraints, you've got horizontal constraints, and then you've got this rule that one particular view's height is always exactly 3/4 of its width. You can't have all of them; that view needs room to change its aspect ratio if you are going to pin everything all the way down and all the way across like this.matt
The 4:3 aspect ratio is for the red UIView. I am still not seeing the conflict between my constraints though. Shouldn't the tableview grow/shrink to fit the red UIView in both orientations? (Also, why is the debugger complaining about the constraints that I'm removing???)ansonl
@matt Okay so my constraints are wrong and are causing errors. Could you provide an example of how my constraints are not fitting together? I made the constraints with my ideal layout in mind and I don't see how the constraints could conflict on the iPhone screen size so perhaps my mental image is not accounting for all possible conflicts in the layout. I have added a list of my programmatic portrait and landscape constraints to the question. Thanks.ansonl
So the constraint debug error is thrown before updateViewConstraints where I swap my constraints...I think that is the problem. Where should I be setting my new constraints? I was going according to this SO post: stackoverflow.com/questions/17772922/… but apparently putting the update code in updateViewConstraints isn't right. Right now I can band-aid patch this by calling setNeedsUpdateConstraints in viewWillLayoutSubviews after the constraints have been set a first time, but not too optimal... :(ansonl
"Could you provide an example of how my constraints are not fitting together?" I don't have to - that is what the layout engine is doing!matt

1 Answers

0
votes

I also filed an Apple Bug Report and TSI for this and got good answers back.

The solution for a method called before the device's returned orientation changes depends on what iOS versions you want to support.

iOS < 8 willRotateToInterfaceOrientation:

- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
    [super willRotateToInterfaceOrientation:toInterfaceOrientation duration:duration];

    [self.view setNeedsUpdateConstraints];
}

iOS 8+ viewWillTransitionToSize: and some checks to determine which orientation we are in.

- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator
{
    if (size.width < size.height && self.view.bounds.size.width > self.view.bounds.size.height) {
        [NSLayoutConstraint deactivateConstraints:landscapeConstraints];
    } else if (size.width > size.height && self.view.bounds.size.width < self.view.bounds.size.height) {
        [NSLayoutConstraint deactivateConstraints:portraitConstraints];
    }
    [self.view setNeedsUpdateConstraints];
}