Swift中MVVM结合async/await调用API传递数据至视图的问题
问题解决:SwiftUI异步API调用与视图数据绑定
问题背景
用户在HomeView输入车牌号,点击搜索后弹出VehicleScreen,需要通过拼接车牌号调用API获取车辆信息并展示。之前尝试在VehicleScreen的init方法中调用异步API报错,用Task包裹也无法正确获取数据绑定到视图。
解决方案步骤
- ViewModel状态重构:将车辆数据、加载状态、错误信息移至
VehicleViewModel,用@Published修饰实现视图自动更新 - 异步请求触发时机:在
VehicleScreen的onAppear回调中启动Task执行API调用,避免在init中处理异步逻辑 - 视图状态处理:在视图中根据ViewModel的状态展示加载提示、车辆信息或错误提示
修改后的完整代码
HomeView(无需大幅修改)
struct HomeView: View { @State var userInput: String = "" @State var pressedSearch = false var body: some View { NavigationView { VStack { Text("Enter license plate number: ") HStack { TextField("e.g. 27382", text: $userInput) .textFieldStyle(RoundedBorderTextFieldStyle()) Button(action: { pressedSearch = true }, label: { Text("Search") }) .sheet(isPresented: $pressedSearch) { VehicleScreen(licensePlateNumber: userInput) } } .padding() } } } }
VehicleViewModel(重构状态管理)
class VehicleViewModel: ObservableObject { enum NetworkError: Error, LocalizedError { case badURL case badStatusCode case vehicleNotRegistered case illegalFormat case decodingError case unspecifiedError case noData var errorDescription: String? { switch self { case .badURL: return "无效的请求地址" case .badStatusCode: return "服务器响应异常" case .vehicleNotRegistered: return "该车牌号未注册" case .illegalFormat: return "车牌号格式非法" case .decodingError: return "数据解析失败" case .unspecifiedError: return "未知错误" case .noData: return "未获取到数据" } } } @Published var vehicle: Vehicle? @Published var isLoading = false @Published var error: NetworkError? func fetchCar(regnumber: String) async { isLoading = true error = nil vehicle = nil do { guard let url = URL(string: "https://www.example.com/" + regnumber) else { throw NetworkError.badURL } var request = URLRequest(url: url) request.httpMethod = "GET" request.allHTTPHeaderFields = ["key" : "value"] // 修正原代码键值对顺序错误 let (data, response) = try await URLSession.shared.data(for: request) guard let response = response as? HTTPURLResponse else { throw NetworkError.badStatusCode } guard response.statusCode == 200 else { switch response.statusCode { case 204: throw NetworkError.vehicleNotRegistered case 400: throw NetworkError.badURL case 422: throw NetworkError.illegalFormat default: throw NetworkError.unspecifiedError } } let car = try JSONDecoder().decode(Vehicle.self, from: data) vehicle = car } catch let networkError as NetworkError { self.error = networkError } catch { self.error = .decodingError } isLoading = false } }
VehicleScreen(调整视图逻辑)
struct VehicleScreen: View { let licensePlateNumber: String @StateObject private var vm = VehicleViewModel() // 使用@StateObject确保ViewModel生命周期稳定 var body: some View { VStack(spacing: 20) { if vm.isLoading { ProgressView("加载中...") } else if let error = vm.error { Text(error.localizedDescription) .foregroundColor(.red) } else if let vehicle = vm.vehicle { VStack(alignment: .leading) { Text("车辆名称: \(vehicle.name)") Text("车辆型号: \(vehicle.model)") // 根据实际Vehicle模型添加更多展示字段 } } else { Text("未获取到车辆信息") } } .padding() .navigationTitle("车辆信息") .onAppear { // 视图出现时启动异步请求 Task { await vm.fetchCar(regnumber: licensePlateNumber) } } } } // 示例Vehicle模型(需根据实际API返回结构调整) struct Vehicle: Codable { let name: String let model: String // 添加其他对应API返回的字段 }
关键说明
- @StateObject的使用:在
VehicleScreen中用@StateObject初始化ViewModel,确保视图生命周期内实例稳定,避免重复创建。 - 状态驱动视图:通过ViewModel的
isLoading、error、vehicle三个@Published变量,让视图自动根据状态切换展示内容。 - 异步逻辑封装:将异步请求完全封装在ViewModel中,视图只负责触发请求和展示状态,符合MVVM架构职责分离原则。
- 错误处理优化:给
NetworkError实现LocalizedError协议,直接展示用户友好的错误提示。
内容的提问来源于stack exchange,提问作者thedarknight
相关产品推荐
相关产品推荐

