iOS:使用ContainerView的动态内容的UIScrollView(一步一步)

Krø*_*lle 6 uitableview uiscrollview uiview ios

虽然这里的问题存在多个问题和答案,但我无法创建UIScrollView静态和动态内容(通过使用a ContainerView)并使大小正常工作.因此,我将提供一步一步的指导,直到我无法取得任何进展,有人可以提供解决方案.通过这种方式,我们将有一个可行的样本,可以一步一步地使其工作.

请注意:为方便起见,所有步骤的输出都上传到https://github.com/oysteinmyrmo/DynamicScrollablePage.可以从那里获取测试Xcode项目并进一步攻击.

更新:在@ agibson007的答案之后,最后有几个步骤可以将原始步骤修复为正常工作的解决方案.通过声明ERROR,查看最终步骤来指出错误.

目标:

有一个UIView带有各种静态UIViews和ContainerView动态内容的长滚动页面.为了完整起见,动态内容将包含一些静态UIViews和一个UITableView将扩展到其全部内容的内容.最后一个因素似乎是我最近偶然发现的各种问题的一个反复出现的主题.

方法:

  1. 我们将从一个新的Xcode项目(Xcode 8.2.1)开始,并使用Swift 3.0.2作为语言.
  2. 我们将一步一步地创建测试UIViews,UIViewControllers和其他所需项目.
  3. 在某些时候,我们有一个"模板",可用于使内容能够由有能力的人动态扩展.

第1步:项目创建

打开Xcode(8.2.1),启动一个新项目.

  1. 选择选项卡式应用程序 我们将UIScrollView在第一个选项卡中创建.
  2. 将产品名称设置为DynamicScrollablePage.
  3. 选择位置并创建项目.

第2步:项目的初始更改

UI的更改将在第一个选项卡中完成.这个过程很大程度上受到这个答案的影响,但我们会ContainerView为动态内容添加几个项目和一个项目.

  1. Main.storyboard,First View(即标签1)删除两个标签.
  2. 单击UIViewController(首先命名).转到它的大小检查,从改变FixedFreeform,改变高度1500这只是故事板视觉变化.
  3. 将剩余的名称重命名UIViewRootView.
  4. 添加UIScrollView内部RootView.说出来ScrollView.约束:
    • ScrollView [顶部,底部,前导,尾随,宽度] = RootView [顶部,底部,前导,尾随,宽度].根据我的经验,还必须设置宽度约束以确保稍后覆盖整个屏幕.
  5. 添加UIView内部ScrollView并命名ContentView.约束:
    • ContentView [Leading,Trailing,Top,Bottom,Width] = ScrollView [Leading,Trailing,Top,Bottom,Width].故事板现在会抱怨滚动高度.在下面的步骤之后它不会抱怨.
  6. 添加项目到ContentView:
    • 首先添加一个UIView,命名它RedView.设置RedView[领先,尾随,顶部] = ContentView[领先,尾随,顶部].设置RedView[高度,背景颜色] = [150,红色].
    • 添加UILabel以下内容RedView,将其名称/文本设置为FirstLabel.设置FirstLabel[Leading,Trailing] = ContentView[Leading,Trailing].设置FirstLabel[顶部] = RedView[底部].
    • UIView下面添加一个FirstLabel名称BlueView.设置BlueView[Leading,Trailing] = ContentView[Leading,Trailing].设置BlueView[顶部] = FirstLabel[底部].设置BlueView[高度,背景颜色] = [450,蓝色].
    • 添加UILabel以下内容BlueView,将其名称/文本设置为SecondLabel.设置SecondLabel[Leading,Trailing] = ContentView[Leading,Trailing].设置SecondLabel[顶部] = BlueView[底部].
    • UIContainerView下面添加一个SecondLabel名称ContainerView.设置ContainerView[Leading,Trailing] = ContentView[Leading,Trailing].设置ContainerView[顶部] = SecondLabel[底部].设置ContainerView[内在尺寸] = [占位符](请参阅尺寸检查器ContainerView).将内在大小设置为占位符告诉Xcode它的大小由其子视图定义(据我所知). 错误,看看最后的步骤
    • UILabel在最后添加一个,命名它BottomLabel.设置BottomLabel[Leading,Trailing] = ContentView[Leading,Trailing].设置BottomView[顶部] = ContainerView[底部].
    • 最后,控制+拖动ScrollViewBottomView并选择Bottom Space to ScrollView.这将确保ScrollView高度正确.

第3步:使用动态内容创建ViewController

现在我们将创建将用于显示动态内容的实际UIViewControllerxib文件.我们将创建一个UITableView内部xib,因此我们还需要UITableViewCell一个简单的标签.

  1. 创建一个Swift文件,TableViewCell.swift内容如下:

    import UIKit
    
    class TableViewCell : UITableViewCell {
    }
    
    Run Code Online (Sandbox Code Playgroud)
  2. 创建一个名为xib/的View文件TableViewCell.xib.请执行下列操作:

    • 删除默认值UIView并将其替换为UITableViewCell.
    • 添加UILabel到该单元格,为其命名DataLabel(它还将添加内容视图UIView).
    • UITableViewCell自定义类设置为TableViewCell.
    • 将表视图单元标识符设置为TableViewCellId.
    • 在双视图模式下,按住Ctrl键并将标签拖到TableViewCell类中.结果应该是:

      import UIKit
      
      class TableViewCell : UITableViewCell {
          @IBOutlet weak var dataLabel: UILabel!
      }
      
      Run Code Online (Sandbox Code Playgroud)
  3. 创建一个DynamicEmbeddedViewController.swift包含以下内容的文件:

    import UIKit
    
    class DynamicEmbeddedViewController : UIViewController, UITableViewDataSource, UITableViewDelegate
    {
        @IBOutlet weak var tableView: UITableView!
        let data = ["First", "Second", "Third", "Fourth", "Fifth", "Sixth", "Last"]
    
        override func viewDidLoad() {
            super.viewDidLoad()
            tableView.register(UINib(nibName: "TableViewCell", bundle: nil), forCellReuseIdentifier: "TableViewCell")
        }
    
        func numberOfSections(in tableView: UITableView) -> Int {
            return 1
        }
    
        func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
            return data.count
        }
    
        func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
            let cell = tableView.dequeueReusableCell(withIdentifier: "TableViewCell", for: indexPath) as! TableViewCell
            cell.dataLabel.text = data[indexPath.row]
            return cell
        }
    }
    
    Run Code Online (Sandbox Code Playgroud)
  4. 创建一个名为xib/的View文件DynamicEmbeddedView.xib.将main重命名UIViewContentView并在以下内容中添加三个项ContentView:

    • 添加一个UIView,命名它GreenView.设置GreenView[领先,尾随,顶部] = ContentView[领先,尾随,顶部].设置GreenView[高度] = [150].
    • 添加一个UITableView,命名它TableView.设置TableView[Leading,Trailing] = ContentView[Leading,Trailing].设置TableView[顶部] = GreenView[底部].设置内在大小=占位符.我不确定这是否是正确的方法.错误,看看最后的步骤
    • UIView下面添加一个TableView名称PurpleView.设置PurpleView[Leading,Trailing] = ContentView[Leading,Trailing].设置PurpleView[顶部] = TableView[底部].
    • 注意:此时我们可能需要在xib中有更多约束,但我不确定是什么以及如何,如果有的话.
    • File's Owner自定义类设置为DynamicEmbeddedViewController.
    • File's Owner视图出口设置为ContainerView.
    • 设置TableView的dataSource和委托给File's Owner.
    • 添加IBOutletTableViewDynamicEmbeddedViewController类.
  5. 连接建立xibUIViewControllerMain.storyboard.

    • ContainerView输出视图控制器的自定义类设置为DynamicEmbeddedViewController.
    • 删除现有ViewContainerViews输出视图控制器.我不确定这是否真的需要.

现状图片:

Main.storyboard

DynamicEmbeddedView.xib

第4步:运行应用程序:

运行应用程序并一直滚动到底部,包括反弹区域,这是结果:

在此输入图像描述

由此我们可以得出结论:

  • 它的位置ContainerView是正确的(即在SecondLabel和之间BottomLabel),但是BottomLabel它的约束力不会低于ContainerView.
  • TableView的高度显然是0.这也可以看出,因为func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath)没有被调用.如果我们在其上设置了高度约束TableView,则会显示项目.
  • ScrollView如果大小增加,则内容大小不会ContainerView增加.
  • 还希望始终显示动态TableView中的所有项目,只需滚动它们就像它们只是静态数据一样ScrollView.
  • 这件事真的很乱.

第五步:问题!

  • 我们怎样才能使ScrollView内容正确包装所有内容,包括TableView生活中的动态数据ContainerView
  • 约束是否设置正确?
  • 我们应该在何处以及如何计算适当的高度/内容大小?
  • 所有这些都是必要的; 有更简单的方法来实现这一目标吗?

第6步:在@ agibson007的回答后修复解决方案:

  • static let CELL_HEIGHT = 44像这样添加:

    import UIKit
    
    class TableViewCell : UITableViewCell {
        @IBOutlet weak var dataLabel: UILabel!
        static let CELL_HEIGHT = 44
    }
    
    Run Code Online (Sandbox Code Playgroud)
  • TableView内在大小恢复DefaultPlaceholder.

  • 设置例如150的高度约束TableView.该值必须大于一个单元格的高度.
  • 将高度约束添加到DynamicEmbeddedViewControlleras IBOutlet.
  • 添加代码以计算和​​设置TableView高度约束.最后一堂课:

    import UIKit
    
    class DynamicEmbeddedViewController : UIViewController, UITableViewDataSource, UITableViewDelegate
    {
        @IBOutlet weak var tableView: UITableView!
        @IBOutlet weak var tableViewHeight: NSLayoutConstraint!
    
        let data = ["First", "Second", "Third", "Fourth", "Fifth", "Sixth", "Last"]
    
        override func viewDidLoad() {
            super.viewDidLoad()
            tableView.register(UINib(nibName: "TableViewCell", bundle: nil), forCellReuseIdentifier: "TableViewCell")
    
            // Resize our constraint
            let totalHeight = data.count * TableViewCell.CELL_HEIGHT
            tableViewHeight.constant = CGFloat(totalHeight)
            self.updateViewConstraints()
            //in a real app a delegate call back would be good to update the constraint on the scrollview
        }
    
        func numberOfSections(in tableView: UITableView) -> Int {
            return 1
        }
    
        func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
            return data.count
        }
    
        func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
            let cell = tableView.dequeueReusableCell(withIdentifier: "TableViewCell", for: indexPath) as! TableViewCell
            cell.dataLabel.text = data[indexPath.row]
            return cell
        }
    
    }
    
    Run Code Online (Sandbox Code Playgroud)
  • ContainerView内在大小恢复DefaultPlaceholder.

  • 设置例如150的高度约束ContainerView.该值将在代码中更新.
  • 添加高度约束到ContainerView作为一个IBOutletFirstViewController.
  • 添加ContainerViewIBOutletFirstViewController.
  • 创建对DynamicEmbeddedViewControllerin的引用,FirstViewController以便可以引用它来进行高度计算.
  • 添加代码以计算和​​设置ContainerView高度约束.最后一FirstViewController堂课:

    import UIKit
    
    class FirstViewController: UIViewController {
    
        @IBOutlet weak var containerView: UIView!
        @IBOutlet weak var containerViewHeightConstraint: NSLayoutConstraint!
        var dynamicView: DynamicEmbeddedViewController?
    
        override func viewDidLoad() {
            super.viewDidLoad()
            // Do any additional setup after loading the view, typically from a nib.
    
            if dynamicView != nil{
                dynamicView?.tableView.reloadData()
                let size = dynamicView?.tableView.contentSize.height
                //cheating on the 300 because the other views in that controller at 150 each
                containerViewHeightConstraint.constant = size! + 300
                self.view.updateConstraintsIfNeeded()
            }
        }
    
        override func didReceiveMemoryWarning() {
            super.didReceiveMemoryWarning()
            // Dispose of any resources that can be recreated.
        }
    
        override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
            if (segue.identifier == "ContainerViewSegue") {
                dynamicView = segue.destination as? DynamicEmbeddedViewController
            }
        }
    
    Run Code Online (Sandbox Code Playgroud)

    }

最后一切都按预期工作!

在此输入图像描述

请注意:为方便起见,所有步骤的输出都上传到https://github.com/oysteinmyrmo/DynamicScrollablePage.可以从那里获取测试Xcode项目并进一步攻击.

agi*_*007 8

我确信这已经得到了回答,但是我不记得是否已经回答了为什么人们在滚动视图中遇到这么多问题.关于UIScrollView和Autolayout,您需要了解的两件事情.

1)scrollview需要能够计算内部内容的宽度和高度.这有助于确定它是否确实需要滚动.

2)某些视图的大小基于内部的内容.示例UILabel具有"固有"内容大小.这意味着如果你将它拖到故事板上,你不需要设置高度或宽度,除非你试图限制它.具有固有大小的视图的其他示例将是UIButton,UIImageView(如果它具有图像),UITextView(具有禁用滚动)以及其中可以具有文本或图像的其他控件.

所以让我们开始非常简单,将UIScrollView拖到故事板上并将其固定到superview上.一切都很好没有警告. 开始

现在将UILabel拖到滚动视图上,并在警告处快速找到峰值.ScrollView模糊可滚动内容(宽度和高度).

UILabelNoConstraints

现在为所有人添加20的顶部,底部,前导和尾随.

没有警告.

UILabelConstraints

测试2)删除UILabel并将UIView拖到滚动视图上并添加20的顶部,底部,前导和尾部.

警告!警告!警告!

UIViewWarnings

问题是UIView没有能力调整自身的大小,而scrollview也不知道它的内容有多大,因此无法设置.


如果这使SENSE == True继续ELSE goBack

现在,它将变得更加复杂,具体取决于我们走了多远,但上述概念决定了整个过程.

调查您的项目和设置,您很好地学习了UIScrollview.

现在让我们回顾一下您的摘要,我将就某些事情进行评论.

从你上面的报价

"我们可以得出结论:ContainerView的位置是正确的(即在SecondLabel和BottomLabel之间),但BottomLabel不会将其约束条件保持在ContainerView之下."

*** - >你在这里的总结实际上是不正确的.转到标签上方的容器,并在界面构建器中选中要绑定的剪辑,然后重新运行项目,标签将位于底部,但不会显示绿色视图.为什么?它不知道应该有多大.当你在它加载之前运行它时它可以和uilabels清楚,所以当它超出它的范围时,它看起来是不正确的.

"TableView的高度显然是0.这也可以看到,因为没有调用func tableView(_ tableView:UITableView,cellForRowAt indexPath:IndexPath).如果我们在TableView上设置一个高度约束,项目就会显示出来."

*** - >这是最棘手的问题.您必须加载tableview并获取tableview内容大小并在代码中更新tableview上的高度约束,以使其更新为scrollview以调整持有控制器的容器的大小.

另一种选择是stackview,如果它所拥有的内容具有内在的内容大小,它可以确定它的高度和宽度.

但回到tableview.您需要在tableview //上设置> = 40,以便在动态视图上开始行高.检查数据源后,如果计数为0,则将约束更新为0并使用委托让scrollview知道更新动态视图控制器上的约束以不显示表.我希望这是有道理的.然后相反,如果计数是数据源中的10个项目,那么将dynamicviewcontroller tableview高度约束的约束更新为10*40就像这样

import UIKit

    class DynamicEmbeddedViewController : UIViewController, UITableViewDataSource, UITableViewDelegate
    {
        @IBOutlet weak var tableViewConstraint: NSLayoutConstraint!
        @IBOutlet weak var tableView: UITableView!

        let data = ["First", "Second", "Third", "Fourth", "Fifth", "Sixth", "Last"]

        override func viewDidLoad() {
            super.viewDidLoad()
            tableView.delegate = self
            tableView.dataSource = self
            tableView.register(UINib(nibName: "TableViewCell", bundle: nil), forCellReuseIdentifier: "TableViewCell")

            //resize our constraint
            let count = data.count
            let constant = count * 40
            tableViewConstraint.constant = CGFloat(constant)
            self.updateViewConstraints()
           //in a real app a delegate call back would be good to update the    constraint on the scrollview
        }
Run Code Online (Sandbox Code Playgroud)

在第一个控制器中它看起来像这样.

import UIKit

class FirstViewController: UIViewController {

    @IBOutlet weak var containerViewHeightConstraint: NSLayoutConstraint!
    @IBOutlet weak var containerView: UIView!
    @IBOutlet weak var scrollView: UIScrollView!
    var dynamicView : DynamicEmbeddedViewController?
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.

        if dynamicView != nil{
            dynamicView?.tableView.reloadData()
            //get size 
            let size = dynamicView?.tableView.contentSize.height
            //cheating on the 300 because i see you set the other views in that controller at 150 each
            containerViewHeightConstraint.constant = size! + 300
            self.view.updateConstraintsIfNeeded()
        }
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if (segue.identifier == "ContainerViewSegue") {
            dynamicView = segue.destination as? DynamicEmbeddedViewController
        }
    }

}
Run Code Online (Sandbox Code Playgroud)

最后 你可以看到我们调整一切以适应.除此之外,你做对了.现在要生产有价值的代码.大多数情况下,您会知道将显示哪些内容,以便您可以控制它.另一种方法是对所有内容使用UITableView或UICollectionView,并根据内容加载不同的单元格.我希望这篇文章能够清除它.我相信我们可以继续添加它,但希望所涵盖的概念就足够了.如果这回答了你的问题,请表现出对这样做的热爱.如果你愿意,我也可以将它上传到GitHub,但你可能最好进入你开始的回购.