swiftuiswiftui-navigationlinkuiviewcontrollerrepresentable

Multiple NavigationLinks leading to UIViewControllerRepresentable destination ends up with blank screen


I have found a minimal SwiftUI app that exhibits a bug:

class HelloWorldVC: UIViewController {
    override func loadView() {
        super.loadView()
        let label = UILabel()
        label.text = "Hello, World"
        view.addSubview(label)

        label.translatesAutoresizingMaskIntoConstraints = false
        label.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor).isActive = true
        label.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor).isActive = true
        label.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true
        label.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor).isActive = true
    }
}

struct ViewControllerContainer: UIViewControllerRepresentable {
    let vc: UIViewController

    func makeUIViewController(context: Context) -> some UIViewController { vc }
    func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {}
}

struct ContentView: View {
    var body: some View {
        NavigationView {

            // Works:
            //NavigationLink("View UIKit VC", destination: ViewControllerContainer(vc: HelloWorldVC()))

            // Only loads the UIKit view once. It's blank on subsequent tries.
            NavigationLink(
                "Detail Screen",
                destination: NavigationLink(
                    "View UIKit VC",
                    destination: ViewControllerContainer(vc: HelloWorldVC())
                )
            )
        }
    }
}

Steps to reproduce:

Expected:

Actual:

Blank screen seen from 2nd try on

Note: In the commented out code, it works properly if you only have one layer deep of NavigationLink.


Solution

  • You are not working with UIViewControllerRepresentable correctly. You need to create a new view controller inside makeUIViewController, reusing it breaks the view controller lifecycle in your case.

    The UIViewControllerRepresentable properties can be passed to the view controller when you create or update it, as follows:

    struct ViewControllerContainer: UIViewControllerRepresentable {
        let props: Int
    
        func makeUIViewController(context: Context) -> some UIViewController { 
            HelloWorldVC(props: props) 
        }
        func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {
            uiViewController.props = props
        }
    }