简体   繁体   中英

Pass @Published properties from view controllers to SwiftUI

Suppose you have a legacy view controller that I'd like to use with SwiftUI. The view controller has one @Published property that contains it current state:

class LegacyViewController: UIViewController {
    enum State {
        case opened
        case closed
        case halfOpened
    }
    
    @Published var state: State
    
    override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
        self.state = .closed
        super.init(nibName: nil, bundle: nil)
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // state is changed after some time
    }
}

Ideally, I'd like to use it with SwiftUI like this:

struct ContentView: View {
    @State var state: LegacyViewController.State
    
    var body: some View {
        VCWrapper(state: $state).overlay (
            Text("\(state)")
        )
    }
}

which would mean that I need to implement UIViewControllerRepresentable protocol:

struct VCWrapper: UIViewControllerRepresentable {
    @Binding var state: LegacyViewController.State
    
    func makeUIViewController(context: Context) -> LegacyViewController {
        let vc = LegacyViewController(nibName: nil, bundle: nil)
        /// where to perform the actual binding?
        return vc
    }
    
    func updateUIViewController(_ uiViewController: LegacyViewController, context: Context) {
        
    }
}

However, I'm having trouble figuring out where to do the actual binding from state property of the LegacyViewController to the state property exposed by VCWrapper . If LegacyViewController exposed a delegate, I could implement the binding through the Coordinator object, but I'm not so sure how to do this considering that I don't use a delegate object?

Here is possible solution - use Combine . Tested with Xcode 12 / iOS 14.

import Combine

struct VCWrapper: UIViewControllerRepresentable {
    @Binding var state: LegacyViewController.State
    
    func makeUIViewController(context: Context) -> LegacyViewController {
        let vc = LegacyViewController(nibName: nil, bundle: nil)

        // subscribe to controller state publisher and update bound
        // external state
        context.coordinator.cancelable = vc.$state
            .sink {
               DispatchQueue.main.async {
                  _state.wrappedValue = $0
               }
            }

        return vc
    }
    
    func updateUIViewController(_ uiViewController: LegacyViewController, context: Context) {
    }
    
    func makeCoordinator() -> Coordinator {
        Coordinator()
    }
    
    class Coordinator {
        var cancelable: AnyCancellable?
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM