如何绑定 SwiftUI 和 UIViewController 行为

How to bind SwiftUI and UIViewController behavior

我有一个带有 UIViewControllers 的 UIKit 项目,我想展示一个基于我的 ViewController 的 SwiftUI 构建的动作 sheet。我需要将动作 sheet 的出现和消失绑定回视图控制器,使视图控制器能够被关闭(并且显示动画只发生在 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"
            })
        }
    }
}

当我设置绑定值 true 时,onChange 从未在 viewDidAppear 之后被调用。我是否完全滥用了新的组合运算符?

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

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

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

代码:

class PassedData: ObservableObject {
    @Published var isShowing = true
}
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()
        }
    }
}
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()
            }
        }
    }
}

结果: