iosswiftswiftuiuihostingcontrollerswiftui-state

SwiftUI view is not updating when embedded into UIHostingController


I have the following simple SwiftUI view:

struct ContentView: View {
    var body: some View {
        Text(self.label)
    }

    @State var label = "Initial text."
}

This view is embedded into the UIKit view hierarchy using a UIHostingController:

let hostingController = UIHostingController(rootView: ContentView())

At the tap of a button, I'm trying to update the state variable of the SwiftUI view:

let action = UIAction(title: "Change Text") { _ in
    hostingController.rootView.label = "New text."
}
let button = UIButton(primaryAction: action)

Tapping on the button, its primary action is invoked, but the SwiftUI view does not change its text on the screen.

I tried keeping an explicit reference to the ContentView around, but that does not resolve the problem:

self.contentView.label = "New text." // not working either

The update to the text is, however, reflected on the screen if it occurs from within the SwiftUI view itself. The following code does make the label change its text when tapped on:

struct ContentView: View {
    var body: some View {
        Text(self.label)
            .onTapGesture {
                self.label = "Tapped."
            }
    }

    @State var label = "Initial text."
}

This gist demonstrates a minimum working example of the problem.

How can I manage to push updates to the SwiftUI view through the hosting controller?


Solution

  • The solution to this problem is hidden in a comment of this post.

    The state of the ContentView needs to be replaced by a custom observable object:

    struct ContentView: View {
        var body: some View {
            Text(self.state.label)
                .onTapGesture {
                    self.state.label = "Tapped."
                }
        }
    
        @StateObject var state = ContentViewState()
    }
    
    final class ContentViewState: ObservableObject {
        @Published var label = "Initial text."
    }
    

    This way, the updates will be reflected by the SwiftUI view.

    Please also see this gist for the working solution.