You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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条记录:
调试结果
应用截图


问题原因

  1. ViewModel数据结构设计错误:productLists被定义为[ProductData]类型,但接口返回的ProductData本身已经包含了一个[Product]数组(即30条产品数据)。在getProductList方法中,你将获取到的单个ProductData实例包装成数组[lists]赋值给productLists,导致列表循环的是只有1个元素的数组,自然只会生成1个单元格。
  2. 单元格视图逻辑错误: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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.07 23:08:10