I'm setting up content in a scroll view with autolayout. The objects in the scrollview are pinned top-to-bottom to the previous one, so that they are under one another. I have a footer view that is added at the end, below these objects.
Here's the catch: when there's few content, the contentView will be smaller than the screen height, so the footer view will appear somewhere in the middle of the screen (which is the normal behavior). But I'd like to prevent that, and make the view stay somewhere at the bottom.
In other words, I would like to setup a double constraint like:
Put this view below all the objects in the scrollview
AND
keep this view at a distance of max [some number] of the bottom of the screen
In a way that both constraints are always satisfied:
If the height of the content is bigger than the screen, then the view appears at the bottom, after scrolling down
If the height is smaller, then the view is "pinned" to the bottom of the screen, leaving a space relatively big between the bottom of the content and the top of this view
How can I achieve that with AutoLayout?
Fairly easy to do with Auto-Layout only... no code required.
The key is to use a "content view" to hold the elements, and a greater-than-or-equal constraint between your "bottom" element and your "footer" view.
In this image, yellow is the main view, green is the scroll view, blue is the content view, the labels are gray and the footer view is pink.
Start with a fresh view controller
add a scroll view, normal constraints (I used 20 all the way around, so we can see the frame)
add a UIView to the scrollView - this will be our "content view"
constrain contentView Top/Bottom/Leading/Trailing all equal to 0 to the scrollView
constrain both the Width and Height of the contentView equal to the scrollView
add your elements - here I used 3 labels
constrain the labels as usual... I used:
LabelA - Top/Leading/Trailing all at 20, vertical spacing to LabelB of 60
LabelB - Leading/Trailing at 20, vertical spacing to LabelC of 60
LabelC - Leading/Trailing at 20
LabelC is also set to Number of Lines: 0 so it will expand with multiple lines of text
Add a UIView as a "footer view" (I stuck a label in it)
constrain the footerView Leading/Trailing/Bottom all at 20 (so we can see the frame)
either set a Height constraint on footerView, or use its content to constrain its height
add a Vertical Spacing constraint from LabelC to footerView, and set it to >= 40
last step, change the Height constraint of contentView to Priority: 250
Now, as you expand/contract the height of LabelC, the footerView will keep at least 40-pts of vertical space. When LabelC gets big enough to "push" footerView below the bottom, scrollView will become scrollable.
Results:
you need to check ContentSize of scrollView and modify FooterView Top Constraint with the required Value
My class code
import UIKit
class scrollViewDrag: UIViewController
{
/// ScrollView Outlet
#IBOutlet weak var mainScrollView: UIScrollView!
/// Footer View top spacing constraint
#IBOutlet weak var footerViewTopConstraint: NSLayoutConstraint!
/// Used for ScrollView Height
var screenHeight = CGFloat()
/// Did Load
override func viewDidLoad() {
super.viewDidLoad()
}
/// Function used to check for height
func checkForHeight(){
/// Get scrollView Height
screenHeight = mainScrollView.frame.size.height
/// Check contentSize Height ?
if mainScrollView.contentSize.height >= screenHeight {
/// When ScrollView is having height greater than your scrollView Height
/// Footer will scroll along other Views
}
else{
/// Issue Case
let spacingValue = screenHeight-mainScrollView.contentSize.height
footerViewTopConstraint.constant = spacingValue
}
}
/// Call the height function in DidAppear
override func viewDidAppear(_ animated: Bool) {
checkForHeight()
}
}
Storyboard
I had used Four View with Equal Heights And at last a footerView is attached as Fourth View
FooterView Top Constraint
Top constraint used as footerViewTopConstraint
Output
Case 1 - Size is greater than scrollView Height
Case 2 - Expected Output
I have a view with an image and a tableview. The image is in the first half of the screen (portrait) and the tableview in the second half. And now I want to move the tableview over the image until it reaches the top and covers the image. Then it should start the "real scrolling".
But what's the best way to do this? Can I like replace the touchesMove of the variable tableView? I could create an extension of the UITableView and override the function, but then I don't have access to the view of my Controller to move the tableView.
Any answer? Thanks!
The imageView should be behind the tableView with constraints top, leading, trailing to superview and height to superview with a multiplier of 0.5. The tableView should fill its superview.
The trick is that you add a tableViewHeader that is invisible and equal to half the height of the screen. This has the effect of pushing the initial content of the tableView off the screen. In interface builder add a UIView to the tableView as header and make it transparent. Also make the background of your tableView transparent. Take an outlet to the headerView and the tableView. In viewDidLayoutSubviews set your headerView.frame.size.height = tableView.frame.size.height / 2.
Apart from what Josh answered, what you can try is:
What you can do is make the UIImageView's height decrease based on the scroll amount until the UIImageView's height is 0 and then start the scrolling otherwise force the contentOffSet of the UITableView to always remain 0. That being said, here is how to do it:
Make an enum to keep track of the various states of the UIImageView like:
enum Layout {
case ImageExpanding,
case ImageDefaultHeight,
case ImageDiminishing,
case ImageNotVisible
}
In your storyboard, add a top, leading and trailing constraint to the superview for your UIImageView and fixed height constraint of lets say 200(don't worry you will change this later). To your UITableView add a leading, trailing and bottom constraint to the superview and a top the UIImageView. Now drag and drop a constraint outlet for the UIImageView height into your UIViewController.
In your viewDidLoad set your heightConstraint to be 1/2 of the total screen height and set the enum state to initially be ImageDefaultHeight
Now in your scrollViewDidScroll you will have to check the direction of the scroll and based on that while checking the current state of the image, increase or decrease the heightConstraint based on the amount a user scrolls by and always check:
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if(/*up*/){
//just telling you about this if condition as this will ensure that
//if your table view is large and if tableview isn't at the top,
//the image shouldn't expand
if layout == .ImageNotVisible{
if scrollView.contentOffset.y <= 0 {
//start expanding once you reach top of tableview
layout = .ImageExpanding;
}
}
//keep track of other enum states and update your uiimageview
//appropriately after calculating the user scroll amount
//until the height reaches your initialDefaultHeight and
//then do nothing. You will have to figure out the code
//for this on your own
}else if(/*down*/){
//make image smaller
}
//dont let table view scroll until image height is 0 when use is scrolling down
if layout != ImageNotVisible{
scrollView.contentOffset = CGPoint(x: 0, y: 0)
}
}
I'm basically trying to reproduce the behavior of the title and message section of an alert.
The title and message labels appear to be in a scroll view. If the label text increases then the alert height also increases along with the intrinsic content size of the labels. But at a certain height, the alert height stops increasing and the title and message text become scrollable.
What I have read:
Articles
Auto Layout Magic: Content Sizing Priorities
Editing Auto Layout Constraints (documentation)
A Fixed Width Dynamic Height ScrollView in AutoLayout
Using UIScrollView with Auto Layout in iOS
Stack Overflow
Adding priority to layout constraints
Inequality Constraint Ambiguity
UIScrollView Scrollable Content Size Ambiguity
Ambiguity with two inequality constraints
IOS scrollview ambiguous scrollable content height in case of autolayout
The answer may be in there but I was not able to abstract it.
What I have tried:
Only focusing on the scroll view with the two labels I tried to make a minimal example in which a parent view would resize according to the intrinsic height of the scrollview. I've played around with a lot of constraints. Here is one combo (among many) that doesn't work:
I've worked with auto layout and normal constraints and even intrinsic content sizes. Also, I do know how to get a basic scroll view working with auto layout. However, I've never done anything with priorities and content hugging and compression resistance. From the reading I've done, I have a superficial understanding of their meanings, but I am at a loss of how to apply them in this instance. My guess is I need to do something with content hugging and priorities.
I think I have achieved an effect similar to the one you wanted with pure Auto Layout.
THE STRUCTURE
First, let me show you my structure:
Content View is the view that has the white background, Caller View and Bottom View have a fixed height. Bottom View has your button, Caller View has your title.
THE SOLUTION
So, after setting the basic constraints (note that the view inside scroll view has top, left, right and bottom to the scroll view AND an equal width) the problem is that the scroll view doesn't know what size should have.
So here comes what I have done:
I wanted that the scroll could grow until a max. So I added a proportional height to the superview that sets that max:
However, this brings two problems: Scroll View still doesn't know what height should have and now you can resize and the scroll view will pass the size of his content (if the content is smaller than the max size).
So, to solve both issues I have added an equal height with a smaller priority from the View inside of the Scroll View and the Scroll View
I hope this can help you out.
Your problem can't be solved with constraints alone, you have to use some code. That's because the scroll view doesn't have an intrinsic content size.
So, create a subclass of scroll view. Maybe give it a property or a delegate or something to tell it what its maximum height should be.
In the subclass, invalidate the intrinsic content size whenever the content size changes, and calculate the new intrinsic size as the minimum of the content size and the maximum allowed size.
Once the scroll view has an intrinsic size your constraints between it and its super view will actually do something meaningful for your problem.
It can be done in Interface builder using auto layout without any difficulties.
set outer container view ("Parent container for scrollview" in your sample) height constraint with "less than or equal" relation.
2.add "equal heights" constraint to content view and scroll view. Then set low priority for this constraint.
That's all. Now your scrollview will be resized by height if content height changed, but only to max height limited by outer view height constraint.
You should be able to achieve this solution via pure autolayout.
Typically if I want labels to grow as their content grows vertically I do this
[label setContentHuggingPriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisHorizontal];
[label setContentCompressionResistancePriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisVertical];
In order for your scrollview to comply to your requirements you will need to make sure a line can be drawn connecting the top of the scrollview all the way through the labels to the bottom of the scrollview so it can calculate it's height. In order for the scrollview to confine to it's parent you can set a height constraint with a multiplier of the superview of say 0.8
You can do this fairly simply with two constraints
Constraint 1: ScrollView.height <= MAX_SIZE. Priority = Required
Constraint 2: ScrollView.height = ScrollView.contentSize.height. Priority = DefaultHigh
AutoLayout will 'try' to keep the scrollView to the contentSize, but will 'give up' when it matches the max height and will stop there.
the only tricky part is setting the height for Constraint 2.
When my UIStackView is in a UIViewController, I do that in viewWillLayoutSubviews
If you're subclassing UIScrollView to achieve this, you could do it in updateConstraints
something like
override func viewWillLayoutSubviews() {
scrollViewHeightConstraint?.constant = scrollView.contentSize.height
super.viewWillLayoutSubviews()
}
To my project, I have a similar problem. You can using the following way to make it work around.
First, Title and bottom action height are fixed. Content has variable height. You can add it the mainView as one child using the font-size, then call layoutIfNeeded, then its height can be calculated and saved as XX. Then removed it from mainView.
Second, using normal constraint to layout the content part with scrollView, mainView has a height constraint of XX and setContentCompressionResistancePriority(.defaultLow, for: .vertical).
Finally, alert can show exact size when short content and show limited size when long size with scrolling.
I have been able to achieve this exact behavior with only AutoLayout constraints. Here is a generic demo of how to do it: It can be applied to your view hierarchy as you see fit.
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let kTestContentHeight: CGFloat = 1200
// Subview that will shrink to fit content and expand up to 50% of the view controller's height
let modalView = UIView()
modalView.backgroundColor = UIColor.gray
// Scroll view that will facilitate scrolling if the content > 50% of view controller's height
let scrollView = UIScrollView()
scrollView.backgroundColor = .yellow
// Content which has an intrinsic height
let contentView = UIView()
contentView.backgroundColor = .green
// add modal view
view.addSubview(modalView)
modalView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([modalView.leftAnchor.constraint(equalTo: view.leftAnchor),
modalView.rightAnchor.constraint(equalTo: view.rightAnchor),
modalView.heightAnchor.constraint(lessThanOrEqualTo: view.heightAnchor,
multiplier: 0.5),
modalView.bottomAnchor.constraint(equalTo: view.bottomAnchor)])
let expandHeight = modalView.heightAnchor.constraint(equalTo: view.heightAnchor)
expandHeight.priority = UILayoutPriority.defaultLow
expandHeight.isActive = true
// add scrollview to modal view
modalView.addSubview(scrollView)
scrollView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([scrollView.topAnchor.constraint(equalTo: modalView.topAnchor),
scrollView.leftAnchor.constraint(equalTo: modalView.leftAnchor),
scrollView.rightAnchor.constraint(equalTo: modalView.rightAnchor),
scrollView.bottomAnchor.constraint(equalTo: modalView.bottomAnchor)])
// add content to scrollview
scrollView.addSubview(contentView)
contentView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([contentView.leftAnchor.constraint(equalTo: scrollView.leftAnchor),
contentView.widthAnchor.constraint(equalTo: modalView.widthAnchor),
contentView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor),
contentView.topAnchor.constraint(equalTo: scrollView.topAnchor),
contentView.heightAnchor.constraint(equalToConstant: kTestContentHeight)])
let contentBottom = contentView.bottomAnchor.constraint(equalTo: modalView.bottomAnchor)
contentBottom.priority = .defaultLow
contentBottom.isActive = true
}
}
I want to create view with only vertical scroll. As it turned out it is epic hard to do in iOs. I have done this steps:
1) Create UIViewController in storyboard;
2) Add ScrollView inside View in UIViewController and add 0 constrains to each side.
3) Add elements in scrollview, as result:
After I launch my app all works, but:
1) How should I disable horizontal scroll? I add 0 constrain to the right to my scrollView + 0 constrain to the right to my uilabel (as u see on screen for some reasons it is not attached to the right, it has different constrain, but in property I set constrain = 0) and, as I thought, label text supposed to be in my screen bounds, but when I launch app I can scroll to right, i.e. uilable text didn't wrap, my scrollview just resize to fit the text.I tried to set my scrollView in code: scrollView.contentSize = CGSize(UIScreen.mainScreen().bounds.width, height: 800), but didn't help.
2) If I scroll to much, then blank space appears and this is not cool, how to fix it?
1) Horizontal scroll enables automatically when the content width in scrollView more than width of scrollView. Therefore, in order to avoid horizontal scrolling is necessary to make width of the content inside scrollView less than or equal to scrollView width.
Leading space and trailing space can't set specific width to views, they just stretch them. In regular views, they no stretch for more than width of view, but scrollView is a special view, actually, with an infinite content width. Therefore, trailing space and leading space constraints in scrollView change the width of views to their maximum possible values (In case with UILabel you can see resize to fit the text).
To avoid horizontal scrolling, you need to set specific width of each view, less than or equal to scrollView width. Specific width of views may be set with width constraints.
Instead of setting each view width, much better to add a view–container and set width to it, and inside it place the views as needed.
Views hierarchy:
View
-> ScrollView
-> ContainerView
-> UILabel
-> UILabel
-> ... other views that you need
Autolayout constraints:
ScrollView
-> leading space to View : 0
-> trailing space to View : 0
-> top space to View : 0
-> bottom space to View : 0
Container View
-> leading space to ScrollView : 0
-> trailing space to ScrollView : 0
-> top space to ScrollView : 0
-> bottom space to ScrollView : 0
-> width equal to ScrollView : 0
To set width equal constraint ctrl+drag from containerView to scrollView.
2) Vertical scroll is dependent on the total height of content. Blank space can be, if the last element inside containerView has a large value of the bottom space to superview.
Or do you mean bounce effect? You can disable vertical bounce of scrollView.
You can also easily do this in code. In my case, I have a UIStackView that's the only subview of a UIScrollView
// Create the stack view
let stackView = UIStackView()
stackView.translatesAutoresizingMaskIntoConstraints = false
// Add things to the stack view....
// Add it as a subview to the scroll view
scrollView.addSubview(stackView)
// Use auto layout to pin the stack view's sides to the scroll view
NSLayoutConstraint.activate([
stackView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor),
scrollView.trailingAnchor.constraint(equalTo: stackView.trailingAnchor),
stackView.topAnchor.constraint(equalTo: scrollView.topAnchor),
scrollView.bottomAnchor.constraint(equalTo: stackView.bottomAnchor)
])
// Now make sure the thing doesn't scroll horizontally
let margin: CGFloat = 40
scrollView.contentInset = UIEdgeInsets(top: margin, left: margin, bottom: margin, right: margin)
scrollView.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true
let stackViewWidthConstraint = stackView.widthAnchor.constraint(equalTo: scrollView.widthAnchor)
stackViewWidthConstraint.constant = -(margin * 2)
stackViewWidthConstraint.isActive = true
The NSLayoutConstraint.activate bit is taken from Dave DeLong's excellent UIView extension here: https://github.com/davedelong/MVCTodo/blob/master/MVCTodo/Extensions/UIView.swift#L26
Let's say I have added more views in UIStackView which can be displayed, how I can make the UIStackView scroll?
In case anyone is looking for a solution without code, I created an example to do this completely in the storyboard, using Auto Layout.
You can get it from github.
Basically, to recreate the example (for vertical scrolling):
Create a UIScrollView, and set its constraints.
Add a UIStackView to the UIScrollView
Set the constraints: Leading, Trailing, Top & Bottom should be equal to the ones from UIScrollView
Set up an equal Width constraint between the UIStackView and UIScrollView.
Set Axis = Vertical, Alignment = Fill, Distribution = Equal Spacing, and Spacing = 0 on the UIStackView
Add a number of UIViews to the UIStackView
Run
Exchange Width for Height in step 4, and set Axis = Horizontal in step 5, to get a horizontal UIStackView.
I present you the right solution
For Xcode 11+
Step 1:
Add a ScrollView and resize it
Step 2:
Add Constraints for a ScrollView
Step 3:
Add a StackView into ScrollView, and resize it.
Step 4:
Add Constraints for a StackView (Stask View -> Content Layout Guide -> "Leading, Top, Trailing, Bottom")
Step 4.1:
Correct Constraints -> Constant (... -> Constant = 0)
Step 5:
Add Constraints for a StackView (Stask View -> Frame Layout Guide -> "Equal Widths")
Step 6 Example:
Add two UIView(s) with HeightConstraints and RUN
I hope it will be useful for you like
Apple's Auto Layout Guide includes an entire section on Working with Scroll Views. Some relevant snippets:
Pin the content view’s top, bottom, leading, and trailing edges to the scroll view’s corresponding edges. The content view now defines
the scroll view’s content area.
(Optional) To disable horizontal scrolling, set the content view’s width equal to the scroll view’s width. The content view now fills the
scroll view horizontally.
(Optional) To disable vertical scrolling, set the content view’s height equal to the scroll view’s height. The content view now fills
the scroll view horizontally.
Furthermore:
Your layout must fully define the size of the content view (except
where defined in steps 5 and 6). … When the content view is taller than the scroll view, the scroll view enables vertical scrolling. When the content view is wider than the scroll view, the scroll view enables horizontal scrolling.
To summarize, the scroll view's content view (in this case, a stack view) must be pinned to its edges and have its width and/or height otherwise constrained. That means that the contents of the stack view must be constrained (directly or indirectly) in the direction(s) in which scrolling is desired, which might mean adding a height constraint to each view inside a vertically scrolling stack view, for example. The following is an example of how to allow for vertical scrolling of a scroll view containing a stack view:
// Pin the edges of the stack view to the edges of the scroll view that contains it
stackView.topAnchor.constraint(equalTo: scrollView.topAnchor).isActive = true
stackView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor).isActive = true
stackView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor).isActive = true
stackView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor).isActive = true
// Set the width of the stack view to the width of the scroll view for vertical scrolling
stackView.widthAnchor.constraint(equalTo: scrollView.widthAnchor).isActive = true
Up to date for 2020.
100% storyboard OR 100% code.
This example is vertical:
Here's the simplest possible explanation:
Have a blank full-screen scene
Add a scroll view. Control-drag from the scroll view to the base view, add left-right-top-bottom, all zero.
Add a stack view in the scroll view. Control-drag from the stack view to the scroll view, add left-right-top-bottom, all zero.
Put two or three labels inside the stack view.
For clarity, make the background color of the label red. Set the label height to 100.
Now set the width of each UILabel:
Surprisingly, control-drag from the UILabel to the scroll view, not to the stack view, and select equal widths.
To repeat:
Don't control drag from the UILabel to the UILabel's parent - go to the grandparent. (In other words, go all the way to the scroll view, do not go to the stack view.)
It's that simple. That's the secret.
Secret tip - Apple bug:
It will not work with only one item! Add a few labels to make the demo work.
You're done.
Tip: You must add a height to every new item. Every item in any scrolling stack view must have either an intrinsic size (such as a label) or add an explicit height constraint.
The alternative approach:
To recap the above approach: surprisingly, set the widths of the labels to the width of the scroll view (not the stack view).
Here is an alternate approach...
Drag from the stack view to the scroll view, and add a "width equal" constraint. This seems strange because you already pinned left-right, but that is how you do it. No matter how strange it seems that's the secret.
So you have two options:
Surprisingly, set the width of each item in the stack view to the width of the scrollview grandparent (not the stackview parent).
or
Surprisingly, set a "width equal" of the stackview to the scrollview - even though you do have the left and right edges of the stackview pinned to the scrollview anyway.
To be clear, do ONE of those methods, do NOT do both.
The constraints in the top-voted answer here worked for me, and I've pasted an image of the constraints below, as created in my storyboard.
I did hit two issues though that others should be aware of:
After adding constraints similar to those in in the accepted answer, I'd get the red autolayout error Need constraints for: X position or width. This was solved by adding a UILabel as a subview of the stack view.
I'm adding the subviews programmatically, so I originally had no subviews on the storyboard. To get rid of the autolayout errors, add a subview to the storyboard, then remove it on load before adding your real subviews and constraints.
I originally attempted to add UIButtons to the UIStackView. The buttons and views would load, but the scroll view would not scroll. This was solved by adding UILabels to the Stack View instead of buttons. Using the same constraints, this view hierarchy with the UILabels scrolls but the UIButtons does not.
I'm confused by this issue, as the UIButtons do seem to have an IntrinsicContentSize (used by the Stack View). If anyone knows why the buttons don't work, I'd love to know why.
Here is my view hierarchy and constraints, for reference:
As Eik says, UIStackView and UIScrollView play together nicely, see here.
The key is that the UIStackView handles the variable height/width for different contents and the UIScrollView then does its job well of scrolling/bouncing that content:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
scrollView.contentSize = CGSize(width: stackView.frame.width, height: stackView.frame.height)
}
Horizontal Scrolling (UIStackView within UIScrollView)
For horizontal scrolling. First, create a UIStackView and a UIScrollView and add them to your view in the following way:
let scrollView = UIScrollView()
let stackView = UIStackView()
scrollView.addSubview(stackView)
view.addSubview(scrollView)
Remembering to set the translatesAutoresizingMaskIntoConstraints to false on the UIStackView and the UIScrollView:
stackView.translatesAutoresizingMaskIntoConstraints = false
scrollView.translatesAutoresizingMaskIntoConstraints = false
To get everything working the trailing, leading, top and bottom anchors of the UIStackView should be equal to the UIScrollView anchors:
stackView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor).isActive = true
stackView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor).isActive = true
stackView.topAnchor.constraint(equalTo: scrollView.topAnchor).isActive = true
stackView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor).isActive = true
But the width anchor of the UIStackView must the equal to or greater than the width of the UIScrollView anchor:
stackView.widthAnchor.constraint(greaterThanOrEqualTo: scrollView.widthAnchor).isActive = true
Now anchor your UIScrollView, for example:
scrollView.heightAnchor.constraint(equalToConstant: 80).isActive = true
scrollView.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true
scrollView.bottomAnchor.constraint(equalTo:view.safeAreaLayoutGuide.bottomAnchor).isActive = true
scrollView.leadingAnchor.constraint(equalTo:view.leadingAnchor).isActive = true
scrollView.trailingAnchor.constraint(equalTo:view.trailingAnchor).isActive = true
Next, I would suggest trying the following settings for the UIStackView alignment and distribution:
topicStackView.axis = .horizontal
topicStackView.distribution = .equalCentering
topicStackView.alignment = .center
topicStackView.spacing = 10
Finally you'll need to use the addArrangedSubview: method to add subviews to your UIStackView.
Text Insets
One additional feature that you might find useful is that because the UIStackView is held within a UIScrollView you now have access to text insets to make things look a bit prettier.
let inset:CGFloat = 20
scrollView.contentInset.left = inset
scrollView.contentInset.right = inset
// remember if you're using insets then reduce the width of your stack view to match
stackView.widthAnchor.constraint(greaterThanOrEqualTo: topicScrollView.widthAnchor, constant: -inset*2).isActive = true
I was looking to do the same thing and stumbled upon this excellent post. If you want to do this programmatically using the anchor API, this is the way to go.
To summarise, embed your UIStackView in your UIScrollView, and set the anchor constraints of the UIStackView to match those of the UIScrollView:
stackView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor).isActive = true
stackView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor).isActive = true
stackView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor).isActive = true
stackView.topAnchor.constraint(equalTo: scrollView.topAnchor).isActive = true
stackView.widthAnchor.constraint(equalTo: scrollView.widthAnchor).isActive = true
Just add this to viewdidload:
let insets = UIEdgeInsetsMake(20.0, 0.0, 0.0, 0.0)
scrollVIew.contentInset = insets
scrollVIew.scrollIndicatorInsets = insets
source:
https://developer.apple.com/library/ios/documentation/UserExperience/Conceptual/AutolayoutPG/LayoutUsingStackViews.html
You can try ScrollableStackView : https://github.com/gurhub/ScrollableStackView
It's Objective-C and Swift compatible library. It's available through CocoaPods.
Sample Code (Swift)
import ScrollableStackView
var scrollable = ScrollableStackView(frame: view.frame)
view.addSubview(scrollable)
// add your views with
let rectangle = UIView(frame: CGRect(x: 0, y: 0, width: 100, height: 55))
rectangle.backgroundColor = UIColor.blue
scrollable.stackView.addArrangedSubview(rectangle)
// ...
Sample Code (Objective-C)
#import ScrollableStackView
ScrollableStackView *scrollable = [[ScrollableStackView alloc] initWithFrame:self.view.frame];
scrollable.stackView.distribution = UIStackViewDistributionFillProportionally;
scrollable.stackView.alignment = UIStackViewAlignmentCenter;
scrollable.stackView.axis = UILayoutConstraintAxisVertical;
[self.view addSubview:scrollable];
UIView *rectangle = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 100, 55)];
[rectangle setBackgroundColor:[UIColor blueColor]];
// add your views with
[scrollable.stackView addArrangedSubview:rectangle];
// ...
Adding some new perspective for macOS Catalyst. Since macOS apps support window resizing, it is possible that your UIStackView will transition from an unscrollable status to a scrollable one, or vice versa. There are two subtle things here:
UIStackView is designed to fit all area it can.
During the transition, UIScrollView will attempt to resize its bounds to account for the newly gained/lost area underneath your navigation bar (or toolbar in the case of macOS apps).
This will unfortunately create an infinite loop. I am not extremely familiar with UIScrollView and its adjustedContentInset, but from my log in its layoutSubviews method, I am seeing the following behavior:
One enlarges the window.
UIScrollView attempts to shrink its bounds (since no need for the area underneath the toolbar).
UIStackView follows.
Somehow UIScrollView is unsatisfied, and decide to restore to the larger bounds. This feels very odd to me since what I am seeing from the log is that UIScrollView.bounds.height == UIStackView.bounds.height.
UIStackView follows.
Then loop to step 2.
It appears to me that two steps would fix the issue:
Align UIStackView.top to UIScrollView.topMargin.
Set contentInsetAdjustmentBehavior to .never.
Here I am concerned with a vertically scrollable view with a vertically growing UIStackView. For a horizontal pair, change the code accordingly.
Hope it helps anyone in the future. Couldn't find anyone mentioning this on the Internet and it costed me quite a long time to figure out what happened.
If any one looking for horizontally scrollview
func createHorizontalStackViewsWithScroll() {
self.view.addSubview(stackScrollView)
stackScrollView.translatesAutoresizingMaskIntoConstraints = false
stackScrollView.heightAnchor.constraint(equalToConstant: 85).isActive = true
stackScrollView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor).isActive = true
stackScrollView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor).isActive = true
stackScrollView.bottomAnchor.constraint(equalTo: visualEffectViews.topAnchor).isActive = true
stackScrollView.addSubview(stackView)
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.topAnchor.constraint(equalTo: stackScrollView.topAnchor).isActive = true
stackView.leadingAnchor.constraint(equalTo: stackScrollView.leadingAnchor).isActive = true
stackView.trailingAnchor.constraint(equalTo: stackScrollView.trailingAnchor).isActive = true
stackView.bottomAnchor.constraint(equalTo: stackScrollView.bottomAnchor).isActive = true
stackView.heightAnchor.constraint(equalTo: stackScrollView.heightAnchor).isActive = true
stackView.distribution = .equalSpacing
stackView.spacing = 5
stackView.axis = .horizontal
stackView.alignment = .fill
for i in 0 ..< images.count {
let photoView = UIButton.init(frame: CGRect(x: 0, y: 0, width: 85, height: 85))
// set button image
photoView.translatesAutoresizingMaskIntoConstraints = false
photoView.heightAnchor.constraint(equalToConstant: photoView.frame.height).isActive = true
photoView.widthAnchor.constraint(equalToConstant: photoView.frame.width).isActive = true
stackView.addArrangedSubview(photoView)
}
stackView.setNeedsLayout()
}
One simple way for dynamic elements in stackview embed in scrollview. In XIB, add a UIStackView inside an UIScrollView and add constraints that the stackview fit the scrollview (top, bottom, lead, trail) and add a constraint to match horizontal center between them. But marked "remove at build time" the last constraint. It make XIB happy and avoid errors.
Example for horizontal scroll:
Then:
Then in your code, just add elements like buttons in your stackview like this:
array.forEach { text in
let button = ShadowButton(frame: .zero)
button.setTitle(text, for: .normal)
myStackView.addArrangedSubview(button)
button.heightAnchor.constraint(equalToConstant: 40).isActive = true
button.widthAnchor.constraint(equalToConstant: 80).isActive = true
}
If you have a constraint to center the Stack View vertically inside the scroll view, just remove it.
Example for a vertical stackview/scrollview (using the EasyPeasy for autolayout):
let scrollView = UIScrollView()
self.view.addSubview(scrollView)
scrollView <- [
Edges(),
Width().like(self.view)
]
let stackView = UIStackView(arrangedSubviews: yourSubviews)
stackView.axis = .vertical
stackView.distribution = .fill
stackView.spacing = 10
scrollView.addSubview(stackView)
stackView <- [
Edges(),
Width().like(self.view)
]
Just make sure that each of your subview's height is defined!
First and foremost design your view, preferably in something like Sketch or get an
idea of what do you want as a scrollable content.
After this make the view controller free form (choose from attribute
inspector) and set height and width as per the intrinsic content
size of your view (to be chosen from the size inspector).
After this in the view controller put a scroll view and this is a
logic, which I have found to be working almost all the times in iOS (it may require going through the documentation of that view class which one can obtain via command + click on that class or via googling)
If you are working with two or more views then first start with a view, which has been introduced earlier or is more primitive and then go to the view which has been introduced
later or is more modern. So here since scroll view has been
introduced first, start with the scroll view first and then go to the
stack view. Here put scroll view constraints to zero in all direction vis-a-vis its super view. Put all your views inside this scroll view and then put them in stack view.
While working with stack view
First start with grounds up(bottoms up approach), ie., if you have labels, text fields and images in your view, then lay out these views first (inside the scroll view) and after that put them in the stack view.
After that tweak the property of stack view. If desired view is still not achieved, then use another stack view.
If still not achieved then play with compression resistance or content hugging priority.
After this add constraints to the stack view.
Also think of using an empty UIView as filler view, if all of the above is not giving satisfactory results.
After making your view, put a constraint between the mother stack view and the scroll view, while constraint children stack view with the mother stack view.
Hopefully by this time it should work fine or you may get a warning from Xcode giving suggestions, read what it says and implement those. Hopefully now you should have a working view as per your expectations:).
For nested or single Stack view scroll view must be set a fixed width with the root view. Main stack view which is inside of scroll view must set the same width. [My scroll view is bellow of a View ignore it]
Set up an equal Width constraint between the UIStackView and
UIScrollView.
Place a scroll view on your scene, and size it so that it fills the scene. Then, place a stack view inside the scroll view, and place the add item button inside the stack view. As soon as everything’s in place, set the following constraints:
Scroll View.Leading = Superview.LeadingMargin
Scroll View.Trailing = Superview.TrailingMargin
Scroll View.Top = Superview.TopMargin
Bottom Layout Guide.Top = Scroll View.Bottom + 20.0
Stack View.Leading = Scroll View.Leading
Stack View.Trailing = Scroll View.Trailing
Stack View.Top = Scroll View.Top
Stack View.Bottom = Scroll View.Bottom
Stack View.Width = Scroll View.Width
code:Stack View.Width = Scroll View.Width is the key.
In my case the number of views inside the stackView was variable and I wanted to center the items. So, for instance, with one view in the stackView, I wanted this view to be centered in the middle of the screen, and if all the views did not fit inside the screen, I wanted the view to be scrollable.
This is the hierarchy of my view.
So I set a fixed width for the button, then, for the stackView:
Same fixed width as the button but with 650 priority.
Align X center to containerView
Trailing >= 0 and leading >= 0 to containerView
Bottom and top space to containerView
For the containerView:
Trailing, leading, bottom, top, equal height, equal width (250 priority) to superview
Fixed height
For the scrollView:
Trailing, leading, bottom, top to superview
The scrollView is also embedded in a view that has leading and trailing constraints.
And about the code I used to approach this:
for index in 0...array.count - 1 {
if index == 0 {
firstButton.setTitle(title, for: .normal)
} else {
let button = UIButton()
button.setTitle(title, for: .normal)
stackView.addArrangedSubview(button)
stackView.setNeedsLayout()
}
}
containerView.layoutIfNeeded()
stackView.distribution = .fillEqually
stackView.spacing = 10
stackView.alignment = .fill
something everyone seems to have missed when doing this with Storyboard is FIX THE MULTIPLIER!
When you're following the steps above in anyones tutorial and resetting the constant to 0 also check the multiplier and reset it to 1, it will have taken on some other factor when visually linking to remain in place
I found I can make a LOOOOOOOOOOOONG text block squash or stretch in a UIStackView simply with
Simply
Add constraints to the scrollview
Top left bottom right, cons 0
Add constraints to the stack view pointing at the scrollbars Content Layout Guide
Then add equal width or equal height constraint from the Frame Layout Guide.
Pick : width if the content needs to scroll vertically, height if it needs to scroll horizontally.
Now here is the key. Edit each constraint and reset the constant to 0 AND set the multiplier back to 1!!!!!
It gets wonky if you don't
If it works you can click on the inner content and mouse scroll