I have a UITableView
with cells that display chat messages. Some messages contain code blocks which I render inside the cell by dynamically creating and adding custom CodeBlockView
instances to a stack view within the cell. Here is an excerpt from my tableView(_:cellForRowAt:)
method:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let row = indexPath.row
let message = message(at: indexPath)
if message.role == Role.assistant.rawValue {
let chatCell = tableView.dequeueReusableCell(withIdentifier: ChatCell.identifier, for: indexPath) as! ChatCell
chatCell.formattingStackView.removeAllArrangedSubviews()
chatCell.index = row
chatCell.delegate = self
chatCell.text = message.content
if TentativeChatModel.shared.tentativeMessage.content != "" && row == TentativeChatModel.shared.chatList.count - 1 && message.role == Role.assistant.rawValue {
return chatCell
}
chatCell.configure(text: message.content)
if let reasoning = message.reasoningContent, let time = message.think_time, time >= 0 {
chatCell.collapseButton.isHidden = false
chatCell.reasoningView.isHidden = false
chatCell.thinkLabel.text = reasoning
chatCell.thoughtForLabel.text = "Thought for \(time)s"
if AppConstants.expandedRowIndex.contains(row) {
chatCell.reasoningView.borderWidth = 0.8
chatCell.thinkFooterView.isHidden = false
chatCell.chevronImageView.image = .chevronUp
} else {
chatCell.reasoningView.borderWidth = 0
chatCell.thinkFooterView.isHidden = true
chatCell.chevronImageView.image = .chevronUp
}
} else {
chatCell.reasoningView.isHidden = true
chatCell.collapseButton.isHidden = true
}
let document = Document(parsing: message.content)
let results = markdown.parserResults(from: document)
for result in results {
if result.isCodeBlock {
let spacer = UIView()
spacer.heightAnchor.constraint(equalToConstant: 10).isActive = true
let codeCell = try? CodeBlockView.view(with: self) as? CodeBlockView
codeCell?.translatesAutoresizingMaskIntoConstraints = false
codeCell?.codeLabel.attributedText = result.attributedString.trimmedTrailingNewlines()
codeCell?.languageLabel.text = result.codeBlockLanguage ?? "Output"
chatCell.formattingStackView.addArrangedSubview(codeCell!)
chatCell.formattingStackView.addArrangedSubview(spacer)
} else {
let label = UILabel()
label.attributedText = result.attributedString
label.textColor = .cLabel
label.translatesAutoresizingMaskIntoConstraints = false
label.numberOfLines = 0
label.lineBreakMode = .byWordWrapping
chatCell.formattingStackView.addArrangedSubview(label)
}
}
if let citations = message.citations, !citations.isEmpty {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
view.heightAnchor.constraint(equalToConstant: 10).isActive = true
chatCell.formattingStackView.addArrangedSubview(view)
let collectionView = CitationsCollectionView(frame: .zero, collectionViewLayout: layoutForCitationsCollectionView())
collectionView.configure(citations)
collectionView.translatesAutoresizingMaskIntoConstraints = false
collectionView.heightAnchor.constraint(equalToConstant: 180).isActive = true
collectionView.widthAnchor.constraint(equalToConstant: tableView.frame.width).isActive = true
chatCell.formattingStackView.addArrangedSubview(collectionView)
collectionView.onCitationTap = { [weak self] title, citation in
guard let self else { return }
customDelegate?.chatTableView(self, didTapCitation: citation, titleForCitation: title)
}
}
chatCell.streamLabel.isHidden = true
chatCell.formattingStackView.isHidden = false
return chatCell
} else {
let userCell = tableView.dequeueReusableCell(withIdentifier: UserCell.identifier, for: indexPath) as! UserCell
userCell.configure(message: message.content)
return userCell
}
}
View Hierarchy of my TableViewCell
The problem is that when a cell contains one or more CodeBlockViews, scrolling the table view becomes very laggy and sometimes gets stuck.
What I suspect:
Creating and configuring views in cellForRowAt is heavy and blocking the main thread.
The markdown parsing and attributed string generation might also be expensive.
Lack of view reuse for the code block views inside cells. What I want to achieve:
Smooth scrolling performance even when multiple code blocks appear inside cells.
Efficient reuse or caching of views to avoid expensive view creation on every scroll.
Could anyone suggest best practices or solutions for efficiently handling dynamic subviews like custom code block views inside table view cells?
What I have tried:
- Caching whole tableview cell.