假设我在UIStackView中添加了更多可以显示的视图,我如何让UIStackView滚动?


正如Eik所说,UIStackView和UIScrollView很好地结合在一起。

关键是UIStackView处理不同内容的可变高度/宽度,然后UIScrollView做它的工作很好地滚动/反弹内容:

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()
    scrollView.contentSize = CGSize(width: stackView.frame.width, height: stackView.frame.height)       
}

只需将这个添加到viewdidload:

let insets = UIEdgeInsetsMake(20.0, 0.0, 0.0, 0.0)
scrollVIew.contentInset = insets
scrollVIew.scrollIndicatorInsets = insets

来源: https://developer.apple.com/library/ios/documentation/UserExperience/Conceptual/AutolayoutPG/LayoutUsingStackViews.html


如果有人正在寻找一个没有代码的解决方案,我创建了一个例子来完成这完全在故事板,使用自动布局。

你可以从github上得到。

基本上,要重新创建这个示例(用于垂直滚动):

创建一个UIScrollView,并设置它的约束。 添加一个UIStackView到UIScrollView 设置约束:前导,后尾,顶部和底部应该等于UIScrollView中的约束 在UIStackView和UIScrollView之间设置一个等宽约束。 在UIStackView上设置轴=垂直,对齐=填充,分布=等间距,间距= 0 添加一些uiview到UIStackView 运行

在第4步中将宽度交换为高度,并在第5步中设置轴=水平,以获得水平的UIStackView。


这里投票最多的答案中的约束条件对我来说是有效的,我已经将在我的故事板中创建的约束条件的图像粘贴在下面。

我确实提到了两个问题,但其他人应该注意:

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.

下面是我的视图层次结构和约束,供参考:


苹果的自动布局指南包括了一个关于使用滚动视图的完整部分。一些相关片段:

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.

此外:

你的布局必须完全定义内容视图的大小(除了 . ...当内容视图比滚动视图高时,滚动视图允许垂直滚动。当内容视图比滚动视图宽时,滚动视图启用水平滚动。

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

如果你有一个约束,使堆栈视图垂直居中在滚动视图内,只需删除它。


到2020年为止。

100%故事板或100%代码。

这个例子是垂直的:


下面是最简单的解释:

有一个空白的全屏场景 添加一个滚动视图。Control-drag从滚动视图拖到基本视图,添加左-右-上-下,全部为零。 在滚动视图中添加堆栈视图。从堆栈视图Control-drag到滚动视图,添加左-右-上-下,全部为零。 在堆栈视图中放置两个或三个标签。

为了清晰,标签的背景颜色为红色。设置标签高度为100。

现在设置每个UILabel的宽度: 令人惊讶的是,从UILabel控件拖动到滚动视图,而不是堆栈视图,并选择相同的宽度。

重复:

不要从UILabel控件拖动到UILabel的父控件,而是拖到祖父控件。(换句话说,一直到滚动视图,不要到堆栈视图。)

就是这么简单。这就是秘诀。

秘密提示——苹果臭虫: 只有一件物品是不行的!添加几个标签以使演示正常工作。

你就完成了。

提示:必须为每个新项添加高度。任何滚动堆栈视图中的每个项都必须具有固有大小(例如标签)或添加显式高度约束。


另一种方法:

回顾一下上面的方法:令人惊讶的是,将标签的宽度设置为滚动视图的宽度(而不是堆栈视图)。

这里有另一种方法……

从堆栈视图拖动到滚动视图,并添加一个“宽度相等”约束。这看起来很奇怪,因为你已经左右固定了,但这就是你怎么做的。不管看起来有多奇怪,这就是秘密。

所以你有两个选择:

令人惊讶的是,将堆栈视图中每个项的宽度设置为scrollview祖父类(而不是stackview父类)的宽度。

or

令人惊讶的是,设置一个“宽度相等”的stackview到scrollview -即使你有stackview的左右边固定到scrollview。

需要明确的是,只使用其中一种方法,不要同时使用两种方法。


你可以试试ScrollableStackView: https://github.com/gurhub/ScrollableStackView

它是Objective-C和Swift兼容的库。它可以通过CocoaPods获得。

样本代码(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)
// ...

样例代码(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]; 
// ...

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.

当使用堆栈视图时

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.

在创建视图之后,在母堆栈视图和滚动视图之间放置一个约束,同时约束子堆栈视图和母堆栈视图。 希望这个时候它能正常工作,否则你可能会从Xcode得到一个警告,给出建议,阅读它所说的并执行这些建议。希望现在你应该有一个工作视图按照你的期望:)。


例如,垂直堆栈视图/滚动视图(使用EasyPeasy自动布局):

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

只要确保每个子视图的高度都已定义!


我也想做同样的事情,偶然发现了这个优秀的帖子。如果您希望使用锚API以编程方式完成此操作,则可以采用这种方式。

总之,在UIScrollView中嵌入UIStackView,并设置UIStackView的锚约束来匹配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

对于嵌套视图或单栈视图,滚动视图必须与根视图设置固定宽度。滚动视图内部的主堆栈视图必须设置相同的宽度。[我的滚动视图是一个视图的波纹,忽略它]

在UIStackView和 UIScrollView。


如果有人在找水平滚动视图

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()
}

水平滚动(UIScrollView中的UIStackView)

用于水平滚动。首先,创建一个UIStackView和一个UIScrollView,并按以下方式将它们添加到您的视图中:

let scrollView = UIScrollView()
let stackView = UIStackView()

scrollView.addSubview(stackView)
view.addSubview(scrollView)

记住在UIStackView和UIScrollView上设置translatesAutoresizingMaskIntoConstraints为false:

stackView.translatesAutoresizingMaskIntoConstraints = false
scrollView.translatesAutoresizingMaskIntoConstraints = false

为了让一切正常工作,UIStackView的尾随,领先,顶部和底部锚应该等于UIScrollView锚:

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

但是UIStackView的宽度锚点必须等于或大于UIScrollView锚点的宽度:

stackView.widthAnchor.constraint(greaterThanOrEqualTo: scrollView.widthAnchor).isActive = true

现在锚定你的UIScrollView,例如:

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 

接下来,我建议尝试以下设置UIStackView对齐和分布:

topicStackView.axis = .horizontal
topicStackView.distribution = .equalCentering
topicStackView.alignment = .center

topicStackView.spacing = 10

最后,你需要使用addArrangedSubview:方法来添加子视图到你的UIStackView。

文本Insets

另一个你可能会发现有用的特性是,因为UIStackView保存在UIScrollView中,你现在可以访问文本insets,使事情看起来更漂亮一点。

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

在你的场景中放置一个滚动视图,并调整它的大小,使它充满场景。然后,在滚动视图中放置一个堆栈视图,并在堆栈视图中放置添加项按钮。一旦一切就绪,就设置以下约束条件:

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

代码:堆栈视图。宽度=滚动视图。宽度是关键。


为macOS Catalyst添加了一些新的视角。由于macOS应用程序支持窗口大小调整,你的UIStackView可能会从一个不可滚动的状态转换为一个可滚动的状态,反之亦然。这里有两件微妙的事情:

UIStackView被设计成适合它所能适应的所有区域。 在过渡期间,UIScrollView将尝试调整其边界大小,以考虑导航栏(或macOS应用程序中的工具栏)下新获得/丢失的区域。

不幸的是,这会造成一个无限循环。我不是非常熟悉UIScrollView和它的adjustdcontentinset,但从我的日志在其layoutSubviews方法,我看到以下行为:

一个人把窗户放大了。 UIScrollView试图缩小它的边界(因为不需要工具栏下面的区域)。 UIStackView。 不知何故UIScrollView不满意,决定恢复到更大的边界。这对我来说感觉很奇怪,因为我从日志中看到的是UIScrollView.bounds.height == UIStackView.bounds.height。 UIStackView。 然后循环到步骤2。

在我看来,有两个步骤可以解决这个问题:

UIStackView一致。top到UIScrollView.topMargin。 设置contentInsetAdjustmentBehavior为。never。

这里我关注的是一个垂直增长的UIStackView的垂直可滚动视图。对于水平对,相应更改代码。

希望它能在未来帮助到任何人。在网上找不到任何人提到这一点,我花了很长时间才弄清楚发生了什么。


我给你一个正确的解决方案

对于 Xcode 11+

步骤1: 添加一个ScrollView并调整它的大小

步骤2: 为ScrollView添加约束

步骤3: 添加一个StackView到ScrollView,并调整它的大小。

步骤4: 为StackView添加约束(Stask View ->内容布局指南->“领先,顶部,后面,底部”)

步骤4.1: 正确的约束->常量(…->常量= 0)

步骤5: 为StackView添加约束(Stask View -> Frame Layout Guide -> "Equal width ")

步骤6示例: 添加两个带有HeightConstraints和RUN的UIView

希望对大家有用


在我的例子中,stackView内的视图数量是可变的,我想将项目居中。例如,在stackView中有一个视图,我希望这个视图位于屏幕中央,如果所有的视图都不适合屏幕,我希望视图是可滚动的。

这是我的视图的层次结构。

所以我为按钮设置了一个固定的宽度,然后,为stackView:

与按钮相同的固定宽度,但优先级为650。 对齐X中心到containerView 尾随>= 0和前导>= 0到containerView 底部和顶部空间到containerView

对于containerView:

拖尾,领先,底部,顶部,等高,等宽(250优先级)到superview 固定的高度

对于scrollView:

尾随,领先,底部,顶部到父视图

scrollView也嵌入到具有前导和后导约束的视图中。

关于我用来解决这个问题的代码:

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

将stackview中的动态元素嵌入到scrollview中的一种简单方法。在XIB中,在UIScrollView中添加一个UIStackView,并添加stackview适合scrollview的约束(top, bottom, lead, trail),并添加一个约束来匹配它们之间的水平中心。但是将最后一个约束标记为“在构建时移除”。它使XIB高兴,避免错误。

水平滚动示例:

然后:

然后在你的代码中,像这样在你的stackview中添加按钮这样的元素:

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
}

当使用故事板做这件事时,每个人似乎都错过了修复乘数! 当你按照上面的步骤在任何人教程和重置常数为0,同时检查乘数,并将其重置为1,它将采取了一些其他因素时,视觉链接保持到位

我发现我可以在UIStackView中简单地做一个loooooooooong文本块挤压或拉伸

简单的 向滚动视图添加约束 左上右下,弊为0

向指向滚动条Content Layout Guide的堆栈视图添加约束

然后从框架布局指南中添加等宽或等高的约束。 选择:宽度如果内容需要垂直滚动,高度如果它需要水平滚动。

这是关键。编辑每个约束并将常数重置为0并将乘数设置回1!!!!!

如果你不这样做,它就会摇摇欲坠

如果它工作,你可以点击内部内容和鼠标滚动