Trenton
Trenton

Reputation: 27

SwiftUI View is not updating after new data

I have implemented a search feature in my application, and would like for my scrollview element to be populated with results from a local API if and when new data is fetched.

I have a view model I am pulling data from:

@MainActor
class BottleViewModel: ObservableObject {
    let bottlesProvider: BottleProvider! = BindexAPI()

    @Published var searchResults = [BottleModel]()
    @Published var searchText: String = ""
    
    func search(q: String) {
        bottlesProvider.getBottleSearch(query: q) {
            switch $0 {
            case let .failure(err):
                print(err)
            case let .success(bottles):
                DispatchQueue.main.async {
                    print(self.searchResults.count)
                    self.searchResults = bottles
                    print(self.searchResults.count)
                }
            }
        }
    }
}

After I enter a query in my textfield and hit enter, I see that when the search is successful the count displays 0 to 1 in the debugger. However, my view is not getting these views.

My assumption is that once the searchResults got updated, anything that is consuming the view model would update, however clearly I am incorrect. Here is the view that I would like to update after a search query.

@StateObject private var viewModel = BottleViewModel()
var body: some View {
    VStack {
        Text("Search count: \(viewModel.searchResults.count)")
        ForEach(viewModel.searchResults, id: \.self) { bottle in
            NavigationLink(destination: BottleDetailView(bottle: bottle)) {
                BottleListing(bottle: bottle)
            }
        }
    }
}
@StateObject private var viewModel = BottleViewModel()
var body: some View {
        HStack {
            TextField("Search \(viewModel.results.count) bottles", text: $term, onEditingChanged: { isEditing in self.isEditing = isEditing })
                .frame(height: 35)
                .disableAutocorrection(true)
                .textFieldStyle(PlainTextFieldStyle())
                .padding(7)
                .padding(.horizontal, 25)
                .background(Color.white)
                .accentColor(Color.black)
                .cornerRadius(4)
                .submitLabel(.search)
                .overlay(
                    HStack {
                        Image(systemName: "magnifyingglass")
                            .foregroundColor(.gray)
                            .frame(minWidth: 0, maxWidth: .infinity, alignment: .leading)
                            .padding(.leading, 8)
                        
                        if isEditing {
                            Button(action: {
                                self.term = ""
                                self.isEditing = false
                                UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
                                
                            }) {
                                Image(systemName: "multiply.circle.fill")
                                    .foregroundColor(.gray)
                                    .padding(.trailing, 8)
                            }
                        }
                    }
                )
                .onSubmit {
                    viewModel.search(q: term)
                }
        }
    }

Am I missing a property that would tell SwiftUI to update after this array is changed or appended to? Any guidance would be greatly appreciated.

Upvotes: 0

Views: 912

Answers (1)

Currently you have two @StateObject private var viewModel = BottleViewModel() that have no relations to each other.

What you want to achieve is to have one source of truth in @StateObject var viewModel = BottleViewModel(). Pass this viewModel to other views, with for example @ObservedObject as shown, or @EnvironmenObject using .environmentObject(viewModel). So that any change of the viewModel is reflected in all views that uses it.

Try this approach, directly passing the viewModel to the other view.

struct ContentView: View {
@StateObject var viewModel = BottleViewModel() // <--- here

var body: some View {
         OtherView(viewModel: viewModel)  // <--- here
        //...
      }
     }


   
struct OtherView: View {
@ObservedObject var viewModel: BottleViewModel  // <--- here

var body: some View {
        HStack {
            TextField(...)
            //...
        }
      }
     }

Upvotes: 1

Related Questions