SwiftUI仅显示单条产品记录而非列表的问题排查求助
问题排查与解决:SwiftUI产品列表仅显示1条数据
我正在使用SwiftUI开发应用,采用Async/Await处理并发逻辑,尝试展示产品列表数据。调用接口https://dummyjson.com/products可获取30条产品记录,调试时能确认获取到全部数据,但运行应用时仅显示1条记录,希望排查问题原因并获取修改方案。相关代码如下:
模型代码
import Foundation // MARK: - Welcome struct ProductData: Codable, Hashable { let products: [Product] } // MARK: - Product struct Product: Codable, Hashable { let id: Int let title, description: String let price: Int let discountPercentage, rating: Double let stock: Int let brand, category: String let thumbnail: String let images: [String] }
ViewModel代码
@MainActor final class ProductListViewModel { @Published private(set) var productLists: [ProductData] = [] @Published private(set) var customError: NetworkError? @Published private(set) var refreshing = true @Published var isErrorOccured = false private let repository: ProductRepository init(repository: ProductRepository) { self.repository = repository } } extension ProductListViewModel: ProductListViewModelAction { func getProductList(urlStr: String) async { refreshing = true guard let url = URL(string: urlStr) else { self.customError = NetworkError.invalidURL refreshing = false isErrorOccured = false return } do { let lists = try await repository.getList(for: url) refreshing = false isErrorOccured = false productLists = [lists] } catch { refreshing = false isErrorOccured = true customError = error as? NetworkError } } }
视图代码
import SwiftUI struct ProductListView: View { @StateObject var viewModel = ProductListViewModel(repository: ProductRepositoryImplementation(networkManager: NetworkManager())) 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.productLists, id: \.self) { product in ProductListViewCell(productData: product) } .listStyle(.grouped) } } } .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"))) } } }
单元格视图代码
import SwiftUI struct ProductListViewCell: View { let productData: ProductData var body: some View { HStack { if let url = URL(string: productData.products.first?.thumbnail ?? ""){ ProductAsyncImageView(url: url) .frame(width: 150, height: 150) .mask(RoundedRectangle(cornerRadius: 16)) } VStack(alignment: .leading,spacing: 5){ Text("Product Name: " + (productData.products.first?.title ?? "")) .frame(maxWidth: .infinity, alignment: .leading) .font(.headline) Text("Product Description: " + (productData.products.first?.description ?? "")) .frame(maxWidth: .infinity, alignment: .leading) Text("Product Price: " + String(productData.products.first?.price ?? 0)) .frame(maxWidth: .infinity, alignment: .leading) .font(.subheadline) } } } }
调试时已确认获取到30条数据,但应用运行后仅显示1条记录:

问题原因
- ViewModel数据结构设计错误:
productLists被定义为[ProductData]类型,但接口返回的ProductData本身已经包含了一个[Product]数组(即30条产品数据)。在getProductList方法中,你将获取到的单个ProductData实例包装成数组[lists]赋值给productLists,导致列表循环的是只有1个元素的数组,自然只会生成1个单元格。 - 单元格视图逻辑错误:
ProductListViewCell接收的是ProductData对象,然后通过products.first仅取出第一条产品数据进行展示,这进一步导致单个单元格也只显示一条产品,而非遍历所有产品。
修改方案
1. 调整ViewModel的数据结构
将productLists的类型从[ProductData]改为[Product],直接存储产品数组,而非包装后的ProductData:
@MainActor final class ProductListViewModel { // 修改类型为[Product] @Published private(set) var productLists: [Product] = [] @Published private(set) var customError: NetworkError? @Published private(set) var refreshing = true @Published var isErrorOccured = false private let repository: ProductRepository init(repository: ProductRepository) { self.repository = repository } } extension ProductListViewModel: ProductListViewModelAction { func getProductList(urlStr: String) async { refreshing = true guard let url = URL(string: urlStr) else { self.customError = NetworkError.invalidURL refreshing = false isErrorOccured = false return } do { let lists = try await repository.getList(for: url) refreshing = false isErrorOccured = false // 直接赋值products数组 productLists = lists.products } catch { refreshing = false isErrorOccured = true customError = error as? NetworkError } } }
2. 更新视图代码,遍历产品数组
修改ProductListView中的列表部分,直接遍历productLists(现在是[Product]数组),并传递单个Product给单元格:
// 替换原列表代码 if viewModel.productLists.count > 0 && !viewModel.refreshing { List(viewModel.productLists, id: \.self) { product in ProductListViewCell(product: product) } .listStyle(.grouped) }
3. 重构单元格视图,接收单个Product对象
修改ProductListViewCell,将输入参数改为单个Product,并直接使用该对象的属性展示:
import SwiftUI struct ProductListViewCell: View { // 修改为接收单个Product let product: Product var body: some View { HStack { if let url = URL(string: product.thumbnail){ ProductAsyncImageView(url: url) .frame(width: 150, height: 150) .mask(RoundedRectangle(cornerRadius: 16)) } VStack(alignment: .leading,spacing: 5){ Text("Product Name: " + product.title) .frame(maxWidth: .infinity, alignment: .leading) .font(.headline) Text("Product Description: " + product.description) .frame(maxWidth: .infinity, alignment: .leading) Text("Product Price: " + String(product.price)) .frame(maxWidth: .infinity, alignment: .leading) .font(.subheadline) } } } }
内容的提问来源于stack exchange,提问作者user21666271
相关产品推荐
相关产品推荐

