SwiftUI ObjectBinding 不会使用 combine 接收来自可绑定对象的 didchange 更新

Posted

技术标签:

【中文标题】SwiftUI ObjectBinding 不会使用 combine 接收来自可绑定对象的 didchange 更新【英文标题】:SwiftUI ObjectBinding won't receive didchange update from bindable object using combine 【发布时间】:2019-07-16 10:11:35 【问题描述】:

我正在测试 Combine 框架并使用 BindableObject 作为通知中心,以便在 SwiftUI ContentView 中的多个视图之间传递数据。

其中一个视图是表格。我单击一行,在打印检查点中检测到值,因此可绑定对象接收更新。

问题是,新字符串没有在 ContentView 上广播到接收端。

我是新手。

带有表格视图的视图控制器.swift(广播):

import SwiftUI
import Combine

final public class NewestString: BindableObject 

    public var didChange = PassthroughSubject<NewestString, Never>()

    var newstring: String 
        didSet 
            didChange.send(self)
            print("Newstring: \(newstring)") //<-- Change detected
        
    

    init(newstring: String) 
        self.newstring = newstring
    

    public func update() 
        didChange.send(self)
        print("--Newstring: \(newstring)")

    





final class AViewController: UIViewController 

    @IBOutlet weak var someTableView: UITableView!
    var returnData = NewestString(newstring:"--")

    override func viewDidLoad() 
        super.viewDidLoad()

    



/// [.....] More extensions here

extension AViewController: UITableViewDelegate 

    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) 
        tableView.deselectRow(at: indexPath, animated: true)
        let completion = someResults[indexPath.row]

    //// [......] More code here

        self.returnData.newstring = "Test string" //<--- change caused 
        
    


主要内容视图(广播目的地):

import SwiftUI
import Combine

struct PrimaryButton: View 
    var title: String = "DefaultTitle"
    var body: some View 
        Button(action:  print("tapped") ) 
            Text(title)
        
   




struct MyMiniView: View 
    @State var aTitle: String = "InitialView"
    var body: some View 
        VStack
            PrimaryButton(title: aTitle)
            
    



struct ContentView: View 

    @State private var selection = 0
    @ObjectBinding var desiredString: NewestString = NewestString(newstring: "Elegir destino") // <-- Expected receiver

    var body: some View 

        TabbedView(selection: $selection)

            ZStack() 

                MyMiniView(aTitle: self.desiredString.newstring ?? "--")
               // expected end use of the change, that never happens
[...]


struct AView: UIViewControllerRepresentable 

    typealias UIViewControllerType = AViewController


    func makeUIViewController(context: UIViewControllerRepresentableContext<AView>) -> AViewController 


        return UIStoryboard(name: "MyStoryboard", bundle: nil).instantiateViewController(identifier: String(describing: AViewController.self)) as! AViewController

    

    func updateUIViewController(_ uiViewController: AViewController, context: UIViewControllerRepresentableContext<AView>) 
        //
    


它编译、运行并打印更改,但 MyMiniView 的 PrimaryButton 没有更新。

【问题讨论】:

【参考方案1】:

我找不到您在哪里使用 AViewController 的实例,但问题在于您使用了可绑定对象 NewestString 的多个实例。

ContentView 作为NewestString 的实例,每次更新都会触发视图重新加载。

struct ContentView: View 

    @State private var selection = 0

    // First instance is here
    @ObjectBinding var desiredString: NewestString = NewestString(newstring: "Elegir destino") // <-- Expected receiver

NewestString 的第二个实例在 AViewController 中,您实际修改了它。但是,由于它不是 NewestString(在内容视图中实际声明的那个)的同一个实例,因此修改它不会触发视图重新加载。

final class AViewController: UIViewController 

    @IBOutlet weak var someTableView: UITableView!
    // The second instance is here
    var returnData = NewestString(newstring:"--")

要解决这个问题,您需要找到一种方法将在您的ContentView 中创建的NewestString 实例“转发”到视图控制器。

编辑:找到一种将ObjectBinding 的实例传递给视图控制器的方法:

当您使用 SwiftUI 将视图添加到层​​次结构中时,您需要传递要从视图控制器访问的值的 Binding

struct ContentView: View 
    @ObjectBinding var desiredString = NewestString(newstring: "Hello")

    var body: some View 
        VStack 
            AView(binding: desiredString[\.newstring])
            Text(desiredString.newstring)
        
    

带有键路径的subscript 将生成给定属性的Binding

protocol BindableObject 
    subscript<T>(keyPath: ReferenceWritableKeyPath<Self, T>) -> > Binding<T>  get 

在视图控制器包装器 (UIViewControllerRepresentable) 中,您需要将给定的 Binding 转发到实际的视图控制器实例。

struct AView: UIViewControllerRepresentable 
    typealias UIViewControllerType = AViewController

    var binding: Binding<String>

    func makeUIViewController(context: UIViewControllerRepresentableContext<AView>) -> AViewController 
        let controller = AViewController()
        controller.stringBinding = binding // forward the binding object
        return controller
    

    func updateUIViewController(_ uiViewController: AViewController, context: UIViewControllerRepresentableContext<AView>) 

    

然后,在您的视图控制器中,您可以使用绑定来更新您的值(使用 .value 属性):

final class AViewController: UIViewController 
    var stringBinding: Binding<String>!

    override func viewDidLoad() 
        super.viewDidLoad()
        stringBinding.value = "Hello world !!"
    

当调用视图控制器的viewDidLoad 时,desiredString(在ContentView 中)将更新为“Hello world !!”,就像显示的文本(Text(desiredString.newstring))一样。

【讨论】:

谢谢@rraphael。我可以看到你暴露的问题。我添加了 UIViewControllerRepresentable 代码,让 AView 栩栩如生。 UIView 被包装了,我不知道如何传递初始化的 ObjectBinding @jpelayo 找到了一种将ObjectBinding 传递给包装好的UIViewController 的方法。请参阅答案的“编辑”部分。

以上是关于SwiftUI ObjectBinding 不会使用 combine 接收来自可绑定对象的 didchange 更新的主要内容,如果未能解决你的问题,请参考以下文章

SwiftUI:动画更改依赖于@ObjectBinding

可以直接使用 Publisher 作为 SwiftUI 中的 @ObjectBinding 属性吗?

结合 SwiftUI 远程获取数据 - ObjectBinding 不更新视图

苹果应该通过视图传递@ObjectBinding 是啥意思?

@BindableObject 对 didChange.send() 的异步调用不会使其视图无效(并且永远不会更新)

当内容适合滚动视图边界时,SwiftUI ScrollView 不会使内容居中