Autolayout Breaks Unambiguous Constraints After dismissViewControllerAnimated:

Originator:xitsej
Number:rdar://14250877 Date Originated:6/24/13
Status:Open Resolved:No
Product:iOS SDK Product Version:6.1
Classification:UI Reproducible:
 
Summary:
After presenting and dismissing a view controller over another view controller containing a scroll view, the contents of that scroll view are shifted upwards, breaking the constraints set on those contents.

Steps to Reproduce:

1) Set up the following in a project:
- A view controller (viewController1)
- A scroll view in viewController1 that has constraints:
  scrollView.top = viewController1.top,
  scrollView.bottom = viewController1.bottom
  scrollView.left = viewController1.left
  scrollView.right = viewController1.right
- Put a column using a UIView in the scroll view with some contents (or a background color) that has constraints:
  @"V:|-(>=0)-[_columnView]|"
  column.width = 200
  column.centerX = scrollView.centerX
  columnView.Height = 3000
- Add a tap target (can be the column view) to a view that calls presentViewController:animated:YES completion: (to show a full screen, non-modal, viewController2) and after a few seconds delay, calls dismissViewControllerAnimated:YES completion:

2) Run the project and do the following:
- Scroll down any amount.
- Tap the tap target to present viewController2
- Once viewController2 has been dismissed, notice that the columnView is higher up in the column view than it should be.  Its origin is above the top of the scroll view, breaking constraint: @"V:|-(>=0)-[_columnView]|".
- Note that the amount it moves up is dependent upon the amount you scrolled down initially.  If you didn't scroll down at all, the column view isn't moved at all.

Expected Results:
The column view (subview of the scroll view) stays in the same place because the constraints have not changed.

Actual Results:
The columnView moves up a seemingly arbitrary amount within the scroll view depending on how far down you scrolled, breaking the constraints without an exception being thrown.

Regression:
This has been a bug for as long as auto layout has been available.


----------- SAMPLE CODE ---------- (put this in a UIViewController subclass)

#import "ViewController.h"

@interface ViewController () <UIScrollViewDelegate>

@property(nonatomic) UIScrollView *scrollView;
@property(nonatomic) UIButton *columnView;
@property(nonatomic) UIViewController *pvc;

@end

/*
 Demonstrates an autolayout bug.
 To replicate:
 - Start app
 - Scroll down any amount other than 0
 - Press the red column (also a button)
 - When the yellow view has been (automatically) dismissed, notice that the red view has moved up
    in the scroll view, breaking the vertical constraint: @"V:|-(>=0)-[_columnView]|"
 - To further verify the constraint is broken, put a breakpoint in the scrollViewDidScroll method
    and check the frame of self.columnView.  Notice that the origin's Y coordinate is negative.
 - Note: The further you have scrolled down before pressing the red button, the further up the red
    view will move after the yellow view is dismissed.
 */
@implementation ViewController

- (id)initialize {
  return [super init];
}

- (void)loadView {
  self.view = [[UIView alloc] initWithFrame:[UIScreen mainScreen].bounds];
  self.view.backgroundColor = [UIColor blueColor];
  self.view.autoresizingMask = UIViewAutoresizingFlexibleWidth |
                               UIViewAutoresizingFlexibleHeight;
  self.scrollView = [[UIScrollView alloc] init];
  self.scrollView.backgroundColor = [UIColor greenColor];
  self.scrollView.bounces = YES;
  self.scrollView.alwaysBounceVertical = YES;
  self.scrollView.delegate = self;
  [self.view addSubview:self.scrollView];
  self.scrollView.translatesAutoresizingMaskIntoConstraints = NO;
  [self.view addConstraints:@[
   [NSLayoutConstraint constraintWithItem:self.scrollView
                                attribute:NSLayoutAttributeTop
                                relatedBy:NSLayoutRelationEqual
                                   toItem:self.view
                                attribute:NSLayoutAttributeTop
                               multiplier:1
                                 constant:0],
   [NSLayoutConstraint constraintWithItem:self.scrollView
                                attribute:NSLayoutAttributeBottom
                                relatedBy:NSLayoutRelationEqual
                                   toItem:self.view
                                attribute:NSLayoutAttributeBottom
                               multiplier:1
                                 constant:0],
   [NSLayoutConstraint constraintWithItem:self.scrollView
                                attribute:NSLayoutAttributeLeft
                                relatedBy:NSLayoutRelationEqual
                                   toItem:self.view
                                attribute:NSLayoutAttributeLeft
                               multiplier:1
                                 constant:0],
   [NSLayoutConstraint constraintWithItem:self.scrollView
                                attribute:NSLayoutAttributeRight
                                relatedBy:NSLayoutRelationEqual
                                   toItem:self.view
                                attribute:NSLayoutAttributeRight
                               multiplier:1
                                 constant:0],
   ]];
  CGFloat columnWidth = 200;
  self.columnView = [[UIButton alloc] init];
  [self.scrollView addSubview:self.columnView];
  self.columnView.translatesAutoresizingMaskIntoConstraints = NO;
  self.columnView.backgroundColor = [UIColor redColor];
  NSDictionary *viewsDictionary = NSDictionaryOfVariableBindings(_columnView);
  [self.scrollView addConstraints:[NSLayoutConstraint
                                   constraintsWithVisualFormat:@"V:|-(>=0)-[_columnView]|"
                                   options:0
                                   metrics:nil
                                   views:viewsDictionary]];  
  [self.scrollView addConstraints:@[
   [NSLayoutConstraint constraintWithItem:self.columnView
                                attribute:NSLayoutAttributeWidth
                                relatedBy:NSLayoutRelationEqual
                                   toItem:nil
                                attribute:NSLayoutAttributeNotAnAttribute
                               multiplier:1
                                 constant:columnWidth],
   [NSLayoutConstraint constraintWithItem:self.columnView
                                attribute:NSLayoutAttributeCenterX
                                relatedBy:NSLayoutRelationEqual
                                   toItem:self.scrollView
                                attribute:NSLayoutAttributeCenterX
                               multiplier:1
                                 constant:0],
   [NSLayoutConstraint constraintWithItem:self.columnView
                                attribute:NSLayoutAttributeHeight
                                relatedBy:NSLayoutRelationEqual
                                   toItem:nil
                                attribute:NSLayoutAttributeNotAnAttribute
                               multiplier:1
                                 constant:3000]
   ]];
  [self.columnView addTarget:self
                  action:@selector(presentOtherViewController)
        forControlEvents:UIControlEventTouchUpInside];
  //self.scrollView.contentInset = UIEdgeInsetsMake(5, 0, 100, 0);
}

- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
  // Put a breakpoint here to see the frame of the column view after pressing the button.
}

- (void)presentOtherViewController {
  self.pvc = [[UIViewController alloc] init];
  self.pvc.view = [[UIView alloc] initWithFrame:self.view.bounds];
  self.pvc.view.backgroundColor = [UIColor yellowColor];
  [self presentViewController:self.pvc animated:YES completion:nil];
  [self performSelector:@selector(dismissOtherViewController) withObject:nil afterDelay:3];
}

- (void)dismissOtherViewController {
  [self.pvc dismissViewControllerAnimated:YES completion:nil];
}

@end

Comments


Please note: Reports posted here will not necessarily be seen by Apple. All problems should be submitted at bugreport.apple.com before they are posted here. Please only post information for Radars that you have filed yourself, and please do not include Apple confidential information in your posts. Thank you!