Set property on ViewController in UIViewControllerRepresentable

Viewed 269

How can I change a property on a UIViewController presented via. a UIViewControllerRepresentable ?

Sample code of how I would expect it to work, however it doesn't. How can I make it work?

(color is just a example, please don't focus on that)

class MyViewController: UIViewController {
    var color: UIColor? = nil {
        didSet {
            guard isViewLoaded else { return }
            view.layer.backgroundColor = color?.cgColor
        }
    }

    override func viewDidLoad() {
        view.layer.backgroundColor = color?.cgColor
    }
}

struct MyView: UIViewControllerRepresentable {
    @State private var color: UIColor?

    func makeUIViewController(context: UIViewControllerRepresentableContext<MyView>) -> MyViewController {
        let viewController = MyViewController()
        viewController.color = color // always nil?
        return viewController
    }


    func updateUIViewController(_ uiViewController: MyViewController,
                                context: UIViewControllerRepresentableContext<MyView>) {
        uiViewController.color = color // always nil?
    }
}

extension MyView {
    func color(_ color: UIColor) -> MyView {
        self.color = color // does nothing?
        return self
    }
}

struct ContentView: View {
    var body: some View {
        MyView()
            .color(.magenta)
    }
}
2 Answers

Here is possible approach (if you expect that color can be modified externally, as it is seen). Tested with Xcode 11.4 / iOS 13.4

struct MyView: UIViewControllerRepresentable {
    @Binding var color: UIColor?

    func makeUIViewController(context: UIViewControllerRepresentableContext<MyView>) -> MyViewController {
        let viewController = MyViewController()
        viewController.color = color // always nil?
        return viewController
    }


    func updateUIViewController(_ uiViewController: MyViewController,
                                context: UIViewControllerRepresentableContext<MyView>) {
        uiViewController.color = color // always nil?
    }
}

struct ContentView: View {
    @State private var color: UIColor? = .magenta

    var body: some View {
        MyView(color: $color)
//        MyView(color: .constant(.magenta)) // alternate usage
    }
}

Another solution based on idea from Asperi's answer:

struct MyView: UIViewControllerRepresentable {
    private class State: ObservableObject {
        var color: UIColor?
    }

    @Binding private var state: State

    init() {
        _state = .constant(State())
    }

    func makeUIViewController(context: UIViewControllerRepresentableContext<MyView>) -> MyViewController {
        return MyViewController()
    }

    func updateUIViewController(_ uiViewController: MyViewController,
                                context: UIViewControllerRepresentableContext<MyView>) {
        uiViewController.color = state.color
    }
}

extension MyView {
    func color(_ color: UIColor) -> MyView {
        self.state.color = color
        return self
    }
}

Or a even simpler version, we just use @Binding for the wrapped ViewController directly

struct MyView: UIViewControllerRepresentable {
    @Binding private var viewController: MyViewController

    init() {
        _viewController = .constant(MyViewController())
    }

    func makeUIViewController(context: UIViewControllerRepresentableContext<MyView>) -> MyViewController {
        return viewController
    }

    func updateUIViewController(_ uiViewController: MyViewController,
                                context: UIViewControllerRepresentableContext<MyView>) {
    }
}

extension MyView {
    func color(_ color: UIColor) -> MyView {
        self.viewController.color = color
        return self
    }
}
Related