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