5

I am adding a UIView to a UIScrollView and constraining it such that it fills the horizontal space, except for some margins. My visual constraint looks like this:

@"|-16-[theLineView]-16-|"

I have made the view one pixel high so it will appear as a line, and placed it between two text labels:

@"V:[someOtherStuff]-[aTextLabel]-[theLineView]-[anotherLabel]"

However, I am finding that the width of the line is only expanding as far as the width of the longest label above/below it.

Why would this be?

P.S I have read this http://developer.apple.com/library/ios/#technotes/tn2154/_index.html

enter image description here

Code

Here is the entirety of the view controller code from a test project that exhibits this issue on the iPad sim.

- (void)viewDidLoad

{ [super viewDidLoad];

self.scrollView = [[UIScrollView alloc] init];
self.scrollView.translatesAutoresizingMaskIntoConstraints = NO;
self.scrollView.backgroundColor = [UIColor greenColor];
[self.view addSubview:self.scrollView];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"|[scrollView]|"
                                                                 options:0
                                                                 metrics:0
                                                                   views:@{@"scrollView":self.scrollView}]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[scrollView]|"
                                                                  options:0
                                                                  metrics:0
                                                                    views:@{@"scrollView":self.scrollView}]];

self.line1 = [[UIView alloc] init];
self.line2 = [[UIView alloc] init];
self.label1 = [[UILabel alloc] init];
self.label2 = [[UILabel alloc] init];
self.label3 = [[UILabel alloc] init];

for (UILabel *label in @[self.label1, self.label2, self.label3])
{
    label.text = @"I am a label and I am long enough that I can be multiline on an iphone but single on ipad";
}

for (UIView *view in @[self.line1, self.line2, self.label1, self.label2, self.label3])
{
    view.translatesAutoresizingMaskIntoConstraints = NO;
    view.backgroundColor = [UIColor redColor];
    [self.scrollView addSubview:view];
}

//horizontal layout - all views/labels should fill the horizontal space expect for margin
for (UIView *view in  @[self.line1, self.line2, self.label1, self.label2, self.label3])
{
    NSArray *constraints = [NSLayoutConstraint constraintsWithVisualFormat:@"|-16-[view]-16-|"
                                                                   options:0
                                                                   metrics:0
                                                                     views:@{@"view":view}];
    [self.scrollView addConstraints:constraints];
}

//vertical layout - stack em up
[self.scrollView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-[lab1]-[line1(==1)]-[lab2]-[line2(==1)]-[lab3]-|"
                                                                  options:0
                                                                  metrics:0
                                                                    views:@{@"lab1":self.label1, @"line1":self.line1, @"lab2":self.label2, @"line2":self.line2, @"lab3":self.label3}]];

}

Ben Packard
  • 26,102
  • 25
  • 102
  • 183
  • Does your 1px `UIView` have any other constraints on it? – Yazid Jul 21 '13 at 13:51
  • Is [view] the same view as [theLineView]? Do you have any alignment options setup in the options parameter? – rdelmar Jul 21 '13 at 16:09
  • No other constraints or anything in the options parameter. I will see if I can reproduce in a test project. Yes those views are the same, corrected the snippet. – Ben Packard Jul 21 '13 at 16:24
  • See here: http://stackoverflow.com/questions/16843633/ios-autolayout-with-uiscrollview-why-does-content-view-of-scroll-view-not-fill – catamphetamine Jan 14 '14 at 19:14

4 Answers4

8

UIScrollView automatically shrinks to fit the views inside it. You need to set the width absolutely somewhere.

Recommended tactic:

  1. Completely fixiate the scrollview inside its parent-view using constraints (leading, trailing, top, bottom).
  2. Create a UIView inside the UIScrollView and put everything you need inside it.
  3. Set the constraints so that the UIView will act as a content-view (this means it is big enough to include all elements). Use intrinsic content-size, shrink-resistance and chaining of elements with constraints a lot. The resulting layout must be well-defined and unique (this means if you were to remove all constraints to the outside, the layout would still work).
  4. Connect the bounds of the UIView with their superview (which is the actual content-view of the UIScrollView, NOT the UIScrollView!).

If you do this in interface-builder (it is possible), you need to re-check your constraints every time you touch something in that scene. And by touch I mean "select" not only "modify".

patric.schenke
  • 942
  • 11
  • 19
  • Didn't work for me, how to I set the UIView to fill up the whole width of the scroll view? – AndiDog Sep 24 '13 at 09:58
  • As I said: the contentView ALWAYS fills up the complete scrollView, because the scrollView shrinks down. You HAVE to set the width inside. In your example, changing the format from "|-16-[view]-16-|" to "|-16-[view(288)]-16-|" should give you a good result. – patric.schenke Sep 24 '13 at 10:09
  • 3
    This works. Number 4 is the better approach in my opinion since it is simple and avoids any magic numbers (which means it supports rotation etc). – Ben Packard Sep 24 '13 at 12:36
2

Found a working solution that should work for your use case, too. See here.

Community
  • 1
  • 1
AndiDog
  • 68,631
  • 21
  • 159
  • 205
2

Expanding on number 4 of Patric Schenke's answer; because the content size of scrollView is fluid, pinning an internal view to its edges just doesn't work for determining the width of the view. Your left side pin will work, but both won't. Calculating the width of your view based on the next level container up is the way to go. As long as your self.scrollView is pinned flush to its container(which I call containerView), this line of code will accomplish what you want. Add this line to your for loop for horizontal constraints:

// Pin view's width to match the scrollView container's width
// -32 constant offset for margins
[containerView addConstraint:
  [NSLayoutConstraint constraintWithItem:view 
                               attribute:NSLayoutAttributeWidth
                               relatedBy:NSLayoutRelationEqual
                                  toItem:containerView
                               attribute:NSLayoutAttributeWidth
                              multiplier:1.0f
                                constant:-32]];
Joel H.
  • 2,764
  • 3
  • 22
  • 34
0

I found a simple constraint-based way to accomplish this (I haven't tested the extent of the brittleness of this solution):

...@"H:|-16-[view]-16-|"... // (your original constraint)

[self.scrollView addConstraint:
  [NSLayoutConstraint constraintWithItem:view 
                               attribute:NSLayoutAttributeCenterX
                               relatedBy:NSLayoutRelationEqual
                                  toItem:self.scrollView
                               attribute:NSLayoutAttributeCenterX
                              multiplier:1.0f
                                constant:0.0f]];

This stretches view all the way out to the other side of the view. This is probably not ideal for content that scrolls horizontally, but should work vertically.

I realize this is over a year later, but it's simpler for the single dimensional scrolling use case than patric.schenke's answers (which are good and more robust).

DanBlakemore
  • 2,306
  • 2
  • 20
  • 23