如何绑定 SwiftUI 和 UIViewController 行为

Posted

技术标签:

【中文标题】如何绑定 SwiftUI 和 UIViewController 行为【英文标题】:How to bind SwiftUI and UIViewController behavior 【发布时间】:2021-08-05 16:40:17 【问题描述】:

我有一个带有 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"
            )
        
    

当我设置绑定值true 时,onChange 永远不会在viewDidAppear 之后被调用。我只是在完全滥用新的组合运算符吗?

【问题讨论】:

我无法让示例在屏幕上显示任何内容,这似乎是CustomActionSheet 的问题,您能否提供一个要点/repo 以便我们直接尝试? @George 这是指向要点的链接gist.github.com/vargero/7a018471856be3be06ef45910f6203df 我已经删除了圆角,因为这是自定义代码,但代码现在应该可以工作了。因为它没有显示任何内容,只有当您在 .onAppear 上调用 showActionSheet 即使在一个完全空白的 UIKit 项目中,屏幕上也没有任何显示(代码也来自 gist) @George 我用所有需要的类更新了 Gist。当有一个以 ViewController 开头的新 Storyboard 项目时,此方法有效 【参考方案1】:

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

现在可以使用了,因此您可以单击原始按钮来呈现 SwiftUI 视图。然后,该视图中的按钮切换passedData.isShowing,这会改变背景颜色。由于这是一个类实例,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()
            
        
    

结果:

【讨论】:

以上是关于如何绑定 SwiftUI 和 UIViewController 行为的主要内容,如果未能解决你的问题,请参考以下文章

SwiftUI - 我们如何重新绑定绑定的可选参数?

如何使用 SwiftUI 将数组绑定到列表

如何在 SwiftUI 中使用可观察对象并与 NavigationLink 绑定?

SwiftUI:绑定到@AppStorage

如何在 SwiftUI 中使用枚举绑定数据?

如何在 SwiftUI 的视图控制器中实现数据绑定?