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>>
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:
Now, my UIScrollView's contentSize width is set properly and things align to its right edge as expected.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With