marcprux
marcprux

Reputation: 10385

@Binding contained within UIViewControllerRepresentable does not get updated

I have a UIHostingController contained in a UIViewControllerRepresentable that holds a reference to a binding. When the binding changes, updateUIViewController is called, but the underlying view is not automatically re-rendered. How can I signal to the embedded UIHostingController that it needs to update its content?

Following is a simplified version of the scenario. Note that when the stepper changes, the first Text automatically updates, but the text contained within the PassthroughView UIViewControllerRepresentable does not automatically see its content get re-rendered.

import SwiftUI

struct ContentView: View {
    @State var number = 99

    var body: some View {
        VStack {
            Stepper("Stepper", value: $number)
            Text("Direct Value: \(number)")
            PassthroughView {
                Text("Passthrough Value: \(number)")
            }
            Spacer()
        }.font(.headline)
    }
}

struct PassthroughView<V: View> : UIViewControllerRepresentable {
    typealias UIViewControllerType = UIHostingController<V>
    let content: V

    init(@ViewBuilder _ content: () -> V) {
        self.content = content()
    }

    func makeUIViewController(context: UIViewControllerRepresentableContext<PassthroughView<V>>) -> UIViewControllerType {
        UIViewControllerType(rootView: content)
    }

    func updateUIViewController(_ controller: UIViewControllerType, context: UIViewControllerRepresentableContext<PassthroughView<V>>) {
        // this is called when the binding changes;
        // how to tell the UIHostingController to re-render?
    }
}

Upvotes: 3

Views: 1094

Answers (1)

krjw
krjw

Reputation: 4450

The following code will work as desired:

I am not sure if it is good practice since I am not very familiar with UIKit.

struct PassthroughView<V: View> : UIViewControllerRepresentable {
    typealias UIViewControllerType = UIHostingController<V>
    let content: V

    init(@ViewBuilder _ content: () -> V) {
        self.content = content()
    }

    func makeUIViewController(context: UIViewControllerRepresentableContext<PassthroughView<V>>) -> UIViewControllerType {
        UIViewControllerType(rootView: content)
    }

    func updateUIViewController(_ controller: UIViewControllerType, context: UIViewControllerRepresentableContext<PassthroughView<V>>) {
        // this is called when the binding changes;
        // how to tell the UIHostingController to re-render?
        controller.rootView = content
    }
}

struct ContentView: View {

    @State var number = 99

    var body: some View {
        VStack {
            Stepper("Stepper", value: $number)
            Text("Direct Value: \(number)")
            PassthroughView {
                Text("Passthrough Value: \(number)")
            }
            Spacer()
        }.font(.headline)
    }

}

I hope this helps!

Upvotes: 4

Related Questions