You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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)
    }
}

备选方案:手动计算行高

如果自动布局方案无效,可根据展开状态手动计算行高:

  1. 在BillingTileGridViewModel中添加展开状态属性:
class BillingTileGridViewModel: ObservableObject {
    @Published var isExpanded = false
    var allItems: [Item] = [] // 你的账户数据
    var displayedItems: [Item] {
        isExpanded ? allItems : Array(allItems.prefix(3))
    }
    
    func toggleExpanded() {
        isExpanded.toggle()
    }
}
  1. 在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
}
  1. 在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.07 15:25:10