It is best to stick to either one framework, and not mix. But if you do need to.. here is how you can communicate between the 2 frameworks, bi-directionally.
Firstly, you can embed SwiftUI in a UIViewController by creating UIHostingController
with any SwiftUI View.
let vc = UIHostingController(rootView: Text("Any SwiftUI View"))
present(vc, animated: true)
Naive solution
Naively, I thought we can have a State
in the UIViewController, while the SwiftUI view has a Binding
, and then simply mutate the state.
But I run into the following errors:
Accessing State’s value outside of being installed on a View. This will result in a constant Binding of the initial value and will not update.
Turns out, you can never use State
attribute in UIKit, because it is a property wrapper for SwiftUI to manage.
It is the same if we declare with StateObject
.
Accessing StateObject’s object without being installed on a View. This will create a new instance each time.
Turns out, we can simply declare without StateObject
attribute!
The solution
class ViewController {
var model = MyModel() // Just declare a normal property. Can't be @StateObject.
}
The SwiftUI view will have to be initialized with the observable model.
class MyModel: ObservableObject {
@Published var value: Int = 0
}
struct ContentView: View {
@ObservedObject var model: MyModel
var body: some View {
Text(model.value)
}
}
That’s it. The view controller can mutate the model, and SwiftUI view will observe the changes.
What about passing from SwiftUI to UIKit?
The model can be similarly mutated by SwiftUI too.
To observe in UIKit, do this in the view controller:
model.$value.sink { v in
print("Value is \(v)")
}.store(in: &cancellableSet)
Data flow is confusing. Mixing the 2 frameworks make it worse. Try not to mix 🤨