Reputation:
I am using Swift UI. I created simple search function to filter the data. OnChnage function I am calling the State property wrapper with view model search function. I done some research that , the data should receive into main thread and that the main reason I have annotated the function with @MainActor but still I got same warning.
Here is the search function code with view model.
@MainActor
final class ProductListViewModel {
@Published private(set) var productLists: [Product] = []
@Published private var filteredProductLists: [Product] = []
private let repository: ProductRepository
init(repository: ProductRepository) {
self.repository = repository
}
}
extension ProductListViewModel: ProductListViewModelAction {
func getProductList(urlStr: String) async {
guard let url = URL(string: urlStr) else {
self.customError = NetworkError.invalidURL
return
}
do {
let lists = try await repository.getList(for: url)
productLists = lists.products
} catch {
// catching the error
}
}
}
extension ProductListViewModel {
@MainActor
func performSearch(keyword: String) {
filteredProductLists = productLists.filter { product in
product.title.contains(keyword)
}
}
var productList: [Product] {
filteredProductLists.isEmpty ? productLists: filteredProductLists
}
}
Here is my view code ..
struct ProductListView: View {
@StateObject var viewModel = ProductListViewModel(repository: ProductRepositoryImplementation(networkManager: NetworkManager()))
@State var searchText = ""
var body: some View {
NavigationStack {
VStack {
if viewModel.customError != nil && !viewModel.refreshing {
alertView()
} else {
if viewModel.refreshing {
progressView()
}
if viewModel.productLists.count > 0 && !viewModel.refreshing {
List(viewModel.productList, id: \.self) { product in
ProductListViewCell(productData: product)
} .listStyle(.grouped)
}
}
}
.searchable(text: $searchText)
.onChange(of: searchText, perform: viewModel.performSearch)
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
getToolBarView()
}
}
.navigationTitle(Text("Product List"))
}.task {
await getDataFromAPI()
}
.refreshable {
await getDataFromAPI()
}
}
func getDataFromAPI() async {
await viewModel.getProductList(urlStr: NetworkURL.productUrl)
}
@ViewBuilder
func getToolBarView() -> some View {
Button {
Task{
await getDataFromAPI()
}
} label: {
HStack {
Image(systemName: "arrow.clockwise")
.padding(.all, 10.0)
}.fixedSize()
}
.cornerRadius(5.0)
}
@ViewBuilder
func progressView() -> some View {
VStack{
RoundedRectangle(cornerRadius: 15)
.fill(.white)
.frame(height: 180)
.overlay {
VStack {
ProgressView().padding(50)
Text("Please Wait Message").font(.headline)
}
}
}
}
@ViewBuilder
func alertView() -> some View {
Text("").alert(isPresented: $viewModel.isErrorOccured) {
Alert(title: Text("General_Error"), message: Text(viewModel.customError?.localizedDescription ?? ""),dismissButton: .default(Text("Okay")))
}
}
}
Error on changes with onChange(of: searchText) { viewModel.performSearch(keyword: searchText) } ..
Here is the screenshot of the warning ..
Upvotes: 0
Views: 483
Reputation: 30736
onChange
is for running an external action, to transform data you should use a computed property into a subview, e.g.
ProductList(products: filteredProducts)
var filteredProducts: [Product] {
products.filter { product in
product.title.contains(searchText)
}
}
Also you need a do/catch inside the .task
so you can set an @State
to the error. Also you need @State
to hold the product lists downloaded. Basically remove the view model object, the View
struct already is main actor there is no need for a main actor object and will just slow things down/cause consistency bugs. You would benefit from making repository
an EnvironmentKey
so you can easily replace it with one containing sample data for previews.
Upvotes: 0