Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Subview won't align trailing edge to its parent UIScrollView

I have a UIScrollView with 2 subviews. I'd like one subview to be "leading-aligned" (left-aligned) where its leading edge lines up with the leading edge of the scrollview. I'd like the other subview to be "trailing-aligned" (right-aligned) where its trailing edge lines up with the trailing edge of the scrollview.

For some reason, autolayout keeps unexpectedly placing the second, trailing-aligned subview outside the bounds of the scrollview, to the leading (left) side of the other subview, such that the subview's trailing edge lines up with the leading edge of the scrollview.

I'm trying to do this programmatically. Code is below. I'm using 2 labels for the 2 subviews. The "alpha" label is correctly leading-aligned, but the "beta" label is not trailing-aligned as it should be.

This also happens if I try using left and right alignments instead of leading and trailing. The right-aligned label show up in the same incorrect place as the trailing-aligned label.

I've read through the iOS 6 release notes and answers here and elsewhere many times and I'm just not sure why this is happening.

In the view controller:


- (void) viewDidLoad
{
    [super viewDidLoad];

    // Create and configure the scroll view.
    UIScrollView * scrollView = [[UIScrollView alloc] init];
    [scrollView setTranslatesAutoresizingMaskIntoConstraints:NO];

    // For debugging.
    [scrollView setClipsToBounds:NO];
    scrollView.layer.borderColor = [UIColor redColor].CGColor;
    scrollView.layer.borderWidth = 1.0;

    [[self view] addSubview:scrollView];

    // Layout scrollview.

    // Horizontal: leading edge to superview's leading edge, with indent.
    [self.view addConstraint:[NSLayoutConstraint constraintWithItem:scrollView
                                                          attribute:NSLayoutAttributeLeading
                                                          relatedBy:NSLayoutRelationEqual
                                                             toItem:self.view
                                                          attribute:NSLayoutAttributeLeading
                                                         multiplier:1.0
                                                           constant:20.0]];

    // Horizontal: trailing edge to superview's trailing edge, with indent.
    [self.view addConstraint:[NSLayoutConstraint constraintWithItem:scrollView
                                                          attribute:NSLayoutAttributeTrailing
                                                          relatedBy:NSLayoutRelationEqual
                                                             toItem:self.view
                                                          attribute:NSLayoutAttributeTrailing
                                                         multiplier:1.0
                                                           constant:-20.0]];

    // Vertical: top edge to superview's top edge, with indent.
    [self.view addConstraint:[NSLayoutConstraint constraintWithItem:scrollView
                                                          attribute:NSLayoutAttributeTop
                                                          relatedBy:NSLayoutRelationEqual
                                                             toItem:self.view
                                                          attribute:NSLayoutAttributeTop
                                                         multiplier:1.0
                                                           constant:20.0]];

    // Vertical: bottom edge to superview's bottom edge, with indent.
    [self.view addConstraint:[NSLayoutConstraint constraintWithItem:scrollView
                                                          attribute:NSLayoutAttributeBottom
                                                          relatedBy:NSLayoutRelationEqual
                                                             toItem:self.view
                                                          attribute:NSLayoutAttributeBottom
                                                         multiplier:1.0
                                                           constant:-20.0]];

    // Create and configure first label which should be leading-aligned with scrollview.
    UILabel * labelAlpha = [[UILabel alloc] init];
    [labelAlpha setTranslatesAutoresizingMaskIntoConstraints:NO];
    [labelAlpha setText:@"Alpha"];
    [scrollView addSubview:labelAlpha];

    // Layout first label.

    // Horizontal: leading edge to scrollview's leading edge.
    [scrollView addConstraint:[NSLayoutConstraint constraintWithItem:labelAlpha
                                                           attribute:NSLayoutAttributeLeading
                                                           relatedBy:NSLayoutRelationEqual
                                                              toItem:scrollView
                                                           attribute:NSLayoutAttributeLeading
                                                          multiplier:1.0
                                                            constant:0.0]];

    // Vertical: top edge to scrollview's top edge.
    [scrollView addConstraint:[NSLayoutConstraint constraintWithItem:labelAlpha
                                                           attribute:NSLayoutAttributeTop
                                                           relatedBy:NSLayoutRelationEqual
                                                              toItem:scrollView
                                                           attribute:NSLayoutAttributeTop
                                                          multiplier:1.0
                                                            constant:0.0]];


    // Create and configure second label which should be trailing-aligned with scrollview.
    UILabel * labelBeta = [[UILabel alloc] init];
    [labelBeta setTranslatesAutoresizingMaskIntoConstraints:NO];
    [labelBeta setText:@"Beta"];
    [scrollView addSubview:labelBeta];

    // Layout second label.

    // Horizontal: trailing edge to scrollview's trailing edge.
    [scrollView addConstraint:[NSLayoutConstraint constraintWithItem:labelBeta
                                                           attribute:NSLayoutAttributeTrailing
                                                           relatedBy:NSLayoutRelationEqual
                                                              toItem:scrollView
                                                           attribute:NSLayoutAttributeTrailing
                                                          multiplier:1.0
                                                            constant:0.0]];
    // Vertical: top edge to scrollview's top edge.
    [scrollView addConstraint:[NSLayoutConstraint constraintWithItem:labelBeta
                                                           attribute:NSLayoutAttributeTop
                                                           relatedBy:NSLayoutRelationEqual
                                                              toItem:scrollView
                                                           attribute:NSLayoutAttributeTop
                                                          multiplier:1.0
                                                            constant:0.0]];

}

For reference, the output from the view controller's view's recursiveDescription backs up the misalignment:

2013-07-15 22:04:23.892 Middleman[5669:907] <UIView: 0x20872960; frame = (0 0; 320 480); transform = [0, -1, 1, 0, 0, 0]; autoresize = RM+BM; layer = <CALayer: 0x20871e60>>
   | <UIScrollView: 0x208718a0; frame = (20 20; 440 280); gestureRecognizers = <NSArray: 0x20871f20>; layer = <CALayer: 0x208728e0>; contentOffset: {0, 0}>
   |    | <UILabel: 0x20872ab0; frame = (0 0; 44 21); text = 'Alpha'; clipsToBounds = YES; userInteractionEnabled = NO; layer = <CALayer: 0x20872b90>>
   |    | <UILabel: 0x208730e0; frame = (-36 0; 36 21); text = 'Beta'; clipsToBounds = YES; userInteractionEnabled = NO; layer = <CALayer: 0x20873170>>
like image 880
Pentalon Avatar asked Jul 16 '13 02:07

Pentalon


2 Answers

I struggled through the same problem and finally found a solution. Note that I am not using objective C, I am using MonoTouch (C#), but the principle should be the same.

The problem is the way that AutoLayout resolves constraints for a UIScrollView. For many other views, autolayout uses the view's alignment rect, which in many cases coincides with the frame of the view. In UIScrollView, however, it seems to use the size (e.g., width) of the frame if the constraint is centered (e.g., centerX), which is why my subview centered normally within the UIScrollView, but when adding constraints to right-align a subview it uses the contentSize width/height. The problem is that the contentSize width was 0 in my case (only vertically scrolling), so right-aligning just meant aligning to a 0 width box, which I why I saw my subview on the left edge.

Normally, I would have the contentSize resolved by constraints as well, but in this case, only the height contentSize was non-zero since I was scrolling vertically. I tried adding constraints in the scroll view to also get the contentSize width to be set properly, but the only way I could do this (while keeping the necessary constraints for my page) was to do the following:

  1. Insert a "blank" UIView as a child of the scrollView.
  2. Set this UIView's width to the width of the highest level View container (above scrollView) using constraints
  3. Apply a constraint to the scrollView such that its right edge must be greater than or equal to the right edge of my blank UIView (similar to the constraint used to set the vertical contentSize for scrolling the page).

Now, my UIScrollView's contentSize width is set properly and things align to its right edge as expected.

like image 114
Camputer Avatar answered Nov 05 '22 02:11

Camputer


My solution to this is to insert some kind of helperView (e.g. UIView, UITableView) into UIScrollView and apply 6 constraints to the helperView:

- equal width to scrollView 
- equal height to scrollView 
- 0 leading space to scrollView 
- 0 trailing space to scrollView 
- 0 top space to scrollView 
- 0 bottom space to scrollView 

You can than align other scrollView's child views towards the helperView edges.

like image 24
dobranoc Avatar answered Nov 05 '22 01:11

dobranoc