Swift Combine 未收到发布的值

Swift Combine Not Receiving Published Values

我正在开发一个使用 Combine 从 Firebase Firestore 获取更新的项目。我有一个 StockListView、一个 StockListCellView 和一个 StockDetailView,我希望在其中注册更新。

StockListView 包含将 StockDetailsView 推入堆栈的 StockListCellView。每个视图也有一个相应的 ViewModel,我正在使用 Combine。

我的问题是我的 StockDetailView 没有收到来自 Combine 的更新,我不明白为什么。下面是每个视图和视图模型的代码的简化版本。我认为这与我在 StockDetailViewModel 中的分配方式有关,但我无法弄清楚。任何帮助将不胜感激。

StockListViewModel - 效果很好

class StockListViewModel: ObservableObject {
    @Published var stockRepository = StockRepository()
    @Published var stockListCellViewModels = [StockListCellViewModel]()

    private var cancellables = Set<AnyCancellable>()

    init() {
        stockRepository.$stocks.map { stocks in
            stocks.map { stock in
                StockListCellViewModel(stockDetailViewModel: StockDetailViewModel(stock: stock))
            }
        }
        .assign(to: \.stockListCellViewModels, on: self)
        .store(in: &cancellables)
    }
}

StockListView - 效果很好

struct StockListView: View {
    @ObservedObject var stockRepository = StockRepository()
    @ObservedObject var stockListVM = StockListViewModel()
        
    var body: some View {
       NavigationView {
                List {
                    ForEach(stockListVM.stockListCellViewModels) { stockListCellVM in
                            NavigationLink(destination: StockDetailView(stockDetailVM: stockListCellVM.stockDetailViewModel)) {
                                StockListCell(stockListVM: stockListVM, stockListCellVM: stockListCellVM)
                            }
                    }
                } // List
                .listStyle(PlainListStyle())
            .navigationBarTitle("stock")
        } // NavigationView
    } // View
}

StockListCellViewModel - 效果很好

class StockListCellViewModel: ObservableObject, Identifiable {
    var id: String = ""
    @Published var stockDetailViewModel: StockDetailViewModel
    @Published var stock: Stock

    private var cancellables = Set<AnyCancellable>()

    init(stockDetailViewModel: StockDetailViewModel) {
        self.stockDetailViewModel = stockDetailViewModel
        self.stock = stockDetailViewModel.stock

        stockDetailViewModel.$stock.compactMap { stock in
            stock.id
        }
        .assign(to: \.id, on: self)
        .store(in: &cancellables)

        stockDetailViewModel.$stock.map { stock in
            StockDetailViewModel(stock: stock)
        }
        .assign(to: \.stockDetailViewModel, on: self)
        .store(in: &cancellables)
    }
}

StockListCellView - 效果很好

struct StockListCell: View {
    @ObservedObject var stockListVM: StockListViewModel
    @ObservedObject var stockListCellVM: StockListCellViewModel

    var body: some View {
                Text(stockListCellVM.stock.ticker)
    }
}

StockDetailViewModel - 未更新

class StockDetailViewModel: ObservableObject, Identifiable {
    var id: String = ""
    @Published var stock: Stock
    
    private var cancellables = Set<AnyCancellable>()

    init(stock: Stock) {
        self.stock = stock
        self.chartColor = UIColor()

        $stock.compactMap { stock in
            stock.id
        }
        .assign(to: \.id, on: self)
        .store(in: &cancellables)
}

StockDetailView - 未更新

struct StockDetailView: View {
    @ObservedObject var stockDetailVM: StockDetailViewModel

    var body: some View {
                    Text(stockDetailVM.stock.ticker)
    }
}

我花了两周的大部分时间才解决这个问题,我想 post 在这里为任何遇到我遇到的相同问题的人解决。我重建了一个简单的例子,使事情更容易理解。这是应用程序的功能。

  1. 连接到 Firestore 数据库并启动 SnapShootListener 以收集 Note 对象。
  2. 收集后,它会将笔记存储在笔记对象数组中。
  3. 这个笔记数组内置在笔记列表中。
  4. 每个笔记都会进入一个 NoteDetailView。

我期望发生的事情是,当注释在服务器上更新时,我会在列表和 DetailView 中实时看到注释更新。我的问题是我只能在列表中看到实时更新。

这是因为您无法从 ForEach 循环绑定数据元素。这意味着在服务器上进行的任何更新永远不会到达 DetailView。

为了解决这个问题,我引入了 EnvironmentObjects 并通过将要在 DetailView 中显示的 Note 的索引传递给 DetailView 来创建 DetailView。这使我能够在 DetailView 中访问正确的注释。下面的代码显示了这是如何完成的。注意:我没有添加 NoteDetailViewModel.swift 来简化事情。

App.swift

   @main
struct ToDoSwiftUITutorialApp: App {
    // 1. An @StateObject was created for the NoteRepository connected to Firestore.
    @StateObject private var noteRepository = NoteRepository()
    
    // Firebase
    init() {
        FirebaseApp.configure()
        Auth.auth().signInAnonymously()
    }
    var body: some Scene {
        WindowGroup {
            ContentView()
                .environmentObject(noteRepository) // noteRepository was added to ContentView as an EnvironmentObject.
        }
    }
}

NoteRepository.swift

    class NoteRepository: ObservableObject {
    let db = Firestore.firestore()
    
    // Publishing the array of Note object received from Firestore.
    @Published var notes = [Note]()
    
    init() {
        loadData()
    }
    
    func loadData() {
        db.collection("notes").addSnapshotListener { (querySnapshot, error) in
            if let querySnapshot = querySnapshot {
                self.notes = querySnapshot.documents.compactMap { document in
                    do {
                        let x = try document.data(as: Note.self)
                        return x
                    }
                    catch {
                        print(error)
                    }
                    return nil
                }
            }
        }
    }
}

NoteListView.swift

    struct NoteListView: View {
    @ObservedObject var noteListViewModel = NoteListViewModel()
    
    var body: some View {
        NavigationView {
                List {
                    ForEach(noteListViewModel.noteCellViewModels.indices, id: \.self) { index in
                        NavigationLink(destination: NoteDetailView(index: index).environmentObject(self.noteListViewModel)) {
                            NoteCellView(noteCellViewModel: noteListViewModel.noteCellViewModels[index])
                            } // NavigationLink
                    } // ForEach
                }// List
            .navigationBarTitle("Notes")
        } // NavigationView
    }
}

NoteListViewModel.swift

    class NoteListViewModel: ObservableObject {
    @Published var noteRepository = NoteRepository()
    @Published var noteCellViewModels = [NoteCellViewModel]()
    
    private var cancellables = Set<AnyCancellable>()
    
    init() {
        noteRepository.$notes.map { notes in
            notes.map { note in
                NoteCellViewModel(note: note)
            }
        }
        .assign(to: \.noteCellViewModels, on: self)
        .store(in: &cancellables)
    }
}

NoteCellView.swift

    struct NoteCellView: View {
    @ObservedObject var noteCellViewModel: NoteCellViewModel
        
    var body: some View {
        Text(noteCellViewModel.note.note)
    }
}

NoteCellViewModel.swift

    class NoteCellViewModel: ObservableObject, Identifiable {
    var id: String = ""
    @Published var note: Note
    
    private var cancellables = Set<AnyCancellable>()
    
    init(note: Note) {
        self.note = note
        
        $note.compactMap { note in
            note.id
        }
        .assign(to: \.id, on: self)
        .store(in: &cancellables)
    }
}

NoteDetailView.swift

    struct NoteDetailView: View {
    @EnvironmentObject var noteRepository: NoteRepository
    var index: Int
    
    var body: some View {
        Text(noteRepository.notes[index].note)
    }
}