UIScrollView with iOS Auto Layout Constraints: Wrong size for subviews

13,797

A couple of observations:

  1. Constraints for subviews in scroll views don't work like constraints in other views. They're used to set the contentSize of the scroll view. (See TN2154.) That way, you throw a bunch of stuff on a scroll view, set the constraints for the stuff inside it, and the contentSize is calculated for you. It's very cool feature, but it's antithetical to what you're trying to do here.

  2. Worse, buttons will, unless you set an explicit constraint for their width and height of a button, will resize according to their content.

The net effect of these two observations is that your existing constraints say "(a) set my container to be the size of my button; (b) let my button resize itself dynamically to the size of the text; and (c) set my scrollview's contentSize according to the size of my container (which is the size of the button)."

I'm unclear as to what the business problem is. But here are some constraints that achieve what I think your technical question was:

- (void)viewDidLoad
{
    [super viewDidLoad];

    UIView *view = self.view;

    UIScrollView *scrollView = [[UIScrollView alloc] init];
    scrollView.backgroundColor = [UIColor redColor]; // just so I can see it
    scrollView.translatesAutoresizingMaskIntoConstraints = NO;
    [self.view addSubview:scrollView];

    UIView *containerView = [[UIView alloc] init];
    containerView.backgroundColor = [UIColor yellowColor]; // just so I can see it
    containerView.translatesAutoresizingMaskIntoConstraints = NO;
    [scrollView addSubview:containerView];

    UIButton *button = [UIButton buttonWithType:UIButtonTypeRoundedRect];
    button.translatesAutoresizingMaskIntoConstraints = NO;
    [button setTitle:@"I'm the right size" forState:UIControlStateNormal];
    [containerView addSubview:button];

    NSDictionary *views = NSDictionaryOfVariableBindings(scrollView, button, view, containerView);

    // set the scrollview to be the size of the root view

    [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[scrollView]|"
                                                                      options:0
                                                                      metrics:nil
                                                                        views:views]];

    [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[scrollView]|"
                                                                      options:0
                                                                      metrics:nil
                                                                        views:views]];

    // set the container to the size of the main view, and simultaneously
    // set the scrollview's contentSize to match the size of the container

    [view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[containerView(==view)]|"
                                                                       options:0
                                                                       metrics:nil
                                                                         views:views]];

    [view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[containerView(==view)]|"
                                                                       options:0
                                                                       metrics:nil
                                                                         views:views]];

    // set the button size to be the size of the container view

    [containerView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[button(==containerView)]"
                                                                          options:0
                                                                          metrics:nil
                                                                            views:views]];

    [containerView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[button(==containerView)]"
                                                                          options:0
                                                                          metrics:nil
                                                                            views:views]];

}

Frankly, I don't understand the business intent of your UI, as this feels like a contortion of auto layout to achieve a very simply UI. I don't know why you have a scroll view if you have "screen sized" content in it (unless you were paging through buttons). I don't know why you'd have a content view with a single item in it. I don't understand why you're using a full-screen button (I'd just put a tap gesture on the root view at that point and call it a day).

I'll assume you have good reasons for all of this, but it might make sense to back up, ask what is your desired user experience is, and then approach the problem fresh to see if there's a more efficient way to achieve the desired effect.

Share:
13,797
Chrizzor
Author by

Chrizzor

Updated on July 28, 2022

Comments

  • Chrizzor
    Chrizzor over 1 year

    I'm trying to generate a view in code. Here's the hierachy of my view object

    • UIScrollView
      • UIView
        • UIButton

    The ScrollView should be the same size as the window. The button should be as big as possible. I'm using iOS auto layout, so the constraint strings for all of my objects look like this

    H:|[object]|
    V:|[object]|
    

    I've also set translatesAutoresizingMaskIntoConstraints to NO for each object.

    The problem is that the button only gets the default button-size. Its parent view object (UIView) only gets the size its subviews need.

    enter image description here

    red: UIScrollView / yellow: UIView

    How can I force those views to be as big as the scrollView?

    When I use a UIView instead of th UIScrollView everything works great...

    Here's some code:

        - (void) viewDidLoad {
    
            [super viewDidLoad];
    
            // SCROLL VIEW
            UIScrollView* scrollView = [UIScrollView new];
            scrollView.backgroundColor=[UIColor redColor];
            scrollView.translatesAutoresizingMaskIntoConstraints = NO;
    
            //CONTAINER VIEW
            UIView *containerView = [UIView new];
            containerView.translatesAutoresizingMaskIntoConstraints = NO;
            containerView.backgroundColor = [UIColor yellowColor];
            [scrollView addSubview:containerView];
    
            // CONSTRAINTS SCROLL VIEW - CONTAINER VIEW
            [scrollView addConstraints:
             [NSLayoutConstraint constraintsWithVisualFormat:@"H:|[containerView]|"
                                                     options:0 metrics:nil
                                                       views:@{@"containerView":containerView}]];
            [scrollView addConstraints:
             [NSLayoutConstraint constraintsWithVisualFormat:@"V:|[containerView]|"
                                                     options:0 metrics:nil
                                                       views:@{@"containerView":containerView}]];
    
            // BUTTON
            UIButton* button = [UIButton buttonWithType:UIButtonTypeRoundedRect];
            button.translatesAutoresizingMaskIntoConstraints = NO;
            [button setTitle:@"I'm way to small" forState:UIControlStateNormal];
            [containerView addSubview:button];
    
            // CONSTRAINTS CONTAINER VIEW - BUTTON
            [containerView addConstraints:
             [NSLayoutConstraint constraintsWithVisualFormat:@"H:|[button]|"
                                                     options:0 metrics:nil
                                                       views:@{@"button":button}]];
            [containerView addConstraints:
             [NSLayoutConstraint constraintsWithVisualFormat:@"V:|[button]|"
                                                     options:0 metrics:nil
                                                       views:@{@"button":button}]];
            self.view = scrollView;
    
        }
    

    UPDATE: I really don't know, why this is happening. If you set up the view in IB, connect the outlets and instanciate the view in code, the scrollview behaves like a normal view (which bounces vertically). Its contentSize is not calculated correctly. More here. But how to do it correctly?

  • Chrizzor
    Chrizzor almost 11 years
    "I don't understand the business intent of your UI..." I'm trying to auto generate any UI based on its description. The button in the example could be any set of subviews and their subviews. In some cases the description says, that a subview has to be as wide as the superview (no explizit width). @Rob Thank you so much!!!!!!
  • Chrizzor
    Chrizzor almost 11 years
    The height of he content size was already calculated correctly in my example, so vertical scrolling was working. I was searching for a contentSize like this: scrollView.contentSize = CGSizeMake(scrollView.frame.size.width, ?). To do so, remove all vertical constraints from @Rob s example: Scrolling is still working and all subviews can be as wide as the scroll view without giving them an explicit width.