How to make a UIStackView
re-distribute it's sub-UITableViews
while the stackView is inside a scrollview?
My layout hierarchy is based on the official documentation from apple about Dynamic content for StackViews
- UISCrollView
- UIStackView
- UIView A
- UIView B
- UIView C
- UITableView X
- UITableView Y
- UIView D
The constraints are set as documented. The initial layout of the StackView is correct showing all visible subviews. When forcing the regular views to expand beyond the screen's height, scrolling is working as expected. Also when viewing the layout in the storyboard, everything stacks as expected.
At this point the UITableView
s are empty. As soon as I add content to the tableView the problem appears.
The problem
When I dynamically update the TableView's by calling .reloadData()
on both of them I see their content appearing. (thanks to this answer about non-scrolling tableViews) but the UIStackView is not stacking the UITableViews.
UIView D
is stacked below UIView C
UITableView X
and UITableView Y
also stacked below UIView B
My guess is that I need to invalidate the stackview, or somehow get it to redistribute it's subviews. How can I do this?
The proper way to set the UIScrollView including UIStackView is to use option #1. Set the width of the UIStackView equal to the UIViewController view. Also add the height constraint to UIStackView marked as removed at build time.
First, create a new content view that will be holding the UIStackView then, change the setupViews method to reflect adding the contentView into the scrollView and add stackView to contentView as a subview: Now let’s update the setupLayout method to reflect the change in layouting.
That would be fine if upon calling the removeArrangedSubview the view wouldn't remain in UIStackView subviews. It basically means that you have to call removeFromSuperview subsequently or just write an extension. Sometimes you may see an unsatisfiable constraints warning.
The second key property of UIStackView is alignment. This property determines how the stack view lays out its views in the direction perpendicular to its axis (i.e. for a vertical stack view, alignment determines the width/horizontal position of its arranged subviews). The options are:
What you're trying to achieve is not really standard iOS behavior. You should first consider a different approach like creating a single grouped table view with multiple sections. You can implement custom views inside your table view as section headers or footers.
... for some important reason you should be aware that a table view doesn't have an intrinsic content size by default. Thus, you need to tell the table view how tall it should be because otherwise it will only shrink down to a zero height.
You can achieve this by either subclassing UITableView
and overriding its intrinsicContentSize()
as Rob suggests in this answer to a similar question.
Or you add a height constraint to each of your table views and set their constants dynamically in code. A quick example:
Create outlets for your table views in the respective view controller:
@IBOutlet weak var tableView1: UITableView!
@IBOutlet weak var tableView2: UITableView!
@IBOutlet weak var tableView1HeightConstraint: NSLayoutConstraint!
@IBOutlet weak var tableView2HeightConstraint: NSLayoutConstraint!
Override the updateViewConstraints()
method of that view controller:
override func updateViewConstraints() {
super.updateViewConstraints()
tableView1HeightConstraint.constant = tableView1.contentSize.height
tableView2HeightConstraint.constant = tableView2.contentSize.height
}
Now whenever the content of any of your table views changes (e.g. when you add or remove rows or change the cell contents) you need to tell your view controller that it needs to update its constraints. Let's say you have a button that adds a cell to tableView1
each time you tap it. You might implement its action like this:
@IBAction func buttonTappen(sender: AnyObject) {
// custom method you implement somewhere else in your view controller
addRowToTableView1DataSource()
// reload table view with the updated data source
tableView1.reloadData()
// triggers an updateViewConstraints() call
view.setNeedsUpdateConstraints()
}
A UITableView
isn't intended for use without scrolling enabled and thus you always need to explicitly set its height when its contents change - may it be using constraints or by overriding the table view's intrinsic content size.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With