UITableView动态行高+reloadRows滚动卡顿,求行高计算及无刷新更新方案
Hey there! Let's break down your problem and cover all the solutions you're looking for:
1. Why reloadRows is causing jank (and how to fix it with automatic dimension)
First off, a quick correction: you can absolutely use UITableViewAutomaticDimension with reloadRows—the issue here is likely that reloading rows triggers redundant layout calculations, especially when scrolling. Here are two better approaches to keep automatic dimension and avoid jank:
Option A: Use beginUpdates() + endUpdates() instead of reloadRows
Instead of reloading specific rows, wrap your update in a begin/end updates block. This tells the table view to batch layout changes and animate them more smoothly, even with automatic row heights:
func valueOfFieldWithIDChanged(id: Int, value: String){ // Update your data model first to keep it in sync with UI updateFormData(id: id, newValue: value) formFieldsTable.beginUpdates() formFieldsTable.endUpdates() }
This block will automatically recalculate row heights for any cells whose content changed, without forcing a full row reload. It's way lighter on performance than reloadRows.
Option B: Optimize cell layout caching
If you still want to use reloadRows, make sure you're caching row heights to avoid recalculating them every time. Store heights in a dictionary keyed by your field ID or index path:
var cachedRowHeights: [Int: CGFloat] = [:] // In tableView(_:heightForRowAt:) override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { let fieldID = formFields[indexPath.row].id if let cachedHeight = cachedRowHeights[fieldID] { return cachedHeight } // Calculate height once and cache it let calculatedHeight = calculateHeightForField(formFields[indexPath.row]) cachedRowHeights[fieldID] = calculatedHeight return UITableView.automaticDimension } // When updating a field, invalidate its cached height func valueOfFieldWithIDChanged(id: Int, value: String){ cachedRowHeights.removeValue(forKey: id) formFieldsTable.reloadRows(at: [indexPath, indexPath2], with: .none) }
This cuts down on repeated layout work, which should reduce scroll jank.
2. Updating cells without reloading rows (the smooth way)
The best way to avoid reload-related jank is to update the cell directly, no reload needed. Here's how:
- First, update your data model with the new value (critical—always keep your model in sync with the UI).
- Grab the visible cell for the target index path using
cellForRow(at:). - Call a custom method on the cell to update its content directly.
- If the cell's height changes after updating, use
beginUpdates()+endUpdates()to tell the table view to adjust the row height smoothly.
Example code:
// Assume your cell class has an update method like this class FormFieldCell: UITableViewCell { @IBOutlet weak var fieldLabel: UILabel! @IBOutlet weak var inputField: UITextField! func update(with field: FormField) { // Update your label and input field here fieldLabel.text = field.label inputField.text = field.value // Trigger layout to reflect changes immediately setNeedsLayout() layoutIfNeeded() } } // In your view controller func valueOfFieldWithIDChanged(id: Int, value: String){ // Step 1: Update the data model guard let index = formFields.firstIndex(where: { $0.id == id }) else { return } formFields[index].value = value let indexPath = IndexPath(row: index, section: 0) // Step 2: Get the visible cell and update it directly if let cell = formFieldsTable.cellForRow(at: indexPath) as? FormFieldCell { cell.update(with: formFields[index]) } // Step 3: If height changed, adjust table view layout smoothly formFieldsTable.beginUpdates() formFieldsTable.endUpdates() }
This approach skips the row reload entirely—your UI updates instantly, and the table view adjusts heights smoothly without jank.
3. Manual row height calculation (if you need it)
If you want to ditch UITableViewAutomaticDimension and calculate heights manually, here's how to handle multi-line labels:
func calculateHeightForField(_ field: FormField) -> CGFloat { // Define the maximum width for your label (match your cell's label width) let labelMaxWidth = UIScreen.main.bounds.width - 32 // Adjust for your cell's left/right padding // Calculate label height let labelAttributes: [NSAttributedString.Key: Any] = [ .font: UIFont.systemFont(ofSize: 16) // Match your label's actual font ] let labelSize = field.label.boundingRect( with: CGSize(width: labelMaxWidth, height: CGFloat.greatestFiniteMagnitude), options: .usesLineFragmentOrigin, attributes: labelAttributes, context: nil ).size let labelHeight = ceil(labelSize.height) // Add height for input field, padding, etc. let inputHeight = 44 // Your input field's fixed height let totalPadding = 16 // Top + bottom padding inside the cell return labelHeight + inputHeight + totalPadding }
Cache this calculated height (like in the earlier example) so you don't recalculate it every time the table view asks for the row height.
内容的提问来源于stack exchange,提问作者Return-1

