【问题标题】:SwiftUI - @State property not updatingSwiftUI - @State 属性未更新
【发布时间】:2021-01-06 12:14:21
【问题描述】:

我在使用 SwiftUI 时遇到了这个非常奇怪的问题/错误。在setupSubscription 方法中,我正在创建对subject 的订阅并将其插入到cancellables 集合中。然而,当我打印cancellables 的计数时,我得到了零。如果我只是在其中插入一个元素,集合怎么可能是空的? 这大概就是为什么当我点击按钮时没有调用handleValue 方法的原因。这是控制台的完整输出:

init
begin setupSubscription
setupSubscription subject sink: receive subscription: (CurrentValueSubject)
setupSubscription subject sink: request unlimited
setupSubscription subject sink: receive value: (initial value)
handleValue: 'initial value'
setupSubscription: cancellables.count: 0
setupSubscription subject sink: receive cancel
sent value: 'value 38'
cancellables.count: 0
sent value: 'value 73'
cancellables.count: 0
sent value: 'value 30'
cancellables.count: 0

我做错了什么?为什么我对subject 的订阅被取消了?为什么我点击按钮时没有调用handleValue

import SwiftUI
import Combine

struct Test: View {
    
    @State private var cancellables: Set<AnyCancellable> = []
    
    let subject = CurrentValueSubject<String, Never>("initial value")
    
    init() {
        print("init")
        self.setupSubscription()
    }
    
    var body: some View {
        VStack {
            Button(action: {
                let newValue = "value \(Int.random(in: 0...100))"
                self.subject.send(newValue)
                print("sent value: '\(newValue)'")
                print("cancellables.count:", cancellables.count)
            }, label: {
                Text("Tap Me")
            })
        }
    }
    
    func setupSubscription() {
        print("begin setupSubscription")
        
        let cancellable = self.subject
            .print("setupSubscription subject sink")
            .sink(receiveValue: handleValue(_:))
        
        self.cancellables.insert(cancellable)
        
        print("setupSubscription: cancellables.count:", cancellables.count) 
        // prints "setupSubscription: cancellables.count: 0"
    
    }
    
    
    func handleValue(_ value: String) {
        print("handleValue: '\(value)'")
    }
    
    
}

【问题讨论】:

    标签: swift swiftui combine


    【解决方案1】:

    您只是错误地使用了状态 - 它与视图相关,并且仅在呈现视图时才可用(准备后存储)(即在body 上下文中)。在 init 中还没有 state back-storage,所以你的cancellable 就消失了。

    这是可能的工作方法(但我建议将所有相关主题移动到单独的视图模型中)

    使用 Xcode 12 / iOS 14 测试

    struct Test: View {
    
        private var cancellable: AnyCancellable?
        private let subject = CurrentValueSubject<String, Never>("initial value")
    
        init() {
            cancellable = self.subject
                .print("setupSubscription subject sink")
                .sink(receiveValue: handleValue(_:))
        }
    
        var body: some View {
            VStack {
                Button(action: {
                    let newValue = "value \(Int.random(in: 0...100))"
                    self.subject.send(newValue)
                    print("sent value: '\(newValue)'")
                }, label: {
                    Text("Tap Me")
                })
            }
        }
    
        func handleValue(_ value: String) {
            print("handleValue: '\(value)'")
        }
    }
    

    【讨论】:

      【解决方案2】:

      你在这里做错了几件事。

      永远不要尝试在 swiftUI 结构中存储东西。每次您的视图更改时,它们都会失效并重新加载。这可能是您的订阅被取消的原因。

      对于这样的事情,您应该使用具有已发布属性的 ObservableObject 或 StateObject。当 ObservableObjects 或 StateObjects 发生变化时。像 @State 或 @Binding 一样重新加载包含它们的视图:

      // ObservedObjects have an implied objectWillChange publisher that causes swiftUI views to reload any time a published property changes. In essence they act like State or Binding variables.
      class ViewModel: ObservableObject {
          // Published properties ARE combine publishers
          @Published var subject: String = "initial value"
      }
      

      那么在你看来:

      @ObservedObject var viewModel: ViewModel = ViewModel()
      

      如果您确实需要使用发布商。或者,如果您需要在可观察对象属性发生更改时执行某些操作。您不需要使用 .sink。这主要用于使用 combine 的 UIKit 应用程序。 SwiftUI 有一个 .onReceive 视图修饰符可以做同样的事情。

      以下是我的上述建议付诸实践:

      struct Test: View {
      
          class ViewModel: ObservedObject {
              @Published var subject: String = "initial value"
          }
      
          @ObservedObject var viewModel: Self.ViewModel
      
          var body: some View {
              VStack {
                  Text("\(viewModel.subject)")
      
                  Button {
                      viewModel.subject = "value \(Int.random(in: 0...100))"
                  } label: {
                      Text("Tap Me")
                  }
              }
              .onReceive(viewModel.$subject) { [self] newValue in
                  handleValue(newValue)
              }
          }
      
          func handleValue(_ value: String) {
              print("handleValue: '\(value)'")
          }
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2021-10-19
        • 1970-01-01
        • 1970-01-01
        • 2020-10-21
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多