Home > Mobile >  UIViewControllerRepresentable not working correctly when wrapped in AnyView
UIViewControllerRepresentable not working correctly when wrapped in AnyView

Time:07-28

I've created this very simple wrapper with UIViewControllerRepresentable:

struct ViewControllerWrapperView: UIViewControllerRepresentable {
    let controller: UIViewController

    func makeUIViewController(context: UIViewControllerRepresentableContext<ViewControllerWrapperView>) -> UIViewController {
        return controller
    }

    func updateUIViewController(_ uiViewController: UIViewController, context: UIViewControllerRepresentableContext<ViewControllerWrapperView>) {}
}

Using it to display ViewControllers in a SwiftUI view works fine:

struct ContentView: View {
    @State private var viewSwitch: Bool = true

    let blueView: ViewControllerWrapperView = {
        let blueViewController = UIViewController()
        blueViewController.view.backgroundColor = .blue
        return ViewControllerWrapperView(controller: blueViewController)
    }()

    let redView: ViewControllerWrapperView = {
        let redViewController = UIViewController()
        redViewController.view.backgroundColor = .red
        return ViewControllerWrapperView(controller: redViewController)
    }()

    var body: some View {
        Button("Switch") { viewSwitch.toggle() }

        if viewSwitch {
            blueView
        } else {
            redView
        }
    }
}

But as soon as I wrap the ViewControllerWrapperViews in AnyView they stop working properly:

struct ContentView: View {
    @State private var viewSwitch: Bool = true

    let blueView: AnyView = {
        let blueViewController = UIViewController()
        blueViewController.view.backgroundColor = .blue
        return AnyView(ViewControllerWrapperView(controller: blueViewController))
    }()

    let redView: AnyView = {
        let redViewController = UIViewController()
        redViewController.view.backgroundColor = .red
        return AnyView(ViewControllerWrapperView(controller: redViewController))
    }()

    var body: some View {
        Button("Switch") { viewSwitch.toggle() }

        if viewSwitch {
            blueView
        } else {
            redView
        }
    }
}

With AnyView the views don't switch when the button is tapped. Looking a bit deeper into it, I discovered the following:

For both scenarios when first displaying the view:

  1. makeUIViewController is called on the ViewControllerWrapperView for the blue view.
  2. updateUIViewController is called on the ViewControllerWrapperView for the blue view and the parameter uiViewController is the blue ViewController.

Now without AnyView when the switch button is tapped the life cycle of the UIViewControllerRepresentable is executed as supposed to:

  1. updateUIViewController is called on the ViewControllerWrapperView for the blue view and the parameter uiViewController is the blue ViewController.
  2. makeUIViewController is called on the ViewControllerWrapperView for the red view.
  3. updateUIViewController is called on the ViewControllerWrapperView for the red view and the parameter uiViewController is the red ViewController.
  4. dismantleUIViewController is called and the parameter uiViewController is the blue view.

But with AnyView when tapping the switch button, the only thing that happens is:

  1. updateUIViewController is called on the ViewControllerWrapperView for the RED view and the parameter uiViewController is the BLUE ViewController.

Am I missing something or is this a bug in SwiftUI?

CodePudding user response:

Am I missing something or is this a bug in SwiftUI?

It is not a bug, AnyView erases type differences, so rendering body SwiftUI engine sees only AnyView replaced with AnyView which are equal, so engine does not replace existed view, but just refreshes it (because state has been changed) that results in updateUIViewController call. All is as expected.

And that's why usage of AnyView should be very very careful and meaningful with clear understanding of process and consequences.

  • Related