Search code examples
mvvmswiftuifull-text-searchalgolia

SwiftUI: View Model does not update the View


I try to implement a Search Bar with Algolia, and I use the MVVM pattern.

Here's my View Model:

class AlgoliaViewModel: ObservableObject {
   
   @Published var idList = [String]()

   func searchUser(text: String){
       
           let client = SearchClient(appID: "XXX", apiKey: "XXX")
           let index = client.index(withName: "Users")
           let query = Query(text)
           index.search(query: query) { result in
             if case .success(let response) = result {
               print("Response: \(response)")
               
                do {
                   let hits: Array = response.hits
                   var idList = [String]()
                   for x in hits {
                       idList.append(x.objectID.rawValue)
                   }
                   
                   DispatchQueue.main.async {
                       self.idList = idList
                       print(self.idList)
                   }
               }
               catch {
                   print("JSONSerialization error:", error)
               }
             }
       }
   
   }
   
}

Here is my View :

struct NewChatView : View {
    
    @State private var searchText = ""
    
    @ObservedObject var viewModel = AlgoliaViewModel()
    
    var body : some View{
        
        VStack(alignment: .leading){
            
            Text("Select To Chat").font(.title).foregroundColor(Color.black.opacity(0.5))
            
            ScrollView(.vertical, showsIndicators: false) {
                
                VStack(spacing: 12){
                    
                    HStack {
                        TextField("Start typing",
                                  text: $searchText,
                                  onCommit: { self.viewModel.searchUser(text: self.searchText) })
                            .textFieldStyle(RoundedBorderTextFieldStyle())
                        Button(action: {
                            self.viewModel.searchUser(text: self.searchText)
                        }) {
                            Image(systemName: "magnifyingglass")
                        }
                    }   .padding()
                    
                    List {
                        
                        ForEach(viewModel.idList, id: \.self){ i in
                            Text(i)
                        }
                        
                    }
                    
                }
            }
        }.padding()
    }
    
}

I often use this pattern with Firebase and everything works fine, but here with Algolia the List remains empty in the NewChatView.

The print(self.idList) statement inside the View-Model shows the right idList, but it does not update the List inside the NewChatView.


Solution

  • You first need to create your own custom Identifiable and Hashable model to display the searchValue in a List or ForEach.

    Something like this:

    struct MySearchModel: Identifiable, Hashable {
        let id = UUID().uuidString
        let searchValue: String
    }
    

    Then use it in your AlgoliaViewModel. Set a default value of an empty array. You can also map the hits received and convert it to your new model. No need for the extra for loop.

    class AlgoliaViewModel: ObservableObject {
        
        @Published var idList: [MySearchModel] = []
        
        func searchUser(text: String) {
            
            let client = SearchClient(appID: "XXX", apiKey: "XXX")
            let index = client.index(withName: "Users")
            let query = Query(text)
            
            index.search(query: query) { result in
                
                if case .success(let response) = result {
                    print("Response: \(response)")
                    
                    do {
                        let hits: Array = response.hits
                        
                        DispatchQueue.main.async {
                            self.idList = hits.map({ MySearchModel(searchValue: $0.objectID.rawValue) })
                            print(self.idList)
                        }
                    }
                    catch {
                        print("JSONSerialization error:", error)
                    }
                }
            }
        }
    }
    

    For the NewChatView, you can remove the ScrollView as it conflicts with the elements inside your current VStack and would hide the List with the results as well. The following changes should display all your results.

    struct NewChatView : View {
    
        @State private var searchText = ""
        @ObservedObject var viewModel = AlgoliaViewModel()
    
        var body: some View{
    
            VStack(alignment: .leading) {
    
                Text("Select To Chat")
                    .font(.title)
                    .foregroundColor(Color.black.opacity(0.5))
    
                VStack {
                    HStack {
                        TextField("Start typing",
                                  text: $searchText,
                                  onCommit: { self.viewModel.searchUser(text: self.searchText) 
                            })
                            .textFieldStyle(RoundedBorderTextFieldStyle())
                        Button(action: {
                            self.viewModel.searchUser(text: self.searchText)
                        }) {
                            Image(systemName: "magnifyingglass")
                        }
                    }   .padding()
    
                    List {
                        ForEach(viewModel.idList) { i in
                            Text(i.searchValue)
                                .foregroundColor(Color.black)
                        }
                    }
                }
            }.padding()
        }
    }