Dynamic height on a static cell with containerView embedded - ios

I want to achieve a desire behaviour using autolayout (or if this is not possible using a delegate or something). What I have is a tableView with one static cell, this cell has a containerView that have a tableViewController with dynamic prototype cells.
What I want is be able to use autolayout to dynamically set the height of the static cell that has the container view embedded.
This is the storyboard:
These are my constraints (static cell with the contentView of the container View):
In the viewController that have the containerView within the static cell what I have is on the ViewDidLoad method:
override func viewDidLoad() {
super.viewDidLoad()
courseDetailTableView.estimatedRowHeight = 200
courseDetailTableView.rowHeight = UITableViewAutomaticDimension
}
And using the delegate of the tableView with the staticCell:
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
But with this the height of the static cell is really small... it means that the autolayout is not capable of setting the height of his content with only the constraints that I have set.. I said only the constraints because if I set one more constraint on the contentView of the containerView that set the height to something like 400 then the height of that cell is 400..
I was just wondering if there is a way with autolayout to set the height of the static cell to match the height of the containerView.
I know that maybe using a delegate that calculates first the height of the containerView and use this height to set the heightForRow at it could possible work I want to know if there is a simpler way
Thank you so much.

I just want to answer my own question just for someone facing maybe the same problem. It doesn't have to be with static cell, this answer applies to static as well as dynamic cells.
What you have to do is in the containerViewController set a property or a method for calculating the height (don't forget to ask for layoutIfNeeded)
func tableViewHeight() -> CGFloat {
tableView.layoutIfNeeded()
return tableView.contentSize.height
}
Then in the master view controller (the one that have the cell in which is the containerViewController embedded) you have to save a reference to the containerViewController for example in the prepare for segue method like so:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "containerSegue" {
guard let containerVC = segue.destination as? SessionCoordinatorController else { return }
sessionController = containerVC
}
}
Then just ask for the container height in the delegate method of UITableView heightForRowAt like so (in the masterViewController):
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
guard let height = sessionController?.tableViewHeight() else { return UITableViewAutomaticDimension }
return height
}
And that's it
Don't forget to add the tableView.isScrollEnabled to false in the containerViewController

So if I correctly understand you actually have those constraints :
ContentView.bottom = ContainerView.bottom
ContentView.trailing = ContainerView.trailing + Standard
ContainerView.leading = ContentView.leading + Standard
ContainerView.top = ContentView.top
Basically what you want is this constraint :
ContentView.height = ContainerView.height right ? But if you put it you have a very small cell ?
If it's the case you can try to put a constraint to fix a minimum of height for the containerView like this :
ContainerView.height >= 400 // At least 400 for the height for example
Then you can try to put an optional constraint for the equal height :
ContentView.height = ContainerView.height (priority 249 or low)
By lowering the priority of the constraint you are saying "I have this extra constraint, it will be great if the contentView matches the containerView height if not keep going or approximate to it".
Here is more info about AutoLayout AutooLayout Understanding
P.S : You don't need to implement tableView:heightForRowAtIndexPath: delegate method by returning automaticDimension.

Related

dynamic tableHeaderView does not work properly

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,

Custom UITableViewCell only displays one UILabel, even though there are two on storyboard

I am making a simple contacts application with controllers: ContactTableViewController and custome cell: ContactTableViewCell. I created a custom table view cell where the style is custom, identifier is ContactTableViewCell, and class is also ContactTableViewCell.
The cell has two UILabel fields, which are exposed to ContactTableViewCell.swift class as follows:
import UIKit
class ContactTableViewCell: UITableViewCell {
#IBOutlet weak var name: UILabel!
#IBOutlet weak var info: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
print("cell loaded with name: ", name)
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
Now I want to display them in my ContactViewController, and the relevant part of my controller looks like this:
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 2
}
// here we communicate with parts of the app that owns the data
override func tableView(_ tableView : UITableView
, cellForRowAt indexPath: IndexPath
) -> UITableViewCell {
let id = "ContactTableViewCell"
// deque a cell as an instance of ContactTableViewCell
guard let cell = tableView.dequeueReusableCell(withIdentifier: id, for : indexPath)
as? ContactTableViewCell else {
fatalError("dequed cell is not instance of ContactTableViewCell")
}
cell.name.text = "hello"
cell.info.text = "hello information"
However, when I run the simulation I only see two rows of "hello", even though it should be something like:
hello
hello information
repeated twice. What is the problem here?
Most probably you don't have enough room to fit both labels. You can either set the row height in storyboard by:
Selecting your tableview
Go to size inspector
Change the Row Height
OR
You can implement tableView(_:heightForRowAt:) method in your tableView's delegate. i.e.
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
// return desired height for your cell
return 90.0
}
It is a contraint issue. You have to define height, width, x and y positioning for both the name UILabel and the info UILabel. Font size places a dominant role for height with UILabels and width can be based on the number of characters in the UILabel x font size so you do not have to explicity express height and width constraints for a UILabel, but for a UIButton you would have to explicitly define constraints for x,y,width,height. For UILabel we only have to define x and y constraints.
What happens when you do not clearly define the constraints for your UI elements is that the rendering of your View will have unpredictable results and UI elements quite often just do not appear on the view.
It looks like, from your code, you are using XCode designer to add your UILabels so that is what you can use to add constraints. You can also add constraints programmatically as well. But I am pretty sure you are using XCode Storyboard designer.
Whether programmatically or through XCode designer you need add a constraint for the name UILabel to the top and left of the super view, you can reposition later, and then constrain x and y alignment of the info UILabel to the name UILabel, horizontally aligning to the name UILabel and +8 vertical spacing to the bottom of the name UILabel, this will place the info UILabel below and centered to the name UILabel.
See this guide: https://developer.apple.com/library/content/documentation/UserExperience/Conceptual/AutolayoutPG/WorkingwithConstraintsinInterfaceBuidler.html
And this Stackoverflow answer: https://stackoverflow.com/a/34448801/1258525
Not the first two constraints I have circled in this picture, this defines the x and y for the Video Quality UILabel:
And then see how the next label below, Onset Threshold constraints itself to the leading edge of the Video Quality label and then the divider below the VideoQuality label:
My theory the 2nd label is truncated not getting sufficient width, and may be the number of lines in the second label is 1. Hence try setting number of line to 0 and check the width and auto layout constraints.

How to change view height according to it is recently added subview?

I have such problem, while trying to create a simple messenger.
Here what I came to:
class ChatBubleUIView that class is responsible for creating a bubleview with label in it. It works fine, calculating view height according to label height
Inside my cell I've created a content view. In the cell class, I'm adding new ChatBubleUIView instance as a subview to content View.
The problem is that, content doesn't scale up to the size of my ChatBubleInstance.
class ChatMessageTableViewCell: UITableViewCell, MessageCellConfiguration {
var message: Message?
override func awakeFromNib() {
super.awakeFromNib()
}
func configureCell() {
let chatBubleView = ChatBubleUIView(message: message!)
self.addSubview(chatBubleView)
}
}
In my tableView delegate
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "messageCell") as! ChatMessageTableViewCell
let data = currentUser.mesaageHistory[indexPath.row]
cell.message = data
cell.configureCell()
return cell
}
Also I have set estimated row height for my tableView
messageTableView.rowHeight = UITableViewAutomaticDimension
messageTableView.estimatedRowHeight = 44
What should I do to set to my tableView row height chatViewBubleUIView instance height.
Previously, I solved this problem using old-school approach, programmaticly determine chatViewBubleUIView instance height and then implement heightForRowAtIndexPath. But I'd like to do that using AutoLayoaut.
set your label's four constraint like top,bottom,leading,trailing and number of height of your label should be 0. Then it will automatically increased it's height as per content. If you are taking this label in any view then view's constrains should be same as label i have mentioned above!

How can I make self sizing UITableViewCell with a UIStackView inside it

I can't achieve a self sizing UITableViewCell with a UIStackView inside it.
For the UITableViewCell I'm doing something like this:
On code I'm doing this:
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.estimatedRowHeight = 80
}
But when I run the app, the UITableViewCells doesn't resizes:
What do I miss to make the UITableViewCell self sizing with a UIStackView inside it?
The cell resizing is working fine. Your problem is that you set a fixed height for the Stack View.
The View in the horizontal Stack View has its height set to 64, most likely with its standard priority set to 1000. This Stack View most likely has its distribution set to Fill. You basically told the Stack View that the containing image has to exactly fill the Stack View with a height of 64. This is also limiting the Stack View to 64 and with it the vertical Stack View besides the View. Change the distribution of the horizontal Stack View that contains the View to Center if you want the vertical Stack View next to the View to get bigger than 64.
Do you have multi line labels? If so make sure you have the lines property in IB set to 0 not 1.
Pin top and bottoms of stackView.
If that doesn't do it, check these:
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
override func tableView(tableView: UITableView, estimatedHeightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
The trick to getting Auto Layout for self sizing cells to work on a UITableViewCell is to ensure you have constraints to pin each subview on all sides — that is, each subview should have leading, top, trailing and bottom constraints. Then, the intrinsic height of the subviews will be used to dictate the height of each cell.
Here is a nice tutorial that will get you through it.
Try pinning the stack view itself. Remember the stackView has the responsibility to equally space, evenly distribute, etc., so it needs to use constraints between itself and its superView to figure all that out.
Here's an apple explanation.
HTH
open class IntrinsicHeightTableView: UITableView {
open override var contentSize: CGSize {
didSet { invalidateIntrinsicContentSize() }
}
open override var intrinsicContentSize: CGSize {
layoutIfNeeded()
return CGSize(width: UIView.noIntrinsicMetric, height: contentSize.height)
} }

Settings UITableViewCell Heights With Multiline UIButtons in Auto Layout

I have a UITableView with custom UITableViewCells, each has a UIButton inside. I'm setting buttons' titles from an array, so the size of the buttons change according to the title. I need to return correct height based on the inner button's size in heightForRowAtIndexPath event.
Since I'm using auto layout, I've created an outlet for the button's height constraint and I'm updating it in the cell's layoutSubviews() event like this:
class CustomCell: UITableViewCell {
/* ... */
override func layoutSubviews() {
super.layoutSubviews()
self.myButton?.layoutIfNeeded()
self.heightConstraint?.constant = self.myButton!.titleLabel!.frame.size.height
}
}
Then I return the height based on the button height and top-bottom margins like so:
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
var cell = tableView.dequeueReusableCellWithIdentifier("CustomCell") as! CustomCell
cell.myButton?.setTitle(self.data[indexPath.row], forState: UIControlState.Normal)
cell.bounds = CGRectMake(0, 0, CGRectGetWidth(tableView.bounds), CGRectGetHeight(cell.bounds))
cell.setNeedsLayout()
cell.layoutIfNeeded()
return cell.myButton!.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize).height + (cell.topMarginConstraint!.constant * 2) /* top-bottom margins */ + 1 /* separator height */
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCellWithIdentifier("CustomCell") as! CustomCell
cell.myButton?.setTitle(self.data[indexPath.row], forState: UIControlState.Normal)
return cell
}
On the first launch, there seems to be no problem. However, after I begin scrolling, then the height of some rows seem to be mistaken. When I get back to the top, I see that previous cell heights get to be broken as well.
When I googled for similar problems, issue seems to be about reusable cells, though I was unable to find another way to calculate the height. What can be done to reuse cells correctly or getting the correct height, perhaps by another method?
More info and source code:
Constraints set by IB like this:
Here's the cells on the first launch:
After some scrolling:
Full code of the project can be found on Github.
According to this
Configure tableView as
func configureTableView() {
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 160.0
}
Call it on your viewDidLoad method
Than configure your uibutton height constraint to be greater then or equal.
Override func tableView(tableView: UITableView, estimatedHeightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat where you can place your estimation height code
First off, it's better if you perform constraint updates in func updateConstraints() method of UIView. So instead of
override func layoutSubviews() {
super.layoutSubviews()
self.myButton?.layoutIfNeeded()
self.heightConstraint?.constant = self.myButton!.titleLabel!.frame.size.height
}
I would do
override func updateConstraints() {
self.myButton?.layoutIfNeeded()
self.heightConstraint?.constant = self.myButton!.titleLabel!.frame.size.height
super.updateConstraints()
}
Note that you should call the super implementation at the end, not at the start. Then you would call cell.setNeedsUpdateConstraints() to trigger a constraint update pass.
Also you should never directly manipulate the cell bounds the way you are doing in heightForRowAtIndePath: method, and even if you are completely sure that manipulating directly is what you want, you should manipulate cell.contentView's bounds, not the cell's bounds. If you are looking to adjust the cell height dynamically with respect to the dimensions of the content, you should use self sizing cells. If you need to support iOS 7, then this answer tells you how to achieve that behaviour with autolayout only (without touching the bounds etc).
To reiterate the answer, you should do:
func viewDidLoad() {
self.dummyCell = CustomCell.init()
// additional setup
}
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
self.dummyCell.myButton?.setTitle(self.data[indexPath.row], forState: UIControlState.Normal)
self.dummyCell.layoutIfNeeded() // or self.dummyCell.setNeedsUpdateConstraints() if and only if the button text is changing in the cell
return self.dummyCell.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize).height
}
Please know that the answer I linked to outlines a strategy to get the cell height via autolayout, so only writing the code changes I proposed won't be enough unless you set your constraints in a way that makes this solution work. Please refer to that answer for more information.
Hope it helps!
First of all, remove the height constraint of button and bind it to top and bottom with cell.
Then, in your cell' height, calculate height of the text based on the width and font of button. This will make the cell's height dynamic and you wont need height constraint anymore.
Refer the link below to get the height of text:
Adjust UILabel height to text
Hope it helps. If you need help further or understanding anything, let me know.. :)

Resources