SwiftUI - @State 属性未更新

标签 swift swiftui combine

我在使用 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)'")
    }
    
    
}

最佳答案

你在这里做错了几件事。

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

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

// 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 View 修饰符可以做同样的事情。

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

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)'")
    }
}

关于SwiftUI - @State 属性未更新,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/63974677/

相关文章:

ios - CGRect 之间的像素间隙

ios - 将一个变量从一个 View Controller 传递到另一个 View Controller

ios - 如何在 SwiftUI 中更新 TextField 的值?

Swift Combine 如何跳过一个事件

ios - 在 Combine 中处理发布者错误的最佳实践是什么? (从 CoreLocation 发布航向更新)

swift - iOS1 3's Combine streams don' t 运算符(operator)使用调度程序后的流程

xcode - Swift - 未生成 `.framework` 文件

ios - 使用 Swift 3 进行 Base64 编码

SwiftUI - 如何仅在一个屏幕上自定义导航栏?

ios - 在 SwiftUI 中,我可以在不在 SceneDelegate 中传递 @EnvironmentObject 吗?喜欢在任何 View 中传递它