How to define proper constraints for scrollview to work effectively
The key to using UIScrollView
: You must have a "chain" of constraints to allow auto-layout to determine the .contentSize
of the scroll view.
Step-by-step (I like to set background colors during layout, to make it easy to see the bounds / frames of the elements)...
Add a UIScrollView
(Cantaloupe color) to your view controller, and constrain it by 20-pts all the way around (just so we can see where it is):
Add a UIView
as your ContentView
(Strawberry color) as a subview of the scroll view. Give it equal Height and Width constraints to the scroll view, and top / leading / trailing / bottom constraints all set to 0
:
Add your Title
UILabel
(Yellow background). Constrain it Top: 30 / Leading: 50 / Trailing: 50
. Add your Body
UILabel
(Yellow background). Constrain its Top to the Title label's Bottom (I used 200
), and Leading: 10 / Trailing: 10
. Add your UIButton
(Light Gray background). Constrain its Top to the Body label's Bottom (I used 50
), and Leading: 50 / Trailing: 50
:
So far, nothing special, and you won't get any scrolling...
Next, add another Bottom constraint of 0
to the ContentView
, and change the first Bottom constraint to be >= 0
and set the Priority of the new Bottom constraint to 250
:
This will (after a couple more steps) allow ContentView
to expand / collapse based on its content, as well as control the .contentSize
of the scroll view.
The next step is to tell the Button
to determine the Height of the ContentView
- so add a Bottom constraint of 30
to the button (which will be equal to the Top: 30
we gave to the title label):
Of course, that's not quite what we want. It stretches the height of the button because we set a height constraint on the ContentView
. So... delete the height constraint from the ContentView
:
The result is that the ContentView
height will shrink to fit 30-pts below the bottom of the button.
If you add some more text to the Body
label (assuming it is set to Number of Lines; 0
), you will see the Body
label expand and "push down" the button, which in turn "pushes down" the bottom of ContentView
:
If you add enough text to Body
to push the button below the bottom of the scroll view - either in IB or via code - it will now scroll vertically.
Hope that's clear... please ask if you have any questions.
My Swift 4 UIScrollView with autolayout constraints is not scrolling
You can do this with Auto Layout. The secret is to constrain the edges of the containerView
to the edges of the scrollView
. It's not intuitive, but constraining the edges of the containerView
doesn't set the size, it just makes sure that the content size of the scrollView
grows as the containerView
grows. By setting constraints for the width of the containerView
to a constant that is a larger number than the width of the scrollView
, the content will scroll horizontally.
Note: When configuring a scrollView
this way, you do not set the contentSize
of the scrollView
. The contentSize
will be computed for you by Auto Layout and it will be equal to the size of the containerView
. It is important to make sure that the size of the containerView
is fully specified by the constraints.
Here's what I changed to make it work:
containerView = UIView()
containerView.backgroundColor = #colorLiteral(red: 0.176470592617989, green: 0.498039215803146, blue: 0.756862759590149, alpha: 1.0)
scrollView.addSubview(containerView)
//containerView.frame = CGRect(x: 0, y: 0, width: 1080, height: 200)
containerView.translatesAutoresizingMaskIntoConstraints = false
containerView.topAnchor.constraint(equalTo:scrollView.topAnchor).isActive = true
containerView.leadingAnchor.constraint(equalTo:scrollView.leadingAnchor).isActive = true
containerView.trailingAnchor.constraint(equalTo:scrollView.trailingAnchor).isActive = true
containerView.bottomAnchor.constraint(equalTo:scrollView.bottomAnchor).isActive = true
containerView.heightAnchor.constraint(equalToConstant: 200).isActive = true
containerView.widthAnchor.constraint(equalToConstant: 1080).isActive = true
Why isn't my content scrolling?
For it to scroll, the containerView
must be larger than the scrollView
. Your error is that you have set the constraints such that the containerView
is the same width and height as the scrollView
, and that is why your content isn't scrolling.
If you want it to scroll horizontally, the width of the containerView
must be larger than the scrollView
's width. You can do this in one of two ways:
Specify an explicit constant width for the
containerView
that is larger than thescrollView
's width.OR
Chain the subviews of the
containerView
from left to right with the left most being constained to the leading edge of thecontainerView
. Fully specify the widths of the subviews, and place distance contraints between the subviews. The rightmost subview must have an offset from the trailing edge of thecontainerView
. By doing this, Auto Layout can compute the width of thecontainerView
and set thecontentSize
of thescrollView
.
Mini project: update
This is a version of your mini project which uses a chain of constrained views to define the containerView
's width. The key is the final constraint after the for
loop in viewDidLoad()
which connects the last button's trailingAnchor (aka startPoint
) to the containerView
's trailingAnchor. This completes the chain of contraints and buttons which connect the leading edge of the containerView
with the trailing edge of containerView
. With this, Auto Layout is able to compute the width of the containerView
and establish the contentSize
of the scrollView
.
import UIKit
import PlaygroundSupport
class FilterViewController: UIViewController {
var filterView: UIView!
var scrollView: UIScrollView!
var containerView: UIView!
override func loadView() {
filterView = UIView()
view = filterView
view.backgroundColor = #colorLiteral(red: 0.909803926944733, green: 0.47843137383461, blue: 0.643137276172638, alpha: 1.0)
scrollView = UIScrollView()
scrollView.backgroundColor = #colorLiteral(red: 0.474509805440903, green: 0.839215695858002, blue: 0.976470589637756, alpha: 1.0)
view.addSubview(scrollView)
scrollView.translatesAutoresizingMaskIntoConstraints = false
scrollView.topAnchor.constraint(equalTo: view.topAnchor, constant: 40).isActive = true
scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
scrollView.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true
scrollView.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.25).isActive = true
scrollView.isScrollEnabled = true
containerView = UIView()
containerView.backgroundColor = #colorLiteral(red: 0.176470592617989, green: 0.498039215803146, blue: 0.756862759590149, alpha: 1.0)
scrollView.addSubview(containerView)
containerView.translatesAutoresizingMaskIntoConstraints = false
// This is key: connect all four edges of the containerView to
// to the edges of the scrollView
containerView.topAnchor.constraint(equalTo: scrollView.topAnchor).isActive = true
containerView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor).isActive = true
containerView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor).isActive = true
containerView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor).isActive = true
// Making containerView and scrollView the same height means the
// content will not scroll vertically
containerView.heightAnchor.constraint(equalTo: scrollView.heightAnchor).isActive = true
}
class Buttons {
let button = UIButton()
init(titleText: String) {
button.backgroundColor = #colorLiteral(red: 0.976470589637756, green: 0.850980401039124, blue: 0.549019634723663, alpha: 1.0)
button.setTitle(titleText, for: .normal)
}
}
override func viewDidLoad() {
super.viewDidLoad()
let b1 = Buttons(titleText: "one")
let b2 = Buttons(titleText: "two")
let b3 = Buttons(titleText: "three")
let b4 = Buttons(titleText: "four")
let b5 = Buttons(titleText: "five")
let buttonArray = [b1, b2, b3, b4, b5]
var startPoint = containerView.leadingAnchor
for btn in buttonArray {
let theBtn = btn.button
containerView.addSubview(theBtn)
theBtn.translatesAutoresizingMaskIntoConstraints = false
theBtn.leadingAnchor.constraint(equalTo: startPoint, constant: 20).isActive = true
theBtn.topAnchor.constraint(equalTo: containerView.topAnchor).isActive = true
theBtn.bottomAnchor.constraint(equalTo: containerView.bottomAnchor).isActive = true
theBtn.widthAnchor.constraint(equalTo: theBtn.heightAnchor).isActive = true
startPoint = theBtn.trailingAnchor
}
// Complete the chain of constraints
containerView.trailingAnchor.constraint(equalTo: startPoint, constant: 20).isActive = true
}
}
let filterViewController = FilterViewController()
PlaygroundPage.current.liveView = filterViewController
Swift: UIScrollView won't work with constraints at all?
Couple of things. First, your containerView
is missing translatesAutoresizingMaskIntoConstraints = false
. Second, mostly, an UI objects needs four constraints to be valid. So you are missing some of them for your container view.
Here is a complete working code
let scrollView: UIScrollView = {
let sv = UIScrollView()
sv.backgroundColor = .red
sv.translatesAutoresizingMaskIntoConstraints = false
return sv
}()
let containerView: UIView = {
let view = UIView()
view.backgroundColor = .white
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(scrollView)
scrollView.contentSize = CGSize(width: 320, height: 1500)
scrollView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
scrollView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
scrollView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
scrollView.addSubview(containerView)
containerView.leftAnchor.constraint(equalTo: scrollView.leftAnchor).isActive = true
containerView.topAnchor.constraint(equalTo: scrollView.topAnchor).isActive = true
containerView.widthAnchor.constraint(equalToConstant: 200).isActive = true
containerView.heightAnchor.constraint(equalToConstant: 100).isActive = true
}
Results is like
UIView in UIScrollView respects some constraints but not other
UIScrollView needs some contents in it to be scrolled. The view you are adding (inside scrollview), does not have size (height and width), so scroll view can't identify size of its content.
Add size for a view (inside scrollview) and it will work.
containerView.topAnchor.constraint(equalTo: scrollView.topAnchor, constant: 0).isActive = true
containerView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor, constant: 0).isActive = true
containerView.leftAnchor.constraint(equalTo: scrollView.leftAnchor, constant: 0).isActive = true
containerView.rightAnchor.constraint(equalTo: scrollView.rightAnchor, constant: 0).isActive = true
// Size constraints
containerView.widthAnchor.constraint(equalTo: scrollView.widthAnchor).isActive = true
containerView.heightAnchor.constraint(equalTo: scrollView.heightAnchor).isActive = true
// To check scrolling of container view; try this
containerView.widthAnchor.constraint(equalTo: scrollView.widthAnchor + 50.0).isActive = true
containerView.heightAnchor.constraint(equalTo: scrollView.heightAnchor + 50.0).isActive = true
Scrollview working by breaking constraint
There is conflict between your constraints with contentView
1. There is Top, bottom, leading, trailing with the scrollview
2. Fixed height and fixed width constraint of contentView
These two will conflict each other as OS will not be sure which constraint to fulfil.
As a solution reduce the priority of height and width constraint
so that the more priority is given to the constraint
with respect to the scrollView
.
For more details please follow below tutorial
How to configure a UIScrollView with Auto Layout
Programmatic UIScrollview with Autolayout
You don't need to create a faux content view, you can add subviews directly to the scroll view (which I prefer). Apple does not recommend creating one, they only suggest that you can.
Subviews of the scroll view shall not rely on the scroll view to determine their sizes, only their positions.
Your constraints must define the left-most, right-most, top-most, and bottom-most edges in order for auto layout to create the content view for you.
When you create a scroll view, you may give its frame the bounds of the controller's view:
scrollView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(scrollView)
scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
scrollView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
scrollView.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true
scrollView.heightAnchor.constraint(equalTo: view.heightAnchor).isActive = true
You must then set the boundaries of the content view by anchoring its subviews to the edges of the scroll view. To achieve vertical-only scrolling, your top-most view must be anchored to the top of the scroll view and none of the subviews anchored to the leading and trailing edges must exceed the width of the scroll view.
topMostView.translatesAutoresizingMaskIntoConstraints = false
scrollView.addSubview(topMostView)
topMostView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor).isActive = true
topMostView.topAnchor.constraint(equalTo: scrollView.topAnchor).isActive = true
topMostView.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true
topMostView.heightAnchor.constraint(equalToConstant: 1000).isActive = true
Notice the topMostView
does not rely on the scroll view to determine its size, only its position. The content in your scroll view now has a height of 1000
but it won't scroll because nothing is anchored to the bottom of the scroll view. Therefore, do that in your bottom-most view.
bottomMostView.translatesAutoresizingMaskIntoConstraints = false
scrollView.addSubview(bottomMostView)
bottomMostView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor).isActive = true
bottomMostView.topAnchor.constraint(equalTo: topMostView.bottomAnchor).isActive = true
bottomMostView.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true
bottomMostView.heightAnchor.constraint(equalToConstant: 1000).isActive = true
bottomMostView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor).isActive = true
The last anchor may seem odd because you're anchoring a view that is 1,000
points tall to an anchor that you just anchored to the bottom of the view which is definitely less than 1,000
points tall. But this is how Apple wants you to do it. By doing this, you do not need to create a content view, auto layout does it for you.
Defining the "edge constraints" (left-most, right-most, top-most, bottom-most) goes beyond scroll views. When you create a custom UITableViewCell
, for example, using auto layout, defining the four edge constraints (i.e. where the top-most subview is anchored to the top of the cell topMostView.topAnchor.constraint(equalTo: self.topAnchor).isActive = true
, the bottom-most subview to the bottom of the cell bottomMostView.topAnchor.constraint(equalTo: self.bottomAnchor).isActive = true
, etc.) is how you create self-sizing cells. Defining the edge constraints is how you create any self-sizing view, really.
UIScrollView how do you constrain a sub view that acts a container to all the other views?
Actually this is a very good idea. I always set up my scrollViews this way. I usually call the view contentView, but it is the same idea.
You're almost there. You haven't yet given Auto Layout anything to go on to figure out the size of your subviewWrapper. The constraints you've set so far pin the subviewWrapper to the edges of the scrollView's content area, but this just establishes the fact that as the subviewWrapper grows, the content size of the scrollView will expand. Currently your subviewWrapper has 0
width and 0
height which is why you see no blue.
Below are 3 examples of how you might establish the size of your subviewWrapper.
Note: Each of the following examples is completely independent. Look at each one separately and as you try them, remember to delete the constraints added by the previous example.
Example 1: Make subviewWrapper 1000 x 1000:
Set constraints to make your subviewWrapper 1000 x 1000
and you will see the blue and it will scroll in both directions.
subviewWrapper.widthAnchor.constraint(equalToConstant: 1000).isActive = true
subviewWrapper.heightAnchor.constraint(equalToConstant: 1000).isActive = true
Example 2: Vertical only scrolling with content size 2X of scrollView height:
If you set the width of your subviewWrapper to be equal to the width of the scrollView then it will only scroll vertically. If you set the height of subviewWrapper to 2X the height of scrollView, then your blue area will be twice the height of the scrollView.
subviewWrapper.widthAnchor.constraint(equalTo: scrollView!.widthAnchor, multiplier: 1.0).isActive = true
subviewWrapper.heightAnchor.constraint(equalTo: scrollView!.heightAnchor, multiplier: 2.0).isActive = true
Example 3: Size of subviewWrapper set by its subviews:
You can also establish the size of your subviewWrapper by adding subviews to it that are fully specified in size and connected in a chain from the top of subviewWrapper to the bottom, and from side to side. If you do this, Auto Layout will have enough information to compute the size of your subviewWrapper
In this example, I've added a yellow 600 x 600 square to the subviewWrapper and set it 100 points from each edge. Without having explicitly set a size for subviewWrapper, Auto Layout can figure out that it is 800 x 800
.
let yellowSquare = UIView()
yellowSquare.translatesAutoresizingMaskIntoConstraints = false
yellowSquare.backgroundColor = .yellow
subviewWrapper.addSubview(yellowSquare)
yellowSquare.widthAnchor.constraint(equalToConstant: 600).isActive = true
yellowSquare.heightAnchor.constraint(equalToConstant: 600).isActive = true
yellowSquare.topAnchor.constraint(equalTo: subviewWrapper.topAnchor, constant: 100).isActive = true
yellowSquare.leadingAnchor.constraint(equalTo: subviewWrapper.leadingAnchor, constant: 100).isActive = true
yellowSquare.trailingAnchor.constraint(equalTo: subviewWrapper.trailingAnchor, constant: -100).isActive = true
yellowSquare.bottomAnchor.constraint(equalTo: subviewWrapper.bottomAnchor, constant: -100).isActive = true
Constraints in ScrollView for centering and scrolling at the same time
Project demonstrating: https://github.com/Aquilosion/TestScrollViewConstraints
This is possible, although I've not been able to stop Xcode warning me that the constraints are incorrect (even though I'm pretty sure they're fine). What you need is:
- Scroll View
- Content View
- Actual Content
I've made a test view controller to demonstrate this. The view hierarchy should look like this:
My test view controller looks like this:
The blue area is the scroll view with the content view, and the white area is the "actual content" (with the label in it). If you enable multiple lines on the label, you get the following:
To get the effect, you need to lock the content view on all four sides to the view controller, and make it equal widths and equal heights. You need to modify the 'equal height constraint' so that it's 'equal to or more than 0', instead of the default 'equals 0'.
Meanwhile, in the 'actual content' view, you need to 'align Y' with its superview (the content view), and lock the top and bottom to its superview, but modify them again so that they are 'greater than or equal to' constraints. After that, it should stay central if too small, or expand properly when it gets bigger.
Related Topics
How to Make a Background Image Scale to Screen Size in Swift
Cleanest Way of Capturing Volume Up/Down Button Press on iOS 8
iOS App Freezes on Pushviewcontroller
How to Know If Two Emojis Will Be Displayed as One Emoji
iOS Background Audio Not Playing
Does H.264 Encoded Video with Bt.709 Matrix Include Any Gamma Adjustment
Why Storyboard UI Elements Not Showing on Uiviewcontroller in Xcode 6
Findobjectsinbackgroundwithblock: Gets Data from Parse, But Data Only Exists Inside the Block
Replacement for C-Style Loop in Swift 2.2
Receiver (<Viewcontroller>) Has No Segue with Identifier 'Addsegue'
Emulating Aspect-Fit Behaviour Using Autolayout Constraints in Xcode 6
How to Get a List of Points from a Uibezierpath
How to Assign an Action for Uiimageview Object in Swift
How to Change Inside Background Color of Uisearchbar Component on iOS
Nspersistentcontainer Concurrency for Saving to Core Data
How to Get the Console Logs from the iOS Simulator
Presenting a Uialertcontroller Properly on an iPad Using iOS 8