UITableViewAutomaticDimension not working with custom table cell - ios

there is plenty of question answering the dynamic height for UITableViewCell of UITableView. However i can't solve my problem.
I have this table cell creation code:
class UINoteTabelViewCell: UITableViewCell {
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
self.selectionStyle = .none
}
func fill(_ note: Note) {
let view = UINote(withNote: note, atPoint: .zero)
self.contentView.addSubview(view)
}
}
This is how i create cell for table view:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "NotePreview", for: indexPath as IndexPath) as! UINoteTabelViewCell
cell.fill(notes[indexPath.row] as! Note)
return cell
}
Also table view estimatedRowHeight and rowHeight are sets to UITableViewAutomaticDimension
But table view still draws with 44.0 rows height.
I have no idea how to fix it.
PS I can't set fixed estimatedRowHeight because every cell have dynamic height

You should give the estimatedRowHeight some default value, say 60.
Then the rows will have a default height of 60 but whenever the content needs height more than 60 at that time UITableViewAutomaticDimension will work.

Automatic height works on calculated cell's contentView height. You therefore have to add constraints that will be used to calculate its real height.
Try this:
class UINoteTabelViewCell: UITableViewCell {
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
self.selectionStyle = .none
}
func fill(_ note: Note) {
let view = UINote(withNote: note, atPoint: .zero)
self.contentView.addSubview(view)
// this will make the difference to calculate it based on view's size:
view.translatesAutoresizingMaskIntoConstraints = false
view.leftAnchor.constraint(equalTo: contentView.leftAnchor).isActive = true
view.rightAnchor.constraint(equalTo: contentView.rightAnchor).isActive = true
view.topAnchor.constraint(equalTo: contentView.topAnchor).isActive = true
view.bottomAnchor.constraint(equalTo: contentView.bottomAnchor).isActive = true
}
}
Also, estimatedRowHeight should be set to a specific value that approximately estimates the size of all cells. But that would not be a problem.

I can see you are just adding NoteView on cell contentView. You should also apply auto layout constraint on your note view like leading, trailing, top and bottom.
If you apply correct auto layout constraint on your note view, hope it will work.

Related

UItableView dynamic height depending on content

How can I make the tableview cell height dynamic,
I have 1 label and 1 image in the cell, My image height is constant of 70, label height depends on the api text, if my label text I large then the image view height so table view cell should adapt label height else image View.
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
below image just picking height of image
Making a UITableViewCells height dynamic based on its content is obtained by doing the following:
Make sure the content of your UITableViewCell is constrained such the dynamic content is pinned to both the top and bottom of the cell.
A contrived example cell:
class Cell: UITableViewCell {
let label = UILabel()
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
// Allows your text to expand multiple lines
label.numberOfLines = 0
label.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(label)
// Constrains a UILabel to the edges of the UITableViewCells content view
label.topAnchor.constraint(equalTo: commentBox.topAnchor).isActive = true
label.bottomAnchor.constraint(equalTo: commentBox.bottomAnchor).isActive = true
label.leadingAnchor.constraint(equalTo: commentBox.leadingAnchor).isActive = true
label.trailingAnchor.constraint(equalTo: commentBox.trailingAnchor).isActive = true
}
}
As you have above, return UITableViewAutomaticDimension from func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat
To help your UITableView compute the dynamic height, its recommended to return an estimated size you think your cell is going to be.
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat

UITableViewCell dynamic cell heights are incorrect until scrolled

I want my UITableViewCell to expand in size when tapped.
The layout of the cell is quite straightforward. Within the UITableViewCell is a UILabel. The UILabel is constrained to the UITableViewCell with top, bottom, left and right anchors.
I also have two stored properties. labelExpandedHeightConstraint stores the UILabel's height constraint for when the label is expanded. labelCompactHeightConstraint stores the UILabel's height constraint for when the label is compacted. Notice that labelCompactHeightConstraint is initially set to active.
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
let spacing = 8
self.addSubview(self.labelView)
self.labelView.translatesAutoresizingMaskIntoConstraints = false
self.labelView.topAnchor.constraint(equalTo: self.topAnchor, constant: spacing).isActive = true
self.labelView.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: -1 * spacing).isActive = true
self.labelView.leftAnchor.constraint(equalTo: self.leftAnchor, constant: spacing).isActive = true
self.labelView.rightAnchor.constraint(equalTo: self.rightAnchor, constant: -1 * spacing).isActive = true
self.labelExpandedHeightConstraint = self.labelView.heightAnchor.constraint(equalToConstant: 120)
self.labelCompactHeightConstraint = self.labelView.heightAnchor.constraint(equalToConstant: 80)
self.labelCompactHeightConstraint.isActive = true
}
The expand() function below is called whenever the user taps a UITapGestureRecognizer. This function is very simple. It expands the cell by disabling labelCompactHeightConstraint and enabling labelExpandedHeightConstraint.
#objc func expand() {
self.labelCompactHeightConstraint.isActive = false
self.labelExpandedHeightConstraint.isActive = true
self.layoutIfNeeded()
}
The problem is that when the expand() function is called, the UITableViewCell and its contents do not change in size. It is not until the user scrolls the cell off the screen, and then scrolls it back onto the screen, that the size adjusts correctly.
How can I get the cell to expand immediately when tapped? I would also like this sizing change to be animated. I would really appreciate any suggestions. Thanks!
You'll need to do it differently.
Something like:
tableView.beginUpdates()
// update data for cell, or if your cell is not dynamically created - update it directly
// Usually, you'll need to update your data structures
// Reload the cell
tableView.reloadRows(at: [indexPath], with: .automatic)
tableView.endUpdates()
From what you wrote, the place to add this code is from:
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
...
}
Also, note that in most cases, you should just change the content (ie. the text in the label) and not the constraint value.
Here is a minimal full example:
class ViewController: UITableViewController {
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 100
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "ident", for: indexPath) as! Cell
if selections.contains(indexPath) {
cell.height.constant = 80
} else {
cell.height.constant = 10
}
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.beginUpdates()
if selections.contains(indexPath){
selections.remove(indexPath)
} else {
selections.insert(indexPath)
}
tableView.reloadRows(at: [indexPath], with: .automatic)
tableView.endUpdates()
}
var selections = Set<IndexPath>()
}
class Cell : UITableViewCell {
#IBOutlet var height : NSLayoutConstraint!
}
Could use the table view's multiple selection but wanted to demonstrate usage of app specific data.

UITableView corner radius doesn't work correctly

I've a tableview and I'd like to make the cells rounded. Every cell has a textfield inside.
On "cellForRowAtIndexPath" I wrote:
cell.layer.cornerRadius = cell.frame.size.height/2;
cell.layer.masksToBounds = true;
cell.clipsToBounds = true;
but I can't understand why, but on one line cells sometimes the corner radius doesn't work causing to be like "pointed".
P.s. the content mode is set to be Aspect fit
Can someone help me?
I advise you to add a comtainer view to your cell controller, you will be able to manage the cell padding better with Auto Layout:
let containerView = UIView()
set property of it:
containerView.backgroundColor = .red
containerView.clipsToBounds = true
containerView.translatesAutoresizingMaskIntoConstraints = false
Now add UIView and set constraints:
addSubview(containerView)
containerView.topAnchor.constraint(equalTo: topAnchor, constant: 0).isActive = true //change the constant to add padding (right and bottom are negative value)
containerView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: 0).isActive = true
containerView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 0).isActive = true
containerView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: 0).isActive = true
After that in cellForRowAt set yuor cell backgroundColor to clear
cell.backgoundColor = .clear
cell.containerView.layer.cornerRadius = cell.bounds.size.height/2;
This is the result with your corner radius applied to containerView:
Or set your tableView and cell like this:
class DummyViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
let tableView = UITableView()
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
tableView.translatesAutoresizingMaskIntoConstraints = false
tableView.register(MyCell.self, forCellReuseIdentifier: "cellId")
view.addSubview(tableView)
tableView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
10
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
300
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cellId", for: indexPath) as! MyCell
cell.layer.cornerRadius = cell.frame.size.height / 2;
return cell
}
}
Your cell:
class MyCell: UITableViewCell {
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
backgroundColor = .blue
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
This is the result with your corner radius applied:
You are relying on the frame property of the cell, which is actually being managed by UIKit and the table view. In the place you refer to the frame, it might not actually be what you expect because it hasn’t been displayed yet. (For example if your cells have dynamic heights and you are recycling an old cell with a different height then it will still have the old frame.)
You could try rounding inside the cell’s implementation code itself. If you override the bounds property and use a didSet property observer you can set the corner radius there (use bounds instead of frame). This way even if the bounds changes 100 times you will always have the corner radius you expect.
As andym said, I'm relying on the frame property that haven't been defined yet.
The best way to accomplish my goal is to put my code inside willDisplayCell method like this:
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath{
cell.layer.cornerRadius = cell.layer.bounds.size.height/2;
}
clipsToBounds needs to be set to true on the superview, which is a tableview in your case.
You should configure the content view instead a cell it self
cell.contentView.layer.cornerRadius = 5 // or the value that you want
cell.contentView.layer.masksToBounds = true

tableView does not appear when centered into its parent view

I have a viewController with the following (static) tableView:
class viewController: UIViewController, UITableViewDelegate {
private let tableView: UITableView = {
let tv = UITableView()
tv.separatorStyle = .singleLine
tv.allowsSelection = true
tv.isScrollEnabled = false
return tv
}()
private let tableData = ["row1", "row2", "row3", "row4"]
override func viewDidLoad() {
super.viewDidLoad()
tableView.translatesAutoresizingMaskIntoConstraints = false
tableView.delegate = self
tableView.tableFooterView = UIView()
view.addSubview(tableView)
NSLayoutConstraints.activate([
tableView.centerXAnchor.constraints(equalTo: view.safeAreaLayoutGuide.centerXAnchor),
tableView.centerYAnchor.constraints(equalTo: view.safeAreaLayoutGuide.centerYAnchor)
)]
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell(style: .subtitle, reuseIdentifier: "cell")
cell.textLabel!.text = tableData[indexPath.row]
return cell
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return tableData.count
}
}
When I run the app, this viewController shows a blank screen. I know that the way I am setting up the tableview's constraints is the problem because when I set up the tableView using topAnchor, bottomAnchor, leftAnchor, and rightAnchor (and with some other tweaking) the tableview appears. Any idea why the app is behaving this way?
Your table view is probably there, and centered, but you didn't define a size, so it's probably being set to zero width and height, that's why you don't see it.
You can fix this by setting a constraint on it's width and height, either to a constant or related to it's superview, depending on what you want.
The problem is this is NOT a static table view. If it were, you would not have implemented cellForRowAt. It is a normal table view and it needs a data source and delegate. Plus it needs a height and a width.

How to size a UIStackView depending on its content?

I would like to have a similar behavior than the <Table> HTML tag, in the sense where the frame is sized according to its content.
In my very context, I use an UIStackView as the content view of a UITableViewCell. As items in the cell are various information, the resulting height of the cell should be variable.
My strategy is to programmatically build a cell as a UIStackView with a .Vertical axis, as in the code snippet as follows:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath)
let sv = UIStackView(frame: tableview.frame)
sv.axis = .Vertical
cell.addSubview(sv)
for i in information {
let l = UILabel()
l.text = i
sv.addSubViewAndArrange(l)
}
return cell
}
Unfortunately, the cell size does not adjust to the content, and as a result I have to set the cell height myself, as follows:
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return cellHeight // a constant
}
How could I fix that?
UIStackView is designed to grow its size according to the content. In order for that to work, you need to set up the constraints between the UIStackView and the UITableViewCell. For example, if UIStackView is first-item and UITableViewCell is it's super-view, then this is how the constraints look like in interface builder:
If you like setting up constraints in code, that should work too.
For example, assuming stackView and cellView are the names, then above constraints' Swift-code would look like:
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.topAnchor.constraint(equalTo: cellView.topAnchor, constant: 0).isActive = true
stackView.bottomAnchor.constraint(equalTo: cellView.bottomAnchor, constant: 0).isActive = true
stackView.leadingAnchor.constraint(equalTo: cellView.leadingAnchor, constant: 0).isActive = true
stackView.trailingAnchor.constraint(equalTo: cellView.trailingAnchor, constant: 0).isActive = true
To demonstrate that this will work, I have this for the cellForRowAt function. Basically, it puts a number of UILabel inside the UIStackView and the label count is depending on the row number.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "TableviewCell", for: indexPath) as! TableviewCell
for i in 1...indexPath.row + 1 {
let label = UILabel()
label.text = "Row \(indexPath.row), Label \(i)"
cell.stackView.addArrangedSubview(label)
}
return cell
}
Here is the final result:
https://github.com/yzhong52/AutosizeStackview
I built this example I hope it helps, I've created a tableView which use a cell that contains a stackView and the views loaded in the stackView are gotten from a nib file
https://github.com/Joule87/stackView-within-TableViewCell

Resources