2017-03-09 115 views
1

即使这里有很多问题和答案,所以我不能创建UIScrollView静态和动态内容(通过使用ContainerView)并使尺寸正常工作。因此,我将提供一步一步的指导,直到我无法取得进展并且有人能够提供解决方案。通过这种方式,我们将有一个可行的样本,可以一步一步地完成工作。iOS:使用ContainerView的动态内容的UIScrollView(一步一步)

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

更新:在@ agibson007的答案后,最后有几个步骤将原始步骤修复为工作解决方案。错误是指出错误,看到最后的步骤

目标:

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

方法:

  1. 我们将开始一个新的Xcode项目(Xcode的8.2.1),并使用SWIFT 3.0.2作为语言。
  2. 我们将一步一步创建测试UIView S,UIViewController S和其他所需物品。
  3. 在某些时候,我们有一个可以用来做内容的人谁能够动态扩展“模板”。

第1步:创建项目

开放的Xcode(8.2.1),开始一个新项目。

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

步骤2:初步改变到Project

到UI的变化将在第一个选项卡来完成。该程序深受this answer影响,但我们将为我们的动态内容添加更多项目和ContainerView

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

第3步:用动态内容

创建一个视图控制器现在,我们将创建用于显示动态内容的实际UIViewControllerxib文件。我们将在xib内部创建一个UITableView,因此为简单起见,我们还需要一个带有简单标签的UITableViewCell

  1. 与内容创建一个文件雨燕,TableViewCell.swift

    import UIKit 
    
    class TableViewCell : UITableViewCell { 
    } 
    
  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! 
      } 
      
  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 
        } 
    } 
    
  4. 创建xib/View文件,命名为DynamicEmbeddedView.xib。重命名主UIViewContentViewContentView中添加三个项目:

    • 添加UIView,将其命名GreenView。设置GreenView [Leading,Trailing,Top] = ContentView [Leading,Trailing,Top]。设置GreenView [高度] = [150]。
    • 添加一个UITableView,将其命名为TableView。设置TableView [Leading,Trailing] = ContentView [Leading,Trailing]。设置TableView [Top] = GreenView [Bottom]。 Set Intrinsic size =占位符。 我不确定这是否是正确的方法错误,请参见最终步骤
    • TableView下面添加UIView,将其命名为PurpleView。设置PurpleView [Leading,Trailing] = ContentView [Leading,Trailing]。设置为PurpleView [Top] = TableView [Bottom]。
    • 注:在这一点上,我们可能需要一些更多的约束在厦门国际银行,但我不确定是什么,以及如何,如果有的话。
    • 设置File's Owner的自定义类来DynamicEmbeddedViewController
    • File's Owner的查看出口设置为ContainerView
    • 设置TableView的数据源和委托File's Owner
    • 添加TableViewIBOutletDynamicEmbeddedViewController类。
  5. Connect在该Main.storyboard创建xibUIViewController

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

现状的图片:

Main.storyboard

DynamicEmbeddedView.xib

第4步:运行应用程序:

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

enter image description here

由此我们可以得出结论:

  • ContainerView的位置是正确的(即在SecondLabelBottomLabel之间),但BottomLabel不遵守其约束低于ContainerView
  • TableView的身高显然是0。这也可以,因为func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath)看到不叫。如果我们在TableView上设置高度限制,则项目将显示。
  • 如果ContainerView的大小增加,ScrollView的内容大小不会增加。
  • 还希望在所有的时间显示在动态TableView所有项目和刚刚滚过他们,如果他们在ScrollView只是静态数据。
  • 这件事真的很混乱。

第5步:问题!

  • 我们怎样才能使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 
    } 
    
  • 还原TableView的内在大小DefaultPlaceholder

  • TableView上设置高度限制例如150。该值必须大于一个单元格的高度。
  • IBOutlet的形式将高度限制添加到DynamicEmbeddedViewController
  • 添加代码来计算并设置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 
        } 
    
    } 
    
  • 还原ContainerView的从Placeholder固有大小Default

  • ContainerView上设置例如150的高度限制。该值将在代码中更新。
  • FirstViewController中将高度限制作为IBOutlet添加到ContainerView
  • FirstViewController中添加ContainerView作为IBOutlet
  • FirstViewController中创建对DynamicEmbeddedViewController的引用,以便它可以用于高度计算。
  • 添加代码来计算并设置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 
         } 
        } 
    

    }

最后按预期工作的一切!

enter image description here

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

回答

4

我确信之前已经回答了这个问题,但我不记得它是否已经回答了为什么人们对scrollview有这么多麻烦。关于UIScrollView和Autolayout,你必须知道两件事。

1)scrollview需要能够计算里面的内容的宽度和高度。这有助于决定是否确实需要滚动。

2)有些视图根据里面的内容有一个大小。示例UILabel具有“内部”内容大小。这意味着,如果将其拖出到故事板上,则无需设置高度或宽度,除非您试图限制它。具有内在大小的视图的其他示例将是UIButton,UIImageView(如果它具有图像),UITextView(禁用滚动)以及其他可能具有文本或图像的控件。

因此,让我们开始非常简单,将UIScrollView拖动到故事板并将其固定到超级视图。一切都很好,没有警告。 Start

现在将UILabel拖动到滚动视图上,并在警告处采取快速高峰。 ScrollView含糊不清的可滚动内容(宽度和高度)。

UILabelNoConstraints

现在添加一个顶部,底部,领导和20所有的尾随。

没有警告。

UILabelConstraints

测试2) 删除的UILabel并拖动一个UIView到滚动视图,并添加顶部,底部,领导和尾随的说20.

警告!警告!警告!

UIViewWarnings

的问题是,一个UIView不具备的能力大小和自身滚动视图不知道它的内容会多大,因此它不能安装。


IF这是有道理的==真 继续 ELSE GoBack的

现在,这里是它会因我们走多远,但上面的概念指导整个过程变得更加复杂。

调查你的项目和设置你很好地学习UIScrollview。

现在让我们回顾一下你的总结,我将就某些事情发表评论。

从你的报价上面

“由此我们可以得出结论: 的ContainerView的位置是正确的,但BottomLabel(SecondLabel和BottomLabel之间即)不符合其约束是ContainerView之下。”

*** - >你在这里总结实际上是不正确的。转到标签上方的容器,并在界面构建器中将复选标记复选到界限,然后重新运行项目,标签将位于底部,但不会显示绿色视图。为什么?它不知道它应该有多大。当它在它装载得最好之前运行它时,它可能和uilabels是清晰的,所以当它超出其界限时,它看起来不正确。如果我们在TableView上设置一个高度约束,项目就会显示出来,因为我们不能调用func tableView(_ tableView:UITableView,cellForRowAt indexPath:IndexPath)。 “

*** - >这是最难处理的。您将不得不加载tableview并获取tableview内容大小并在代码中更新tableview上的高度约束,以便为scrollview更新以调整持有该控制器的容器的大小。

另一种选择是堆栈视图,它可以确定它的高度和宽度,如果它保存的内容具有内在内容大小。

但是回到tableview。您需要在tableview上设置> = 40 //您的行高才能在动态视图上启动。如果计数为0,则检查数据源后,将约束更新为0,并使用委托让scrollview知道更新它对dynamicviewcontroller的约束,以不显示表。我希望这是有道理的。然后反过来如果计数是说,在数据源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 
     } 

并在第一控制器它是这样的。

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 
     } 
    } 

} 

Final 你可以看到,我们的一切调整以适应。除此之外,你是对的。现在生产值得的代码。大多数时候你会知道将显示什么内容,以便你可以控制它。另一种方法是对所有内容使用UITableView或UICollectionView,并根据内容加载不同的单元。我希望这篇文章能够澄清一点。我相信我们可以继续增加它,但希望涵盖的概念就足够了。如果这回答你的问题,请展示一些爱的时间来做到这一点。如果你愿意的话,我也可以把它上传到GitHub,但最好在你开始的回购中进行。

+0

我不得不说,在被这个问题困住了几个星期后,我有点期待解决方案如此简单。浮在互联网上的各种解决方案正在做各种不适合我的事情。所以这里是我要做的事情:1)将你的问题标记为答案,2)逐步更新你的步骤并更新git repo,3)开始并奖励你奖励你的时间来帮助我(和其他人)。我知道这不是在几分钟内完成的。无论如何,我打算做赏金,但你太快了! :-) –

+0

让我知道你是否需要我在完成步骤后所做的一份副本。 – agibson007