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

使用SwiftUI Hosting Cell时滚动出现单元格重叠问题求助

问题描述

我尝试通过Hosting Cell将SwiftUI视图作为UITableView的单元格视图,已设置UITableViewAutomaticDimension为单元格高度。但滚动时出现单元格重叠的情况,我猜测这可能与单元格复用有关,请问该如何解决这个问题?

相关代码

单元格获取方法

private func cellView(_ index: Int) -> UITableViewCell {
    guard let filterCell = tableView.dequeueReusableCell(withIdentifier: "HostingCell<CellView>") as? HostingCell<CellView>,
          let viewModel = viewModel.data else {
        return HostingCell<CellView>()
    }
    let cellViewModel = viewModel.viewModelForRadioButton(at: index, theme: theme)
    filterCell.set(rootView: FilterCellView(viewModel: cellViewModel, isSelected: viewModel.selectedIndex() == index), parentController: self)
    return filterCell
}

HostingCell实现

class HostingCell <Content: View>: UITableViewCell {

private let hostingController = UIHostingController<Content?>(rootView: nil)

override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
    super.init(style: style, reuseIdentifier: reuseIdentifier)
}

required init?(coder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
}

func set(rootView: Content,
         parentController: UIViewController,
         hostingControllerBackground: UIColor? = nil) {
    self.hostingController.rootView = rootView
    self.hostingController.view.backgroundColor = hostingControllerBackground
    self.hostingController.view.invalidateIntrinsicContentSize()

    let requiresControllerMove = hostingController.parent != parentController
    if requiresControllerMove {
        parentController.addChild(hostingController)
    }

    if !self.contentView.subviews.contains(hostingController.view) {
        self.contentView.addSubview(hostingController.view)
        hostingController.view.translatesAutoresizingMaskIntoConstraints = false
        hostingController.view.leadingAnchor.constraint(equalTo: self.contentView.leadingAnchor).isActive = true
        hostingController.view.trailingAnchor.constraint(equalTo: self.contentView.trailingAnchor).isActive = true
        hostingController.view.topAnchor.constraint(equalTo: self.contentView.topAnchor).isActive = true
        hostingController.view.bottomAnchor.constraint(equalTo: self.contentView.bottomAnchor).isActive = true
    }

    if requiresControllerMove {
        hostingController.didMove(toParent: parentController)
    }
}

问题截图

单元格重叠效果


解决方案

你的猜测正确,单元格重叠确实是复用机制下内容清理不彻底、布局刷新不及时导致的,以下是具体修复步骤:

1. 重写prepareForReuse清理旧内容

在HostingCell中添加该方法,重置hostingController的内容,避免复用旧的SwiftUI视图:

override func prepareForReuse() {
    super.prepareForReuse()
    hostingController.rootView = nil
    hostingController.view.invalidateIntrinsicContentSize()
    contentView.layoutIfNeeded()
}

2. 优化set方法的布局刷新逻辑

设置新的rootView后,强制触发多层布局更新,确保SwiftUI视图的高度被正确计算:

func set(rootView: Content,
         parentController: UIViewController,
         hostingControllerBackground: UIColor? = nil) {
    self.hostingController.rootView = rootView
    self.hostingController.view.backgroundColor = hostingControllerBackground
    
    // 先更新内容,再逐层刷新布局
    hostingController.view.invalidateIntrinsicContentSize()
    hostingController.view.layoutIfNeeded()
    contentView.layoutIfNeeded()

    let requiresControllerMove = hostingController.parent != parentController
    if requiresControllerMove {
        parentController.addChild(hostingController)
    }

    if !self.contentView.subviews.contains(hostingController.view) {
        self.contentView.addSubview(hostingController.view)
        hostingController.view.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            hostingController.view.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
            hostingController.view.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
            hostingController.view.topAnchor.constraint(equalTo: contentView.topAnchor),
            hostingController.view.bottomAnchor.constraint(equalTo: contentView.bottomAnchor)
        ])
    }

    if requiresControllerMove {
        hostingController.didMove(toParent: parentController)
    }
}

3. 修正单元格复用的初始化问题

当前代码中,dequeueReusableCell失败时直接初始化的单元格未设置复用标识符,会导致后续复用异常,修改为:

private func cellView(_ index: Int) -> UITableViewCell {
    let cellIdentifier = "HostingCell<CellView>"
    guard let viewModel = viewModel.data else {
        return UITableViewCell(style: .default, reuseIdentifier: cellIdentifier)
    }
    
    let filterCell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier) as? HostingCell<CellView> ?? HostingCell<CellView>(style: .default, reuseIdentifier: cellIdentifier)
    
    let cellViewModel = viewModel.viewModelForRadioButton(at: index, theme: theme)
    filterCell.set(rootView: FilterCellView(viewModel: cellViewModel, isSelected: viewModel.selectedIndex() == index), parentController: self)
    
    return filterCell
}

4. 完善TableView的高度配置

除了设置自动高度,还需指定合理的预估高度,帮助TableView减少滚动时的布局跳动:

// 在viewDidLoad或TableView初始化时设置
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 80 // 根据你的Cell内容设置一个接近的预估高度

内容的提问来源于stack exchange,提问作者Pratyush Kumar

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.13 21:15:48