dynamic tableHeaderView does not work properly - ios

I have a tableHeaderView that should be with dynamic height according to its content.
I tried use the systemLayoutSizeFitting & sizeToFit method in order to set new height for the table view, Unfortunately It's seems to be work well but not as I want (one of the dynamic UI get cropped). I tried to set the content compression resistance priority of the UIs that i want to be dynamic to (1000) but its dose not work as well.. every time at least one UI cropped.
#IBOutlet weak var tableView: UITableView!
#IBOutlet weak var podView: PodView!
#IBOutlet weak var postCaption: UILabel!
var pod: Pod!
override func viewDidLoad() {
super.viewDidLoad()
//set header view
podView.setPod(image: pod.image, title: pod.title, description: pod.description, viewWidth: UIScreen.main.bounds.width)
podView.sizeToFit()
postCaption.text = pod.description
postCaption.sizeToFit()
let height = tableView.tableHeaderView!.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize).height
tableView.tableHeaderView!.frame.size.height = height
}
edit: constraint:
view constraint
label Constraint

For having TableHeaderView with dynamic height. Add following code to your viewController. It will work like charm.
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
if let headerView = tableView.tableHeaderView {
let height = headerView.systemLayoutSizeFitting(UILayoutFittingCompressedSize).height
var headerFrame = headerView.frame
//Comparison necessary to avoid infinite loop
if height != headerFrame.size.height {
headerFrame.size.height = height
headerView.frame = headerFrame
tableView.tableHeaderView = headerView
}
}
}
Note: Make sure your tableHeaderView is having proper AutoLayout Constraints to get proper height.

Try this steps for creating dynamic tableview header cell :-
1 - Add one UItableViewCell on tableview from storyboard
2 - Create tableView header UI as per your requirement.
3 - Create class as TableViewHeaderCell something according to your requirement what you want to show in header cell.
4 - Then in a ViewController class implement headerview delegate method.
/**
Tableview header method
*/
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
}
5 - In this method you want to create TableViewHeaderCell object and return cell content View like this.
/**
Table Header view cell implement and return cell content view when you create cell object with you identifier and cell name after that you have to mention height for header cell
*/
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell Identifier") as! CellName
return cell.contentView
}
6 - Implement Tableview header height method
/**
Here you can specify the height for tableview header which is actually your `TableViewHeader Cell height`
*/
override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
//For eg
return 100
}
From this steps you can acheive dynamic Header view cell as well as footer view cell also, but for footer view cell implement Tableview footer view delegate methods.
Thank You,

Related

UITableViewCell not able to adjust height based on the dynamic CollectionView it contains

So let me start by explaining the view hierarchy.
I'm using a normal UITableView, inside the UITableViewCell there is a collectionView which is dynamic in height.
How I'm setting the datasource and delegate for UICollectionView at the willDisplay cell of UITableView:
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
if indexPath.section == 0 {
guard let collectionCell = cell as? movieListingTableViewCell else { return }
collectionCell.setCollectionViewDataSourceDelegate(self, forRow: indexPath.row)
collectionCell.selectionStyle = .none
}
}
The collectionView has the top, bottom, leading, trailing and height constraints.
Then I'm using the height constraint and setting it to the collectionView height as so in the UITableView cell subclass,
override func layoutSubviews() {
timingCollectionHeight.constant = timingCollection.collectionViewLayout.collectionViewContentSize.height
}
What happens is that my cells are not appearing properly as in there are blank spaces. For eg, if there is one UITableView cell which has a collectionView with a large height, other UITableView cells also somehow use the same height. After scrolling through the table the cells appear correctly.
Swift Example
First, get an outlet of your collectionViewHeightConstraint in your tableViewCell Class as shown below:-
#IBOutlet weak var collectionViewHeight: NSLayoutConstraint!
Then inside viewDidLoad() method of your ViewController Class add below code for dynamic height of tableViewCell:-
myTableView.estimatedRowHeight = 100; // Default tableViewCell height
myTableView.rowHeight = UITableViewAutomaticDimension;
Then inside your tableView cellForRowAtindexPath delegate add below code to update the height of tableView Cell as per CollectionView height:-
cell.frame = tableView.bounds; // cell of myTableView
cell.layoutIfNeeded()
cell.myCollectionView.reloadData()
cell.collectionViewHeight.constant = cell.myCollectionView.collectionViewLayout.collectionViewContentSize.height;
Hope this helps.
Happy Coding :)
Another approach:
After setting UITableView cell height to be dynamic.
tableView.estimatedRowHeight = 100
tableView.rowHeight = UITableViewAutomaticDimension
Create a subclass for collectionView and override intrinsicContentSize.
class DynamicCollectionView: UICollectionView {
override func layoutSubviews() {
super.layoutSubviews()
if !__CGSizeEqualToSize(bounds.size, self.intrinsicContentSize) {
self.invalidateIntrinsicContentSize()
}
}
override var intrinsicContentSize: CGSize {
return collectionViewLayout.collectionViewContentSize
}
}
In Interface builder change the class of your collectionView to DynamicCollectionView (subclass UICollectionView).
Set estimated cell size of UICollectionViewFlowLayout.
flowLayout.estimatedItemSize = CGSize(width: 1,height: 1)

UITableView header dynamic height in run-time

I know there are a lot of posts about it, but maybe in newest iOS there are some updates on this...
I think all of us had a task to create viewController that has a lot of content at the top, most of them are self-sizing, and at the very bottom it figures out that you need to show some tableView with many items...
The first solution that can be done is to use UIScrollView, and don't care about reusableCells at all.
The second is to use UITableView's headerView and adjust its height manually (or by calling systemLayoutSizeFittingSize:) each time when it is needed.
Maybe the third solution is to use UITableView and self-sized UIView separately, with having UIEdgeInsets on tableView. And depending on what object has higher "zIndex", it can bring problems with handling interactions...
The forth solution is to use whole content above the cell, like a separate cell. Not sure this is a good idea at all...
Question: Is there any new solution to this problem? I haven't dig into it for like 2 years... Maybe in new iOS there is something like reusableViews for UIScrollView... Of course, the goal is to have reusable cells, and header with using autolayout without necessity of updating its height manually...
I am guessing you are talking about section headers of table view here. If that is so you can absolutely use auto layout for section headers.
Use the below two code in viewDidLoad:
tableView.sectionHeaderHeight = UITableViewAutomaticDimension
tableView.estimatedSectionHeaderHeight = 36;
Now in viewForHeaderInSection: try the below code just to get an idea how things are working out. Change it according to your requirement.
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let label: UILabel = {
let lb = UILabel()
lb.translatesAutoresizingMaskIntoConstraints = false
lb.text = "HEADER \(section) with a loooooooooooooooonnngngngngngngngng texxxxxxxxxxxxxxxxt"
lb.textColor = .black
lb.backgroundColor = .yellow
lb.numberOfLines = 0
return lb
}()
let header: UIView = {
let hd = UIView()
hd.backgroundColor = .blue
hd.addSubview(label)
label.leadingAnchor.constraint(equalTo: hd.leadingAnchor, constant: 8).isActive = true
label.topAnchor.constraint(equalTo: hd.topAnchor, constant: 8).isActive = true
label.trailingAnchor.constraint(equalTo: hd.trailingAnchor, constant: -8).isActive = true
label.bottomAnchor.constraint(equalTo: hd.bottomAnchor, constant: -8).isActive = true
return hd
}()
return header
}
I'm using XCode 10.3 and this is my solution worked with your second solution using table header view.
First, you would create a separating view with xib file, for example with a label inside. And you apply the constraints for this label, top, left, bottom, right to the cell's container view. And set numberOfLines = 0.
Update your awakeFromNib() function inside your view class.
override func awakeFromNib() {
super.awakeFromNib()
ourLabel.translatesAutoresizingMaskIntoConstraints = false
}
Second, on your viewController, setup your tableView:
tableView.sectionHeaderHeight = UITableView.automaticDimension
tableView.estimatedSectionHeaderHeight = 64
Remember don't delegate this method:
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat
because we set the constraints of our view already.
Finally, you return it on the delegate method
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView?
the view for header.
let view = UINib(nibName: String(describing: SimpleHeaderTitleView.self), bundle: nil).instantiate(withOwner: nil, options: nil)[0] as! SimpleHeaderTitleView
view.ourLabel.text = "Your longgggg text"
return view
Done! Check it works.
I like the way it's done here:
• If you want to set your tableview header height dynamically based on it's content, just call self.tableView.layoutTableFooterView() right after you have set your headerView as TableViewHeader (so after self.tableView.tableHeaderView = view )
• If you need to update your tableview header height on runtime, also call self.tableView.layoutTableFooterView() right after you have updated the values of your tableview header.
(This obviously also works with tableviewFooters, though, is not to be used for sectionHeaders/Footers)
extension UITableView {
//Variable-height UITableView tableHeaderView with autolayout
func layoutTableHeaderView() {
guard let headerView = self.tableHeaderView else { return }
headerView.translatesAutoresizingMaskIntoConstraints = false
let headerWidth = headerView.bounds.size.width
let temporaryWidthConstraint = headerView.widthAnchor.constraint(equalToConstant: headerWidth)
headerView.addConstraint(temporaryWidthConstraint)
headerView.setNeedsLayout()
headerView.layoutIfNeeded()
let headerSize = headerView.systemLayoutSizeFitting(UILayoutFittingCompressedSize)
let height = headerSize.height
var frame = headerView.frame
frame.size.height = height
headerView.frame = frame
self.tableHeaderView = headerView
headerView.removeConstraint(temporaryWidthConstraint)
headerView.translatesAutoresizingMaskIntoConstraints = true
}
}
source
Copied from useyourloaf.com
Swift 5.0
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
guard let headerView = tableView.tableHeaderView else {return}
let size = headerView.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize)
if headerView.frame.size.height != size.height {
headerView.frame.size.height = size.height
tableView.tableHeaderView = headerView
tableView.layoutIfNeeded()
}
}
Step One :
From interface builder Drag a UIView and drop into
UItableView
This view will automatically act as a UITableView Header (Mind it not section Header)
. Suppose this the width of this view is 200 .If you run the UItableView This view will automatically appear as a Header .
Create a Outlet of this drag-drop View .
#property (weak, nonatomic) IBOutlet UIView *tableViewHeader; // height of this view is 200
Now my goal is increase the height of the table View header .
Step Two :
Add this method
- (void)viewDidLayoutSubviews
{
int increasedHeight = 100;
// set a frame of this view Like example
self.tableViewHeader.frame = CGRectMake(0, 0, self.view.frame.size.width , 200 + increasedHeight );
self.tableView.tableHeaderView = self.tableViewHeader;
}
Now you tableview header height will be 300 .
This is how i have approached it
Using tableview
I have created the UI For the header in XIB
Now in the following delegate method
func tableView(tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
}
I Create a UIView for the header and calculate the height based on the content and return the same.
Now i can return the same header view from the following delegate method
func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
}
Based on the section i again create a view from xib and return that view from the method.
In my case i needed only one headerview for table so i kept 2 sections and returned the headerview for section one.
If you are using Interface Builder simply you check these buttons
You can do that in viewDidLayoutSubviews of the UIViewController that contains your UITableView:
#IBOutlet weak var tableView: UITableView!
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
// Resize header view with dynamic size in UITableView
guard let headerView = tableView.tableHeaderView else {
return
}
let size = headerView.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize)
if headerView.frame.height != size.height {
tableView.tableHeaderView?.frame = CGRect(
origin: headerView.frame.origin,
size: size
)
tableView.layoutIfNeeded()
}
}
Xcode 14 + Swift 5
This is how I made it.
First step - configuring of table view for dynamic height headers usage:
In viewViewDidLoad you need to add:
tableView.sectionHeaderHeight = UITableView.automaticDimension
tableView.estimatedSectionHeaderHeight = 40
This also can be done in xib/storyboard where your tableView is located instead of configuring in code:
Second step - creation of header view:
You need to create custom view to use as table's header. For example view with label as a subview, pinned with constraints to superview. Any view that contains some subviews that could cause it to have different height. The main thing - you should set up content of header with constraints to make it resize itself.
Third step - configuration of tableView's delegate:
In your tableView delegate implement viewForHeaderInSection method and just return configured instance of your custom header view.
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView?
IMPORTANT - heightForHeaderInSection method shouldn't be implemented, just don't add it.
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat

Activating NSLayoutConstraint in `cellForRowAt` is not rendering until after scrolling

I need to support UITableViewAutomaticDimension (for dynamic height) with variations in the constraints: some need to be active, some not.
I setup the storyboard with aConstraint not installed, and bConstraint installed. I activate/deactivate them on need in tableView(_:cellForRowAt:).
class ViewController: UIViewController {
#IBOutlet weak var tableView: UITableView! {
didSet {
tableView.estimatedRowHeight = 10
tableView.rowHeight = UITableViewAutomaticDimension
}
}
}
class MyTableViewCell: UITableViewCell {
#IBOutlet var aConstraint: NSLayoutConstraint!
#IBOutlet var bConstraint: NSLayoutConstraint!
}
extension ViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 20
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "MyTableViewCell", for: indexPath) as! MyTableViewCell
cell.contentView.translatesAutoresizingMaskIntoConstraints = false
if indexPath.row % 2 == 0 {
NSLayoutConstraint.deactivate([cell.aConstraint])
NSLayoutConstraint.activate([cell.bConstraint])
} else {
NSLayoutConstraint.deactivate([cell.bConstraint])
NSLayoutConstraint.activate([cell.aConstraint])
}
return cell
}
}
Issue
The initial visible layout is ignoring all those activations/deactivations, and all the cells are identical to the original storyboard state.
You will notice that the correct constraints are only applied after scrolling.
Attempts
I did try without success some cell.setNeedsUpdateConstraints(); cell.updateConstraintsIfNeeded(); cell.layoutIfNeeded(); ...
Sample project shared on https://github.com/Coeur/dynamic-cell-height
Setup storyboard with both 'aConstraint' and 'bConstraint' installed, but put a lower priority on 'aConstraint' to remove warnings and it works :)
A couple of problems setting up constraints in your code.
1.
UILabel intrinsicContentSize will participate auto layout.
Auto layout system will create a width and height constraints based on intrinsicContentSize.
You explicitly set a height constraint of the label in xib file, causing an ambiguity, then added a vertical centre Y constraint to cover the problem.
2
If you want to try active or deactivate constraints, you may want to do that with a simple UIView.
3
If you want to do various height rows, take advantage of intrinsicContentSize, and set preferredWidth of UILabel.

adjusting tableView height while using self sizing cells and a non scrollable table

The problem I have is that I am using self-sizing cells, and estimated row height.
My tableview is a non scrollable table in a scrollview. I have a variable for the table height which I initially set to 0.
I am trying to update my tables height to be the right size depending on how many cells will fill it and depending on the size those cells will take up. I am trying to use tableview.contentSize.height to get the tables height and set the variable to that size.
I then recall my alignment function to reset the tables height. When I build and run the tables size isn't big enough to show all of the cells that it should.
How can I fix this?
You can do it from the following steps ->
Construct the tableview within the UIView with 0 Leading, Top, bottom, Trailing constant
Add height constraint of the UIView and define outlet of it
3. Load Data in the tableview and get tableview's ContentHeight
Update the height constraint of the UIView equal to the tableview's ContentHeight
Dont forget to call layoutIfNeeded() after updating constraint
class ViewController: UIViewController {
#IBOutlet weak var containerViewHeightConstraint: NSLayoutConstraint!
#IBOutlet weak var nameTableView: UITableView!
var dataArrary = [String]()
override func viewDidLoad() {
super.viewDidLoad()
nameTableView.rowHeight = UITableViewAutomaticDimension
nameTableView.estimatedRowHeight = 44.0
nameTableView.tableFooterView = UIView(frame:CGRectZero)
nameTableView.tableHeaderView = UIView(frame:CGRectZero)
dataArrary = ["item1 bshfklsdflkjsdfkljsdklfjlsdkfjlksdjflksdjflkdsjflkdsjflksdjflkjdslkfjdslkjflkdsfjdsfkljsdflkjdslkfj", "item2hfdshjgfhjdsgfhjdgfhjdgfhjdgfhjgfhsdf", "item3", "item4", "item5", "item6kfjdskfljsdlkfjlksdfjlksdjfkldsjfklfjdkslf"]
}
override func viewDidAppear(animated: Bool) {
updateView()
}
func updateView() {
self.nameTableView.scrollEnabled = false
self.nameTableView.frame.size = CGSizeMake(self.view.frame.width, self.nameTableView.contentSize.height)
containerViewHeightConstraint.constant = self.nameTableView.frame.height
// assign height equal to the tableView's Frame
self.view.layoutIfNeeded()
// redraws views and subviews with updated constraint
}
}
extension ViewController:UITableViewDataSource {
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return dataArrary.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as UITableViewCell
cell.textLabel?.numberOfLines = 0
cell.textLabel?.text = dataArrary[indexPath.row]
return cell
}
}
It should look like this

Fix the aspect ratio of the first row in UITableView

I'm creating a screen, imagine the facebook profile screen.. A tableview and the first cell is an image. The goal is that this image maintains an aspect ratio, so with different screen sizes I don't have to worry about sizing it..
I can't get this to work with any view, even if I set specific constraints, the cell won't set the correct height..
I have a little project showing constraints, in this link..
My VC Code:
class ViewController: UITableViewController{
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.dataSource = self
self.tableView.delegate = self
self.tableView.estimatedRowHeight = 100
self.tableView.rowHeight = UITableViewAutomaticDimension
self.tableView.reloadData()
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath)
return cell
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
}
My constrains:
If the height of the table view cell is basically a function of the table view's width, it might be easier to implement the heightForRowAtIndexPath delegate method to return the width of the table view multiplied by the ratio you want. That would take constraints out of the picture entirely, and probably be more efficient.
This is what you should do:
Insert a table view header view
Update the frame height keep the desired aspect ratio in the viewWillLayoutSubviews function.
class ViewController: UITableViewController {
#IBOutlet weak var headerView: UIView!
override func viewWillLayoutSubviews() {
var frame = headerView.frame;
// Check and see if the aspect ratio of the frame
// of the header view is the desired aspect ratio.
if frame.size.height != frame.width * 0.5 {
// If it is not, update the frame
frame.size.height = frame.width * 0.5;
headerView.frame = frame;
// reset the header view
self.tableView.tableHeaderView = headerView
}
}
}

Resources