Для простоты я изменил свой проект и создал воспроизводимый код.
Предположим, у меня есть следующая структура для данных 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 также должно быть обновлено. Но он не обновляется.
В реальном проекте у меня есть изображение в ячейке. Изображение меняется в зависимости от значения издателя. Поскольку код станет нечитаемым, если я поделюсь исходным кодом проекта, вы не сможете воспроизвести его без моей базы данных, поэтому я упростил исходную проблему.
Для простоты мой издатель публикует только данные Bool, и мне нужно привязать @State var Bool, вот и все, чтобы представление обновлялось. Вот и все.





В общем, вам следует использовать 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, которые были помещены в массив.
Используйте @ObserverdObject, если вы хотите, чтобы body вызывался при изменении любого из свойств элемента, а затем получал доступ к свойству из body, например:
struct ItemCell: View {
@ObserverdObject var item: Item
// called every time item changes
var body: some View {
HStack {
HStack {
Image(systemName: item.isChecked ? "checkmark.circle.fill" : "checkmark.circle")
.foregroundStyle(.blue)
В ObservableObject используйте .assignTo вместо sink и store, например. .assignTo(&$isChecked)
Привет @malhal, я думаю, ты неправильно понял вопрос. Здесь item.isChecked имеет тип AnyPublisher<Bool, Never>. Значение isChecked само по себе не меняется, а публикует поток значений. В любом случае, спасибо за ваш ответ. Я думаю, что ответ @Sweeper решил мою проблему.
вы можете просто использовать .assignTo(&$isChecked) внутри объекта, чтобы исправить тип isChecked
«Представьте, что у меня есть следующая структура данных элемента». Почему у вас такая структура? Какую полезную цель оно может иметь? Что вы действительно хотите сделать? С самого начала это похоже на проблему xy.