简体   繁体   English

具有动态大小内容的 UIScrollView

[英]UIScrollView with dynamically sized content

(Xcode 11, Swift) (Xcode 11,斯威夫特)

Being a newbie to iOS and Autolayout, I'm struggling with implementing a fairly simple (IMHO) view which displays a [vertical] list of items.作为 iOS 和 Autolayout 的新手,我正在努力实现一个相当简单的(恕我直言)视图,该视图显示 [垂直] 项目列表。 The only problem is that items are decided dynamically and each of them could be either text or image (where either of those could be fairly large so scrolling would be required).唯一的问题是项目是动态决定的,每个项目都可以是文本或图像(其中任何一个都可能相当大,因此需要滚动)。 WebView is not an option, so it has to be implemented natively. WebView 不是一个选项,因此它必须在本地实现。

This is how I understand the process:我是这样理解这个过程的:

  • Make in IB a UIScrollView and size it to the size of the outer frame.在 IB 中制作一个 UIScrollView 并将其调整为外框的大小。
  • Make a container view as a subview of UIScrollView (again, in IB) and size it the same.将容器视图作为 UIScrollView 的子视图(同样,在 IB 中)并将其大小设置为相同。
  • Set constraint on equal width of both设置两个相等宽度的约束
  • At runtime, populate container view with UILabels/UIImageViews and also set constraints programmatically to ensure proper layout.在运行时,使用 UILabels/UIImageViews 填充容器视图,并以编程方式设置约束以确保正确的布局。
  • "Tell" scrollview about the subview height in order to make it manage the scrolling thereof. “告诉”滚动视图有关子视图的高度,以使其管理其滚动。

Is this the right approach?这是正确的方法吗? It doesn't seem to work for me (for a toy example of dynamically adding a very tall image to a container view - I cannot get the scrolling to work).它似乎对我不起作用(对于一个将非常高的图像动态添加到容器视图的玩具示例 - 我无法让滚动工作)。 What would be the proper way to do the last step in the process above - just force the contentSize of the scrollview to the size of the populated container view (it doesn't seem to work for me).在上述过程中执行最后一步的正确方法是什么 - 只需将滚动视图的 contentSize 强制为填充容器视图的大小(它似乎对我不起作用)。 Any help would be appreciated.任何帮助,将不胜感激。

When adding multiple elements to a scroll view at run-time, you may find it much easier to use a UIStackView ... when setup properly, it will automatically grow in height with each added object.在运行时向滚动视图添加多个元素时,您可能会发现使用UIStackView更容易......如果设置正确,它会随着每个添加的对象自动增加高度。

As a simple example...举个简单的例子...

1) Start by adding a UIScrollView (I gave it a blue background to make it easier to see). 1)首先添加一个UIScrollView (我给它一个蓝色背景以便更容易看到)。 Constrain it to Zero on all 4 sides:将其在所有 4 个方面都约束为零:

在此处输入图像描述

Note that we see the "red circle" indicating missing / conflicting constraints.请注意,我们看到“红色圆圈”表示缺少/冲突的约束。 Ignore that for now.暂时忽略它。

2) Add a UIView as a "content view" to the scroll view (I gave it a systemYellow background to make it easier to see). 2) 将UIView作为“内容视图”添加到滚动视图(我给它一个 systemYellow 背景,以便于查看)。 Constrain it to Zero on all 4 sides to the Content Layout Guide -- this will (eventually) define the scroll view's content size.其在内容布局指南所有 4 个方面都约束为零——这将(最终)定义滚动视图的内容大小。 Also constrain it equal width and equal height to the Frame Layout Guide :还要将其限制为等宽和等高Frame Layout Guide

在此处输入图像描述

Important Step: Select the Height constraint, and in the Size Inspector pane select the Placeholder - Remove at build time checkbox.重要步骤:选择 Height 约束,然后在Size Inspector窗格中选择Placeholder - Remove at build time复选框。 This will satisfy auto-layout in IB during design time, but will allow the height of that view to shrink / grow as necessary.这将在设计期间满足 IB 中的自动布局,但将允许该视图的高度根据需要缩小/增长。

3) Add a Vertical UIStackView to the "content view". 3) 将一个 Vertical UIStackView添加到“内容视图”。 Constrain it to Zero on all 4 sides.将其在所有 4 个面上都约束为零。 Configure its properties to Fill / Fill / 8 (as shown below):将其属性配置为Fill / Fill / 8 (如下图):

在此处输入图像描述

4) Add an @IBOutlet connection to the stack view in your view controller class. 4) 在视图控制器类中添加一个@IBOutlet连接到堆栈视图。 Now, at run-time, as you add UI elements to the stack view, all of your "scrollability" will be handled by auto-layout.现在,在运行时,当您将 UI 元素添加到堆栈视图时,您的所有“可滚动性”都将由自动布局处理。

Here is an example class:这是一个示例类:

class DynaScrollViewController: UIViewController {

    @IBOutlet var theStackView: UIStackView!

    override func viewDidLoad() {
        super.viewDidLoad()

        // local var so we can reuse it
        var theLabel = UILabel()
        var theImageView = UIImageView()

        // create a new label
        theLabel = UILabel()
        // this gets set to false when the label is added to a stack view,
        // but good to get in the habit of setting it
        theLabel.translatesAutoresizingMaskIntoConstraints = false
        // multi-line
        theLabel.numberOfLines = 0
        // cyan background to make it easy to see
        theLabel.backgroundColor = .cyan
        // add 9 lines of text to the label
        theLabel.text = (1...9).map({ "Line \($0)" }).joined(separator: "\n")

        // add it to the stack view
        theStackView.addArrangedSubview(theLabel)

        // add another label
        theLabel = UILabel()
        // multi-line
        theLabel.numberOfLines = 0
        // yellow background to make it easy to see
        theLabel.backgroundColor = .yellow
        // add 5 lines of text to the label
        theLabel.text = (1...5).map({ "Line \($0)" }).joined(separator: "\n")

        // add it to the stack view
        theStackView.addArrangedSubview(theLabel)

        // create a new UIImageView
        theImageView = UIImageView()
        // this gets set to false when the label is added to a stack view,
        // but good to get in the habit of setting it
        theImageView.translatesAutoresizingMaskIntoConstraints = false
        // load an image for it - I have one named background
        if let img = UIImage(named: "background") {
            theImageView.image = img
        }
        // let's give the image view a 4:3 width:height ratio
        theImageView.widthAnchor.constraint(equalTo: theImageView.heightAnchor, multiplier: 4.0/3.0).isActive = true

        // add it to the stack view
        theStackView.addArrangedSubview(theImageView)

        // add another label
        theLabel = UILabel()
        // multi-line
        theLabel.numberOfLines = 0
        // yellow background to make it easy to see
        theLabel.backgroundColor = .green
        // add 2 lines of text to the label
        theLabel.text = (1...2).map({ "Line \($0)" }).joined(separator: "\n")

        // add it to the stack view
        theStackView.addArrangedSubview(theLabel)

        // add another UIImageView
        theImageView = UIImageView()
        // this gets set to false when the label is added to a stack view,
        // but good to get in the habit of setting it
        theImageView.translatesAutoresizingMaskIntoConstraints = false
        // load a different image for it - I have one named AquariumBG
        if let img = UIImage(named: "AquariumBG") {
            theImageView.image = img
        }
        // let's give this image view a 1:1 width:height ratio
        theImageView.heightAnchor.constraint(equalTo: theImageView.widthAnchor, multiplier: 1.0).isActive = true

        // add it to the stack view
        theStackView.addArrangedSubview(theImageView)

    }

}

If the steps have been followed, you should get this output:如果已遵循这些步骤,您应该得到以下输出:

在此处输入图像描述

and, after scrolling to the bottom:并且,滚动到底部后:

在此处输入图像描述

Alignment constraints (leading/trailing/top/bottom)对齐约束(前导/尾随/顶部/底部)

The alignment constraint between Scroll View and Content View defines the scrollable range of the content . Scroll View 和 Content View 之间的对齐约束定义了内容的scrollable range of the content For example ,例如

  • If scrollView.bottom = contentView.bottom, it means Scroll View is scrollable to the bottom of Content View.如果scrollView.bottom = contentView.bottom,则表示Scroll View 可以滚动到Content View 的底部。
  • If scrollView.bottom = contentView.bottom + 100, the scrollable bottom end of Scroll View will exceed the end of Content View by 100 points.如果 scrollView.bottom = contentView.bottom + 100,则 Scroll View 的可滚动底端将超过 Content View 的底端 100 个点。
  • If scrollView.bottom = contentView.bottom — 100, the bottom of Content View will not be reached even the scrollView is scrolled to the bottom end.如果scrollView.bottom = contentView.bottom — 100,即使scrollView滚动到最底端也不会到达Content View的底部。

That is, the (bottom) anchor on Scroll View indicates the (bottom) edge of the outer frame, ie, the visible part of Content View;即Scroll View上的(底部)anchor表示外框的(底部)边缘,即Content View的可见部分; the (bottom) anchor on Content View refers to the edge of the actual content, which will be hidden if not scrolled to. Content View 上的(底部)锚点是指实际内容的边缘,如果不滚动到该边缘,它将被隐藏。 Unlike normal use cases, alignment constraints between Scroll View and Content View have nothing to do with the actual size of Content View.与普通用例不同, Scroll View 和 Content View 之间的对齐约束与 Content View 的实际大小无关。 They affect only “scrollable range of content view” but NOT “actual content size”.它们仅影响“内容视图的可滚动范围”,而不影响“实际内容大小”。 The actual size of Content View must be additionally defined.内容视图的实际大小必须另外定义。

Size constraints (width/height)尺寸限制(宽度/高度)

To actually size Content View, we may set the size of Content View to a specific length, like width/height of 500. If the width/height exceeds the width/height of Scroll View, there will be a scrollbar for users to scroll.要实际调整 Content View 的大小,我们可以将 Content View 的大小设置为特定的长度,比如宽/高为 500。如果宽/高超过了 Scroll View 的宽/高,就会有一个滚动条供用户滚动。 However, a more common case will be, we want Content View to have the same width (or height) as Scroll View.但是,更常见的情况是,我们希望 Content View 与 Scroll View 具有相同的宽度(或高度)。 In this case, we will have在这种情况下,我们将有

contentView.width = scrollView.width

The width of Content View refers to the actual full width of content. Content View 的宽度是指内容的实际全宽。 On the other hand, the width of Scroll View refers to the outer container frame width of Scroll View.另一方面,Scroll View 的宽度是指 Scroll View 的外部容器框架宽度。 Of course, it doesn't have to be the same width, but can be other forms like a * scrollView.width + b.当然,不一定非要等宽,也可以是a * scrollView.width + b等其他形式。 And if we have Content View higher (or wider) than Scroll View, a scrollbar appears.如果我们的 Content View 比 Scroll View 更高(或更宽),就会出现一个滚动条。 Content View can not only be a single view, but also multiple views, as long as they are appropriately constrained using alignment and size constraints to Scroll View. Content View 不仅可以是单个视图,还可以是多个视图,只要它们使用 Scroll View 的对齐和大小约束进行适当约束即可。

For details, you may follow this article: Link .详情可以关注这篇文章: 链接

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM