Search code examples
swiftuikitswiftui

How to use a SwiftUI view in place of table view cell


How can I use a SwiftUI view struct in place of a traditional cell and xib in a UITableViewController?

import UIKit
import SwiftUI

class MasterViewController: UITableViewController {

    var detailViewController: DetailViewController? = nil
    var objects = [Any]()

    // MARK: - View Lifecycle
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
        navigationItem.title = "Table View"

        //...
    }



    // MARK: - Table View Methods

    override func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return objects.count
    }

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

        let cell = tableView.dequeueReusableCell(MySwiftUIView())

        // ...
        return cell
    }
} ...

The issue is obvious in that UIHostedController SwiftUI view is not a table cell, but how could I use it like one?


Solution

  • Thanks for answering your own question here. Your solution helped me make a generic HostingTableViewCell class. I'll post it here if anyone finds this question on Google like I did.

    import SwiftUI
    
    class HostingTableViewCell<Content: View>: UITableViewCell {
    
        private weak var controller: UIHostingController<Content>?
    
        func host(_ view: Content, parent: UIViewController) {
            if let controller = controller {
                controller.rootView = view
                controller.view.layoutIfNeeded()
            } else {
                let swiftUICellViewController = UIHostingController(rootView: view)
                controller = swiftUICellViewController
                swiftUICellViewController.view.backgroundColor = .clear
                
                layoutIfNeeded()
                
                parent.addChild(swiftUICellViewController)
                contentView.addSubview(swiftUICellViewController.view)
                swiftUICellViewController.view.translatesAutoresizingMaskIntoConstraints = false
                contentView.addConstraint(NSLayoutConstraint(item: swiftUICellViewController.view!, attribute: NSLayoutConstraint.Attribute.leading, relatedBy: NSLayoutConstraint.Relation.equal, toItem: contentView, attribute: NSLayoutConstraint.Attribute.leading, multiplier: 1.0, constant: 0.0))
                contentView.addConstraint(NSLayoutConstraint(item: swiftUICellViewController.view!, attribute: NSLayoutConstraint.Attribute.trailing, relatedBy: NSLayoutConstraint.Relation.equal, toItem: contentView, attribute: NSLayoutConstraint.Attribute.trailing, multiplier: 1.0, constant: 0.0))
                contentView.addConstraint(NSLayoutConstraint(item: swiftUICellViewController.view!, attribute: NSLayoutConstraint.Attribute.top, relatedBy: NSLayoutConstraint.Relation.equal, toItem: contentView, attribute: NSLayoutConstraint.Attribute.top, multiplier: 1.0, constant: 0.0))
                contentView.addConstraint(NSLayoutConstraint(item: swiftUICellViewController.view!, attribute: NSLayoutConstraint.Attribute.bottom, relatedBy: NSLayoutConstraint.Relation.equal, toItem: contentView, attribute: NSLayoutConstraint.Attribute.bottom, multiplier: 1.0, constant: 0.0))
            
                swiftUICellViewController.didMove(toParent: parent)
                swiftUICellViewController.view.layoutIfNeeded()
            }
        }
    }
    

    In your UITableViewController:

    override func viewDidLoad() {
        super.viewDidLoad()
        tableView.register(HostingTableViewCell<Text>.self, forCellReuseIdentifier: "textCell")
    }
    
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "textCell") as! HostingTableViewCell<Text>
        cell.host(Text("Yay!"), parent: self)
        return cell
    }
    

    Might turn this into a package if people seem to use it.