CollectionView内嵌TableView显示不全问题求助
问题描述
我有一个聊天样式的CollectionView,发送消息实现代码如下。每个CollectionView Cell内部包含一个TableView用于展示问题的可选答案,该TableView设置了estimatedRowHeight = 44、isScrollEnabled = false、rowHeight = UITableView.automaticDimension,Cell的尺寸计算代码也附在下方。
当前遇到的问题:当答案选项文本过长时,TableView内容会被裁剪,无法完整显示;只有将该Cell滚出屏幕再返回后,内容才会正常展示。需求是Cell首次出现在CollectionView中时,TableView就能立即完整显示所有内容。
相关代码片段
发送消息实现
private func sendMessage(message: ChatMessage) { self.collectionView.performBatchUpdates({ [weak self] in let indexPath = IndexPath(item: self?.messages.count ?? 0, section: 0) self?.messages.append(message) self?.collectionView.insertItems(at: [indexPath]) }) { [weak self] _ in self?.scrollToBottom(animated: true) } }
Cell尺寸计算代码
@discardableResult func layout() -> CGFloat { let tableViewSize = tableView.sizeThatFits(CGSize(width: ChatCellConstants.textMaxSize, height: .greatestFiniteMagnitude)) let titleSize = titleLabel.sizeThatFits(CGSize(width: ChatCellConstants.textMaxSize, height: .greatestFiniteMagnitude)) let contentWidth = max(tableViewSize.width, titleSize.width) let tableViewHeight = answers.reduce(0) { partialResult, data in return partialResult + cell.set(text: data.title, isSelected: false, animated: false).layoutHeight() } if isMyMessage { tableView.layout( right: right - ChatCellConstants.textHOffset, top: titleLabel.bottom + ChatCellConstants.standartOffset, width: contentWidth, height: tableViewHeight ) chatBubleView.backgroundColor = AppTheme.shared.colors.purple.withAlphaComponent(0.6) } else { tableView.layout( top: titleLabel.bottom + ChatCellConstants.standartOffset, left: left, width: contentWidth, fitHeight: tableViewHeight ) chatBubleView.backgroundColor = AppTheme.shared.colors.cian.withAlphaComponent(0.6) } titleLabel.layout(left: tableView.left, right: tableView.right, top: ChatCellConstants.standartOffset, height: titleSize.height) checkButton.layout(left: tableView.left, right: tableView.right, top: tableView.bottom + ChatCellConstants.standartOffset, height: 48) chatBubleView.layout( left: tableView.left - ChatCellConstants.standartOffset, right: tableView.right + ChatCellConstants.standartOffset, top: titleLabel.top - ChatCellConstants.standartOffset, bottom: checkButton.bottom + ChatCellConstants.standartOffset ) return chatBubleView.bottom }
解决方案
1. 插入Cell后强制刷新内部TableView布局
修改sendMessage方法,在滚动到底部后,获取刚插入的Cell,触发内部TableView的重加载与布局,再刷新CollectionView的布局:
private func sendMessage(message: ChatMessage) { self.collectionView.performBatchUpdates({ [weak self] in guard let self = self else { return } let indexPath = IndexPath(item: self.messages.count, section: 0) self.messages.append(message) self.collectionView.insertItems(at: [indexPath]) }) { [weak self] _ in guard let self = self else { return } self.scrollToBottom(animated: true) // 处理刚插入的Cell,确保内部TableView布局完成 let lastIndexPath = IndexPath(item: self.messages.count - 1, section: 0) if let chatCell = self.collectionView.cellForItem(at: lastIndexPath) as? YourChatCellClass { chatCell.tableView.reloadData() chatCell.tableView.layoutIfNeeded() // 触发CollectionView重新计算Cell高度 self.collectionView.performBatchUpdates(nil, completion: nil) } } }
注意:将
YourChatCellClass替换为你实际的Cell类名。
2. 修正Cell内TableView高度计算逻辑
原代码手动累加Cell高度容易出现偏差,改为让TableView先加载数据,通过sizeThatFits获取准确的高度:
@discardableResult func layout() -> CGFloat { // 先加载TableView数据并完成布局 tableView.reloadData() tableView.layoutIfNeeded() let tableViewSize = tableView.sizeThatFits(CGSize(width: ChatCellConstants.textMaxSize, height: .greatestFiniteMagnitude)) let titleSize = titleLabel.sizeThatFits(CGSize(width: ChatCellConstants.textMaxSize, height: .greatestFiniteMagnitude)) let contentWidth = max(tableViewSize.width, titleSize.width) // 直接使用TableView自动计算的高度 let tableViewHeight = tableViewSize.height // 后续布局代码保持不变 if isMyMessage { tableView.layout( right: right - ChatCellConstants.textHOffset, top: titleLabel.bottom + ChatCellConstants.standartOffset, width: contentWidth, height: tableViewHeight ) chatBubleView.backgroundColor = AppTheme.shared.colors.purple.withAlphaComponent(0.6) } else { tableView.layout( top: titleLabel.bottom + ChatCellConstants.standartOffset, left: left, width: contentWidth, fitHeight: tableViewHeight ) chatBubleView.backgroundColor = AppTheme.shared.colors.cian.withAlphaComponent(0.6) } titleLabel.layout(left: tableView.left, right: tableView.right, top: ChatCellConstants.standartOffset, height: titleSize.height) checkButton.layout(left: tableView.left, right: tableView.right, top: tableView.bottom + ChatCellConstants.standartOffset, height: 48) chatBubleView.layout( left: tableView.left - ChatCellConstants.standartOffset, right: tableView.right + ChatCellConstants.standartOffset, top: titleLabel.top - ChatCellConstants.standartOffset, bottom: checkButton.bottom + ChatCellConstants.standartOffset ) return chatBubleView.bottom }
3. 确保CollectionView的Cell高度计算准确
在CollectionView的sizeForItemAt方法中,确保为Cell设置完整数据后再计算高度:
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { let message = messages[indexPath.item] let cell = YourChatCellClass() cell.answers = message.answers cell.isMyMessage = message.isMyMessage // 调用layout方法获取准确高度 let cellHeight = cell.layout() return CGSize(width: collectionView.bounds.width, height: cellHeight) }
原因说明
问题本质是插入新Cell时,内部TableView的异步布局尚未完成,导致计算的高度不准确;当Cell滚出屏幕再返回时,会触发重新布局,此时TableView内容已完成渲染,高度计算正确。通过强制触发TableView的重加载与布局,以及修正高度计算逻辑,可确保Cell首次显示时就能获取到准确的高度,完整展示内容。
内容的提问来源于stack exchange,提问作者PavelPud

