Search code examples
objective-cios7migrationios8

Different layout on different orientations on iPad ios8


For my application I've designed a view that has two subviews, which change their location when device is rotated. Namely, they are stacked while in portrait view and side by side while in horizontal. Code I wrote for ios7 worked flawlessly. After migrating to ios8 I cannot make it to work again. View will display correctly when appearing first, but after rotation or getting back from other screen it seems like both views are rendered outside of the screen (I see it during animation). I cannot figure out what am I doing wrong here. Logs are silent about constraints that cannot be satisfied.

My code related to display, constraints and rotation:

    - (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator
    {
        [coordinator animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> context) {
            UIInterfaceOrientation o = size.height > size.width ? UIInterfaceOrientationPortrait : UIInterfaceOrientationLandscapeLeft;
            [self updateLayoutWithOrientation:o];
        } completion:nil];
        [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
    }

    - (void)viewWillAppear:(BOOL)animated
    {
        [super viewWillAppear:animated];
        [self updateLayoutWithOrientation:[[UIApplication sharedApplication] statusBarOrientation]];
    }

    - (void)updateLayoutWithOrientation:(UIInterfaceOrientation)o
    {
        [self.view removeConstraints:self.view.constraints];
        if (UIInterfaceOrientationIsPortrait(o)) {
            [self setDetailsAndGalleryViewsConstraints:@"V:|-[galleryView(<=664)]-[detailsView]-|"];
            [self setGalleryViewConstraint:@"H:|-[galleryView]-|"];
            [self setDetailsViewConstraint:@"H:|-[detailsView]-|"];
        } else {
            [self setDetailsAndGalleryViewsConstraints:@"H:|-[galleryView(==664)]-[detailsView]-|"];
            [self setGalleryViewConstraint:@"V:|-[galleryView]-|"];
            [self setDetailsViewConstraint:@"V:|-[detailsView]-|"];
        }
    }

    - (void)setGalleryViewConstraint:(NSString *)format
    {
        [self setConstraint:format onView:self.view views:NSDictionaryOfVariableBindings(galleryView);];
      }

    - (void)setDetailsViewConstraint:(NSString *)format
    {
        [self setConstraint:format onView:self.view views:NSDictionaryOfVariableBindings(detailsView)];
    }

    - (void)setDetailsAndGalleryViewsConstraints:(NSString *)format
    {
        [self setConstraint:format onView:self.view views:NSDictionaryOfVariableBindings(galleryView, detailsView)];
    }
    - (void)setConstraint:(NSString *)format onView:(UIView *)targetView views:(NSDictionary *)views
    {
         [targetView addConstraints:[NSLayoutConstraint
                           constraintsWithVisualFormat:format
                           options:NSLayoutFormatDirectionLeadingToTrailing
                           metrics:nil
                           views:views
                           ]];
    }

UPDATE

Following @sha suggestion I've inspected constraints at runtime. Modified code a little to see how relations are translated:

[self setDetailsAndGalleryViewsConstraints:@"V:|-(1)-[galleryView(<=664)]-(2)-[detailsView]-(3)-|"];
[self setGalleryViewConstraint:@"H:|-(4)-[galleryView]-(5)-|"];
[self setDetailsViewConstraint:@"H:|-(6)-[detailsView]-(7)-|"];

And the results are:

Runtime constraints in inspector

Thing that surprises me is that first view has top specified twice, once to superview and then to its sibling. Yet I have no idea if it is expected and if not, how to fix it.

UPDATE

I've tried to skip setting up layout manually and use size classes instead, but it looks like iPad has regular width and regular height so it is not possible to make changes for certain orientations. Tried to override traits collection with no luck

- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator
{
    [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
    // tried it also in coordinator:animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> method
    [self updateLayoutWithOrientation:size];
}

- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];
    [self updateLayoutWithOrientation:self.view.bounds.size];
}

- (void)updateLayoutWithOrientation:(CGSize)screenSize
{
    NSArray *traitArray;
    if (screenSize.height > screenSize.width) {
        traitArray = [NSArray arrayWithObjects:
                      [UITraitCollection traitCollectionWithVerticalSizeClass:UIUserInterfaceSizeClassRegular],
                      [UITraitCollection traitCollectionWithHorizontalSizeClass:UIUserInterfaceSizeClassRegular],
                      nil];
    } else {
        traitArray = [NSArray arrayWithObjects:
                      [UITraitCollection traitCollectionWithVerticalSizeClass:UIUserInterfaceSizeClassCompact],
                      [UITraitCollection traitCollectionWithHorizontalSizeClass:UIUserInterfaceSizeClassRegular],
                      nil];
    }

    UITraitCollection *traits = [UITraitCollection traitCollectionWithTraitsFromCollections:traitArray];
    [self setOverrideTraitCollection:traits forChildViewController:self];
}

Solution

  • I am also fighting with the constraints in iOS 8 (no problem in iOS 7)

    I discovered that the call

    [self.view removeConstraints:self.view.constraints];
    

    does not remove ALL constraints!

    Finally, to solve my issue and remove all warnings, I played with the constraints priority. In this way I don't need to remove any constraint and I don't get any warning. Hope this can help.