即使这里有多个问题和答案,我也无法创建具有静态和动态内容的 UIScrollView
(通过使用 ContainerView
)和使尺寸正常工作.因此,我将提供分步指南,直到我无法取得任何进展并且有人可以提供解决方案.这样我们就有了一个可行的示例,可以一步一步地进行操作.
请注意:所有步骤的输出都上传到
第 4 步:运行应用:
运行应用程序并一直滚动到底部,包括反弹区域,结果如下:
由此我们可以得出结论:
ContainerView
的位置正确(即在SecondLabel
和BottomLabel
之间),但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
:
导入 UIKit类 TableViewCell : UITableViewCell {@IBOutlet 弱变量数据标签:UILabel!静态让 CELL_HEIGHT = 44}
将 TableView
的固有大小从 Placeholder
恢复为 Default
.
TableView
上设置高度限制,例如 150.该值必须大于一个单元格的高度.DynamicEmbeddedViewController
作为 IBOutlet
.添加代码来计算和设置TableView
高度约束.最后一课:
导入 UIKit类 DynamicEmbeddedViewController : UIViewController, UITableViewDataSource, UITableViewDelegate{@IBOutlet 弱 var tableView:UITableView!@IBOutlet 弱 var tableViewHeight:NSLayoutConstraint!让数据= [第一",第二",第三",第四",第五",第六",最后"]覆盖 func viewDidLoad() {super.viewDidLoad()tableView.register(UINib(nibName: "TableViewCell", bundle: nil), forCellReuseIdentifier: "TableViewCell")//调整我们的约束让 totalHeight = data.count * TableViewCell.CELL_HEIGHTtableViewHeight.constant = CGFloat(totalHeight)self.updateViewConstraints()//在一个真实的应用程序中,一个委托回调会很好地更新滚动视图上的约束}func numberOfSections(in tableView: UITableView) ->诠释{返回 1}func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) ->诠释{返回数据.count}func tableView(_tableView: UITableView, cellForRowAt indexPath: IndexPath) ->UITableViewCell {让 cell = tableView.dequeueReusableCell(withIdentifier: "TableViewCell", for: indexPath) as!表视图单元格cell.dataLabel.text = 数据[indexPath.row]返回单元格}}
将 ContainerView
的固有大小从 Placeholder
恢复为 Default
.
ContainerView
上设置高度限制,例如 150.此值将在代码中更新.ContainerView
作为 FirstViewController
中的 IBOutlet
.ContainerView
添加为 FirstViewController
中的 IBOutlet
.FirstViewController
中创建对 DynamicEmbeddedViewController
的引用,以便在计算高度时引用它.添加代码来计算和设置 ContainerView
高度约束.最终的
导入 UIKit类 FirstViewController: UIViewController {@IBOutlet 弱 var containerView:UIView!@IBOutlet 弱变量 containerViewHeightConstraint:NSLayoutConstraint!var dynamicView: DynamicEmbeddedViewController?覆盖 func viewDidLoad() {super.viewDidLoad()//在加载视图后做任何额外的设置,通常是从一个 nib.如果动态视图!= nil{动态视图?.tableView.reloadData()让 size = dynamicView?.tableView.contentSize.height//在 300 上作弊,因为该控制器中的其他视图每个 150containerViewHeightConstraint.constant = 大小!+ 300self.view.updateConstraintsIfNeeded()}}覆盖 func didReceiveMemoryWarning() {super.didReceiveMemoryWarning()//处理所有可以重新创建的资源.}覆盖函数准备(对于segue:UIStoryboardSegue,发件人:任何?){if (segue.identifier == "ContainerViewSegue") {dynamicView = segue.destination 作为?DynamicEmbeddedViewController}}
}
最后一切都按预期进行!
请注意:所有步骤的输出都上传到
现在将 UILabel 拖到滚动视图上并快速查看警告.ScrollView 模糊的可滚动内容(宽度和高度).
现在为所有人添加一个顶部、底部、前导和尾随 20.
没有警告.
测试 2)删除 UILabel 并将 UIView 拖到滚动视图上并添加顶部、底部、前导和尾随,例如 20.
警告!警告!警告!
问题在于 UIView 无法调整自身大小,并且滚动视图不知道其内容有多大,因此无法设置.
<小时>如果这有意义 == 真继续别的返回
现在根据我们的进展情况,它会变得更加复杂,但上面的概念支配着整个过程.
调查您的项目和设置,您在学习 UIScrollview 方面做得很好.
现在让我们回顾一下你的总结,我会就一些事情发表评论.
根据您上面的报价
由此我们可以得出结论:ContainerView 的位置是正确的(即在 SecondLabel 和 BottomLabel 之间),但 BottomLabel 不遵守其低于 ContainerView 的约束."
***-> 你这里的总结其实是不正确的.转到标签上方的容器并在界面生成器中选中剪辑到边界并重新运行项目,标签将在底部,但不会有绿色视图.为什么?它不知道它应该有多大.当你在它加载之前运行它时它可以并且 uilabels 是清晰的,所以当它超出它的边界时它看起来不正确.
"TableView的高度明显是0,这也是因为没有调用func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath),如果我们给TableView设置了高度约束,item就会出现.
***-> 这是最难处理的.您必须加载 tableview 并获取 tableview 内容大小并在代码中更新 tableview 的高度约束以使其更新,以便滚动视图调整容纳控制器的容器的大小.
如果它包含的内容具有内在的内容大小,则可以使用 stackview 来确定它的高度和宽度.
但回到表格视图.您需要在 tableview//您的行高上设置 >= 40 才能从动态视图开始.如果计数为 0,则检查数据源后,您会将约束更新为 0 并使用委托让滚动视图知道更新它对动态视图控制器的约束以不显示表.我希望这是有道理的.然后相反,如果计数是数据源中的 10 个项目,则将 dynamicviewcontroller tableview 高度约束的约束更新为 10 * 40,就像这样
导入 UIKit类 DynamicEmbeddedViewController : UIViewController, UITableViewDataSource, UITableViewDelegate{@IBOutlet 弱 var tableViewConstraint:NSLayoutConstraint!@IBOutlet 弱 var tableView:UITableView!让数据= [第一",第二",第三",第四",第五",第六",最后"]覆盖 func viewDidLoad() {super.viewDidLoad()tableView.delegate = 自我tableView.dataSource = 自我tableView.register(UINib(nibName: "TableViewCell", bundle: nil), forCellReuseIdentifier: "TableViewCell")//调整我们的约束让 count = data.count让常数 = 计数 * 40tableViewConstraint.constant = CGFloat(常数)self.updateViewConstraints()//在一个真实的应用程序中,一个委托回调会很好地更新滚动视图上的约束}
在第一个控制器中它看起来像这样.
导入 UIKit类 FirstViewController: UIViewController {@IBOutlet 弱变量 containerViewHeightConstraint:NSLayoutConstraint!@IBOutlet 弱 var containerView:UIView!@IBOutlet 弱变量滚动视图:UIScrollView!var dynamicView : DynamicEmbeddedViewController?覆盖 func viewDidLoad() {super.viewDidLoad()//在加载视图后做任何额外的设置,通常是从一个 nib.如果动态视图!= nil{动态视图?.tableView.reloadData()//获取大小让 size = dynamicView?.tableView.contentSize.height//在 300 上作弊,因为我看到您将该控制器中的其他视图设置为每个 150containerViewHeightConstraint.constant = 大小!+ 300self.view.updateConstraintsIfNeeded()}}覆盖 func didReceiveMemoryWarning() {super.didReceiveMemoryWarning()//处理所有可以重新创建的资源.}覆盖函数准备(对于segue:UIStoryboardSegue,发件人:任何?){if (segue.identifier == "ContainerViewSegue") {dynamicView = segue.destination 作为?DynamicEmbeddedViewController}}}
你可以看到我们调整了所有东西的大小以适应.除此之外,你做对了.现在到生产有价值的代码.大多数时候,您会知道将显示哪些内容,以便您可以控制它.另一种方法是对所有内容使用 UITableView 或 UICollectionView,并根据内容加载不同的单元格.我希望这篇文章能澄清一点.我相信我们可以继续添加它,但希望所涵盖的概念就足够了.如果这回答了您的问题,请对这样做所花费的时间表示一些爱.如果你愿意,我也可以将它上传到 GitHub,但最好放在你开始的 repo 中.
Even though there are multiple questions and answers to this questions here on SO I just cannot create a UIScrollView
with both static and dynamic content (by using a ContainerView
) and make the sizes work properly. I will therefore provide a step by step guide until the point where I cannot make any progress and someone can provide a solution. This way we will have a workable sample that can be followed step by step to make it work.
Please note: The output from all of the steps is uploaded to https://github.com/oysteinmyrmo/DynamicScrollablePage for convenience. The test Xcode project can be fetched from there and hacked on further.
Update: After @agibson007's answer, there are a few steps at the end to fix the original steps to a working solution. Errors are noted by stating ERROR, SEE FINAL STEPS.
Goal:
Have a long scrollable UIView
page with various static UIView
s and a ContainerView
with dynamic content. For completeness' sake the dynamic content will consist of some static UIView
s and a UITableView
that will be expanded to its entire contents. The last element seems to be a reoccurring theme in the various questions I have stumbled upon lately.
Method:
UIView
s, UIViewController
s and other required items.Step 1: Project Creation
Open Xcode (8.2.1), start a new project.
UIScrollView
in the first tab.Step 2: Initial Changes to the Project
The changes to the UI will be done in the first tab. The procedure is heavily influenced by this answer, but we will add a couple of more items and a ContainerView
for our dynamic content.
Main.storyboard
, First View
(i.e. tab 1) delete the two labels.UIViewController
(named first). Go to its size inspector, change from Fixed
to Freeform
and change the height to 1500. This is only a visual change in the storyboard.UIView
as RootView
.UIScrollView
inside RootView
. Name it ScrollView
. Constraints:
UIView
inside ScrollView
and name it ContentView
. Constraints:
ContentView
:
UIView
, name it RedView
. Set RedView
[Leading, Trailing, Top] = ContentView
[Leading, Trailing, Top]. Set RedView
[Height, Background Color] = [150, Red].UILabel
below RedView
, set its name/text to FirstLabel
. Set FirstLabel
[Leading, Trailing] = ContentView
[Leading, Trailing]. Set FirstLabel
[Top] = RedView
[Bottom].UIView
below FirstLabel
, name it BlueView
. Set BlueView
[Leading, Trailing] = ContentView
[Leading, Trailing]. Set BlueView
[Top] = FirstLabel
[Bottom]. Set BlueView
[Height, Background Color] = [450, Blue].UILabel
below BlueView
, set its name/text to SecondLabel
. Set SecondLabel
[Leading, Trailing] = ContentView
[Leading, Trailing]. Set SecondLabel
[Top] = BlueView
[Bottom].UIContainerView
below SecondLabel
, name it ContainerView
. Set ContainerView
[Leading, Trailing] = ContentView
[Leading, Trailing]. Set ContainerView
[Top] = SecondLabel
[Bottom]. Set ContainerView
[Intrinsic size] = [Placeholder] (see Size inspector for the ContainerView
). Setting the intrinsic size to placeholder tells Xcode that the size of it is defined by its child views (as far as I understand). ERROR, SEE FINAL STEPSUILabel
at the end, name it BottomLabel
. Set BottomLabel
[Leading, Trailing] = ContentView
[Leading, Trailing]. Set BottomView
[Top] = ContainerView
[Bottom].ScrollView
to BottomView
and select Bottom Space to ScrollView
. This will ensure that the ScrollView
's height is correct.Step 3: Create a ViewController with Dynamic Content
Now we will create the actual UIViewController
and xib
file that will be used to display the dynamic contents. We will create a UITableView
inside the xib
and thus we will also need a UITableViewCell
with a simple label for simplicity.
Create a Swift file, TableViewCell.swift
with the contents:
import UIKit
class TableViewCell : UITableViewCell {
}
Create a xib
/View
file, named TableViewCell.xib
. Do the following:
UIView
and replace it with a UITableViewCell
.UILabel
to that cell, name it DataLabel
(it will also add a content view UIView
).UITableViewCell
's custom class to TableViewCell
.TableViewCellId
.In dual-view mode, ctrl+drag the label to the TableViewCell
class. The result should be:
import UIKit
class TableViewCell : UITableViewCell {
@IBOutlet weak var dataLabel: UILabel!
}
Create a file DynamicEmbeddedViewController.swift
with the contents:
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
}
}
Create a xib
/View
file, named DynamicEmbeddedView.xib
. Rename the main UIView
to ContentView
and add three items within the ContentView
:
UIView
, name it GreenView
. Set GreenView
[Leading, Trailing, Top] = ContentView
[Leading, Trailing, Top]. Set GreenView
[Height] = [150].UITableView
, name it TableView
. Set TableView
[Leading, Trailing] = ContentView
[Leading, Trailing]. Set TableView
[Top] = GreenView
[Bottom]. Set Intrinsic size = Placeholder. I am not sure if this is the correct approach. ERROR, SEE FINAL STEPSUIView
below TableView
, name it PurpleView
. Set PurpleView
[Leading, Trailing] = ContentView
[Leading, Trailing]. Set PurpleView
[Top] = TableView
[Bottom].File's Owner
's custom class to DynamicEmbeddedViewController
.File's Owner
's View outlet to ContainerView
.TableView
's dataSource and delegate to File's Owner
.IBOutlet
of the TableView
to the DynamicEmbeddedViewController
class.Connect the created xib
and UIViewController
in the Main.storyboard
.
ContainerView
's output View Controller to DynamicEmbeddedViewController
.View
in the ContainerViews
output View Controller. I am not sure if this is really needed.Images of Current Situation:
Step 4: Running the app:
Running the app and scrolling all the way to the bottom, including bounce area, this is the result:
From this we can conclude:
ContainerView
is correct (i.e. between SecondLabel
and BottomLabel
), but the BottomLabel
does not adhere its constraint to be below the ContainerView
.TableView
's height is obviously 0. This can also be seen since func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath)
is not called. If we set a height constraint on the TableView
, items will show up.ScrollView
does not increase if the size of the ContainerView
increases.TableView
at all time and just scroll past them as if they were just static data in the ScrollView
.Step 5: The questions!
ScrollView
's content properly wrap all the contents, including the dynamic data in the TableView
living inside the ContainerView
?Step 6: Fixing the solution after @agibson007's answer:
Add static let CELL_HEIGHT = 44
like this:
import UIKit
class TableViewCell : UITableViewCell {
@IBOutlet weak var dataLabel: UILabel!
static let CELL_HEIGHT = 44
}
Revert TableView
's intrinsic size to Default
from Placeholder
.
TableView
. This value must be greater than one cell's height.DynamicEmbeddedViewController
as an IBOutlet
.Add code to calculate and set TableView
height constraint. Final class:
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
}
}
Revert ContainerView
's intrinsic size to Default
from Placeholder
.
ContainerView
. This value will be updated in code.ContainerView
as an IBOutlet
in the FirstViewController
.ContainerView
as an IBOutlet
in the FirstViewController
.DynamicEmbeddedViewController
in FirstViewController
so that it may be referenced for height calculation.Add code to calculate and set ContainerView
height constraint. Final FirstViewController
class:
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
}
}
}
And finally everything works as expected!
Please note: The output from all of the steps is uploaded to https://github.com/oysteinmyrmo/DynamicScrollablePage for convenience. The test Xcode project can be fetched from there and hacked on further.
I am sure this has been answered before but I cannot remember if it has been answered down to why people have so much trouble with a scrollview. It comes down to 2 things that you have to know about UIScrollView and Autolayout.
1) The scrollview needs to be able to calculate the width and height of the content that is inside. This helps with deciding if it actually needs to scroll.
2) Some views have a size based on the content inside. Example a UILabel has an "intrinsic" content size. That means if you drag it out onto the storyboard you do not need to set a height or width unless you are trying to constraint it someway. Other examples of views with intrinsic sizes would be a UIButton, UIImageView(if it has an image), UITextView(with scrolling disabled) and other controls that may have text or images in them.
So let's start really simple and drag a UIScrollView onto the storyboard and pin it to the superview. All is good no warnings.
Now drag a UILabel onto the scrollview and take a quick peak at the warnings. ScrollView ambiguous scrollable content(width and height).
Now add a top, bottom,leading, and trailing of 20 for all.
No warnings.
Test 2) Delete the UILabel and drag a UIView onto the scrollview and add top,bottom,leading,and trailing of say 20.
Warning! Warning! Warning!
The problem is that a UIView does not have the ability to size itself and the scrollview does not know how big its content will be so it cannot setup.
IF THIS MAKES SENSE == True continue ELSE goBack
Now here is where it will get more complex depending on how far we go but the concepts above govern the entire process.
Investigating your project and setup you did pretty well to be learning UIScrollview.
Now lets go over your summary and I will comment in line as to some things.
From your quote above
"From this we can conclude: The position of the ContainerView is correct (i.e. between SecondLabel and BottomLabel), but the BottomLabel does not adhere its constraint to be below the ContainerView."
***-> You summary here is actually incorrect. Go to the container above the label and checkmark clip to bounds in interface builder and re run the project and the label will be at the bottom but there will be no green view. Why? It does not know how big it is supposed to be. When you ran it before it loaded best it could and uilabels are clear so when it went outside its bounds it looked like it was not correct.
"The TableView's height is obviously 0. This can also be seen since func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) is not called. If we set a height constraint on the TableView, items will show up."
***-> This is the trickiest to deal with. You would have to load the tableview and get the tableview content size and update a height constraint on the tableview in code to get it to update for the scrollview to resize for the container that is holding the controller.
An alternative would be a stackview that can determine it's height and width if the content it holds has intrinsic content size.
But back to the tableview. You would need to set a >= 40 on the tableview//your row height to start on the dynamic view. After you check your datasource if the count is 0 you would update the constraint to 0 and use a delegate to let the scrollview know to update it's constraint on the dynamicviewcontroller to not show the table. I hope this makes sense. Then conversely if the count is say 10 items in the datasource update the constraint on both the dynamicviewcontroller tableview height constraint to 10 * 40 like so
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
}
And in the first controller it would look like this.
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
}
}
}
You can see we resize everything to fit. Other than that you got it right. Now to production worthy code. Most times you would know what content would be shown so you can control it. Another way is to use UITableView or UICollectionView for all the content and have different cells that would load based on the content. I hope this post clears it up a bit. I am sure we could continue to add to it but hopefully the concepts covered will be enough. If this answers your questions please show some love for the time it takes to do this. I can also upload this to GitHub if you like but it might be best to go in the repo you started.
这篇关于iOS:使用 ContainerView 动态内容的 UIScrollView(一步一步)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持html5模板网!