SwiftUI - @State 属性 未更新
SwiftUI - @State property not updating
我在使用 SwiftUI 时遇到了这种非常奇怪的情况 issue/bug。在 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)'")
}
}
您只是错误地使用了状态 - 它与视图相关并且仅在呈现视图时(即在 body
上下文中)才可用(准备 back-store)。在 init 中还没有状态 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)'")
}
}
你在这里做错了几件事。
切勿尝试将内容存储在 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 viewmodifier 做同样的事情。
以下是我将以上建议付诸实践:
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 时遇到了这种非常奇怪的情况 issue/bug。在 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)'")
}
}
您只是错误地使用了状态 - 它与视图相关并且仅在呈现视图时(即在 body
上下文中)才可用(准备 back-store)。在 init 中还没有状态 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)'")
}
}
你在这里做错了几件事。
切勿尝试将内容存储在 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 viewmodifier 做同样的事情。
以下是我将以上建议付诸实践:
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)'")
}
}