无法将`AnyPublisher<Bool, Never>`的值绑定到SwiftUI和Combine中的`@State`变量

问题描述 投票:0回答:1

为了简单起见,我修改了我的项目并创建了可重现的代码。

考虑我有以下

Item
数据结构:

struct Item: Identifiable {
    let id = UUID()
    let isChecked: AnyPublisher<Bool, Never>
    let name: String
}

我有以下

ItemCell
,当
isChecked
值发布任何新值时应该更新。

struct ItemCell: View {
    var item: Item
    @State var imgState: Bool = false
    
    var cancellables = Set<AnyCancellable>()
    
    init(_ item: Item) {
        self.item = item
        item.isChecked
            .print("State for item \(item.name): ") // --> Check this print
            .assign(to: \.imgState, on: self)
            .store(in: &cancellables)
    }
    
    var body: some View {
        HStack {
            HStack {
                Image(systemName: imgState ? "checkmark.circle.fill" : "checkmark.circle")
                    .foregroundStyle(.blue)
                Text(item.name)
            }
        }
    }
}

我已在调试控制台中检查到

isChecked
始终发出值,但视图未更新。

无论如何,要测试我创建的虚拟代码,您只需复制并粘贴即可测试:

完整代码:

import SwiftUI
import Combine

struct Item: Identifiable {
    let id = UUID()
    let isChecked: AnyPublisher<Bool, Never>
    let name: String
}


struct ItemCell: View {
    var item: Item
    @State var imgState: Bool = false
    
    var cancellables = Set<AnyCancellable>()
    
    init(_ item: Item) {
        self.item = item
        item.isChecked
            .print("State for item \(item.name): ")
            .assign(to: \.imgState, on: self)
            .store(in: &cancellables)
    }
    
    var body: some View {
        HStack {
            HStack {
                Image(systemName: imgState ? "checkmark.circle.fill" : "checkmark.circle")
                    .foregroundStyle(.blue)
                Text(item.name)
            }
        }
    }
}

struct ContentView: View {
    var viewModel = ContentViewModel()
    
    var body: some View {
        List(viewModel.items) { item in
            ItemCell(item)
        }
    }
}

class ContentViewModel: ObservableObject {
    var itemNames = ["A", "B", "C", "D"]
    @Published var checkList = [false, false, false, false]
    
    init() {
        updateChecklist()
    }
    
    // Dummy function to update data
    func updateChecklist() {
        Timer.scheduledTimer(withTimeInterval: 2, repeats: true) { [weak self] _ in
            
            for index in 0...3 {
                self?.checkList[index] = Bool.random()
            }
        }
    }
    
    // Dummy items whose isChecked data are continuously updating with publisher
    var items: [Item] {
        (0...3).map { index in
            Item(
                isChecked: $checkList
                    .map { $0[index] == true }
                    .eraseToAnyPublisher(),
                name: itemNames[index]
            )
        }
    }
}

#Preview {
    ContentView()
}

我预计当

isChecked
发布者发出值时,SwiftUI 视图
ItemCell
也应该更新。但还没更新。

swift swiftui state combine publisher
1个回答
0
投票

一般来说,您应该使用

onReceive
来监听发布者发布的值:

struct ItemCell: View {
    var item: Item
    @State var imgState: Bool = false
    
    init(_ item: Item) {
        self.item = item
    }
    
    var body: some View {
        HStack {
            HStack {
                Image(systemName: imgState ? "checkmark.circle.fill" : "checkmark.circle")
                    .foregroundStyle(.blue)
                Text(item.name)
            }
        }
        .onReceive(item.isChecked.print("State for item \(item.name): ")) {
            imgState = $0
        }
    }
}

这对于您的玩具示例来说是正确的。

远离你人为的示例,假设发布的值来自某个外部源,而不是

@Published
属性(这是非常人为的)。您的
@Published
中应该有一个
[Item]
类型的
ContentViewModel
属性。
sink
分配给此数组中的
Item
的发布者。

那么

ItemView
不需要任何代码来观察变化。
ContentView
观察
ContentViewModel.items
,并自动传播更改。

struct Item: Identifiable {
    let name: String
    var isChecked: Bool
    
    // suppose Item is identified by its name
    var id: String { name }
}


struct ItemCell: View {
    let item: Item
    
    init(_ item: Item) {
        self.item = item
    }
    
    var body: some View {
        HStack {
            HStack {
                Image(systemName: item.isChecked ? "checkmark.circle.fill" : "checkmark.circle")
                    .foregroundStyle(.blue)
                Text(item.name)
            }
        }
    }
}

struct ContentView: View {
    @StateObject var viewModel = ContentViewModel()
    
    var body: some View {
        List(viewModel.items) { item in
            ItemCell(item)
        }
    }
}

class ContentViewModel: ObservableObject {
    @Published var items: [Item] = []
    
    private var cancellables: Set<AnyCancellable> = []
    
    init() {
        makeItem(name: "Foo")
        makeItem(name: "Bar")
        makeItem(name: "Baz")
    }
    
    func makeItem(name: String) {
        items.append(Item(name: name, isChecked: false))
        
        let someExternalPublisher = // get a publisher from somewhere...
        // for example let's just say this is a timer publisher
        Timer.publish(every: 2, on: .main, in: .default)
            .autoconnect()
            .map { _ in Bool.random() }
        
        someExternalPublisher
            .sink { bool in
                if let index = self.items.firstIndex(where: { $0.name == name }) {
                    self.items[index].isChecked = bool
                }
            }
            .store(in: &cancellables)
    }
}

在 iOS 17+ 上,您可以将

Item
设为
@Observable class
,并让每个项目管理自己的发布者:

@Observable
class Item: Identifiable {
    let name: String
    var isChecked: Bool = false
    
    @ObservationIgnored
    var cancellables = Set<AnyCancellable>()
    
    init(name: String) {
        self.name = name
        let someExternalPublisher = // get a publisher from somewhere...
        // for example let's just say this is a timer publisher
        Timer.publish(every: 2, on: .main, in: .default)
            .autoconnect()
            .map { _ in Bool.random() }
        
        someExternalPublisher.sink {
            self.isChecked = $0
        }
        .store(in: &cancellables)
    }
}

这对

ObservableObject
不起作用,因为 SwiftUI 无法观察已放入数组的
ObservableObject
的变化。

© www.soinside.com 2019 - 2024. All rights reserved.