SwiftUI适配API结构变更,正确获取分页事件数据的方法
解决方案:适配嵌套API结构的SwiftUI TabBar事件展示
1. 定义匹配API结构的Codable模型
根据你提供的API响应嵌套结构,创建对应的Codable模型,自动处理下划线字段与驼峰命名的转换:
import Foundation // 顶层API响应模型 struct EventResponse: Codable { let status: Int let message: String let data: EventData } // data字段的嵌套结构 struct EventData: Codable { let upcoming: EventSection? let past: EventSection? } // 包含事件数组与分页信息的结构 struct EventSection: Codable { let events: [Event] let pagination: PaginationInfo } // 事件对象模型(根据实际API返回字段补充) struct Event: Codable { let id: Int let title: String let eventDate: String let location: String? // 其他API返回的事件字段 } // 分页信息模型(根据实际API返回字段补充) struct PaginationInfo: Codable { let currentPage: Int let totalPages: Int let totalItems: Int }
2. 修正网络请求与数据解析逻辑
调整请求参数编码与解码逻辑,适配新的嵌套结构,同时优化错误捕获:
import Foundation func fetchEvents(timeline: Int, page: Int) async throws -> [Event] { guard let url = URL(string: "https://dev.myhss.org.uk/api/v1/events/eventlist") else { throw NetworkError.invalidURL } var request = URLRequest(url: url) request.httpMethod = "POST" request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type") // 编码请求参数为x-www-form-urlencoded格式 let parameters = [ "timeline": String(timeline), "current_page": String(page) ] request.httpBody = parameters.percentEncoded() let (data, response) = try await URLSession.shared.data(for: request) // 验证响应状态码 guard let httpResponse = response as? HTTPURLResponse, (200...299).contains(httpResponse.statusCode) else { throw NetworkError.invalidResponse } // 解码JSON,自动转换下划线为驼峰 let decoder = JSONDecoder() decoder.keyDecodingStrategy = .convertFromSnakeCase let eventResponse = try decoder.decode(EventResponse.self, from: data) // 根据timeline返回对应事件数组 switch timeline { case 1: return eventResponse.data.upcoming?.events ?? [] case 2: return eventResponse.data.past?.events ?? [] default: return [] } } // 辅助工具:字典转x-www-form-urlencoded数据 extension Dictionary where Key == String, Value == String { func percentEncoded() -> Data? { map { key, value in let escapedKey = key.addingPercentEncoding(withAllowedCharacters: .urlQueryValueAllowed)! let escapedValue = value.addingPercentEncoding(withAllowedCharacters: .urlQueryValueAllowed)! return "\(escapedKey)=\(escapedValue)" } .joined(separator: "&") .data(using: .utf8) } } extension CharacterSet { static let urlQueryValueAllowed: CharacterSet = { let generalDelimitersToEncode = ":#[]@" let subDelimitersToEncode = "!$&'()*+,;=" var allowed = CharacterSet.urlQueryAllowed allowed.remove(charactersIn: "\(generalDelimitersToEncode)\(subDelimitersToEncode)") return allowed }() } // 自定义网络错误枚举 enum NetworkError: Error, LocalizedError { case invalidURL case invalidResponse var errorDescription: String? { switch self { case .invalidURL: return "无效的请求地址" case .invalidResponse: return "服务器响应异常" } } }
3. SwiftUI TabBar视图实现
用TabView分别展示即将到来和过往事件,结合异步加载逻辑:
import SwiftUI struct EventTabView: View { @State private var upcomingEvents: [Event] = [] @State private var pastEvents: [Event] = [] @State private var isLoading = false @State private var errorMsg: String? var body: some View { TabView { EventList(events: upcomingEvents, isLoading: isLoading, errorMsg: errorMsg) .tabItem { Label("即将到来", systemImage: "calendar.badge.clock") } .onAppear { Task { await loadEvents(timeline: 1) } } EventList(events: pastEvents, isLoading: isLoading, errorMsg: errorMsg) .tabItem { Label("过往事件", systemImage: "calendar") } .onAppear { Task { await loadEvents(timeline: 2) } } } } private func loadEvents(timeline: Int) async { isLoading = true errorMsg = nil do { let events = try await fetchEvents(timeline: timeline, page: 1) timeline == 1 ? (upcomingEvents = events) : (pastEvents = events) } catch { errorMsg = "加载失败:\(error.localizedDescription)" } isLoading = false } } struct EventList: View { let events: [Event] let isLoading: Bool let errorMsg: String? var body: some View { List { if isLoading { ProgressView() .frame(maxWidth: .infinity) } else if let errorMsg = errorMsg { Text(errorMsg) .foregroundColor(.red) .frame(maxWidth: .infinity) } else if events.isEmpty { Text("暂无相关事件") .frame(maxWidth: .infinity) } else { ForEach(events, id: \.id) { event in VStack(alignment: .leading, spacing: 4) { Text(event.title) .font(.headline) Text(event.eventDate) .font(.subheadline) .foregroundColor(.secondary) if let location = event.location { Text(location) .font(.caption) } } .padding(.vertical, 8) } } } .navigationTitle("事件列表") } }
关键注意事项
- 确保
Event和PaginationInfo的字段完全匹配API返回的字段名,若API有新增字段,需同步更新模型。 - 若API返回的
upcoming/past可能为null,模型中需声明为可选类型(EventSection?),避免解码崩溃。 - 错误处理部分替换了原有的模糊报错,可直接展示具体错误信息,方便排查API请求或解码问题。
内容的提问来源于stack exchange,提问作者user20925367
相关产品推荐
相关产品推荐

