Skip to content

Instantly share code, notes, and snippets.

@rlnucci
Last active April 3, 2025 09:58
Show Gist options
  • Select an option

  • Save rlnucci/31f116763d001ec2a93d30473703e117 to your computer and use it in GitHub Desktop.

Select an option

Save rlnucci/31f116763d001ec2a93d30473703e117 to your computer and use it in GitHub Desktop.
import Foundation
import SwiftUI
// Implemented based on this article:
// https://noahgilmore.com/blog/swiftui-self-sizing-cells
// MARK: - HostingTableViewCell
public final class HostingTableViewCell<Content: View>: UITableViewCell {
private var hostingController = UIHostingController<Content?>(rootView: nil)
public override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
backgroundColor = .clear
contentView.backgroundColor = .clear
hostingController.view.backgroundColor = .clear
}
required init?(coder: NSCoder) {
fatalError("init(coder:) is not supported")
}
public override func prepareForReuse() {
super.prepareForReuse()
hostingController.view.removeFromSuperview()
hostingController = UIHostingController<Content?>(rootView: nil)
}
}
// MARK: Public Methods
public extension HostingTableViewCell {
func set(rootView: Content, parentViewController: UIViewController) {
hostingController = UIHostingController(rootView: rootView)
hostingController.view.invalidateIntrinsicContentSize()
let shouldMoveParentViewController = hostingController.parent != parentViewController
if shouldMoveParentViewController {
parentViewController.addChild(hostingController)
}
if hostingController.view.superview == nil {
hostingController.view.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(hostingController.view)
hostingController.view.constrainToSuperview()
}
if shouldMoveParentViewController {
hostingController.didMove(toParent: parentViewController)
}
}
}
import Foundation
import UIKit
import SwiftUI
// MARK: PaymentSuccessViewController
final class PaymentSuccessViewController: UIHostingController<PaymentSuccessView> {
init(viewModel: PaymentSuccessViewModelImpl) {
let rootView = PaymentSuccessView(viewModel: viewModel.eraseToAnyViewModel())
super.init(rootView: rootView)
self.navigationItem.hidesBackButton = true
viewModel.delegate = self
}
@objc required dynamic init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) is not supported")
}
}
extension PaymentSuccessViewController: PaymentSuccessViewModelDelegate {
func showAcountView() {
viewHierarchyNavigator?.presentAccount()
}
}
import UIKit
import SwiftUI
public struct TableCellWrapper<Cell>: UIViewRepresentable where Cell: UITableViewCell {
public typealias UIViewType = Cell
let cellType: Cell.Type
let configure: ((Cell) -> Void)?
public init(cellType: Cell.Type, configure: ((Cell) -> Void)? = nil) {
self.cellType = cellType
self.configure = configure
}
public func makeUIView(context: Context) -> Cell {
let bundle = Bundle(for: Cell.self)
let cell: Cell
if bundle.path(forResource: Cell.nibName, ofType: "nib") != nil {
let nib = UINib(nibName: Cell.nibName, bundle: bundle)
cell = nib.instantiate(withOwner: nil, options: nil).first as! Cell
} else {
cell = Cell()
}
configure?(cell)
return cell
}
public func updateUIView(_ uiView: Cell, context: Context) {
uiView.prepareForReuse()
configure?(uiView)
}
}
@ULazdins
Copy link

DO NOT reset hostingController each time the cell is reused. Learned that the hard way.

You need to have the same instance of UIHostingController, otherwise the contained SwiftUI hierarchy will be rendered from scratch instead of modifying the previous tree

Here is the updated code:

public final class HostingTableViewCell<Content: View>: UITableViewCell {
    private let hostingController = UIHostingController<Content?>(rootView: nil)

    ...

    public override func prepareForReuse() {
        super.prepareForReuse()
        hostingController.view.removeFromSuperview()
    }
}

public extension HostingTableViewCell {
    func set(rootView: Content, parentViewController: UIViewController) {
        hostingController.rootView = rootView

        ...
    }
}

@nonisolated
Copy link

nonisolated commented Mar 20, 2023

@ULazdins

public override func prepareForReuse() {
   super.prepareForReuse()
   hostingController.view.removeFromSuperview()
}

In my case, your code did not work properly. Sometimes, the reuse functionality did not work correctly.
However, if I add hostingController = UIHostingController<Content?>(rootView: nil), then everything works fine

@ulazdins-afs
Copy link

@betraying , can't check right now, but I believe that will again re-render all of the table. Ideally there should be a way to keep the existing hostingController instance around instead of creating a new one each time

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment