Search code examples
listswiftuimvvmsearchable

SwiftUI searchable in Model()


I am having issues using searchable in SwiftUI for my data model. I have read the data from firestore and saved as an array in GBplayerModel().

When using searchable it filters the list of players but won't go back to the original list view when the search text is removed.

Essentially I want to main view be the full list that can be searched through.

Data Model

    import Foundation
        import Firebase
        
        struct GBplayer: Identifiable {
            var id: String
            var name: String
            var status: String
            var height: String
            var team: String
            var position: String
        }
      
// mock data that we can use for previews  
    extension GBplayer {
        static func mock()  -> GBplayer {
            GBplayer(id: "Aaron Menzies", name: "Aaron Menzies", status: "Active", height: "7ft 3", team: "Leicester Riders", position: "C")
        }
    }
    
    class GBplayerModel: ObservableObject {
        
        @Published var playerList = [GBplayer]()
    
        func getGBplayers() {
               
            // get reference to the db
            let db = Firestore.firestore()
            
            db.collection("GBplayers").getDocuments { snapshot, error in
                
                //check for errors
                if error == nil {
    
                    // no errors
                    print("your in")
                    
                    if let snapshot = snapshot {
                        
                        // Update the list property in the main thread
                        DispatchQueue.main.async {
                            
                            // get all the documents and create list of players
                            self.playerList = snapshot.documents.map { d in
                                
                                //create a player struc for each player in the document
                                return GBplayer(id: d.documentID,
                                                name: d["name"] as? String ?? "",
                                                status: d["status"] as? String ?? "",
                                                height: d["height"] as? String ?? "",
                                                team: d["team"] as? String ?? "",
                                                position: d["position"] as? String ?? "",
                            }
                        }
                    }
                } else {
                    // handle the error
                    print("No Documents")
                    return
                }    
            }   
        }    
    }

View

import SwiftUI

struct playerView: View {
    
    @ObservedObject var model = GBplayerModel()
    @State private var searchPlayer = ""
    
    var body: some View {
        
        NavigationView {
            
            List {
                ForEach (model.playerList) { player in
                    
                    NavigationLink  {
                        Text("Detailed View")   //need to add detailed view here     
                    } label: {
                        PlayerRow(player: player)
                    }
                }
            }
            .listStyle(.plain)
            .navigationTitle("Players")
            
    // update data from Firebase 
            .onAppear() {
                self.model.getGBplayers()                
            }
            
            .searchable(text: $searchPlayer, placement: .navigationBarDrawer(displayMode: .always), prompt: "Find a player")
            
            .onChange(of: searchPlayer) { search in
                if !search.isEmpty {model.playerList = model.playerList.filter { $0.name.contains(searchPlayer) }
                } else {
                    model.playerList = model.playerList
                }
            }
        }
    }  
}

Any help would be greatly appreciated, spent a lot time of this and appreciate it maybe something simple


Solution

  • Right now, when you search, you're mutating your playerList when you do this:

    model.playerList = model.playerList.filter { ... }
    

    That means that if you change your search term (or get rid of it), the items that were filtered out no longer exist.

    Instead, you should always keep the full playerList and only display a filtered version:

    var filteredList: [GBplayer] {
      if search.isEmpty {
        return model.playerList
      } else {
        return model.playerList.filter { ... }
      }
    }
    // ...
    
    ForEach (filteredList) { player in ... }
    

    And remove your onChange code