UIScrollView and Auto Layout.
Posted: | Author: Jörn | Filed under: iOS, Swift | Tags: Auto Layout, Masonry, SnapKit | 9 Comments »Some people still seem to struggle when it comes to using Apple’s Auto Layout in a UIScrollView. There are a lot of questions on StackOverflow like “Why is my UIScrollView not scrolling when using AutoLayout?”
So here is a short explanation on how to use Auto Layout with a UIScrollView that should scroll vertically:
There are just a few things you have to take care of:
1. The topmost subview must have a top constraint with the UIScrollView
2. All other subviews must have a top constraint with the bottom constraint of the subview above them
3. The bottommost subview must have a bottom constraint with the UIScrollView
To ensure that the UIScrollView only scrolls vertically you have to make sure that its subviews don’t become wider than the UIScrollView.
Do not rely on left and right constraints to define the width of a subview. If for example you have a UILabel that has a lot of text and should break into several lines, it just won’t, even if you set its numberOfLines property to 0. That’s because the UIScrollView will give it enough space by allowing horizontal scrolling. So if you just set a left and right constraint on the UILabel the UIScrollView will scroll horizontal and the label will be very wide and have only 1 line.
Instead you should define a left and a width constraint. Set the width constraint to the width of the UIScrollView and the UILabel will not become wider than the UIScrollView. It will wrap into multiple lines instead.
If you follow those steps you don’t have to set the UIScrollView’s contentSize property any more to make the UIScrollView scroll. Auto Layout will handle that for you.
To make it more clear, here is an image with the constraints that you have to set:
If you are using Masonry or SnapKit here is a code example on how to set the constraints programmatically:
topView.snp_makeConstraints { (make) -> Void in
make.top.equalTo(0)
make.left.equalTo(0)
make.width.equalTo(scrollView)
}
label1.snp_makeConstraints { (make) -> Void in
make.top.equalTo(topView.snp_bottom)
make.left.equalTo(0)
make.width.equalTo(scrollView)
}
label2.snp_makeConstraints { (make) -> Void in
make.top.equalTo(label1.snp_bottom)
make.left.equalTo(0)
make.width.equalTo(scrollView)
}
label3.snp_makeConstraints { (make) -> Void in
make.top.equalTo(label2.snp_bottom)
make.left.equalTo(0)
make.width.equalTo(scrollView)
make.bottom.equalTo(0)
}
1
Mareksaid atThank you! I have been trying do it this for an hours. Your solution works. Thanks a lot again.
2
Juliansaid atHey Jörn, do you think you could write out how it would work for a horizontal scroll. I know if should translate well but I’m getting a stuck scroll when I remove contentsize even when I set the right constraint on the last view.
below is where I’m setting it.
if index == currentStory.count {
let secondToLastbox = textBoxes[currentStory.count-1]
currentView.snp_makeConstraints(closure: { (make) in
make.width.equalTo(storyScrollView.snp_width)
make.height.equalTo(storyScrollView.snp_height).multipliedBy(0.35)
make.left.equalTo(secondToLastbox.snp_right)
make.right.equalTo(storyScrollView.snp_right))
make.top.equalTo(storyScrollView)
})
}
3
Jörnsaid atHi Julian,
try setting the last view’s right constraint to ‘0’ instead of ‘storyScrollView.snp_right’.
Best regards,
Jörn
4
Juliansaid atHi Jörn!
Thanks for the quick response and for helping! After changing it, I still have a stuck scrollview.
I have commented out this line:
//self.storyScrollView.contentSize = CGSizeMake(self.storyScrollView.frame.width * CGFloat(currentImages.count), 1.0)
And I am creating the views within a for loop defined by the count of an array. Do you think the problem is where I am creating the views and constraints?
Here is the full set of three if statements to cover all constraint possibilities:
if index == 0 {
currentView.snp_makeConstraints(closure: { (make) in
make.width.equalTo(storyScrollView.snp_width)
make.height.equalTo(storyScrollView.snp_height).multipliedBy(0.65)
make.left.equalTo(storyScrollView.snp_left)
make.top.equalTo(storyScrollView)
//make.centerX.equalTo(storyScrollView.snp_centerX)
})
}
if index > 0 && index < currentStory.count{
let previousView = views[index-1]
currentView.snp_makeConstraints(closure: { (make) in
make.width.equalTo(storyScrollView.snp_width)
make.height.equalTo(storyScrollView.snp_height).multipliedBy(0.65)
make.left.equalTo(previousView.snp_right)
make.top.equalTo(storyScrollView)
})
}
if index == currentStory.count {
let secondToLastView = views[currentStory.count-1]
currentView.snp_makeConstraints(closure: { (make) in
make.width.equalTo(storyScrollView.snp_width)
make.height.equalTo(storyScrollView.snp_height).multipliedBy(0.65)
make.left.equalTo(secondToLastView.snp_right)
make.right.equalTo(0)
make.top.equalTo(storyScrollView)
})
}
5
Jörnsaid atHi Julian,
In your last if block it has to be if index == currentStory.count – 1. Otherwise that block won’t be called and the right constraint is never set. That’s why the scroll view does not scroll.
Don’t forget to change the first line in that if block to let secondToLastView = views[currentStory.count-2]
Here is a gist with a working example: https://gist.github.com/pixeldock/3172b70b6b7a96f30f0300ac464f7e18
Best regards,
Jörn
6
Juliansaid atOf course, the classic index mistake!
THANKS! You’re the best!
7
Nunosaid atThanks for this, Jörn!
I got it working without having to set the starting constraint to the ending constraint of the previous view in the scrollview, but by just setting the starting constraint to the right offset value and by setting the last view’s ending constraint to 0, as indicated by you.
By ‘starting’ I mean either ‘left’ (horizontal) or ‘top’ (vertical) and by ‘ending’ I mean either ‘right’ (horizontal) or ‘bottom’ (vertical).
Thanks again!
8
Jörnsaid atThanks for the feedback, Nuno!
9
Kesaid atthank you so much , you saved my day man!