如何在Swift中将API请求的数据存入列表并在ContentView展示
解决SwiftUI中展示网球选手排名数据的问题
要把API获取的网球选手数据展示在SwiftUI列表中,核心是通过状态管理让视图响应数据变化,以下是具体实现步骤:
1. 创建数据管理ViewModel
首先创建一个遵循ObservableObject的ViewModel类,用来处理API请求和数据状态,这样数据更新时视图会自动刷新:
import Foundation class RankingsViewModel: ObservableObject { // 存储选手排名数据,@Published修饰会自动通知视图更新 @Published var rankings: [Ranking] = [] // 加载状态,用于显示加载提示 @Published var isLoading = false // 错误信息,用于展示请求失败提示 @Published var errorMessage: String? private let apiURL = "https://tennisapi1.p.rapidapi.com/api/tennis/rankings/atp/?rapidapi-key=APIKEY" func fetchRankings() { isLoading = true errorMessage = nil let task = URLSession.shared.dataTask(with: URL(string: apiURL)!) { [weak self] data, response, error in DispatchQueue.main.async { self?.isLoading = false guard let data = data, error == nil else { self?.errorMessage = "请求失败,请检查网络" return } do { let result = try JSONDecoder().decode(RankingsResponse.self, from: data) self?.rankings = result.rankings } catch { self?.errorMessage = "数据解析失败:\(error.localizedDescription)" } } } task.resume() } } // 保留你原来的Codable结构体 // MARK: - RankingsResponse struct RankingsResponse: Codable { let rankings: [Ranking] let updatedAtTimestamp: Int } // MARK: - Ranking struct Ranking: Codable { let bestRanking: Int let bestRankingDateTimestamp: Int? let country: RankingCountry let id, points, previousPoints, previousRanking: Int let ranking: Int let rowName: String let team: Team let tournamentsPlayed, type: Int } // MARK: - RankingCountry struct RankingCountry: Codable { } // MARK: - Team struct Team: Codable { let country: TeamCountry let disabled: Bool? let gender: Gender let id: Int let name, nameCode: String let national: Bool let ranking: Int let shortName, slug: String let type, userCount: Int } // MARK: - TeamCountry struct TeamCountry: Codable { let alpha2, name: String } enum Gender: String, Codable { case m = "M" }
2. 修改ContentView展示数据
在ContentView中使用ViewModel,通过@StateObject实例化(iOS14+),然后用List遍历数据,同时处理加载、错误和空数据的状态:
import SwiftUI struct ContentView: View { // 持有ViewModel实例,StateObject确保视图生命周期内唯一 @StateObject private var viewModel = RankingsViewModel() var body: some View { NavigationStack { // iOS16+用NavigationStack,之前版本用NavigationView VStack(spacing: 16) { Button("获取排名") { viewModel.fetchRankings() } .buttonStyle(.borderedProminent) // 加载状态提示 if viewModel.isLoading { ProgressView("加载中...") } // 错误提示 else if let error = viewModel.errorMessage { Text(error) .foregroundColor(.red) .multilineTextAlignment(.center) .padding() } // 数据列表 else { List(viewModel.rankings) { ranking in HStack(alignment: .top, spacing: 12) { // 排名数字 Text("\(ranking.ranking)") .font(.title) .fontWeight(.bold) .foregroundColor(.gray) VStack(alignment: .leading, spacing: 4) { // 选手姓名 Text(ranking.rowName) .font(.headline) // 所属国家 Text(ranking.team.country.name) .font(.subheadline) .foregroundColor(.secondary) // 积分 Text("积分: \(ranking.points)") .font(.caption) } } } .listStyle(.plain) } } .navigationTitle("ATP排名") .padding() } } } // 让Ranking遵循Identifiable,这样List可以直接遍历 extension Ranking: Identifiable { var id: Int { self.id } // 用结构体里已有的id字段作为唯一标识 } struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView() } }
关键修改说明
- ViewModel状态管理:用
@Published修饰数据和状态,确保数据变化时视图自动刷新;API请求完成后切换到主线程更新UI,避免线程问题。 - 视图状态处理:分别展示加载中、错误、数据列表三种状态,提升用户体验。
- Identifiable扩展:让
Ranking遵循Identifiable,List可以直接使用数组而无需指定id参数。 - 视图结构调整:修正了原代码中ScrollView嵌套NavigationView的错误,使用正确的导航容器层级。
内容的提问来源于stack exchange,提问作者Tepeker
相关产品推荐
相关产品推荐

