假设我在 UIStackView 中添加了更多可以显示的视图,如何使 UIStackView
滚动?
如果有人正在寻找没有代码的解决方案,我创建了一个示例,使用自动布局在情节提要中完全做到这一点。
您可以从 github 获得它。
基本上,要重新创建示例(用于垂直滚动):
创建一个 UIScrollView,并设置它的约束。将 UIStackView 添加到 UIScrollView 设置约束:Leading、Trailing、Top 和 Bottom 应该等于 UIScrollView 中的约束 在 UIStackView 和 UIScrollView 之间设置相等的宽度约束。在UIStackView上设置Axis=Vertical,Alignment=Fill,Distribution=Equal Spacing,Spacing=0 给UIStackView运行添加若干个UIView
在第 4 步中将 Width
交换为 Height
,并在第 5 步中设置 Axis
= Horizontal
,以获得水平 UIStackView。
Apple 的自动布局指南包括关于 Working with Scroll Views 的整个部分。一些相关的片段:
将内容视图的顶部、底部、前导和后沿固定到滚动视图的相应边缘。内容视图现在定义了滚动视图的内容区域。 (可选)要禁用水平滚动,请将内容视图的宽度设置为等于滚动视图的宽度。内容视图现在水平填充滚动视图。 (可选)要禁用垂直滚动,请将内容视图的高度设置为等于滚动视图的高度。内容视图现在水平填充滚动视图。
此外:
您的布局必须完全定义内容视图的大小(步骤 5 和 6 中定义的除外)。 … 当内容视图高于滚动视图时,滚动视图启用垂直滚动。当内容视图比滚动视图宽时,滚动视图启用水平滚动。
总而言之,滚动视图的内容视图(在本例中为堆栈视图)必须固定在其边缘,并且必须限制其宽度和/或高度。这意味着堆栈视图的内容必须(直接或间接)在需要滚动的方向上受到约束,例如,这可能意味着向垂直滚动堆栈视图内的每个视图添加高度约束。以下是如何允许垂直滚动包含堆栈视图的滚动视图的示例:
// 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
Need constraints for: Y position or height.
只有当您为堆栈视图设置宽度和高度约束时,此错误才会消失,这会禁用垂直滚动。这段代码仍然对你有用吗?
我为您提供正确的解决方案
对于 Xcode 11+
第 1 步:添加 ScrollView 并调整其大小
https://i.stack.imgur.com/eVLTi.png
第 2 步:为 ScrollView 添加约束
https://i.stack.imgur.com/xe9wu.png
第 3 步:将 StackView 添加到 ScrollView,并调整其大小。
https://i.stack.imgur.com/7PVnN.png
第 4 步:为 StackView 添加约束(任务视图 -> 内容布局指南 -> “Leading, Top, Trailing, Bottom”)
https://i.stack.imgur.com/VMllS.png
步骤 4.1:正确的约束 -> 常量(... -> 常量 = 0)
https://i.stack.imgur.com/NQ0BQ.png
第 5 步:为 StackView 添加约束(任务视图 -> 框架布局指南 -> “等宽”)
https://i.stack.imgur.com/hjXbh.png
https://i.stack.imgur.com/JzEal.png
我希望它对你有用
safeAreaLayoutGuide
创建等宽约束,因为如果存在安全区域插入并导致水平滚动,这将有所不同。
更新到 2020 年。
100% 故事板或 100% 代码。
这个例子是垂直的:
这是最简单的解释:
有一个空白的全屏场景添加一个滚动视图。按住 Control 键从滚动视图拖动到基本视图,添加左-右-上-下,全部为零。在滚动视图中添加堆栈视图。按住 Control 从堆栈视图拖动到滚动视图,添加左-右-上-下,全部为零。在堆栈视图中放置两个或三个标签。
为清楚起见,将标签的背景颜色设为红色。将标签高度设置为 100。
现在设置每个 UILabel 的宽度:令人惊讶的是,从 UILabel 控制拖动到滚动视图,而不是堆栈视图,并选择相等的宽度。
重复:
不要控制从 UILabel 拖动到 UILabel 的父级 - 转到祖父级。 (换句话说,一直到滚动视图,不要到堆栈视图。)
就是这么简单。这就是秘密。
秘密提示 - Apple 错误:它不适用于仅一件物品!添加一些标签以使演示工作。
你完成了。
提示:您必须为每个新项目添加高度。任何滚动堆栈视图中的每个项目都必须具有固有大小(例如标签)或添加显式高度约束。
替代方法:
回顾一下上述方法:令人惊讶的是,将标签的宽度设置为滚动视图(而不是堆栈视图)的宽度。
这是另一种方法...
从堆栈视图拖动到滚动视图,并添加“宽度相等”约束。这看起来很奇怪,因为您已经将左右固定,但您就是这样做的。不管它看起来多么奇怪,这就是秘密。
所以你有两个选择:
令人惊讶的是,将堆栈视图中每个项目的宽度设置为滚动视图祖父(而不是堆栈视图父)的宽度。
或者
令人惊讶的是,将堆栈视图的“宽度相等”设置为滚动视图 - 即使您确实将堆栈视图的左右边缘固定到滚动视图。
需要明确的是,使用其中一种方法,不要同时使用两种方法。
此处投票最多的答案中的约束对我有用,并且我粘贴了下面的约束图像,就像在我的故事板中创建的那样。
我确实遇到了两个问题,但其他人应该知道:
添加类似于接受答案中的约束后,我会得到红色的自动布局错误需要约束:X 位置或宽度。这是通过添加 UILabel 作为堆栈视图的子视图来解决的。我以编程方式添加子视图,所以我最初在情节提要上没有子视图。要摆脱自动布局错误,请在情节提要中添加一个子视图,然后在加载时将其删除,然后再添加真正的子视图和约束。我最初试图将 UIButtons 添加到 UIStackView。按钮和视图会加载,但滚动视图不会滚动。这是通过将 UILabels 添加到堆栈视图而不是按钮来解决的。使用相同的约束,带有 UILabel 的视图层次结构会滚动,但 UIButtons 不会。我对这个问题感到困惑,因为 UIButtons 似乎确实有一个 IntrinsicContentSize (由 Stack View 使用)。如果有人知道为什么按钮不起作用,我很想知道为什么。
这是我的视图层次结构和约束,供参考:
https://i.stack.imgur.com/uomXU.png
正如 Eik 所说,UIStackView
和 UIScrollView
可以很好地配合使用,请参阅 here。
关键是 UIStackView
处理不同内容的可变高度/宽度,然后 UIScrollView
很好地滚动/弹跳该内容:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
scrollView.contentSize = CGSize(width: stackView.frame.width, height: stackView.frame.height)
}
我一直在寻找做同样的事情,但偶然发现了这个 excellent post. 如果您想使用锚 API 以编程方式执行此操作,这就是要走的路。
总而言之,将 UIStackView
嵌入到 UIScrollView
中,并将 UIStackView
的锚约束设置为与 UIScrollView
的匹配:
stackView.leadingAnchor.constraintEqualToAnchor(scrollView.leadingAnchor).active = true
stackView.trailingAnchor.constraintEqualToAnchor(scrollView.trailingAnchor).active = true
stackView.bottomAnchor.constraintEqualToAnchor(scrollView.bottomAnchor).active = true
stackView.topAnchor.constraintEqualToAnchor(scrollView.topAnchor).active = true
stackView.widthAnchor.constraintEqualToAnchor(scrollView.widthAnchor).active = true
水平滚动(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。
文字插图
您可能会发现另一项有用的功能是,因为 UIStackView
包含在 UIScrollView
中,您现在可以访问文本插入以使内容看起来更漂亮一些。
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
只需将其添加到 viewdidload
:
let insets = UIEdgeInsetsMake(20.0, 0.0, 0.0, 0.0)
scrollVIew.contentInset = insets
scrollVIew.scrollIndicatorInsets = insets
您可以尝试 ScrollableStackView:https://github.com/gurhub/ScrollableStackView
它是 Objective-C 和 Swift 兼容的库。它可通过 CocoaPods 获得。
示例代码(斯威夫特)
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];
// ...
为 macOS Catalyst 添加一些新视角。由于 macOS 应用程序支持窗口大小调整,您的 UIStackView
可能会从不可滚动状态转换为可滚动状态,反之亦然。这里有两件微妙的事情:
UIStackView 旨在适应所有区域。
在过渡期间,UIScrollView 将尝试调整其边界以考虑导航栏(或 macOS 应用程序中的工具栏)下方新获得/丢失的区域。
不幸的是,这将创建一个无限循环。我对 UIScrollView
及其 adjustedContentInset
不是很熟悉,但是从我登录到它的 layoutSubviews
方法中,我看到了以下行为:
一放大窗户。
UIScrollView 尝试缩小其边界(因为不需要工具栏下方的区域)。
UIStackView 紧随其后。
不知何故 UIScrollView 不满意,并决定恢复到更大的界限。这让我感觉很奇怪,因为我从日志中看到的是 UIScrollView.bounds.height == UIStackView.bounds.height。
UIStackView 紧随其后。
然后循环到第 2 步。
在我看来,两个步骤可以解决这个问题:
将 UIStackView.top 与 UIScrollView.topMargin 对齐。
将 contentInsetAdjustmentBehavior 设置为 .never。
在这里,我关心的是一个垂直可滚动的视图,它具有垂直增长的 UIStackView
。对于水平对,相应地更改代码。
希望它对将来的任何人有所帮助。在互联网上找不到任何人提到这一点,我花了很长时间才弄清楚发生了什么。
如果有人在寻找水平滚动视图
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()
}
将stackview中的动态元素嵌入scrollview的一种简单方法。在 XIB 中,在 UIScrollView 内添加 UIStackView 并添加 stackview 适合滚动视图(顶部、底部、前导、尾随)的约束,并添加约束以匹配它们之间的水平中心。但最后一个约束标记为“在构建时删除”。它使 XIB 高兴并避免错误。
https://i.stack.imgur.com/s81rB.png
https://i.stack.imgur.com/H1HfQ.png
然后在您的代码中,只需在 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
}
如果您有约束将 Stack View 在滚动视图内垂直居中,只需将其删除。
垂直堆栈视图/滚动视图的示例(使用 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)
]
只需确保定义了每个子视图的高度!
首先设计你的视图,最好是像 Sketch 这样的东西,或者了解你想要什么作为可滚动内容。在此之后使视图控制器自由形式(从属性检查器中选择)并根据视图的内在内容大小设置高度和宽度(从尺寸检查器中选择)。之后在视图控制器中放置一个滚动视图,这是一个逻辑,我发现它在 iOS 中几乎一直都在工作(它可能需要通过该视图类的文档,可以通过命令 + 单击获取该类或通过谷歌搜索)如果您正在使用两个或多个视图,则首先从一个较早引入或更原始的视图开始,然后转到稍后引入或更现代的视图。所以这里既然先介绍了滚动视图,那就先从滚动视图开始,然后再进入堆栈视图。这里将滚动视图约束在所有方向上相对于其超级视图都为零。将所有视图放入此滚动视图中,然后将它们放入堆栈视图中。
使用堆栈视图时
首先从地面开始(自下而上的方法),即,如果您的视图中有标签、文本字段和图像,则首先布置这些视图(在滚动视图内),然后将它们放入堆栈视图。
之后调整堆栈视图的属性。如果仍未获得所需的视图,则使用另一个堆栈视图。
如果仍未实现,则使用抗压缩性或内容拥抱优先级。
在此之后向堆栈视图添加约束。
如果以上所有方法都没有给出令人满意的结果,还可以考虑使用空的 UIView 作为填充视图。
制作视图后,在母堆栈视图和滚动视图之间放置一个约束,同时约束子堆栈视图与母堆栈视图。希望到这个时候它应该可以正常工作,或者您可能会从 Xcode 收到警告,给出建议,阅读它所说的内容并实施这些建议。希望现在您应该按照您的期望有一个工作视图:)。
对于嵌套或单个 Stack 视图,滚动视图必须与根视图设置固定宽度。滚动视图内部的主堆栈视图必须设置相同的宽度。 [我的滚动视图是视图忽略它的波纹管]
在 UIStackView 和 UIScrollView 之间设置相等的宽度约束。
https://i.stack.imgur.com/hwKw3.png
在场景上放置一个滚动视图,并调整其大小以填充场景。然后,在滚动视图中放置一个堆栈视图,并将添加项目按钮放置在堆栈视图中。一切就绪后,设置以下约束:
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
https://i.stack.imgur.com/LES1F.png
代码:Stack View.Width = Scroll View.Width
是关键。
在我的情况下,stackView 中的视图数量是可变的,我想将项目居中。因此,例如,对于 stackView 中的一个视图,我希望该视图位于屏幕中间,如果所有视图都不适合屏幕,我希望该视图是可滚动的。
这是我的观点的层次结构。
https://i.stack.imgur.com/xM3Za.png
所以我为按钮设置了一个固定的宽度,然后,为stackView:
与按钮相同的固定宽度,但优先级为 650。
将 X 中心对齐到 containerView
尾随 >= 0 和前导 >= 0 到 containerView
容器视图的底部和顶部空间
https://i.stack.imgur.com/JDCeZ.png
对于容器视图:
尾随,前导,底部,顶部,等高,等宽(250优先级)到superview
固定高度
https://i.stack.imgur.com/YbVIK.png
对于滚动视图:
尾随、前导、底部、顶部到超级视图
scrollView 也嵌入在具有前导和尾随约束的视图中。
https://i.stack.imgur.com/pfSjA.png
关于我用来解决这个问题的代码:
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
使用 Storyboard 执行此操作时,每个人似乎都错过了修复乘数!当您按照任何人教程中的上述步骤并将常量重置为 0 时,还会检查乘数并将其重置为 1,当视觉链接保持原位时,它将采取一些其他因素
我发现我可以简单地在 UIStackView 中制作 LOOOOOOOOOOOONG 文本块壁球或拉伸
只需将约束添加到滚动视图左上右下,缺点 0
将约束添加到指向滚动条的堆栈视图内容布局指南
然后从 Frame Layout Guide 添加等宽或等高约束。 Pick :如果内容需要垂直滚动,则为宽度,如果需要水平滚动,则为高度。
现在这里是关键。编辑每个约束并将常量重置为 0 并将乘数设置回 1!!!!!!!
如果你不这样做,它会变得很奇怪
如果有效,您可以单击内部内容并鼠标滚动