iOS:如何根据嵌入的动态SwiftUI视图调整UITableView行高?
解决SwiftUI视图嵌入UITableView后展开行高不更新的问题
核心问题分析
SwiftUI视图展开后,其intrinsicContentSize发生变化,但UITableView未及时感知到该变化,导致自动行高失效。以下是针对性的解决方案:
方案1:优化回调中的布局触发逻辑
在adjustHeight闭包中,不仅触发TableView批量更新,还要强制SwiftUI宿主视图重新计算尺寸并刷新布局:
// 在cellForRowAt中创建视图时 let viewModel = BillingTileGridViewModel() let view = BillingTileGridView(viewModel: viewModel, adjustHeight: { [weak self, indexPath] in guard let self = self else { return } self.tableView.performBatchUpdates({ // 定位当前单元格并强制刷新布局 if let cell = self.tableView.cellForRow(at: indexPath) as? SwiftUIHostTableViewCell<BillingTileGridView> { cell.hostingController.view.invalidateIntrinsicContentSize() cell.contentView.setNeedsLayout() cell.contentView.layoutIfNeeded() } }, completion: nil) })
注意:需要将indexPath捕获到闭包中,确保能定位到当前行的单元格。
方案2:确保SwiftUI视图自身正确传递尺寸变化
修改BillingTileGridView,用VStack包裹内容并设置垂直方向自适应:
struct BillingTileGridView: View { @ObservedObject var viewModel: BillingTileGridViewModel let adjustHeight: () -> Void var body: some View { VStack(spacing: 10) { LazyHGrid(columns: /* 你的列定义 */, spacing: 10) { // 渲染账户项 ForEach(viewModel.displayedItems) { item in // 你的Item视图 } // More按钮 Button(action: { viewModel.toggleExpanded() adjustHeight() }) { Text("More") } } } .frame(maxWidth: .infinity) .fixedSize(horizontal: false, vertical: true) // 允许垂直方向自适应高度 .padding() } }
该设置能确保SwiftUI视图展开时,intrinsicContentSize正确反映内容高度。
方案3:优化宿主单元格的布局配置
在SwiftUIHostTableViewCell中,确保contentView能跟随子视图变化:
class SwiftUIHostTableViewCell<Content: View>: UITableViewCell { let hostingController = UIHostingController<Content>(rootView: /* 初始视图 */) let contentViewWrapper = UIView() override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) setupContentView() } required init?(coder: NSCoder) { super.init(coder: coder) setupContentView() } private func setupContentView() { contentView.addSubview(contentViewWrapper) contentViewWrapper.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ contentViewWrapper.topAnchor.constraint(equalTo: contentView.topAnchor), contentViewWrapper.bottomAnchor.constraint(equalTo: contentView.bottomAnchor), contentViewWrapper.leadingAnchor.constraint(equalTo: contentView.leadingAnchor), contentViewWrapper.trailingAnchor.constraint(equalTo: contentView.trailingAnchor) ]) // 允许contentView自适应子视图高度 contentView.autoresizingMask = [.flexibleWidth, .flexibleHeight] contentView.clipsToBounds = true } // 你的host方法... }
同时更新host方法,设置rootView后强制触发布局:
func host(_ view: Content, parent: UIViewController) { hostingController.rootView = view hostingController.view.invalidateIntrinsicContentSize() // 新增:强制视图重新布局 hostingController.view.setNeedsLayout() hostingController.view.layoutIfNeeded() let requiresControllerMove = hostingController.parent != parent if requiresControllerMove { removeHostingControllerFromParent() parent.addChild(hostingController) } if !contentViewWrapper.subviews.contains(hostingController.view) { contentViewWrapper.addSubview(hostingController.view) hostingController.view.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ hostingController.view.topAnchor.constraint(equalTo: contentViewWrapper.topAnchor), hostingController.view.bottomAnchor.constraint(equalTo: contentViewWrapper.bottomAnchor), hostingController.view.leadingAnchor.constraint(equalTo: contentViewWrapper.leadingAnchor), hostingController.view.trailingAnchor.constraint(equalTo: contentViewWrapper.trailingAnchor) ]) } if requiresControllerMove { hostingController.didMove(toParent: parent) } }
备选方案:手动计算行高
如果自动布局方案无效,可根据展开状态手动计算行高:
- 在
BillingTileGridViewModel中添加展开状态属性:
class BillingTileGridViewModel: ObservableObject { @Published var isExpanded = false var allItems: [Item] = [] // 你的账户数据 var displayedItems: [Item] { isExpanded ? allItems : Array(allItems.prefix(3)) } func toggleExpanded() { isExpanded.toggle() } }
- 在UIKit控制器中实现
heightForRowAt:
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { // 判断当前行是否是目标行 guard indexPath.row == 你的目标行索引 else { return UITableView.automaticDimension } // 获取ViewModel的展开状态 let viewModel = BillingTileGridViewModel() let itemCount = viewModel.isExpanded ? viewModel.allItems.count : 3 // 根据Grid列数计算行数,假设为2列 let rows = (itemCount + 1) / 2 // 每行高度(含item高度和间距),根据实际UI调整数值 let rowHeight: CGFloat = 60 // 加上按钮和内边距高度 return CGFloat(rows) * rowHeight + 40 }
- 在
adjustHeight回调中刷新行:
let viewModel = BillingTileGridViewModel() let view = BillingTileGridView(viewModel: viewModel, adjustHeight: { [weak self, indexPath] in guard let self = self else { return } viewModel.toggleExpanded() self.tableView.reloadRows(at: [indexPath], with: .automatic) })
内容的提问来源于stack exchange,提问作者KDBartleby
相关产品推荐
相关产品推荐

