Reputation: 1239
I'm making a chat like application, where the tableView displays dynamic height cells.
The cells have their views&subviews constrained in the right way
So that the AutoLayout can predict the height of the cells
(Top, Bottom, Leading, Trailing)
But still - as you can see in the video - the scroll indicator bar shows that wrong heights were calculated:
It recalculates the heights when a new row is appearing.
Video: https://youtu.be/5ydA5yV2O-Q
(On the second attempt to scroll down everything is fine)
Code:
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableView.automaticDimension
}
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableView.automaticDimension
}
It is a simple problem. Can someone help me out?
Added github:
https://github.com/krptia/Test
Upvotes: 9
Views: 1540
Reputation: 1670
This is expected behaviour when using coarse cell height estimates (or not providing them at all, as you do). The actual height is computed only when the cells come on screen, so the travel of the scroll bar is adjusted at that time. Expect jumpy insertion/deletion animations too, if you use them.
Upvotes: 2
Reputation: 626
I hope you heard about this a lot. so take short break and come back on desk and apply 2 - 3 steps for step this.
1) Make sure Autolayouts of label of Cell is setup correct like below.
2) UILabel's number of lines set zero for dynamic height of text.
3) setup automatic dimension height of cell.
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableView.automaticDimension
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableView.automaticDimension
}
and I believe its should be work. see results of my code.
Upvotes: 1
Reputation: 1780
Just remove highlighted view from UITableView
and it's work like a charm.
Hope it helps.
Upvotes: 2
Reputation: 1427
But still - as you can see in the video - the scroll indicator bar shows that wrong heights were calculated:
So what you want is precise content height.
For that purpose, you cannot use static estimatedRowHeight
.
You should implement more correct estimation like below.
...
var sampleCell: WorldMessageCell?
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(UINib(nibName: "WorldMessageCell", bundle: nil), forCellReuseIdentifier: "WorldMessageCell")
sampleCell = UINib(nibName: "WorldMessageCell", bundle: nil).instantiate(withOwner: WorldMessageCell.self, options: nil)[0] as? WorldMessageCell
}
...
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
if let cell = sampleCell {
let text = self.textForRowAt(indexPath)
// note: this is because of "constrain to margins", which value is actually set after estimation. Do not use them to remove below
let margin = UIEdgeInsets(top: 8, left: 20, bottom: 8, right: 20)
// without "constrain to margins"
// let margin = cell.contentView.layoutMargins
let maxSize = CGSize(width: tableView.frame.size.width - margin.left - margin.right,
height: CGFloat.greatestFiniteMagnitude)
let attributes: [NSAttributedString.Key: Any]? = [NSAttributedString.Key.font: cell.messageLabel.font]
let size: CGRect = (text as NSString).boundingRect(with: maxSize,
options: [.usesLineFragmentOrigin], attributes: attributes, context: nil)
return size.height + margin.top + margin.bottom
}
return 100
}
This is too precise (actually real row height) and maybe slow, but you can do more approximate estimation for optimization.
Upvotes: 4
Reputation: 1657
What you want to do is eliminate the extra blank cells. You can do so by setting the tableFooterView
to an empty UIView
in the viewDidLoad
method. I cloned the code from your GitHub and revised the method:
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(UINib(nibName: "WorldMessageCell", bundle: nil), forCellReuseIdentifier: "WorldMessageCell")
tableView.tableFooterView = UIView()
}
setting the tableFooterView
to nil
worked for me as well
tableView.tableFooterView = nil
Upvotes: 0
Reputation: 773
The problem is with your estimatedHeightForRowAt
method. As the name implies it gives the estimated height to the table so that it can have some idea about the scrollable content until the actual content will be displayed. The more accurate value will result in a more smooth scrolling and height estimation.
You should set this value to big enough so that it can represent the height of your cell with the maximum content. In your case 650 is working fine.
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
return 650
}
The result would be far better with this approach.
Also, there is no need to implement delegate method for height until you want a variation on index bases. You can simply set table view property.
tableView.estimatedRowHeight = 650.0
tableView.rowHeight = .automaticDimension
Optimization
One more thing I noticed in your demo project. You've used too many if-else
in your cellForRowAtIndexPath
which is making it little slower. Try to minimize that. I've done some refinement to this, and it improves the performance.
Define an array which holds your message text.
var messages = ["Lorem ipsum,"many more",.....]
Replace your cellForRowAt indexPath
with below:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell : WorldMessageCell
cell = tableView.dequeueReusableCell(withIdentifier: "WorldMessageCell", for: indexPath) as! WorldMessageCell
if indexPath.row < 14 {
cell.messageLabel.text = messages[indexPath.row]
}
else if indexPath.row >= 14 && indexPath.row != 27 {
cell.messageLabel.text = messages[14]
}
else if indexPath.row == 27 {
cell.messageLabel.text = messages.last
}
return cell
}
Upvotes: 2
Reputation: 9990
According to your answer on my comment that when you set
estimatedHeightForRowAt and heightForRowAt the same values it does work
I can confirm that you are right and that there is the problem that AutoLayout cannot calculate the right value for estimatedHeightForRowAt
. So basically there are two possible things to do:
Upvotes: 2
Reputation: 1587
Configure your tableview with these in viewDidLoad()
tableView.estimatedRowHeight = 100.0
tableView.rowHeight = UITableView.automaticDimension
tableView.tableFooterView = UIView()
And you should remove both height datasource method.
Upvotes: 0
Reputation: 902
why you add view in table view , it can also work without it. I just delete that view and change some constraints(like bottom constraints change safe area to superview) , and it works fine.
download storyboard and add it to your project and then check
Upvotes: 0
Reputation: 1473
You need to set tableFooterView
to empty.
override func viewDidLoad() {
super.viewDidLoad()
tableView.tableFooterView = UIView()
// your staff
}
Upvotes: 2