Reputation: 27
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
Reputation: 36782
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