I have a non scrolling UITableView inside of a larger UIScrollView. Since the table view is non scrolling, I want its frame size to always match its content size.
I have the following method that I call:
- (void)resizeTableViewFrameHeight
{
// Table view does not scroll, so its frame height should be equal to its contentSize height
CGRect frame = self.tableView.frame;
frame.size = self.tableView.contentSize;
self.tableView.frame = frame;
}
The problem is that I must be calling this method at the wrong time (before the contentSize is set). I was wondering, is there a property that I can set on UITableView that will cause its frame to always match its content size automatically?
There is no property that dynamically adjusts table view's frame. Also, I do not think that a UITableView can calculate its entire contentSize up-front because a table view could be practically infinite in size, depending what is specified from its datasource and other delegate methods such as heightForRowAtIndexPath. These methods can be dynamic, making it virtually impossible to predict the entire size of a table view.
I conclude that you are perhaps not using this UI element correctly.
Workaround:
By having the frame of the table view constrained to the visible area of the scroll view you could simply allow it to scroll. Perhaps you need to prevent your scroll view from scrolling while the table view is scrolling and vice versa. You could easily do this in the UIScrollView delegate methods.
I needed to do something like this in iOS 8: A scroll view that contained a non-scrolling fixed-width table view with variable number of rows of content, so I wanted to dynamically resize the height of the table view.
What worked for me was to set up a height constraint on the table view with a dummy value, and then create outlets to both the table view and the constraint. Then this was all I needed:
class ViewController: UIViewController, UITableViewDataSource {
#IBOutlet weak var tableViewHeightConstraint: NSLayoutConstraint!
#IBOutlet weak var tableView: UITableView!
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
tableViewHeightConstraint.constant = tableView.contentSize.height
}
// (plus additional data source methods, etc.)
}
I can imagine situations (maybe with lots of rows, or rows of variable height) where it might not be this simple, but this worked for me.
Related
I have a main vertical stack view (Frame 1), that has a horizontal paging collection view with flow layout (CollectionView), which has a couple of cells (Cell). Each cell comes with a vertical stack view of a couple of labels (Title, Description).
Is there a way to let the tallest cell determine the collection view's height, while the main stack view width determines the width of the cells?
By default, the collection view is not visible when I use layout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize (seemingly it does not "inherit" the cells' height).
For now, I worked around the issue by setting a fixed height for the collection view, and defined the item size manually in the collectionView(_:layout:sizeForItemAt:) (and the cells are flexible).
Since CollectionViews (and TableViews) don't have an intrinsicContentSize, there is no way for the layout engine to know the correct size of your collection without the appropriate constraints. A subclass is needed to provide this property.
In your subclass:
override var intrinsicContentSize: CGSize {
return CGSize(self.contentSize.height, self.superview!.bounds.width)
}
Also, don't forget to invalidate your content size when reloading data:
override func reloadData() {
super.reloadData()
self.invalidateIntrinsicContentSize()
}
For a more detailed explanation on intrinsicContentSize check Apple's Documentation
As the content is static, I think simply using UIScrollView is a pretty viable alternative here (as the dynamic nature of the collection view is not a requirement). The collection view could be a stack embedded into a scroll view, then the contains between the dimensions can be simply set upon initialization.
I am creating a collection view full of menu items. The collection view is supposed to be tall enough so that it fits perfectly in the size of view.
I can set the UICollectionView with a fixed height constraint, but if there are 20 items then that height might be too small or if there are 5 cells the height would be too big.
Is there a way to change the frames of the main view and the Collection view based off how many cells the view will have? Possibly in the viewDidLoad() function?
set the height constraint of the collection view in storyboard with any value , and hook it as IBOutlet say collectionHCon
override func viewDidLayoutSubviews() {
self.collectionHCon.constant = numberOfItems * itemHeight
super.viewDidLayoutSubviews()
}
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 UITableViews 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?
First, a warning:
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.
Now if you really wanna go with your original approach...
... 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:
Add both your table views to a vertical stack view in Interface Builder.
Give both table views a leading and a trailing constraint to pin their left and right edges to the stack view.
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()
}
tl;dr:
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.
I have a table view with various cells. They are all set to resize using
tableView.estimatedRowHeight = 45.0
tableView.rowHeight = UITableViewAutomaticDimension
For example if the cell is a label type and the label contains multiple lines of text, the label will expand and cause the cells height to increase. I have just created a new type of cell with a collection view inside it. It works great however the problem is that when more cells are added the collection view it's frame doesn't grow in size and thus the cell's height doesn't expand. Instead the collection view just becomes scrollable. Is there a way to make the collection view frame expand as more cells are added instead of making it scrollable? Any pointers on this would be really appreciated! Thanks!
You can set a constraint to the collection view and then resize it by code.
#IBOutlet weak var collectionViewHeightConstraint: NSLayoutConstraint!
let height = cells.count * a //a = height of the cells within the collection view
collectionViewHeightConstraint.constant = CGFloat(height)
I'm building a View Controller with some elements of UIView and one UITableView. This view will be larger than an iPhone screen than I put all those elements in a UIScrollView and configured auto layout.
The number of rows in a UITableView are dynamic, and I need that all content be visible in a single screen. I don't want to enable UITableView scroll property because will be complete messy working with master UIScrollView and the UITableview scroll
I have been researching for days on StackOverflow for posts about this problem and couldn't find anything like my problem.
This is the code trying to resize UITableView and UIScrollView
#IBOutlet weak var scrollView: UIScrollView!
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
// Configuring dataSource an delegate
self.tableView.dataSource = self
self.tableView.delegate = self
// Sizing all the elements inside scrollView
var contentRect = CGRectZero
for view:UIView in self.scrollView.subviews {
contentRect = CGRectUnion(contentRect, view.frame)
}
print(contentRect)
// Trying to resize the tableView and scrollView to fit more contents
self.tableView.contentSize = CGSizeMake(scrollView.frame.width, CGFloat(441))
scrollView.contentSize = CGSizeMake(scrollView.frame.width, CGFloat(877))
scrollView.delaysContentTouches = true
scrollView.canCancelContentTouches = false
// Looking for a new size and nothing change
var contentRect2 = CGRectZero
for view:UIView in self.scrollView.subviews {
contentRect2 = CGRectUnion(contentRect2, view.frame)
}
print(contentRect2)
print("Tableview width \(self.tableView.frame.width)")
print("Tableview height \(self.tableView.frame.height)")
}
The sample project on GitHub
I appreciate any help
Layout of ViewControl:
The short, but wrong answer to this question is to add an autolayout constraint to the tableview constraining the height to a constant size. You can set it to your best guess, but at runtime set it equal to the tableview's content size's height.
The problem with this approach is that it completely eliminates the benefit of tableview's and reusable cells. Table views are expressly designed to minimize the memory footprint of your view. It takes a lot more memory to store the view (cell) that represents your data than it does to keep track with data you're filling these cells in with.
So instead, the better solution is to use multiple sections within a table view. Using the example from your screenshot, your parent scroll view will be a tableview instead of a scroll view. It would have four sections. In the first section, we return a single row with the yellow view which is configured as a table view cell, and we do the same in the third & fourth sections. In the second section, we return however many rows you were using in the table view.
Alternatively, the yellow view could be thought as the table header, and the blue & red views become the table footer, with the rows in the middle.