[英]SwiftUI List Repeating ID Value
我正在开发一个 SwiftUI 项目,该项目使用 Combine 从 Firebase Firestore 中提取数据。 每个用户都可以在应用程序中创建“优惠”。 为了在他们的帐户页面上列出他们的报价,我使用 onAppear 将 currentUserUid 传递给我的 View Model,以便我可以使用 currentUserUid 过滤数据库结果。 OfferHistoryView 如下。 这在视图首次出现时效果很好。 我的问题是当我从 OfferDetailView 返回时,我收到以下消息。
ForEach<Array, String, NavigationLink<OfferRowView, ModifiedContent<OfferDetailView, _EnvironmentKeyWritingModifier<Optional>>>>:ID 在集合中出现多次,这将给出未定义的结果!
虽然这不会使应用程序崩溃,但它并不理想。 我尝试过,每次加载视图时都从集合中删除所有项目,并且每次调用 combine 都不能解决问题。 我还添加了打印语句来尝试捕获重复项,但我从未看到重复项。 您可以在下面看到我的打印语句和相应文件的 rest。 任何帮助,将不胜感激。
OfferViewHistory - 消息的来源。
struct OfferHistoryView: View {
let db = Firestore.firestore()
@EnvironmentObject var authSession: AuthSession
@EnvironmentObject var offerHistoryViewModel: OfferHistoryViewModel
var body: some View {
return VStack {
List {
ForEach(self.offerHistoryViewModel.offerRowViewModels, id: \.id) { offerRowViewModel in
NavigationLink(destination: OfferDetailView(offerDetailViewModel: OfferDetailViewModel(offer: offerRowViewModel.offer, listing: offerRowViewModel.listing ?? testListing1))
.environmentObject(authSession)
) {
OfferRowView(offerRowViewModel: offerRowViewModel)
}
} // ForEach
} // List
.navigationBarTitle("Offer History")
} // VStack
.onAppear(perform: {
for offerRowViewModel in self.offerHistoryViewModel.offerRowViewModels {
print("Before startCombine: \(offerRowViewModel.id)")
}
self.offerHistoryViewModel.startCombine(currentUserUid: self.authSession.currentUserUid)
for offerRowViewModel in self.offerHistoryViewModel.offerRowViewModels {
print("After startCombine: \(offerRowViewModel.id)")
}
})
} // View
}
OfferHistoryViewModel - 调用 combine 的地方。
class OfferHistoryViewModel: ObservableObject {
var offerRepository: OfferRepository
// Published Properties
@Published var offerRowViewModels = [OfferRowViewModel]()
// Combine Cancellable
private var cancellables = Set<AnyCancellable>()
// Intitalizer
init(offerRepository: OfferRepository) {
self.offerRepository = offerRepository
}
// Starting Combine - Filter results for offers created by the current user only.
func startCombine(currentUserUid: String) {
for offerRowViewModel in self.offerRowViewModels {
print("Before startCombine func: \(offerRowViewModel.id)")
}
offerRepository
.$offers
.receive(on: RunLoop.main)
.map { offers in
offers
.filter { offer in
(currentUserUid != "" ? offer.userId == currentUserUid : false)
}
.map { offer in
OfferRowViewModel(offer: offer, listingRepository: ListingRepository())
}
}
.assign(to: \.offerRowViewModels, on: self)
.store(in: &cancellables)
for offerRowViewModel in self.offerRowViewModels {
print("After startCombine func: \(offerRowViewModel.id)")
}
}
}
报价行视图
struct OfferRowView: View {
@ObservedObject var offerRowViewModel: OfferRowViewModel
var body: some View {
// Convenience variable for accessing the offer & listing.
let offer = offerRowViewModel.offer
let listing = offerRowViewModel.listing
return VStack {
Text(offer.id ?? "ID")
Text(listing?.id ?? "ID")
} // VStack
} // View
}
OfferRowViewModel
class OfferRowViewModel: ObservableObject, Identifiable {
// Properties
var id: String = ""
var listingRepository: ListingRepository
// Published Properties
@Published var offer: Offer
@Published var listing: Listing?
// Combine Cancellable
private var cancellables = Set<AnyCancellable>()
// Initializer
init(offer: Offer, listingRepository: ListingRepository) {
self.offer = offer
self.listingRepository = listingRepository
self.startCombine()
}
// Starting Combine
func startCombine() {
// Get Offer
$offer
.receive(on: RunLoop.main)
.compactMap { offer in
offer.id
}
.assign(to: \.id, on: self)
.store(in: &cancellables)
// Get Connected Listing
listingRepository
.$listings
.receive(on: RunLoop.main)
.map { listings in
listings
.first(where: { $0.id == self.offer.listingId})
}
.assign(to: \.listing, on: self)
.store(in: &cancellables)
}
}
例如,如果您查看这篇文章https://www.hackingwithswift.com/books/ios-swiftui/why-does-self-work-for-foreach,您可以假设 offerRowViewModel.id 的 Hashable 实现无法正常工作。
我遇到的问题与我的存储库文件的多个实例有关。 我用解析器实现了依赖注入,这解决了这个问题。 如果有人有具体问题,请随时提问。
问题是 OfferRowViewModel 声明了一个id
属性,最初将其设置为""
,然后使用组合发布者将其更新为“真实” id 作为基础属性。
当您遵守Identifiable
时,使用计算属性通常非常方便。 这将始终提供正确的 id 并且永远不会不同步:
var id: String { offer.id }
示例中还有另一个值得解决的问题。 OfferRowViewModel 建立了一个发布者,它监视列表并过滤它们。 但正如所写,这意味着每个 OfferRowViewModel 将遍历每个列表,或者换句话说,每次优惠或列表更改时执行 O(n * m) 操作。 这在实践中可能没问题,但是如果有很多报价,或者很多列表,或者它们经常更改,则可能会产生性能问题。 Combine 确实很强大,但它的缺点之一是它会让我们更难看到这样的效率问题。
通过将 OfferRowViewModel 替换为简单的 model 类型,可以简化此代码:
struct OfferAndListing: Identifiable {
var offer: Offer
var listing: Listing
var id: String { offer.id }
}
然后,您的提供优惠的发布商可能看起来更像这样:
Publishers.CombineLatest(offerRepository.$offers, listingRepository.$listings)
.receive(on: RunLoop.main)
.map { (offers, listings) in
offers
.filter { $0.userId == currentUserUid }
.map { OfferAndListing(
offer: $0,
listing: listings.first(where: { })
)}
}
.sink { [weak self] in self?.offersAndListings = $0 }
.store(in: &cancellables)
这还没有提高效率,但它提供了一个单一的地方来查看工作在哪里完成和优化,使用一个更容易推理的发布者链,并简化了 model 类型,因此它不需要知道任何关于合并、数据存储库等。
我已经有同样的问题很长时间了,只是通过使我的 Identifiable 结构符合 Hashable & Equatable 来解决它。 如果您不熟悉,请研究如何做到这一点。 我认为我不能很好地解释它,但它有效并且为我摆脱了那个错误。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.