SwiftUI中如何在List视图实现数据懒加载?
实现SwiftUI List滚动到底部自动加载更多数据
现有代码的核心问题
- 数据覆盖而非追加:当前
self.cards = results.results会直接替换原有列表,加载下一页后之前的卡片会消失,无法实现滚动加载的累加效果。 - 无重复请求防护:没有加载状态控制,滚动时可能多次触发请求,导致
currentPage错误递增,请求无效的页码。 - 缺少滚动触底检测:没有逻辑判断用户滚动到列表最后一项,无法自动触发下一页数据请求。
修改后的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() } }
关键修改说明
- 数据追加逻辑:把替换列表的赋值改为
append(contentsOf:),确保新页数据添加到现有列表末尾,实现滚动加载的累加效果。 - 加载状态防护:新增
isLoading变量,请求开始时锁定状态,结束后解锁,避免滚动时重复触发请求导致页码错误。 - 触底检测触发:通过
enumerated()获取元素索引,当滚动到最后一个元素且当前未在加载时,自动调用加载下一页的方法。 - currentPage有效性:现在页码只会在请求成功后递增,配合加载状态防护,不会出现无效递增的情况,你之前添加的
currentPage += 1代码可以正常生效。
内容的提问来源于stack exchange,提问作者John Paul
相关产品推荐
相关产品推荐

