UIScrollView与动态大小内容的使用

5

(Xcode 11,Swift)

对于 iOS 和 Autolayout 的新手来说,我正在努力实现一个(在我看来)相当简单的视图,它显示一个[垂直]项目列表。唯一的问题是,项目是动态决定的,每个项目都可以是文本或图像(其中任何一个都可能非常大,因此需要滚动)。WebView 不是选项,因此必须进行本地实现。

这是我理解的过程:

  • 在 IB 中制作 UIScrollView 并将其大小调整为外部框架的大小。
  • 将容器视图作为 UIScrollView 的子视图(同样在 IB 中),并将其大小设置为相同大小。
  • 设置两者等宽的约束。
  • 在运行时,使用 UILabel/UIImageView 填充容器视图,并以编程方式设置约束以确保正确的布局。
  • “告诉” scrollview 关于子视图高度,以便让它管理滚动。

这是正确的方法吗?对我来说似乎不起作用(对于一个将非常高的图像动态添加到容器视图的玩具示例 - 我无法使滚动起作用)。在上述过程的最后一步中,应该采取什么正确的方式 - 只需强制滚动视图的 contentSize 为填充的容器视图的大小吗(对我来说似乎不起作用)?任何帮助将不胜感激。

2个回答

13

在运行时向滚动视图添加多个元素时,您可能会发现使用UIStackView更加容易...当正确设置后,它将自动随每个添加的对象增长高度。

作为一个简单的例子...

1)首先添加一个UIScrollView(我给它一个蓝色背景以使它更容易看到)。将其约束为四个方向上的零:

enter image description here

请注意,我们看到“红色圆圈”表示缺少/冲突的约束。暂时忽略它。

2)将UIView添加为滚动视图的“内容视图”(我给它一个系统黄色的背景以使它更容易看到)。将其限制为四个方向上的零 内容布局指南 - 这将(最终)定义滚动视图的内容大小。还要将其限制为等宽和等高 框架布局指南

enter image description here

重要步骤:选择高度约束,在Size Inspector窗格中选择Placeholder - Remove at build time复选框。这将在设计时满足IB中的自动布局,但允许该视图的高度根据需要收缩/增长。

3)向“内容视图”添加垂直UIStackView。将其限制为四个方向上的零。将其属性配置为Fill / Fill / 8(如下所示):

enter image description here

4)在您的视图控制器类中添加@IBOutlet连接到堆栈视图。现在,在运行时,当您向堆栈视图添加UI元素时,所有“可滚动性”都将由自动布局处理。

这是一个示例类:

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)

    }

}

如果按照步骤操作,应该会得到以下输出:

输入图像描述

然后向下滚动:

输入图像描述


0

对齐约束(前导/尾随/顶部/底部)

滚动视图和内容视图之间的对齐约束定义了可滚动的内容范围例如,

  • 如果 scrollView.bottom = contentView.bottom,则意味着滚动视图可滚动到内容视图底部。
  • 如果 scrollView.bottom = contentView.bottom + 100,则滚动视图的可滚动底端将超出内容视图末尾100个点。
  • 如果 scrollView.bottom = contentView.bottom - 100,则即使滚动视图滚动到底部,也无法到达内容视图底部。

也就是说,滚动视图上的(bottom)锚定点表示外框架的(bottom)边缘,即内容视图的可见部分;内容视图上的(bottom)锚定点指的是实际内容的边缘,如果没有滚动,则会被隐藏。 与正常用例不同,滚动视图和内容视图之间的对齐约束与内容视图的实际大小无关。它们仅影响“内容视图的可滚动范围”,而不是“实际内容大小”。必须额外定义内容视图的实际大小。

大小约束(宽度/高度)

为了实际调整内容视图(Content View)的大小,我们可以将内容视图的大小设置为一个特定的长度,例如宽度/高度为500。如果宽度/高度超过了滚动视图(Scroll View)的宽度/高度,用户就会看到滚动条。 然而,更常见的情况是,我们希望内容视图和滚动视图具有相同的宽度(或高度)。在这种情况下,我们可以使用以下代码:

contentView.width = scrollView.width

内容视图的宽度指的是内容的实际全宽度。另一方面,滚动视图的宽度指的是滚动视图的外部容器框架宽度。当然,它们的宽度并不一定相同,也可以采用其他形式,比如 * scrollView.width + b。如果内容视图高度(或宽度)大于滚动视图,则会出现滚动条。 内容视图不仅可以是单个视图,还可以是多个视图,只要它们通过对齐和大小约束适当地限制到滚动视图即可。

详情请参阅这篇文章:链接


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接