如何绑定 SwiftUI 和 UIViewController 行为

Bru*_*ero 5 uikit ios swift swiftui combine

我有一个带有 UIViewControllers 的 UIKit 项目,我想从我的 ViewController 中展示一个基于 SwiftUI 构建的操作表。我需要将动作表的出现和消失绑定回视图控制器,使视图控制器能够被解除(并且显示动画只发生在 viewDidAppear 上,以避免在使用时发生一些奇怪的动画行为.onAppear)。这是一个代码示例,说明我希望绑定如何工作以及它如何不按我的预期工作:

import UIKit
import SwiftUI

class ViewController: UIViewController {
    let button = UIButton(type: .system)
    var show = true
    lazy var isShowing: Binding<Bool> = .init {
        self.show
    } set: { show in
        // This code gets called
        self.show = show
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        
        view.backgroundColor = .white
        button.setTitle("TAP THIS BUTTON", for: .normal)
        view.addSubview(button)
        button.translatesAutoresizingMaskIntoConstraints = false
        button.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
        button.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
        button.addTarget(self, action: #selector(tapped), for: .touchUpInside)
    }
    
    @objc private func tapped() {
        let vc = UIHostingController(rootView: BindingProblemView(testBinding: isShowing))
        vc.modalPresentationStyle = .overCurrentContext
        present(vc, animated: false)
        
        DispatchQueue.main.asyncAfter(deadline: .now() + 5) { [self] in
            isShowing.wrappedValue.toggle()
            isShowing.update()
        }
    }
}

struct BindingProblemView: View {
    @Binding var testBinding: Bool
    @State var state = "ON"
    
    var body: some View {
        ZStack {
            if testBinding {
                Color.red.ignoresSafeArea().padding(0)
            } else {
                Color.green.ignoresSafeArea().padding(0)
            }
            
            Button("Test Binding is \(state)") {
                testBinding.toggle()
            }.onChange(of: testBinding, perform: { value in
                // This code never gets called
                state = testBinding ? "ON" : "OFF"
            })
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

发生的情况是在我设置绑定值onChange后永远不会被调用。我只是完全滥用新的组合运算符吗?viewDidAppeartrue

Geo*_*e_E 2

您可以通过 s 传递数据ObservableObject,而不是使用Bindings。这里的想法是ViewController引用一个PassedData实例,该实例被传递到 SwiftUI 视图,该视图接收对象的更改,因为它是一个@ObservedObject.

现在可以使用了,因此您可以单击原始按钮来显示 SwiftUI 视图。然后,该视图中的按钮会切换passedData.isShowing以更改背景颜色。由于这是一个类实例,因此ViewController也可以访问该值。例如,isShowing也在 5 秒后切换以显示可以从tapped()更改值。ViewController BindingProblemView

尽管不再需要它,但onChange(of:perform:)仍然会触发。

代码:

class PassedData: ObservableObject {
    @Published var isShowing = true
}
Run Code Online (Sandbox Code Playgroud)
class ViewController: UIViewController {
    let button = UIButton(type: .system)
    let passedData = PassedData()

    override func viewDidLoad() {
        super.viewDidLoad()

        view.backgroundColor = .white
        button.setTitle("TAP THIS BUTTON", for: .normal)
        view.addSubview(button)
        button.translatesAutoresizingMaskIntoConstraints = false
        button.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
        button.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
        button.addTarget(self, action: #selector(tapped), for: .touchUpInside)
    }

    @objc private func tapped() {
        let newView = BindingProblemView(passedData: passedData)
        let vc = UIHostingController(rootView: newView)
        vc.modalPresentationStyle = .overCurrentContext
        present(vc, animated: false)

        // Example of toggling from in view controller
        DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
            self.passedData.isShowing.toggle()
        }
    }
}
Run Code Online (Sandbox Code Playgroud)
struct BindingProblemView: View {
    @ObservedObject var passedData: PassedData

    var body: some View {
        ZStack {
            if passedData.isShowing {
                Color.red.ignoresSafeArea().padding(0)
            } else {
                Color.green.ignoresSafeArea().padding(0)
            }

            Button("Test Binding is \(passedData.isShowing ? "ON" : "OFF")") {
                passedData.isShowing.toggle()
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

结果:

结果