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 ViewControllerWrapperView
s 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:
makeUIViewController
is called on theViewControllerWrapperView
for the blue view.updateUIViewController
is called on theViewControllerWrapperView
for the blue view and the parameteruiViewController
is the blue ViewController.
Now without AnyView
when the switch button is tapped the life cycle of the UIViewControllerRepresentable
is executed as supposed to:
updateUIViewController
is called on theViewControllerWrapperView
for the blue view and the parameteruiViewController
is the blue ViewController.makeUIViewController
is called on theViewControllerWrapperView
for the red view.updateUIViewController
is called on theViewControllerWrapperView
for the red view and the parameteruiViewController
is the red ViewController.dismantleUIViewController
is called and the parameteruiViewController
is the blue view.
But with AnyView
when tapping the switch button, the only thing that happens is:
updateUIViewController
is called on theViewControllerWrapperView
for the RED view and the parameteruiViewController
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.