I needed to do a quick proof-of-concept for a project and instead of dealing with UITableViews (whose API looks so archaic now, you need both a datasource and a delegate, and a separate piece of state to hold the items for rows) and UIKit in general, I decided to try out SwiftUI for the first time to build a small piece of the PoC UI. Specifically, I needed to embed a SwiftUI view in a storyboard and be able to update its state. I’ve found a working solution after a lot of searching online because there were different pieces in different places. Here’s a short post and a sample repository on how to do that.
Static counter
As an example for this post, I implemented a primitive counter here (later I added support for a missing counter, but it doesn’t really matter):
Then I added a UIButton and a hosting controller to the storyboard. To display the CounterView there, the storyboard needs to know what controller to load, but you can’t use a UIHostingController<CounterView> in the storyboard directly; instead I had to use a new CounterHostingController, which is a dummy controller for now because it doesn’t do much:
You need to capture the storyboard’s hosting controller in the ViewController first. It’s done in a funky, non-type-safe way by getting the instance from prepare(for:sender:) after checking for the correct segue (oh those ugly implicitly-unwrapped optionals, but that’s the life with UIViewControllers):
Finally we reach the step of updating the embedded counter. CounterHostingController now stores CounterView.State as a private property that it can update on request:
Now the application starts up with “No counter”, clicking the Increment button updates it to “Counter: 0”, then “Counter: 1”, and so on.
The SwiftUI experience was fine in general, quick previews are great, even though Xcode was failing with previews all the time: “Automatic preview updating disabled” and some mysterious build errors. The project where I implemented this initially also had issues with previews when my SwiftUI file imported some models from another target, but Xcode previews did not like that at all, so I had to extract this one SwiftUI view into its own target and move the necessary models there too.
Sources
Here’s a list of articles I’ve used to figure it out:
How do I update a SwiftUI View in UIKit when value changes? has ideas with NotificationCenter (it’s an overkill to use a global notification system), @EnvironmentObject (doesn’t feel right to me, the state isn’t really an environment) and finally ObservableObject.
Communicating state changes between UIKit and SwiftUI has a different take of putting a @Published property into a view controller and passing that view controller into a SwiftUI view — I don’t like this because the view knows too much about a view controller, this is an inverse dependency.
SwiftUI and UIKit interoperability – Part 2 also has this solution, but it created a UIHostingController directly in code, the SwiftUI view observed a view model and I feel that example is more realistic and thus more complicated.