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 在这里为任何遇到我遇到的相同问题的人解决。我重建了一个简单的例子,使事情更容易理解。这是应用程序的功能。
- 连接到 Firestore 数据库并启动 SnapShootListener 以收集 Note 对象。
- 收集后,它会将笔记存储在笔记对象数组中。
- 这个笔记数组内置在笔记列表中。
- 每个笔记都会进入一个 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)
}
}
我正在开发一个使用 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 在这里为任何遇到我遇到的相同问题的人解决。我重建了一个简单的例子,使事情更容易理解。这是应用程序的功能。
- 连接到 Firestore 数据库并启动 SnapShootListener 以收集 Note 对象。
- 收集后,它会将笔记存储在笔记对象数组中。
- 这个笔记数组内置在笔记列表中。
- 每个笔记都会进入一个 NoteDetailView。
我期望发生的事情是,当注释在服务器上更新时,我会在列表和 DetailView 中实时看到注释更新。我的问题是我只能在列表中看到实时更新。
这是因为您无法从 ForEach 循环绑定数据元素。这意味着在服务器上进行的任何更新永远不会到达 DetailView。
为了解决这个问题,我引入了 EnvironmentObjects 并通过将要在 DetailView 中显示的 Note 的索引传递给 DetailView 来创建 DetailView。这使我能够在 DetailView 中访问正确的注释。下面的代码显示了这是如何完成的。注意:我没有添加 NoteDetailViewModel.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)
}
}