Dynamic height TableView in a Scroll View - ios

I am designing a page having a scroll view and above it a table view(scroll disabled). For doing this I have referred answers in this question - Make UITableView not scrollable and adjust height to accommodate all cells ,but wasn't successful.
Hierarchy of views along with provided constraints-
-Main View
-Scroll view
pinned to all sides of main view(0,0,0,0), constraint to margins
-Content View
pinned to scroll view(0,0,0,0),equal width to main view,equal height to main view(priority - 250)
-Table view inside content view
scroll disabled,having 50 point spaces from all sides,Height(>=),bottom spacing 50(relation >=).I have put greater than equal so as to increase height dynamically.
Now when I populate my table view I use the code as
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
let cell = tableview.dequeueReusableCellWithIdentifier("cellreuse", forIndexPath: indexPath)
cell.textLabel?.text = name[indexPath.row]
tableview.frame.size = tableview.contentSize
return cell
}
So when I run my code, it increases the tableview frame but doesn't stretch the content size and it just becomes weird as my scroll view doesn't scroll to the end of the table view neither my table view obeys the auto layout constraints.

Just I needed to do this -
remove the line - tableView.frame.size = tableView.contentSize
Add a height constraint for table view.
Set priority to High
Create an outlet of the height constraint(Ctrl+Drag).
Wherever you need to reload data of your table, set the height constraint to tableview's content height.
tableHeightConstraint.constant = tableview.contentSize.height

Assign a table height. Let it be constant 0.
Just add below lines.
tableView.heightConstant.constant = CGFloat.greatestFiniteMagnitude
tableView.reloadData()
tableView.layoutIfNeeded()
tableView.heightConstant.constant = tableView.contentSize.height
With this, you can easily achieve dynamic table height. Working on iOS 13, Swift 5.

Had the same issue and resolved it by doing the following:
Create an outlet of the height constraint for the table view with a priority of 1000
#IBOutlet private weak var tableViewHeight: NSLayoutConstraint!
On viewDidLayoutSubview call layoutIfNeeded on the table view and then set the table view height constraint to the height of the content view
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
tableView.layoutIfNeeded()
tableViewHeight.constant = tableView.contentSize.height
}
Tested on iOS 14.1 and iOS 16.1

Related

Pin cell height to UICollectionView height

I have a horizontal UICollectionView that's contained inside a UICollectionView header of a vertical UICollectionView. The header of the vertical UICollectionView has a dynamic height that changes due to user interaction (dragging the outer UICollectionView down expands it, scrolling up collapses it). The horizontal UICollectionView is constrained to the size of the header and will also shrink in height when the header is reduced in height. When this happens I get this error
the behavior of the UICollectionViewFlowLayout is not defined because:
2018-07-19 13:42:09.959 IDAGIO[81891:2239798] the item height must be
less than the height of the UICollectionView minus the section insets
top and bottom values, minus the content insets top and bottom values.
2018-07-19 13:42:09.959 IDAGIO[81891:2239798] Please check the values
return by the delegate.
because the UICollectionView shrinks in height, but the cells of it keep their old size and are therefore higher than allowed (I only get this error while scrolling up, so it's not related to any insets, the heights are fine, they just don't match exactly at that point in time).
I already tried to call collectionView.collectionViewLayout.invalidateLayout() every time I get a scroll event of the vertical collection view and then return the collection view size in
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
but the layout update then is always a bit too late, so I still get the above error. Also it looks weird as the cell height also visually is a bit behind.
My question: is there a way to dynamically respond to height changes of a horizontal UICollectionView so the cell height is updated accordingly and in time? (Maybe setup some height constraint that automatically keeps the cell height equal to the collection view height?)
I know about dynamic cell sizing, but that usually means to constrain a cell to the size of it's content. What I want to achieve is to always constrain the height of the cell (and it's content) to the collection view height.
I found an easy solution myself, without having to pass or store the scroll offset at all:
Just subclass UICollectionViewFlowLayout, override shouldInvalidateLayout and set the itemSize to the size of the new bounds.
override func shouldInvalidateLayout(forBoundsChange: CGRect) -> Bool {
if !forBoundsChange.size.equalTo(collectionView!.bounds.size) {
itemSize = forBoundsChange.size
return true
}
return false
}
In Swift 4.2
override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
if !newBounds.size.equalTo(collectionView!.bounds.size) {
itemSize = newBounds.size
return true
}
return false
}
UICollectionView has a property derived from UIScrollView called contentinsetadjustmentbehavior, setting that to false should fix your issue if you are setting the size of the items on the collectionView to the size of the bounds of the collectionView.
Because the scrollView can be adjusting the insets which in turns changes the collectionView.adjustedContentInset
https://developer.apple.com/documentation/uikit/uiscrollview/2902261-contentinsetadjustmentbehavior
So basically setting:
collectionView.contentInsetAdjustmentBehavior = .never
Reloading data on scroll event is one of the options but it is definitely not the best one because of all the calculations involved in reloading data again and again. You'll probably find a better solution by subclassing UICollectionViewLayout and do all the calculations on prepare(). Then you can just use delegation to pass the scroll offset to your custom layout and make the cell sizing accordingly.

How to Update TableView and its parent ScrollView Height according to its content

I have a UITableView that is set to not enable scrolling, and it exists in a UIScrollView. I'm doing it this way as the design specs call for something that looks like a tableView. The TableView Cell having dynamic height So I use
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
Now once I fetched data from API and before Reloading TableView.
I update height constraint of both tableView and ScrollView. But height does not get update. I used the below code
self.view.layoutIfNeeded()
tvHeightConstraint.constant = tvPrdtDetail.contentSize.height
svHeightConstraint.constant = tvPrdtDetail.contentSize.height + 8
Here tvHeightConstraint and svHeightConstraint are height constraint of tableView and ScrollView
#IBOutlet weak var tvHeightConstraint: NSLayoutConstraint!
#IBOutlet weak var svHeightConstraint: NSLayoutConstraint!
Log always show 0 when I print TableView ContentSize
print("Content Size: \(tvPrdtDetail.contentSize.height)")
Please help me out . Thanks in advance.
Re order like this as call to layoutIfNeeded should be after updating the constants of the constraints
tvHeightConstraint.constant = tvPrdtDetail.contentSize.height
svHeightConstraint.constant = tvPrdtDetail.contentSize.height + 8 // remove this
self.view.layoutIfNeeded()
Also no need for 2nd line as if you hook constraints properly in the scrollview from top to bottom updating height constraint of the table will update the scrollview
Edit:
1- give the table height constraint say 3000 ( some big number ) followed by self.view.layoutIfNeeded()
2- reload the table
3- get the y frame of the last cell
4- assign it to the height constraint
5- layout the view
do this after you get the data
Try this hope so this will work.Call this when you need constraint should update,And override updateConstraints function
self.layoutIfNeeded()
override public func updateConstraints() {
super.updateConstraints()
tvHeightConstraint.constant = tvPrdtDetail.contentSize.height
svHeightConstraint.constant = tvPrdtDetail.contentSize.height + 8
}

how to make scrollview grow based on collection view number of rows in ios?

i have a scrollview which has a picture, text, button, label and then a collectionView whose number of rows is dynamic in nature. Its like collectionview will grow in height and hence the scrollview should assume collectionView height as well as other elements height to get a smooth scroll. How to achieve this using auto Layout in ios?
So my doubt is how to make a dynamic scrollview height based on a growing colelctionview or tableview height.
You can increase the height of CollectionView Height Constraint , connect Height contains to your ViewController and than .
how to connect constraint to outlet ?
let height = self.collectionView.contentSize.height;
self.heightConstraints = height;
self.scrollView.contentSize = CGSize.init(widht:self.scrollView.frame.size.width,height:height)
FOr Swift 4.2
override func viewDidLayoutSubviews() {
let heightFilterCOll = self.filterCollectionView.contentSize.height
self.filterCOllectionViewCOnstraintHeight.constant = heightFilterCOll
self.ScrollView.contentSize = CGSize(width:self.ScrollView.frame.size.width,height:heightFilterCOll + 77)
}
These methods didn't work for me. Try,
self.collectionView.reloadData()
DispatchQueue.main.async {
self.heightCollectionViewConstraint.constant = self.collectionViews.collectionViewLayout.collectionViewContentSize.height
}

Can't center views in tableHeaderView with auto layout and storyboards

I can't center a view that has been placed as a subview of a UITableView's tableHeaderView in a storyboard using auto layout.
In a storyboard, I have a UITableView. I dragged a UIView (the red view) to the top, released, and it created a table header view automatically. I then dragged and dropped another UIView (the yellow view) on top of the table header view, resized, and applied some constraints to ensure it stays centered:
When I run the app on the simulator, here's what I get:
The yellow view is obviously not centered. However, the "Filter" button at the bottom is.
I know it's tricky to get the height right using auto layout and storyboards and table header views (and you can see that the height of the red view is definitely incorrect), but at this point, I'm just trying to solve for horizontally centering my yellow view.
Am I able to set this all up in my storyboard without having to configure my constraints in code?
Make sure that your UITableView has the leading, trailing, bottom, top constraints set up against its superview.
Check the table header view and all sub views have Autoresize Subviews enabled:
You can also force the table to re-render the header view by re-setting it to the same view:
[self.tableView setTableHeaderView:self.outletToHeaderView];
Update: to resize the table header view, give give it an appropriate frame in viewWillAppear:
CGRect newFrame = self.outletToHeaderView.frame;
newFrame.size.width = self.tableView.bounds.size.width;
newFrame.size.height = 44;
[self.outletToHeaderView setFrame:newFrame];
// Then reset it to force the table view to re-render/accommodate
[self.tableView setTableHeaderView:self.outletToHeaderView]
In your file TableViewHeader:
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
yourView.translatesAutoresizingMaskIntoConstraints = false
yourView.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true
}
You need to use prototype cell from table view, than dequeue it with reusable identifier and return it contentView. Only that's do the trick
var headerView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
self.headerView = (self.tableView.dequeueReusableCellWithIdentifier("CustomHeaderCell") as! UITableViewCell).contentView
}
override func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
return self.headerView
}
upd:
Oh sorry, my example is written in Swift. But it's easy to understand how to do the same in Obj-C

iOS Auto-Layout: Dynamic height for table view cell

I have a table view with a bunch of cells (custom cell, which only has its content view).
In tableView:cellForRowAtIndexPath: I'm adding a predefined UIView (which has several subviews) to the content view of the custom cell. I set up all constraints for the UIView and its subviews before.
Last but not least, I set the vertical and horizontal constraints for the content view of my custom cell (superview) and the UIView, which was added before (subview).
The constraint strings look like this:
H:|[view]|
V:|[view]|
Unfortunately, I still get the default height for all table view cells. I'm wondering If there's a way to let auto layout do the calculation of the height automatically according to content size.
Check out my detailed answer to this question here: https://stackoverflow.com/a/18746930/796419
It takes a bit of work to set up, but you can absolutely have Auto Layout constraints driving a completely dynamic table view without a single hardcoded height (and let the constraint solver do the heavy lifting and provide you with the row height).
Auto Layout won't help with the cell height. You'll need to set that in tableView:heightForRowAtIndexPath. I guess you're probably asking this because your cell heights are variable, not fixed. i.e., they depend on the content.
To resolve that, pre-calculate the cell heights and store them in an array. Return the value for the appropriate indexPath in the tableView:heightForRowAtIndexPath method.
Be sure to calculate content sizes on the main thread, using sizeThatFits of UILabel classes and such like.
If your calculation is intensive, do the majority of it off main apart from the view related methods such as sizeThatFits.
I solved the problem by using CGSize size = [view systemLayoutSizeFittingSize:UILayoutFittingCompressedSize]; in tableView:heightForRowAtIndexPath:.
To set automatic dimensions for row height, ensure following steps to make, auto dimension effective for cell/row height layout.
Assign and implement dataSource and delegate
Assign UITableViewAutomaticDimension to rowHeight & estimatedRowHeight
Implement delegate/dataSource methods (i.e. heightForRowAt and return a value UITableViewAutomaticDimension to it)
Swift:
#IBOutlet weak var table: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
// Don't forget to set dataSource and delegate for table
table.dataSource = self
table.delegate = self
// Set automatic dimensions for row height
// Swift 4.2 onwards
table.rowHeight = UITableView.automaticDimension
table.estimatedRowHeight = UITableView.automaticDimension
// Swift 4.1 and below
table.rowHeight = UITableViewAutomaticDimension
table.estimatedRowHeight = UITableViewAutomaticDimension
}
// UITableViewAutomaticDimension calculates height of label contents/text
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
// Swift 4.2 onwards
return UITableView.automaticDimension
// Swift 4.1 and below
return UITableViewAutomaticDimension
}
For label instance in UITableviewCell
Set number of lines = 0 (& line break mode = truncate tail)
Set all constraints (top, bottom, right left) with respect to its superview/ cell container.
Optional: Set minimum height for label, if you want minimum vertical area covered by label, even if there is no data.

Resources