Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

UIScrollView with iOS Auto Layout Constraints: Wrong size for subviews

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?

like image 548
Chrizzor Avatar asked May 08 '13 13:05

Chrizzor


1 Answers

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.

like image 132
Rob Avatar answered Nov 15 '22 21:11

Rob