iOS技术问题:在UIKit的UITableView中复用SwiftUI视图时出现UI变形
UITableViewCell复用SwiftUI视图时出现UI变形的解决方案
嘿,我一眼就看出问题出在哪了——你踩了UITableView复用机制和重复添加视图的坑!
每次给title赋值的时候,你都会新建一个UIHostingController,然后把它的view直接加到cell的contentView里。但UITableView的cell是会被复用的啊!当cell滑出屏幕再滑回来时,旧的hostingView根本没被移除,新的又叠加上去,多个SwiftUI视图挤在一起,可不就变形了嘛。
给你两种靠谱的解决方案,按需选:
方案一:复用前清理旧的HostingView
这个方法简单直接,每次更新内容前先把之前的hostingView删掉,再添加新的。我们给cell加个属性存当前的hostingView,方便管理:
class TestCell: UITableViewCell { // 保存当前的hostingView引用,方便后续移除 private var currentHostingView: UIView? var title: String? { didSet { // 先把旧的视图删掉,避免叠加 currentHostingView?.removeFromSuperview() guard let title = title else { return } let hostingController = UIHostingController(rootView: TestView(text: title)) let hostingView = hostingController.view! hostingView.translatesAutoresizingMaskIntoConstraints = false contentView.addSubview(hostingView) // 更新当前视图的引用 currentHostingView = hostingView // 约束要绑到contentView上,别绑cell本身! NSLayoutConstraint.activate([ hostingView.topAnchor.constraint(equalTo: contentView.topAnchor), hostingView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor), hostingView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor), hostingView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor) ]) } } override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } // 加个保险:cell准备复用的时候彻底重置状态 override func prepareForReuse() { super.prepareForReuse() currentHostingView?.removeFromSuperview() currentHostingView = nil title = nil } }
方案二:只创建一次HostingView,通过Binding更新内容
如果你的SwiftUI视图比较复杂,频繁创建销毁会有性能损耗,那可以用这种方法——只初始化一次hostingView,通过绑定值来更新内容:
首先修改你的SwiftUI视图,用@Binding接收文本:
struct TestView: View { @Binding var text: String var body: some View { Text(text) // 这里放你原来的SwiftUI布局 } }
然后修改TestCell:
class TestCell: UITableViewCell { private var textBinding = Binding<String>(get: { "" }, set: { _ in }) private let hostingController: UIHostingController<TestView> var title: String? { didSet { textBinding.wrappedValue = title ?? "" } } override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { // 初始化绑定和hostingController var currentText = "" textBinding = Binding<String>( get: { currentText }, set: { currentText = $0 } ) hostingController = UIHostingController(rootView: TestView(text: textBinding)) super.init(style: style, reuseIdentifier: reuseIdentifier) let hostingView = hostingController.view! hostingView.translatesAutoresizingMaskIntoConstraints = false contentView.addSubview(hostingView) NSLayoutConstraint.activate([ hostingView.topAnchor.constraint(equalTo: contentView.topAnchor), hostingView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor), hostingView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor), hostingView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor) ]) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func prepareForReuse() { super.prepareForReuse() textBinding.wrappedValue = "" } }
小提醒
- 约束一定要绑到
contentView上,而不是cell本身,这是UITableViewCell布局的最佳实践,能避免很多奇怪的布局问题。 prepareForReuse方法一定要实现,复用前重置状态,彻底避免旧数据残留。
内容的提问来源于stack exchange,提问作者Koh
相关产品推荐
相关产品推荐

