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

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返回的字段
}

关键说明

  1. @StateObject的使用:在VehicleScreen中用@StateObject初始化ViewModel,确保视图生命周期内实例稳定,避免重复创建。
  2. 状态驱动视图:通过ViewModel的isLoading、error、vehicle三个@Published变量,让视图自动根据状态切换展示内容。
  3. 异步逻辑封装:将异步请求完全封装在ViewModel中,视图只负责触发请求和展示状态,符合MVVM架构职责分离原则。
  4. 错误处理优化:给NetworkError实现LocalizedError协议,直接展示用户友好的错误提示。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.17 14:25:26