使用Swift编程动态创建垂直UIScrollView

11

我已经寻找了好几天一个(可行的)UIScrollView垂直滚动的教程或者例子应用程序,特别是通过编程实现。有大量关于使用故事板的教程,这让我感到困惑。

我查看了苹果的文档,他们的“指南”仍然没有给出一个具体的例子或提示从哪里开始。到目前为止,我尝试了以下一些方法。

在类中直接将我的视图设置为UIScrollView

let scrollView = UIScrollView(frame: UIScreen.mainScreen().bounds)

然后在我的viewDidLoad函数中将其分配给视图

self.view = scollView

试图更改内容大小。

self.scrollView.contentSize = CGSize(width:2000, height: 5678)

尝试启用滚动

scrollView.scrollEnabled = true

我能找到的最后一个关于如何以程序化方式完成这个的建议

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()
    scrollView.frame = view.bounds
}

目前我还没有尝试将我的对象添加到滚动视图中(我不需要缩放,只需垂直滚动),但是我无论如何都没能使任何东西运作起来 :( 实际上,使用这些添加项运行应用程序只会导致界面问题,屏幕会奇怪地向上移动,而且它不能适合整个屏幕宽度?我尝试通过使框架边界等于宽度来修复它,但仍然没有效果

我没有收到任何错误信息。

我想看到一个可以垂直滚动的viewcontroller示例!或者任何帮助都将被非常赞赏!

这里是一张灾难性的截图,试图使我的视图可滚动。

Screen Shot

(我让滚动视图的背景变成了红色,以查看它是否正确显示。虽然它似乎是正确的,但我无法在任何地方滚动

如评论所建议的,我试着将self.view = self.scrollview改为将scrollview添加为self.view的子视图,但并没有任何积极的结果。

添加:

scrollView.contentSize = CGSize(width:2000, height: 5678)

按照下面的评论建议,查看了viewDidLayoutSubviews后,我的视图变得可以滚动了!

但出于某些原因,我的布局仍然看起来很混乱(在我将其设置为可滚动之前,它看起来像应该的样子)。

这是我的topBar(蓝色部分)的一个示例约束条件,它应该占据整个水平空间。

self.scrollView.addConstraints(
        NSLayoutConstraint.constraintsWithVisualFormat(
            "H:|[topBar]|", options: nil, metrics: nil, views: viewsDictionary))

有什么想法为什么这不起作用?


你试过不是替换当前视图,而是将scrollView作为子视图添加吗? - tkanzakic
我将其作为子视图添加,并将所有的“self.view.addsubview(..)”更改为“self.scrollView.addSubView(...)”,同时也修改了我的约束。但它仍然看起来很糟糕。让我更新我的帖子,加上一张灾难性的图片。 - Mark L
self.scrollView.contentSize = CGSize(width:2000, height: 5678)放在viewDidLayoutSubviews中,然后告诉我会发生什么......还有在viewDidAppear中也要这样做... - Fahim Parkar
我认为问题出在这里:self.view = scollView应该是 self.view.addSubview(scollView),不确定 Swift 的语法是什么,但是添加子视图的方法应该是这样的... - Fahim Parkar
将其放在viewDidLayoutSubviews中,最终使我的视图可滚动了。然而,我的布局仍然完全混乱。 (如上图所示)。让我编辑它,使用一个应该占据整个水平空间的约束示例。但是并没有。 - Mark L
@MarkL:非常抱歉,我从未使用过自动布局,因为它不能像 Android 布局一样好用。 - Fahim Parkar
3个回答

34

Swift 4.2

我用自动布局制作了一个简单而完整的滚动堆栈视图示例。

所有视图都在代码中,不需要使用故事板。

import UIKit

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        view.addSubview(scrollView)
        scrollView.addSubview(scrollViewContainer)
        scrollViewContainer.addArrangedSubview(redView)
        scrollViewContainer.addArrangedSubview(blueView)
        scrollViewContainer.addArrangedSubview(greenView)

        scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
        scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
        scrollView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
        scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true

        scrollViewContainer.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor).isActive = true
        scrollViewContainer.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor).isActive = true
        scrollViewContainer.topAnchor.constraint(equalTo: scrollView.topAnchor).isActive = true
        scrollViewContainer.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor).isActive = true
        // this is important for scrolling
        scrollViewContainer.widthAnchor.constraint(equalTo: scrollView.widthAnchor).isActive = true
    }

    let scrollView: UIScrollView = {
        let scrollView = UIScrollView()

        scrollView.translatesAutoresizingMaskIntoConstraints = false
        return scrollView
    }()

    let scrollViewContainer: UIStackView = {
        let view = UIStackView()

        view.axis = .vertical
        view.spacing = 10

        view.translatesAutoresizingMaskIntoConstraints = false
        return view
    }()

    let redView: UIView = {
        let view = UIView()
        view.heightAnchor.constraint(equalToConstant: 500).isActive = true
        view.backgroundColor = .red
        return view
    }()

    let blueView: UIView = {
        let view = UIView()
        view.heightAnchor.constraint(equalToConstant: 200).isActive = true
        view.backgroundColor = .blue
        return view
    }()

    let greenView: UIView = {
        let view = UIView()
        view.heightAnchor.constraint(equalToConstant: 1200).isActive = true
        view.backgroundColor = .green
        return view
    }()
}

希望这可以帮助到您!


12

严格地说,我觉得问题出在下面这一行。

self.view = scollView

应该是self.view.addSubview(scollView)

然后将所有的标签、按钮等都添加到滚动视图中,然后设置内容大小。

内容大小是告诉滚动视图要滚动的参数。

viewDidLayoutSubviews中放置self.scrollView.contentSize = CGSize(width:2000, height: 5678)


不过有一件事。由于我仍然遇到布局问题,但这不是我的原始问题。我应该新建一个问题来解决,对吧? - Mark L
@MarkL:你把它标记为已接受的答案是很好的点。如果我接受了回答,我不会给它投赞成票...只有在它帮助了你但未被接受为答案时才会投赞成票... - Fahim Parkar
那个 viewDidLayoutSubviews 对我非常有帮助。谢谢! - Trig3rz
@FahimParkar 在这里你写了contentSize静态,但我们的要求是动态的。 - Berlin

3

感谢Tom Insam的帮助,帮助我整理了这个答案:

您需要像通常一样添加所有约束,即:

  • 为滚动视图及其父视图添加约束。
  • 为滚动视图的内容添加约束。

然后暂停和理解,此时即使您知道scrollView的框架是400 * 600,其内容的大小仍然未知。它可以是400 * 6000或400 * 300或其他任何大小。

没有其他基于边缘的约束(leading、trailing、left、right、top、bottom、margins)可用于计算滚动视图的内容大小

除非您的视图具有某些intrinsicContentSize。假设您的视图没有intrinsicContentSize,则如果运行代码,会出现运行时错误,提示“ScrollView内容大小不明确”。继续阅读以了解更多关于intrinsicContentSize的信息。

解决方案是什么?

https://developer.apple.com/library/archive/technotes/tn2154/_index.html

使用纯自动布局方法,请按照以下步骤进行:

  • translatesAutoresizingMaskIntoConstraints 在所有涉及的视图上设置为 NO
  • 使用与滚动视图 外部的 约束来定位和调整滚动视图的大小。
  • 使用约束来布局滚动视图中的子视图,确保这些约束与滚动视图的四个边缘连接,并且不依赖于滚动视图来获取它们的大小。

我加粗的部分是我的答案的重点。您需要设置一个与边缘无关的(水平/垂直)约束。否则,它将依赖于滚动视图来获取其大小。

相反,您需要明确设置宽度、高度或附加到轴的中心的约束条件。如果视图具有intrinsicContentSize(例如标签、文本视图、按钮可以根据字体、字符长度和换行符计算其内容大小(基本上任何带有文本的东西都会有一个内容大小),则第3个项目是不必要的。要深入了解intrinsicContentSize,请参见此处


换句话说:

ScrollView 需要:

  • 相对于其父视图的外部约束
  • 用于其内容的内部约束
  • 边缘到边缘/链接子视图,以一种可以计算其高度(如果垂直滚动)的方式。对于布局引擎来说,设置一个高度为1000的 contentSize 不应该与将多个子视图链接在一起,其中所有内容的高度可以计算为1000 的情况有任何不同。例如,您有两个标签,它们一起有400行。每行占用2个点,有100个点的行间距和100个点的标签之间的空间,总共是1000个点(400 * 2 + 100 + 100)。

您看,操作系统可以在不查看标签周围环境的情况下计算出这个值。这就是我所说的在不依赖于 ScrollView 的情况下计算大小。


同时也请参考这里的文档。


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