繁体   English   中英

SwiftUI 列出重复 ID 值

[英]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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM