UIScrollView with Auto Layout Constraints: Auto Content Size Calculation

| | August 7, 2015

I’m having troubles with UIScrollView using auto layout constraints. I have the following view hierarchy, with constraints set through IB:

- ScrollView (leading, trailing, bottom and top spaces to Superview)
-- ContainerView (leading, trailing, bottom and top spaces to ScrollView)
--- Button 1 (full width, **top space to ContainerView**)
--- Button 2 (full width, below Button 1)
--- Button n (full width, below Button n-1, **bottom space to ContainerView**)

enter image description here

I want a simple scrollabel list of buttons. Here is my code:

- (void)viewDidLoad
{
    [super viewDidLoad];

    [self.view setBackgroundColor:[UIColor redColor]];
    [self.contentView setBackgroundColor:[UIColor yellowColor]];

    UIView *lastView= self.contentView; // use for top constraint

    NSInteger topBottomMargin= 10, leftRightMargin= 16;
    for (int i=0; i<10; i++) {
        UIButton *button= [UIButton buttonWithType:UIButtonTypeSystem];
        button.translatesAutoresizingMaskIntoConstraints= NO;
        [button setTitle:[NSString stringWithFormat:@"Button %d", i] forState:UIControlStateNormal];

        [self.contentView addSubview:button];

        // add constraints
        // top
        [self.contentView addConstraint:[NSLayoutConstraint
                                         constraintWithItem:lastView
                                         attribute:NSLayoutAttributeBottom
                                         relatedBy:NSLayoutRelationGreaterThanOrEqual
                                         toItem:button
                                         attribute:NSLayoutAttributeTop
                                         multiplier:1.0 constant:-topBottomMargin]];
        // left
        [self.contentView addConstraint:[NSLayoutConstraint
                                         constraintWithItem:self.contentView
                                         attribute:NSLayoutAttributeLeading
                                         relatedBy:NSLayoutRelationEqual
                                         toItem:button
                                         attribute:NSLayoutAttributeLeading
                                         multiplier:1.0 constant:-leftRightMargin]];
        // right
        [self.contentView addConstraint:[NSLayoutConstraint
                                         constraintWithItem:self.contentView
                                         attribute:NSLayoutAttributeTrailing
                                         relatedBy:NSLayoutRelationEqual
                                         toItem:button
                                         attribute:NSLayoutAttributeTrailing
                                         multiplier:1.0 constant:leftRightMargin]];

        lastView= button;
    }
    // bottom
    [self.contentView addConstraint:[NSLayoutConstraint
                                     constraintWithItem:self.contentView
                                     attribute:NSLayoutAttributeBottom
                                     relatedBy:NSLayoutRelationEqual
                                     toItem:lastView
                                     attribute:NSLayoutAttributeBottom
                                     multiplier:1.0 constant:topBottomMargin]];    
}

It seems the height of contentView is 0! But there are constraints both for top and bottom of it. It should be like this:

enter image description here

But with my code it’s like this. Any Help would be great.

enter image description here

4 Responses to “UIScrollView with Auto Layout Constraints: Auto Content Size Calculation”

  1. I found the solution i was looking for here:

    [super viewDidLoad];
    UIScrollView* sv = [UIScrollView new];
    sv.backgroundColor = [UIColor whiteColor];
    sv.translatesAutoresizingMaskIntoConstraints = NO;
    [self.view addSubview:sv];
    [self.view addConstraints:
     [NSLayoutConstraint constraintsWithVisualFormat:@"H:|[sv]|"
                                             options:0 metrics:nil
                                               views:@{@"sv":sv}]];
    [self.view addConstraints:
     [NSLayoutConstraint constraintsWithVisualFormat:@"V:|[sv]|"
                                             options:0 metrics:nil
                                               views:@{@"sv":sv}]];
    UILabel* previousLab = nil;
    for (int i=0; i<30; i++) {
        UILabel* lab = [UILabel new];
        lab.translatesAutoresizingMaskIntoConstraints = NO;
        lab.text = [NSString stringWithFormat:@"This is label %i", i+1];
        [sv addSubview:lab];
        [sv addConstraints:
         [NSLayoutConstraint constraintsWithVisualFormat:@"H:|-(10)-[lab]"
                                                 options:0 metrics:nil
                                                   views:@{@"lab":lab}]];
        if (!previousLab) { // first one, pin to top
            [sv addConstraints:
             [NSLayoutConstraint constraintsWithVisualFormat:@"V:|-(10)-[lab]"
                                                     options:0 metrics:nil
                                                       views:@{@"lab":lab}]];
        } else { // all others, pin to previous
            [sv addConstraints:
             [NSLayoutConstraint
              constraintsWithVisualFormat:@"V:[prev]-(10)-[lab]"
                                      options:0 metrics:nil
                                        views:@{@"lab":lab, @"prev":previousLab}]];
        }
        previousLab = lab;
    }
    // last one, pin to bottom and right, this dictates content size height
    [sv addConstraints:
     [NSLayoutConstraint constraintsWithVisualFormat:@"V:[lab]-(10)-|"
                                             options:0 metrics:nil
                                               views:@{@"lab":previousLab}]];
    [sv addConstraints:
     [NSLayoutConstraint constraintsWithVisualFormat:@"H:[lab]-(10)-|"
                                             options:0 metrics:nil
                                               views:@{@"lab":previousLab}]];
    // look, Ma, no contentSize!
    
  2. I don’t think we can’t add auto layout directly to a ContainerView inside ScrollView with Intrinsic Size as Default, so I add ContainerView as subview programmatically:

    - (void)viewDidLoad{
        [super viewDidLoad];
    
        self.contentView = [[UIView alloc] initWithFrame:CGRectZero];
        [self.scrollView addSubview:self.contentView];
    
        //Then add your button here as normal.
        //...
    }
    

    And Gurtej Singh is right, we have to update the frame in the viewDidLayoutSubviews:

    - (void)viewDidLayoutSubviews
     {
         [super viewDidLayoutSubviews];
        //Don't for get to update your self.scrollView.contentSize if you want to able to scroll
        //...
    
        //Update your contentView frame based on scrollview frame and self.scrollView.contentSize.
         self.contentView.frame = self.scrollView.bounds or ....;
     }
    

    I just want to help, it might not a good solution, but it work for me.

  3. swapnali patil on November 30, -0001 @ 12:00 AM

    You can add constraint to container view to scroll view as Equal height & equal width. Also when you add constraint to buttons don’t forget add bottom constraints to buttons as it will decide the end of scroll view(content size).

  4. Since you are using auto-layout constraints on the contentView, it’s height (frame) will be zero in the viewDidLoad method. You should move your code into the viewDidLayoutSubviews method and try to add your buttons there.

    You should get the height of the contentView there. Please let me know if that works. Hope this helps.

    See this question for reference: iOS AutoLayout – get frame size width

Leave a Reply