Is It Possible For Uistackview to Scroll

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):

  1. Create a UIScrollView, and set its constraints.
  2. Add a UIStackView to the UIScrollView
  3. Set the constraints: Leading, Trailing, Top & Bottom should be equal to the ones from UIScrollView
  4. Set up an equal Width constraint between the UIStackView and UIScrollView.
  5. Set Axis = Vertical, Alignment = Fill, Distribution = Equal Spacing, and Spacing = 0 on the UIStackView
  6. Add a number of UIViews to the UIStackView
  7. 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:

Sample Image

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:

Sample Image

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:

Sample Image

Sample Image

Sample Image

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:

Sample Image

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)

ScrollView and ContentView's view hierarchy and constraints

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...

  1. Add a view - height 50 leading/top/trailing - blue background

Sample Image


  1. 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

Sample Image


  1. add a button to the scroll view

Sample Image


  1. duplicate it so you have 12 buttons

Sample Image


  1. group them into a stack view, and set the stack view's constraints to 0 leading/top/trailing/bottom

Sample Image


  1. and set the stack view's distribution to "equal spacing"

Sample Image


  1. result running in simulator (with no code at all):

Sample Image

and the buttons scroll left and right... no code setting of .contentSize...



Related Topics



Leave a reply



Submit