There are multiple ways to impove the scrolling for UITableView/UICollectionView, today I will give my way to solved it, hope it will help you guys a little bit:
Now we should think about: Why we got lagging when scroll your table view or collection view
As my experiences, 2 important things I think those will root to lagging when scroll your table view/collection view:
- Configure cells too expensive
- Time to calculate height/size for cell take too long
So 2 methods need to change with UITableView
:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {}
and
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {}
1. Now we go with configure cells first:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell: YourTableViewCell = tableView.dequeueReusableCell(withIdentifier: "YourTableViewCell", for: indexPath) as! MessageTableViewCell
let model: YourModel = models[indexPath.row]
DispatchQueue(label: "com.geek-is-stupid.queue.configure-cell").async {
let image: URL = model.image
let age: Int = model.age
let name: String = model.name
let message: String = model.message
DispatchQueue.main.async {
cell.imageView.image = image
cell.nameLabel.text = name
cell.ageLabel.text = "\(age)"
cell.messageLabel.text = message
}
}
return cell
}
2. Impove calculating height for cells
- Calculate cell’s height:
class YourTableViewCell: UITableViewCell {
static let shared: MessageTableViewCell = UINib(nibName: "YourTableViewCell", bundle: nil).instantiate(withOwner: nil, options: nil)[0] as! YourTableViewCell
func height(model: YourModel) -> CGFloat {
//Depend on your cell's layout, you will have different way to get cell's height.
// e.g There are 3 vertically labels on my cell
let messageHeight: CGFloat = model.message.size(font: message.messageFont, width: message.messageWidth).height
let nameHeight: CGFloat = model.name.size(font: message.messageFont, width: message.messageWidth).height
let ageHeight: CGFloat = "\(model.age)".size(font: message.messageFont, width: message.messageWidth).height
return messageHeight + nameHeight + ageHeight
}
}
- Need an array to cache calcuated heights:
Back to your view controller
fileprivate var cachedHeights: [Int: CGFloat] = [:]
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if let cellHeight = cachedHeights[indexPath.row] {
return cellHeight
}
let model: YourModel = models[indexPath.row]
let cell: YourTableViewCell = .shared
let cellHeight: CGFloat = cell.height(model: model)
cachedHeights[index] = cellHeight
return cellHeight
}
Method to get size for a string:
extension String {
func size(font: UIFont, width: CGFloat) -> CGSize {
let attrString: NSAttributedString = NSAttributedString(string: self, attributes: [NSFontAttributeName: font])
let bounds: CGRect = attrString.boundingRect(with: CGSize(width: width, height: 0.0), options: .usesLineFragmentOrigin, context: nil)
let size: CGSize = CGSize(width: bounds.width + 1, height: bounds.height + 2*(font.lineHeight - font.pointSize))
return size
}
}
OK, hope this works for you guys!!!