Невозможно привязать значение AnyPublisher<Bool, Never> к переменной @State в SwiftUI и Joint

Для простоты я изменил свой проект и создал воспроизводимый код.

Предположим, у меня есть следующая структура для данных 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 также должно быть обновлено. Но он не обновляется.

«Представьте, что у меня есть следующая структура данных элемента». Почему у вас такая структура? Какую полезную цель оно может иметь? Что вы действительно хотите сделать? С самого начала это похоже на проблему xy.

matt 20.04.2024 15:03

В реальном проекте у меня есть изображение в ячейке. Изображение меняется в зависимости от значения издателя. Поскольку код станет нечитаемым, если я поделюсь исходным кодом проекта, вы не сможете воспроизвести его без моей базы данных, поэтому я упростил исходную проблему.

Mahi Al Jawad 20.04.2024 15:05

Для простоты мой издатель публикует только данные Bool, и мне нужно привязать @State var Bool, вот и все, чтобы представление обновлялось. Вот и все.

Mahi Al Jawad 20.04.2024 15:06
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
3
59
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

В общем, вам следует использовать 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 решил мою проблему.

Mahi Al Jawad 21.04.2024 12:53

вы можете просто использовать .assignTo(&$isChecked) внутри объекта, чтобы исправить тип isChecked

malhal 21.04.2024 13:04

Другие вопросы по теме