0

I am populating a List in a SwiftUI app by making a call to the ViewModel file and passing the current user in the onAppear(). This filters results pulled from a database to only show results related to the user. Everything below has worked well until I installed iOS 15 on device. The code works as expected in the iOS Simulator in iOS 14, iOS 15, and on device with iOS 14. The issue occurs with iOS 15 on device. The message printed is below.

ForEach<Array, String, NavigationLink<OfferRowView, ModifiedContent<OfferDetailView, _EnvironmentKeyWritingModifier>>>: the ID occurs multiple times within the collection, this will give undefined results!

The view does not load the items in the list. I've printed to test to see if they are duplicated and they are not.

View File

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: {
            self.offerHistoryViewModel.startCombine(currentUserUid: self.authSession.currentUserUid)
        })
    } // View
}

View Model File

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) {
        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)
    }
}

Any help would be greatly appreciated.

jonthornham
  • 2,881
  • 4
  • 19
  • 35
  • Where exactly did you put your print to see if `offerRowViewModel` have unique id? Could you add this: `let _ = print("--> id: \(offerRowViewModel.id)")` just before `NavigationLink` and show us the result. – workingdog support Ukraine Oct 01 '21 at 07:01
  • @workingdog Wow, this is clever. When the stack pops, it's not printed. When first loaded it is. I can't understand why. – jonthornham Oct 01 '21 at 15:53
  • @workingdog also at times it print with no id and will double the list value. – jonthornham Oct 01 '21 at 15:55
  • @workingdog When using the simulator the ID always prints as expected. – jonthornham Oct 01 '21 at 18:02
  • it's just a print statement. I don't understand what you mean by `when the stack pops`, but now you can see the ids are not what you thought. PS: you don't need to have `return VStack {...}` in `OfferHistoryView`, just `VStack {...}` is enough. Note also, you create a new `OfferDetailViewModel` in `OfferHistoryView` for every loop of the ForEach. This is probably not what you want. Use one model. – workingdog support Ukraine Oct 01 '21 at 23:23
  • Maybe you could re-think your code structure, you seem to have (ObservableObject) models all over the place, eg, `OfferHistoryViewModel` with an array of `OfferRowViewModel` that you pass and create `OfferDetailViewModel` etc... not very clean. – workingdog support Ukraine Oct 01 '21 at 23:30
  • @workingdog I’ve worked through several MVVM models trying to make things clean. This is the best I’ve found. I am however open to suggestion. Do you recommend a way to make this cleaner? – jonthornham Oct 01 '21 at 23:33
  • By the stack pops what I mean is, when in the detail view and you hit back, the print statement is not called. – jonthornham Oct 01 '21 at 23:35
  • There are many articles, blogs and tutorials on MVVM. I'm not a great fan of this, especially the naming, SomethingViewModelModelView of view model observable within yet another SomethingViewModelModelView ..., but each to its own. I found this article interesting: https://matteomanferdini.com/mvvm-pattern-ios-swift/ Regarding `the print statement is not called`, this is "normal", the View does its own thing whenever it feels like it. So the print should not really be in there, it was just to show that the ids are not what you thought. – workingdog support Ukraine Oct 02 '21 at 00:39
  • I'm proposing this may be a bug. Doing a bit of research it appears the onAppear method has had issues in the passed. I created a State variable that checks to see if the onAppear has been called. Once called once I prevent it from being called again. This returns the functioning of the app back to normal status. – jonthornham Oct 02 '21 at 00:47
  • @workingdog Thanks for the article link. I will go through this in more detail. – jonthornham Oct 02 '21 at 00:48

0 Answers0