使用自动布局的滚动视图(Scroll View)- Swift

23

我知道这个问题已经被问了很多次,但这是我长期以来一直在努力解决的问题,我相信其他人也是如此,即使有当前的答案和教程。

添加 Scroll View 时,我按照以下步骤进行:

  1. 将一个 ScrollView 添加为视图控制器视图中原始视图的子视图。固定顶部、左侧、右侧和底部,确保未选中“约束到边距”。

  2. 将 UIView 添加为 Scroll View 的子视图。固定顶部、左侧、右侧和底部的约束。

  3. 在内容视图和视图控制器视图之间添加等宽度约束。

此时如果运行应用程序,则内容视图不会出现,而滚动视图会占据整个屏幕。

  1. 接下来,我向 Content View 中添加包含 4 个 UIView 的元素来测试所有内容。我给每个 UIView 添加顶部、左侧和右侧约束,最后一个 UIView 添加底部约束。

现在如果我运行应用程序,则内容视图和滚动视图各占屏幕的一半,我可以在 Content View 周围滚动。请参见下面的照片。

我已经遵循了所有可以找到的教程,并尝试实现我找到的所有 SO 答案,但似乎无法让它正常工作。如果有人遇到过这个问题或知道解决方案,请您帮忙解决!

绿色是 Content View,蓝色是 Scroll View。

enter image description here

Scroll View 和子视图约束

enter image description here


你能在问题中添加约束条件截图吗?同时,请告诉我 scrollview 的大小和其子视图的大小。 - Sanman
添加了约束截图。你所说的“大小”是什么意思?我正在使用所有iPhone的纵向大小类,并填充视图。 - 01Riv
之前我表达不够清楚...我想知道你为底部约束设置了什么值。将其设置为零。当我说尺寸时,指的是x、y和高度、宽度。确保contentview的高度大于scrollview。试验时只需创建高度为100的滚动视图和高度为400的contentview,并在末尾放置一些按钮。你添加的约束是正确的,只需将底部约束设置为零。希望能有所帮助 :) - Sanman
谢谢反馈!你指的是最下面哪一个?是哪个底部约束?我将大小设置为自由形式以进行测试,以便可以添加额外内容。 - 01Riv
将contentView的底部约束设置为scrollView。这样做的作用是,如果您的内容比滚动视图大,则允许您向下滚动到底部。 - Sanman
我跟着你的意思,但是除非我看错了限制条件,否则内容视图到滚动视图的设置为0。 - 01Riv
13个回答

78

在其他答案的帮助下,我弄清楚了这个问题,但我必须进行一些调整以使其按照我想要的方式工作。以下是我采取的步骤:

  1. 将滚动视图添加为主视图的子视图。

  2. 选择滚动视图,取消“约束到边缘”并固定顶部、左侧、右侧和底部的约束条件。

  3. 将一个UIView添加为滚动视图的子视图。将此视图命名为“Content View”。

  4. 选择Content View并固定顶部、左侧、右侧和底部的约束条件。然后添加一个水平居中的约束条件。

  5. 接下来从Content View到Main View添加等宽和等高约束。

  6. 在Content View中添加您需要的任何元素。将刚刚添加的元素的顶部、左侧、右侧和高度约束固定。

  7. 在Content View内最底部的项目上设置底部约束。选择此约束并更改为“大于或等于”。 将常量更改为20。

添加到Content View内项目的约束条件非常重要,特别是添加到最后一个项目的底部约束条件。它们有助于确定滚动视图的内容大小。按照我描述的添加底部约束将使视图能够滚动,如果内容太大而无法适合屏幕,则禁用滚动。


4
黄金。我找到的所有教程都没有讨论底部项目约束的重要性。它们都建议指定scrollView的contentSize。但是大多数情况下,我们需要一个根据其中内容自动调整大小的scrollView!谢谢。 - Mr_Vlasov
1
我已经遵循了步骤1-4有一段时间了,但是我用以下步骤替换了步骤5和7,效果更好:除了水平居中约束之外,在内容视图上添加一个垂直居中约束。 在“垂直居中容器约束”的属性部分中,在“构建时删除”下面打勾。使用这两个居中约束,界面生成器将允许您将对象放置在内容视图内,就像应该的那样。 - Padawan
仍然遇到scrollView的“需要Y位置或高度约束”的问题。 - Meshach
工作正常。但如果我需要隐藏一些视图,使用:https://dev59.com/EWIj5IYBdhLWcg3w95rA#19707579 我如何更改内容大小以适应隐藏的视图? - Ali Hassan
谢谢!但是当你需要水平滚动视图时会发生什么?你需要设置一个跟随约束,而不是底部约束,对吗? - A. Trejo
显示剩余2条评论

24

Xcode 11 Swift 5

更多信息在此处

为了使Auto Layout中的滚动视图(scrollview)正常工作,滚动视图必须知道其可滚动内容(滚动视图内容)的宽度和高度,以及自身的框架(X、Y、宽度、高度),也就是superview应该放置滚动视图的位置和大小。

使用Xcode 11引入了Interface Builder中的两个新功能Content Layout guideFrame Layout guide来实现scrollView的操作。

现在要从Storyboard中实现scrollView,你需要:

  • 将一个scrollView放到ViewController中并设置约束(leading, top, trailing, bottom-全为0)
  • 将一个View(contentView)放到scrollView中并设置约束(leading, top, trailing, bottom-全为0)
  • 在视图层次结构中,从新的View(contentView)到scrollView的Content Layout guide进行ctrl拖动,并在出现的上下文菜单中选择所有约束(leading, top, trailing, bottom-全为0)
  • 如果我们只想让scrollView垂直滚动,则创建一个相等的宽度约束,将视图和scrollView的Frame Layout guide联系起来(如果要水平滚动,则创建一个相等的高度约束)
  • 将所有内容视图元素放置在其中,并以使contentView的顶部和底部通过其内部的约束相连接的方式创建所有垂直约束。
  • 就是这样


    2
    这最终解决了我的问题,谢谢。请仔细审查创建的这些限制,并更改任何不等于1的乘数和任何不等于0的常数。有时候界面构建器会过于“热心”。 - Martin Gunnarsson

    5

    我已经在代码中创建了一个简单的视图,应该是不需要解释的并且会对你有所帮助。它概述了让滚动视图正常工作所需的所有步骤。

    如果还有什么不清楚的,请随时留言。

    import UIKit
    
    class TutorialView: UIView {
    
        lazy var sv: UIScrollView = {
            let object = UIScrollView()
    
            object.backgroundColor = UIColor.whiteColor()
            object.translatesAutoresizingMaskIntoConstraints = false
            return object
        }()
    
        lazy var tutorialPageOne: UIView = {
            let object = UIView(frame: UIScreen.mainScreen().bounds)
            object.translatesAutoresizingMaskIntoConstraints = false
            object.backgroundColor = UIColor.cyanColor()
    
            return object
        }()
    
        lazy var tutorialPageTwo: UIView = {
            let object = UIView(frame: UIScreen.mainScreen().bounds)
            object.translatesAutoresizingMaskIntoConstraints = false
            object.backgroundColor = UIColor.lightGrayColor()
    
            return object
        }()
    
        lazy var tutorialPageThree: UIView = {
            let object = UIView(frame: UIScreen.mainScreen().bounds)
            object.translatesAutoresizingMaskIntoConstraints = false
            object.backgroundColor = UIColor.redColor()
    
            return object
        }()
    
        override init(frame: CGRect) {
            super.init(frame: frame)
    
            self.addSubview(self.sv)
            self.sv.addSubview(self.tutorialPageOne)
            self.sv.addSubview(self.tutorialPageTwo)
            self.sv.addSubview(self.tutorialPageThree)
        }
    
        required init?(coder aDecoder: NSCoder) {
            fatalError("init(coder:) has not been implemented")
        }
    
        override func layoutSubviews() {
            super.layoutSubviews()
    
            let vc = nextResponder() as? UIViewController
            let mainSreenWidth = UIScreen.mainScreen().bounds.size.width
            let mainScreenHeight = UIScreen.mainScreen().bounds.size.height
    
            NSLayoutConstraint.activateConstraints([
                self.sv.topAnchor.constraintEqualToAnchor(vc?.topLayoutGuide.bottomAnchor),
                self.sv.leadingAnchor.constraintEqualToAnchor(self.leadingAnchor),
                self.sv.bottomAnchor.constraintEqualToAnchor(vc?.bottomLayoutGuide.topAnchor),
                self.sv.trailingAnchor.constraintEqualToAnchor(self.trailingAnchor)
            ])
    
            NSLayoutConstraint.activateConstraints([
                self.tutorialPageOne.widthAnchor.constraintEqualToConstant(mainSreenWidth),
                self.tutorialPageOne.heightAnchor.constraintEqualToConstant(mainScreenHeight),
                self.tutorialPageOne.topAnchor.constraintEqualToAnchor(self.sv.topAnchor),
                self.tutorialPageOne.leadingAnchor.constraintEqualToAnchor(self.sv.leadingAnchor),
                self.tutorialPageOne.bottomAnchor.constraintEqualToAnchor(self.sv.bottomAnchor)
            ])
    
    
            NSLayoutConstraint.activateConstraints([
                self.tutorialPageTwo.widthAnchor.constraintEqualToConstant(mainSreenWidth),
                self.tutorialPageTwo.heightAnchor.constraintEqualToConstant(mainScreenHeight),
                self.tutorialPageTwo.topAnchor.constraintEqualToAnchor(self.sv.topAnchor),
                self.tutorialPageTwo.leadingAnchor.constraintEqualToAnchor(self.tutorialPageOne.trailingAnchor),
                self.tutorialPageTwo.bottomAnchor.constraintEqualToAnchor(self.sv.bottomAnchor)
            ])
    
            NSLayoutConstraint.activateConstraints([
                self.tutorialPageThree.widthAnchor.constraintEqualToConstant(mainSreenWidth),
                self.tutorialPageThree.heightAnchor.constraintEqualToConstant(mainScreenHeight),
                self.tutorialPageThree.topAnchor.constraintEqualToAnchor(self.sv.topAnchor),
                self.tutorialPageThree.leadingAnchor.constraintEqualToAnchor(self.tutorialPageTwo.trailingAnchor),
                self.tutorialPageThree.bottomAnchor.constraintEqualToAnchor(self.sv.bottomAnchor),
                self.tutorialPageThree.trailingAnchor.constraintEqualToAnchor(self.sv.trailingAnchor)
            ])
    
        }
    }
    

    Tom,你的代码确实很自解释,特别是在Swift 3中。假设Tutorial.swift需要成为“ViewController”的SubView,这可能会成为另一篇帖子的被接受答案,在那里我的代码可以通过autolayout进行改进。如果你感兴趣,这是帖子的链接:http://stackoverflow.com/questions/40996685/in-uiscrollview-uiview-displays-successive-frame-information-correctly-while-uil/41006889#41006889 - Greg

    3

    我花了很多时间才弄清楚这个问题,但其实很简单。以下是解决方案,请按照以下步骤进行操作:

    • 将ScrollView添加为主视图的子视图
    • 在容器中垂直和水平方向上固定顶部、左侧、右侧、底部以及整个容器
    • 将容器视图添加为滚动视图的子视图
    • 在容器中垂直和水平方向上固定顶部、左侧、右侧、底部以及整个容器
    • 在容器视图中添加所需约束条件的子视图
    • 相应地设置滚动视图的内容大小

    继续滚动吧 :)


    你设置了ScrollView的内容大小吗? - Fahad Azeem
    我刚刚按照你提供的步骤进行了操作。我应该如何设置它? - 01Riv
    在容器视图中添加像TextField的视图,并将其固定到顶部、右侧并设置宽度高度。现在将ScrollView的ContentSize设置为大于您视图的高度。例如,对于iPhone 5,设置CGSizeMake(320,700),然后运行代码。然后您就可以滚动该视图了。 - Fahad Azeem

    3

    按照以下步骤进行操作:

    1. 将滚动视图作为主视图的子视图添加。
    2. 选择滚动视图并取消“约束到边缘”,然后将上、左、右、下的约束固定。
    3. 将一个UIView作为滚动视图的子视图添加。将此视图命名为“内容视图”。
    4. 将“内容视图”的约束(上、下、前导和尾随)设置为(0,0,0,0)。
    5. 现在你可以看到层次结构如下:view -> scroll view -> view(content view)
    6. 我们的“内容视图”必须与父视图具有相同的宽度和高度。
    7. 选择“内容视图”的高度约束并将优先级设置为低(250)
    8. 现在你可以使用任何高度来设计你的视图。
    9. 内容视图中添加所需的元素。将刚刚添加的元素的顶部、左侧、右侧和高度约束固定。
    10. 恭喜你完成了带自动布局的滚动视图。

    2

    如果您想创建一个比ViewController更大的视图,则可以从大小检查器中增加ViewController的高度。选择模拟大小为Freeform,并根据图像所示设置所需的高度,现在您的viewController具有所述高度。

    enter image description here

    1. 滚动视图拖入您的ViewController并应用四个约束(Leading、Trailing、Top和Bottom) enter image description here

    2. 现在在ScrollView内拖动一个与ScrollView宽度和高度相同的视图,并设置6个约束(Leading、Trailing、Top、Bottom、FixHeight、=WidthToScrollView)。在我的情况下,我有15 L和T空间以及其他相应的边距。您的视图可能有不同的边距,但约束应该是相同的。

    enter image description here. enter image description here

    1. 现在,您可以相应地拖动视图并设置约束。我在视图内有两个视图。

    enter image description here

    1. 对于第一个视图,我设置了Leading、Top、Trailing并希望具有固定高度。对于第二个,我设置了Leading、bottom、trailing,并希望具有固定高度。

    enter image description here. enter image description here

    1. 恭喜!您已经全部设置好了,现在运行您的应用程序。

    enter image description hereenter image description here


    2

    对于那些正在寻找UIScrollView和AutoLayout的示例的人。我使用SnapKit,它适用于Xcode 9,Swift 3。

        let scrollView = UIScrollView()
        view.addSubview(scrollView)
        scrollView.snp.makeConstraints { (make) in
            make.edges.equalTo(view).inset(UIEdgeInsetsMake(0, 0, 0, 0))
            make.top.left.right.equalTo(view)
        }
    
        let view1 = UIView()
        view1.backgroundColor = UIColor.red
        scrollView.addSubview(view1)
        view1.snp.makeConstraints { (make) in
            make.top.equalTo(scrollView.snp.top).offset(0)
            make.left.equalTo(scrollView.snp.left).offset(0)
            make.width.equalTo(scrollView.snp.width)
            make.height.equalTo(300)
        }
    
        let view2 = UIView()
        view2.backgroundColor = UIColor.blue
        scrollView.addSubview(view2)
        view2.snp.makeConstraints { (make) in
            make.top.equalTo(view1.snp.bottom)
            make.left.equalTo(scrollView)
            make.width.equalTo(scrollView)
            make.height.equalTo(300)
        }
    
        let view3 = UIView()
        view3.backgroundColor = UIColor.green
        scrollView.addSubview(view3)
        view3.snp.makeConstraints { (make) in
            make.top.equalTo(view2.snp.bottom)
            make.left.equalTo(scrollView)
            make.width.equalTo(scrollView)
            make.height.equalTo(100)
            make.bottom.equalTo(scrollView.snp.bottom).offset(-20)
        }
    

    1

    添加ScrollView的简单方法:

    1. 在视图控制器中添加ScrollView
    2. 将ScrollView的左、右、上和下约束添加到其父视图中
    3. 在ScrollView中添加UIView
    4. 将UIView的左、右、上和下约束添加到内容布局指南中
    5. 将UIView的等宽约束添加到框架布局指南中
    6. 现在添加您的子组件(如果您只是想检查ScrollView是否正常工作,请给出固定高度并进行检查)

    1
    使用此ScrollView,并使用类似于UITableViewCell / UICollectionViewCell的contentView。
    class ScrollView: UIScrollView {
    
    lazy var contentView : UIView = {
        let contentView = UIView(frame: .zero)
        contentView.translatesAutoresizingMaskIntoConstraints = false
        contentView.backgroundColor = .orange
        return contentView
    }()
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        viewSetup()
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    private func viewSetup() {
        addSubview(contentView)
        setupConstraints()
    }
    
    private func setupConstraints() {
    
        NSLayoutConstraint.activate([
            contentView.leadingAnchor.constraint(equalTo: leadingAnchor),
            contentView.trailingAnchor.constraint(equalTo: trailingAnchor),
            contentView.topAnchor.constraint(equalTo: topAnchor),
            contentView.bottomAnchor.constraint(equalTo: bottomAnchor),
            contentView.widthAnchor.constraint(equalTo: widthAnchor)
        ])
    
        let contraint = contentView.heightAnchor.constraint(equalTo: heightAnchor)
        contraint.priority = .defaultLow
        contraint.isActive = true
    }
    

    }


    1

    如何以编程方式完成?

    记住:(您的主视图) ->(您的滚动视图) ->(您的内容视图) -> 所有内容

    截至Swift4

        let scrollView = UIScrollView()
        scrollView.translatesAutoresizingMaskIntoConstraints = false
        addSubview(scrollView)
        
        NSLayoutConstraint.activate([scrollView.topAnchor.constraint(equalTo: topAnchor),
                                     scrollView.leadingAnchor.constraint(equalTo: leadingAnchor),
                                     scrollView.trailingAnchor.constraint(equalTo: trailingAnchor),
                                     scrollView.bottomAnchor.constraint(equalTo: bottomAnchor)])
        
        let contentView = UIView()
        contentView.translatesAutoresizingMaskIntoConstraints = false
        scrollView.addSubview(contentView)
        
        NSLayoutConstraint.activate([contentView.topAnchor.constraint(equalTo: scrollView.topAnchor),
                                     contentView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor),
                                     contentView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor),
                                     contentView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor),
                                     contentView.widthAnchor.constraint(equalTo: scrollView.widthAnchor)])
    

    然后您可以在ContentView中添加其他子视图。
    故障排除:
    确保每个约束都从contentView.TopAnchor连接到contentView.BottomAnchor,这样您就可以动态地获得contentSize。

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