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

SwiftUI中如何在List视图实现数据懒加载?

实现SwiftUI List滚动到底部自动加载更多数据

现有代码的核心问题

  1. 数据覆盖而非追加:当前self.cards = results.results会直接替换原有列表,加载下一页后之前的卡片会消失,无法实现滚动加载的累加效果。
  2. 无重复请求防护:没有加载状态控制,滚动时可能多次触发请求,导致currentPage错误递增,请求无效的页码。
  3. 缺少滚动触底检测:没有逻辑判断用户滚动到列表最后一项,无法自动触发下一页数据请求。

修改后的NetworkManager.swift

import Foundation

class NetworkManager: ObservableObject {
    
    @Published var cards = [CardInfo]()
    private var currentPage = 1
    @Published var isLoading = false // 添加加载状态,防止重复请求
    
    func fetchData() {
        guard !isLoading else { return } // 正在加载时直接返回,避免重复请求
        isLoading = true
        
        if let url = URL(string: "https://schoolido.lu/api/cards/?page=\(currentPage)") {
            let session = URLSession(configuration: .default)
            let task = session.dataTask(with: url) { [weak self] (data, response, error) in
                defer {
                    DispatchQueue.main.async {
                        self?.isLoading = false // 请求结束后重置加载状态
                    }
                }
                
                if let error = error {
                    print("请求错误:\(error)")
                    return
                }
                
                guard let safeData = data else {
                    print("无返回数据")
                    return
                }
                
                do {
                    let results = try JSONDecoder().decode(Results.self, from: safeData)
                    DispatchQueue.main.async {
                        self?.cards.append(contentsOf: results.results) // 追加新数据,而非覆盖
                        self?.currentPage += 1 // 请求成功后页码递增,此处代码可正常生效
                    }
                } catch {
                    print("解析错误:\(error)")
                }
            }
            task.resume()
        }
    }
}

修改后的IdolListView.swift

import SwiftUI

struct IdolListView: View {
    @ObservedObject var networkManager = NetworkManager()
    
    @State private var searchString = ""
    @State var thumbImage: UIImage = UIImage()
    
    var body: some View {
        NavigationView {
            List(networkManager.cards.enumerated().map({ $0 }), id: \.element.id) { index, card in
                NavigationLink(destination: IdolDetailView(
                    card_idolized_image: card.card_idolized_image,
                    idol_name: card.idol.name,
                    idol_name_jp: card.idol.japanese_name,
                    idol_school: card.idol.school,
                    idol_year: card.idol.year,
                    idol_subunit: card.idol.sub_unit), label: {
                    HStack(alignment: .center) {
                        AsyncImage(url: URL(string: "https:\(card.round_card_image)")) { thumbImage in
                            thumbImage.image?.resizable()
                        }
                        .frame(width: 48, height: 48)
                        
                        VStack(alignment: .leading) {
                            Text(card.idol.name)
                                .fontWeight(.semibold)
                            
                            Text(card.idol.school)
                                .font(.callout)
                                .padding(.bottom, 0.25)
                            
                            if let sub_unitString = card.idol.sub_unit {
                                Text("\(card.attribute) • \(card.idol.year) year • \(sub_unitString)")
                                    .font(.caption)
                                    .foregroundColor(.gray)
                            } else {
                                Text("\(card.attribute) • \(card.idol.year) year")
                                    .font(.caption)
                                    .foregroundColor(.gray)
                            }
                        }
                    }
                })
                // 检测滚动到最后一个元素时触发加载更多
                .onAppear {
                    if index == networkManager.cards.count - 1 && !networkManager.isLoading {
                        networkManager.fetchData()
                    }
                }
            }
            .searchable(text: $searchString)
            .navigationTitle("School idols")
        }
        .onAppear {
            if networkManager.cards.isEmpty {
                networkManager.fetchData()
            }
        }
    }
}

struct IdolListView_Previews: PreviewProvider {
    static var previews: some View {
        IdolListView()
    }
}

关键修改说明

  1. 数据追加逻辑:把替换列表的赋值改为append(contentsOf:),确保新页数据添加到现有列表末尾,实现滚动加载的累加效果。
  2. 加载状态防护:新增isLoading变量,请求开始时锁定状态,结束后解锁,避免滚动时重复触发请求导致页码错误。
  3. 触底检测触发:通过enumerated()获取元素索引,当滚动到最后一个元素且当前未在加载时,自动调用加载下一页的方法。
  4. currentPage有效性:现在页码只会在请求成功后递增,配合加载状态防护,不会出现无效递增的情况,你之前添加的currentPage += 1代码可以正常生效。

内容的提问来源于stack exchange,提问作者John Paul

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.20 15:39:58