SwiftUI Previews and Generic Types That Implement Objective-C Protocols

I have a project that uses UIKit. In this project, I have a need to create multiple table views that look the same but represent different contents. Sounds like a great use case for generics! Here’s a basic view controller that achieves this:

import UIKit

class GenericTableViewController<ItemType>: UIViewController, UITableViewDataSource, UITableViewDelegate {

    weak var tableView: UITableView?

    var items: [ItemType] = [] {
        didSet {
            tableView?.reloadData()
        }
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        let tableView = UITableView(frame: view.bounds)
        self.tableView = tableView
        view.addSubview(tableView)

        tableView.delegate = self
        tableView.dataSource = self
    }

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

    func tableView(_ tableView: UITableView,
                   numberOfRowsInSection section: Int) -> Int {
        guard section == 0 else { return 0 }

        return items.count
    }

    func tableView(_ tableView: UITableView,
                   cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = UITableViewCell(style: .default, reuseIdentifier: nil)

        cell.textLabel?.text = String(describing: items[indexPath.row])

        return cell
    }
    
}

Now, I’m also using @mattt’s excellent Gist for previewing view controllers. The relevant bit is here:

import SwiftUI

@available(iOS 13.0, *)
struct UIViewControllerPreview<ViewController: UIViewController>: UIViewControllerRepresentable {
    let viewController: ViewController

    init(_ builder: @escaping () -> ViewController) {
        viewController = builder()
    }

    // MARK: - UIViewControllerRepresentable

    func makeUIViewController(context: Context) -> ViewController {
        viewController
    }

    func updateUIViewController(_ uiViewController: ViewController,
                                context: Context) {
        return
    }
}

Either one of these works great independently. I can create a sample project with the view controller and use it to display multiple values. However, if I try to use them together, Xcode fails to build the preview with the following error:

extensions of generic classes cannot contain '@objc' members

I’m not sure where the extension comes in, but looking at the diagnostics it looks like my code is being replaced dynamically at runtime with versions that have a __preview__ prefix:

/Users/jeff/Library/Developer/Xcode/DerivedData/GenericViewController-gppxxshymxycwxafswamwxroqimy/Build/Intermediates.noindex/Previews/GenericViewController/Intermediates.noindex/GenericViewController.build/Debug-iphonesimulator/GenericViewController.build/Objects-normal/x86_64/GenericViewController.2.preview-thunk.swift:23:72: error: extensions of generic classes cannot contain '@objc' members
@_dynamicReplacement(for: tableView(_:cellForRowAt:)) private func __preview__tableView(_ tableView: UITableView,

This happens even if I adjust UIViewControllerPreview to not take a generic ViewController type and instead operate solely on UIViewController. Has anyone else run into this—and is there a way to prevent the SwiftUI preview system from dynamically replacing @objc methods like this in an extension?

3 Likes

Digging deeper, looking at the source file in the DerivedData file, we can see that the method is indeed overwritten by the preview but unchanged:

extension GenericTableViewController {
    @_dynamicReplacement(for: tableView(_:cellForRowAt:)) private func __preview__tableView(_ tableView: UITableView,
                   cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        #sourceLocation(file: "/Users/jeff/Desktop/GenericViewController/GenericViewController/GenericViewController.swift", line: 43)
        let cell = UITableViewCell(style: .default, reuseIdentifier: nil)

        cell.textLabel?.text = String(describing: items[indexPath.row])

        return cell
#sourceLocation()
    }
}