UIStackView是否可以滚动?


210

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


为什么不使用带有(或多个)堆栈视图的滚动视图来显示内容?
2015年

2
单句解决方案是对于宽度,您可以控制拖动到祖父母滚动视图,而不是父堆栈视图。解释我的答案!
Fattie

如何在iOS的UIScrollView中嵌入UIStackView github.com/onmyway133/blog/issues/324
onmyway133

关于iOS中的这个关键问题。 实际上,有两种(只有两种)方法可以执行此操作 -这是唯一的方法,您必须完全遵循这两个过程之一。我在下面的答案总是更新的。
Fattie

是。您必须具有一个中间UIView作为UIScrollView的内容视图。然后将UIStackView放置为内容视图的子视图。raywenderlich.com/...
poGUIst

Answers:


577

万一有人在寻找没有代码的解决方案,我创建了一个示例,使用自动版式在情节提要中完全做到这一点。

您可以从github获得它。

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

  1. 创建一个UIScrollView,并设置其约束。
  2. 将添加UIStackViewUIScrollView
  3. 设定限制:LeadingTrailingTopBottom应该等于从那些UIScrollView
  4. WidthUIStackView和之间设置相等的约束UIScrollView
  5. 在轴上设置轴=垂直,对齐=填充,分布=等间距,以及间距= 0 UIStackView
  6. 添加了许多UIViewsUIStackView

交换WidthHeight在步骤4中,和组Axis= Horizontal在步骤5中,获得一个水平UIStackView。


55
谢谢!“ 4.在UIStackView和UIScrollView之间设置相等的Width约束。” 是关键;)
空白

19
数字6也很重要。想要在代码中添加所有视图,但是自动布局随后会在IB中显示缺少的约束。

11
大家好,看来这种解决方案不适用于水平滚动。在步骤4中,我假定不设置相等的宽度,而是设置相等的高度,并且Axis = Horizo​​ntal。我试过了,没有用。
Seto Elkahfi'2

5
添加约束以使stackview和scrollview具有相等的宽度将删除来自界面生成器的警告,但随后将禁用滚动。仅当scrollview内部的内容视图大于 scrollview时才发生滚动!
杰森·摩尔

6
详细说明第6点。如果堆栈视图中没有元素,但所有约束都已到位,则会显示“滚动视图需要Y位置或高度的约束”。只需在堆栈视图中添加标签,该警告就会消失!
Faisal Memon

45

Apple的《自动布局指南》包括有关使用滚动视图的整个章节。一些相关的摘要:

  1. 将内容视图的顶部,底部,前边缘和后边缘固定到滚动视图的相应边缘。现在,内容视图定义了滚动视图的内容区域。
  2. (可选)要禁用水平滚动,请将内容视图的宽度设置为等于滚动视图的宽度。现在,内容视图将水平填充滚动视图。
  3. (可选)要禁用垂直滚动,请将内容视图的高度设置为等于滚动视图的高度。现在,内容视图将水平填充滚动视图。

此外:

您的布局必须完全定义内容视图的大小(步骤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

1
谢谢,但是省略高度限制(以允许垂直滚动)会在Storyboard中产生此错误:Need constraints for: Y position or height.仅当您同时为堆栈视图设置宽度和高度限制时,该错误才会消失,这将禁用垂直滚动。这段代码对您仍然有用吗?
Crashalot

@Crashalot要消除该错误,您需要在堆栈视图中添加一个子视图。请参阅下面的答案。
pkamb

16

这里投票最多的答案中的约束对我有用,并且我粘贴了以下约束的图像,如在情节提要中创建的那样。

我确实遇到了两个问题,尽管其他人应该意识到:

  1. 在添加与接受的答案中的约束相似的约束之后,我将得到红色的autolayout错误Need constraints for: X position or width。通过添加UILabel作为堆栈视图的子视图来解决此问题。

    我是通过编程方式添加子视图的,因此故事板最初没有子视图。要消除自动布局错误,请在情节提要中添加一个子视图,然后在加载时将其删除,然后再添加真正的子视图和约束。

  2. 我最初试图添加UIButtons到UIStackView中。按钮和视图将加载,但滚动视图将不会滚动。通过添加UILabels到堆栈视图而不是按钮来解决此问题。使用相同的约束,带有UILabels的视图层次结构会滚动,但UIButtons不会。

    我对此问题感到困惑,因为UIButton 似乎确实具有IntrinsicContentSize(由Stack View使用)。如果有人知道为什么按钮不起作用,我很想知道为什么。

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

滚动视图中堆栈视图的约束[1]


3
您只是节省了我几个小时的头部撞墙和沮丧的感觉,我处于完全相同的情况,无法弄清警告的原因以及为什么不能使用按钮。为什么苹果为什么!?
PakitoV

15

正如Eik所说,UIStackView并且UIScrollView一起玩得很好,请参见此处

关键是该UIStackView句柄处理不同内容的可变高度/宽度,UIScrollView然后很好地完成滚动/弹跳该内容的工作:

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

2
不必将手动和自动布局相结合以允许可滚动的堆栈视图。您只需要限制堆栈视图的宽度和/或高度。请参阅我的答案以获取详细信息,以及指向相关Apple文档的链接。
titaniumdecoy

github的例子非常完美,非常清楚。感谢分享。
Behr

stackView.translatesAutoresizingMaskIntoConstraints = false,这对我来说是成功的诀窍
Howl Jenkins

10

我当时想做同样的事情,却偶然发现了这个出色的帖子。如果要使用锚点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

请不要对多个问题添加相同的答案。回答最好的一个,并将其余的标记为重复。请参阅为几个问题添加重复答案是否可以接受?
FelixSFD

10
在这种情况下,它们并不是真正的重复项。另一个问题具体询问如何以编程方式做到这一点,而另一个却没有。确实,此问题的最高答案提供了一种用于获得结果的GUI方法。尽管如此,程序化解决方案可能对某些用户还是有价值的。
Dalmazio

9

截至2020年。

100%故事板或100%代码。


这是最简单的解释:

  1. 全屏空白

  2. 添加滚动视图。按住Control键从滚动视图拖动到基本视图,然后将left-right-top-bottom添加为零。

  3. 在滚动视图中添加一个堆栈视图。按住Control键从堆栈视图拖动到滚动视图,将left-right-top-bottom添加为零。

  4. 在堆栈视图中放置两个或三个标签。

为了清楚起见,请将标签的背景色设为红色。将标签高度设置为100。

  1. 现在设置每个UILabel 的宽度

    令人惊讶的是,按住Control键从中拖动UILabel到滚动视图,而不是堆栈视图,然后选择width相等

重复:

不要控制从UILabel拖动到UILabel的父级-转到祖父母。(换句话说,一直转到滚动视图,而不要进入堆栈视图。)

就这么简单。那是秘密。

秘诀-Apple错误:

是行不通的,只有一个项目!添加一些标签以使演示工作。

你完成了。

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


替代方法:

在上面的代码中:令人惊讶的是,将UILabels的宽度设置为滚动视图(而不是堆栈视图)的宽度。

交替...

从堆栈视图拖动到滚动视图,并添加“宽度相等”约束。这似乎很奇怪,因为您已经左右固定了,但这就是您的做法。无论看起来多么奇怪,这都是秘密。

因此,您有两种选择:

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

要么

  1. 出人意料的是,将stackview的“宽度等于”设置为scrollview- 即使您确实确实将stackview的左右边缘固定在scrollview上。

为了清楚起见,请选择其中一种方法,不要同时使用。


8

水平滚动(UIScrollView中的UIStackView)

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

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

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

记住设置translatesAutoresizingMaskIntoConstraints,以falseUIStackViewUIScrollView

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功能保存在a中,因此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

7

只需将其添加到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


无法告诉您这为我节省了Autolayout + ScrollView的麻烦。在该Apple链接上找到了很棒的发现。
2016年

怎么办?没有这个会发生什么?
亲爱的

这只会将滚动视图内容放在状态栏下方。
turingtest

2

您可以尝试ScrollableStackViewhttps : //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]; 
// ...

嗨,彼得,实际上是个好主意!始终欢迎您捐款,请随时发送请求请求。最好。
mgyky


0

垂直stackview / scrollview的示例(使用EasyPeasyfor自动布局):

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

只需确保定义了每个子视图的高度!


0
  1. 首先,最重要的是设计视图,最好使用Sketch之类的方法,或者了解您想要的可滚动内容。

  2. 之后,使视图控制器自由显示(从属性检查器中选择),并根据视图的固有内容大小(从大小检查器中选择)设置高度和宽度。

  3. 之后,在视图控制器中放入滚动视图,这是一种逻辑,我发现它在iOS中几乎一直都在工作(它可能需要仔细阅读该视图类的文档,可以通过命令+单击该课程或通过谷歌搜索)

    如果使用两个或多个视图,则首先从一个较早引入的视图或更原始的视图开始,然后转到一个较晚引入的视图或更现代的视图。因此,在这里,因为首先引入了滚动视图,所以先从滚动视图开始,然后再进入堆栈视图。在此,将滚动视图约束与其超级视图在所有方向上都设为零。将所有视图放入此滚动视图,然后将其放入堆栈视图。

在使用堆栈视图时

  • 首先从基础开始(从下至上的方法),即,如果视图中有标签,文本字段和图像,则先布局这些视图(在滚动视图内部),然后将它们放置在堆栈视图中。

  • 之后,调整堆栈视图的属性。如果仍然无法实现所需的视图,请使用另一个堆栈视图。

  • 如果仍未实现,则应优先考虑抗压缩性或内含物。
  • 之后,将约束添加到堆栈视图。
  • 如果上述所有方法都不能令人满意,则还可以考虑使用空的UIView作为填充视图。

创建视图之后,在母堆栈视图和滚动视图之间放置一个约束,而约束子堆栈视图与母堆栈视图一起放置。希望这次可以正常工作,否则您可能会从Xcode收到警告,给出建议,阅读建议内容并实施。希望现在您应该按照自己的期望进行工作:)。


0

对于嵌套或单个堆栈视图,滚动视图必须使用根视图设置为固定宽度。滚动视图内部的主堆栈视图必须设置相同的宽度。[我的滚动视图在视图的下面,将其忽略]

在UIStackView和UIScrollView之间设置相等的Width约束。

在此处输入图片说明


0

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

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

 }

0

在您的场景上放置一个滚动视图,并调整其大小以使其充满场景。然后,将堆栈视图放置在滚动视图内,然后将添加项目按钮放置在堆栈视图内。一切就绪后,设置以下约束:

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

在此处输入图片说明

代码:Stack View.Width = Scroll View.Width是关键。


0

为macOS Catalyst添加了一些新观点。由于macOS应用程序支持窗口大小调整,因此您可能UIStackView会从不可滚动状态转换为可滚动状态,反之亦然。这里有两件事:

  • UIStackView 被设计为适合所有区域。
  • 在过渡期间,UIScrollView将尝试调整其边界大小,以解决导航栏(对于macOS应用程序为工具栏)下方新获得/丢失的区域。

不幸的是,这将造成无限循环。我UIScrollView对它及其不是很熟悉adjustedContentInset,但是从其layoutSubviews方法登录后,我看到了以下行为:

  • 一个扩大窗口。
  • UIScrollView 尝试缩小其边界(因为不需要工具栏下方的区域)。
  • UIStackView 如下。
  • 不知何故UIScrollView,并决定恢复到更大的界限。这让我感到很奇怪,因为我从日志中看到的是UIScrollView.bounds.height == UIStackView.bounds.height
  • UIStackView 如下。
  • 然后循环到步骤2。

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

  • 对齐UIStackView.topUIScrollView.topMargin
  • 设置contentInsetAdjustmentBehavior.never

在这里,我关注的是垂直滚动的视图以及垂直增长的视图UIStackView。对于水平对,请相应地更改代码。

希望它对将来的任何人有帮助。在互联网上找不到任何人提及此事,这花了我很长时间才能弄清发生了什么。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.