Is it possible for UIStackView to 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 theUIScrollView
- Set the constraints:
Leading
,Trailing
,Top
&Bottom
should be equal to the ones fromUIScrollView
- Set up an equal
Width
constraint between theUIStackView
andUIScrollView
. - Set Axis = Vertical, Alignment = Fill, Distribution = Equal Spacing, and Spacing = 0 on the
UIStackView
- Add a number of
UIViews
to theUIStackView
- Run
Exchange Width
for Height
in step 4, and set Axis
= Horizontal
in step 5, to get a horizontal UIStackView.
Stack view within a scroll view doesn't scroll
This answer did work for me.
Add the scroll view
and pin four edges to safe area
. Make sure value of constraints is 0.
Add the stackview
inside scrollview
and pin four edges to scrollview
with constraint value 0.
Set stack view's
width equal to scroll view's
width.
Add as many views inside stackview
.
To test if scrolling works, set fixed height for views inside stackview
to make sure that scrollview
has scrollable height. Then with the stackview
selected, drag with two fingers inside the viewcontroller
. The stackview
should scroll within the scrollview
.
This is how the constraints are set:
Fitting UIStackView in UIScrollView programmatically
What you probably want to do...
- constrain all 4 sides of the stack view to the scroll view's Content Layout Guide
- constrain the Height of the stack view equal to the Height of the scroll view's Frame Layout Guide
- do NOT constrain the Width of the stack view
- set the stack view's Distribution to Fill
Create a "tab view" - here's an example with a 50 x 50
centered image view, rounded top corners and a 1-pt outline:
We can create that with this simple class:
class MyTabView: UIView {
let imgView = UIImageView()
init(with image: UIImage) {
super.init(frame: .zero)
imgView.image = image
commonInit()
}
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
func commonInit() -> Void {
imgView.translatesAutoresizingMaskIntoConstraints = false
// light gray background
backgroundColor = UIColor(white: 0.9, alpha: 1.0)
addSubview(imgView)
NSLayoutConstraint.activate([
// centered
imgView.centerXAnchor.constraint(equalTo: centerXAnchor),
imgView.centerYAnchor.constraint(equalTo: centerYAnchor),
// 50x50
imgView.widthAnchor.constraint(equalToConstant: 50.0),
imgView.heightAnchor.constraint(equalTo: imgView.widthAnchor),
])
// a little "styling" for the "tab"
clipsToBounds = true
layer.cornerRadius = 12
layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
layer.borderWidth = 1
layer.borderColor = UIColor.darkGray.cgColor
}
}
For each "tab" that we add to the stack view, we'll set its Width constraint equal to the scroll view's Frame Layout Guide widthAnchor
with multiplier: 1.0 / 3.0
. That way each "tab view" will be 1/3rd the width of the scroll view:
With 1, 2 or 3 "tabs" there will be no horizontal scrolling, because they all fit within the frame.
Once we have more than 3 "tabs" the stack view's width will exceed the width of the frame, and we'll have horizontal scrolling:
Here's the view controller I used for that. It creates 9 "tab images"... starts with a single "tab"... each tap will ADD a "tab" until we have all 9, at which point each tap will REMOVE a "tab":
class StackAsTabsViewController: UIViewController {
let stackView: UIStackView = {
let v = UIStackView()
v.axis = .horizontal
v.distribution = .fill
v.translatesAutoresizingMaskIntoConstraints = false
return v
}()
let scrollView: UIScrollView = {
let v = UIScrollView()
v.translatesAutoresizingMaskIntoConstraints = false
return v
}()
// a label to show what's going on
let statusLabel: UILabel = {
let v = UILabel()
v.numberOfLines = 0
v.translatesAutoresizingMaskIntoConstraints = false
return v
}()
// array to hold our "tab" images
var images: [UIImage] = []
// we'll add a "tab" on each tap
// until we reach the end of the images array
// then we'll remove a "tab" on each tap
// until we're back to a single "tab"
var isAdding: Bool = true
override func viewDidLoad() {
super.viewDidLoad()
// add the "status" label
view.addSubview(statusLabel)
// add stackView to scrollView
scrollView.addSubview(stackView)
// add scrollView to view
view.addSubview(scrollView)
// respect safe area
let g = view.safeAreaLayoutGuide
// scrollView Content and Frame Layout Guides
let contentG = scrollView.contentLayoutGuide
let frameG = scrollView.frameLayoutGuide
NSLayoutConstraint.activate([
// constrain scrollView Top / Leading / Trailing
scrollView.topAnchor.constraint(equalTo: g.topAnchor),
scrollView.leadingAnchor.constraint(equalTo: g.leadingAnchor),
scrollView.trailingAnchor.constraint(equalTo: g.trailingAnchor),
// height = 58 (image will be 50x50, so a little top and bottom padding)
scrollView.heightAnchor.constraint(equalToConstant: 58.0),
// constrain stackView all 4 sides to scrollView Content Layout Guide
stackView.topAnchor.constraint(equalTo: contentG.topAnchor),
stackView.leadingAnchor.constraint(equalTo: contentG.leadingAnchor),
stackView.trailingAnchor.constraint(equalTo: contentG.trailingAnchor),
stackView.bottomAnchor.constraint(equalTo: contentG.bottomAnchor),
// stackView Height equal to scrollView Frame Height
stackView.heightAnchor.constraint(equalTo: frameG.heightAnchor),
// statusLabel in the middle of the view
statusLabel.topAnchor.constraint(equalTo: scrollView.bottomAnchor, constant: 40.0),
statusLabel.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 40.0),
statusLabel.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -40.0)
])
// let's create 9 images using SF Symbols
for i in 1...9 {
guard let img = UIImage(systemName: "\(i).circle.fill") else {
fatalError("Could not create images!!!")
}
images.append(img)
}
// add the first "tab view"
self.updateTabs()
// tap anywhere in the view
let t = UITapGestureRecognizer(target: self, action: #selector(gotTap(_:)))
view.addGestureRecognizer(t)
}
@objc func gotTap(_ g: UITapGestureRecognizer) -> Void {
updateTabs()
}
func updateTabs() -> Void {
if isAdding {
// get the next image from the array
let img = images[stackView.arrangedSubviews.count]
// create a "tab view"
let tab = MyTabView(with: img)
// add it to the stackView
stackView.addArrangedSubview(tab)
let frameG = scrollView.frameLayoutGuide
NSLayoutConstraint.activate([
// each "tab view" is 1/3rd the width of the scroll view frame
tab.widthAnchor.constraint(equalTo: frameG.widthAnchor, multiplier: 1.0 / 3.0),
// each "tab view" is the same height as the scroll view frame
tab.heightAnchor.constraint(equalTo: frameG.heightAnchor),
])
} else {
stackView.arrangedSubviews.last?.removeFromSuperview()
}
if stackView.arrangedSubviews.count == 1 {
isAdding = true
} else if stackView.arrangedSubviews.count == images.count {
isAdding = false
}
updateStatusLabel()
}
func updateStatusLabel() -> Void {
// we'll do this async, to make sure the views have been updated
DispatchQueue.main.async {
let numTabs = self.stackView.arrangedSubviews.count
var str = ""
if self.isAdding {
str += "Tap anywhere to ADD a tab"
} else {
str += "Tap anywhere to REMOVE a tab"
}
str += "\n\n"
str += "Number of tabs: \(numTabs)"
str += "\n\n"
if numTabs > 3 {
str += "Tabs WILL scroll"
} else {
str += "Tabs will NOT scroll"
}
self.statusLabel.text = str
}
}
}
Play with it, and see if that's what you're going for.
Swift - StackView in ScrollView doesn't scroll
You should set ContentView
in your ScrollView
with constraints like:
ScrollView's Constraints:
- Leading to superView
- Trailing to superView
- Top to superView
Bottom to superView
These constraint's constants are 0
ContentView's Constraints:
- Leading to superView
- Trailing to superView
- Top to superView
Bottom to superView
Equal height to ViewController's
View
(which is in the top of the view hierarchy)Equal width to ViewController's
View
Note: You should set ContentView's height constraint's priority to 700 etc. (lower than default high value)
Attention:
Your stackViews and collectionView must have height for scrollable.
I hope it is works.
Enjoy.
How to scroll UIStackView horizontally when VoiceOver swipe?
The solution is to subclass UIButton and override its accessibilityElementDidBecomeFocused method.
MyButton.h
@interface MyButton : UIButton
@property (weak, nonatomic) UIScrollView *scrollView;
@end
MyButton.m
#import "MyButton.h"
@implementation MyButton
- (void)accessibilityElementDidBecomeFocused
{
[self.scrollView scrollRectToVisible:CGRectMake(self.frame.origin.x, self.frame.origin.y, self.bounds.size.width, self.bounds.size.height) animated:YES];
}
@end
UIStackView in UIScrollView not scrolling
To manage with this problem stackView height must be dynamic. Try to set height constraint priority to 750, after subviews did layout, everything must work
Adding Stackview to UIScrollView
Step-by-Step for setting this up in IB / Storyboards...
- Add a view - height 50 leading/top/trailing - blue background
- add a scrollview to that view - pin leading/top/trailing/bottom to 0 - set scrollview background to yellow so we can see where it is
- add a button to the scroll view
- duplicate it so you have 12 buttons
- group them into a stack view, and set the stack view's constraints to 0 leading/top/trailing/bottom
- and set the stack view's distribution to "equal spacing"
- result running in simulator (with no code at all):
and the buttons scroll left and right... no code setting of .contentSize
...
Related Topics
Uicollectionview Adding Uicollectioncell
Upload Image to the PHP Server from iOS
Uploading Archive Error: "Missing iOS Distribution Signing Identity for ..."
Table Header View Height Is Wrong When Using Auto Layout, Ib, and Font Sizes
iOS Designated Initializers:Using Ns_Designated_Initializer
Disable Swipe Back Gesture in Swift
Is iOS Carplay API Public? How to Integrate Carplay
Show iPhone Cut Copy Paste Menu on Uilabel
iOS 6 - Viewdidunload Migrate to Didreceivememorywarning
Setneedslayout and Setneedsdisplay
Getting Text from Image on iOS (Image Processing)
Warning: Attempt to Present * on * Whose View Is Not in the Window Hierarchy - Swift
While Scrolling on an iOS Device, the Z-Index of Elements Isn't Working
How to Handle iPhone Screen Sizes/Resolution for Background Images