SwiftUI中延迟.task执行及LoveViewModel数据初始化优化问题
问题1:提前发起API请求、跳转后先显示默认数据的修复
你的代码中LoveCalculator视图的.task修饰符会在视图渲染完成后立即执行loveCal方法,此时firstName和secondName为空字符串,导致提前发起无效请求;同时loveData初始化了默认值,跳转后会先显示默认内容,直到API请求完成才更新。
修复要点:
- 移除
.task修饰符,将API请求触发时机改为点击"CHECK IT!"按钮时 - 禁用按钮直到两个输入框都有内容,避免空参数请求
- 配合可选类型的
loveData,确保数据加载完成后再跳转
问题2:将loveData改为可选类型的实现
完全可以把loveData设为可选类型,无需默认值。同时建议添加加载状态和错误状态,提升用户体验。
修改后的完整代码
1. LoveData(无需修改)
struct LoveData: Codable { let percentage: String let result: String }
2. LoveViewModel
class LoveViewModel: ObservableObject { // 改为可选类型,取消默认值 @Published var loveData: LoveData? @Published var isLoading = false @Published var errorMessage: String? let baseURL = "https://love-calculator.p.rapidapi.com/getPercentage" let apiKey = "c6c134a7f0msh980729b528fe273p1f337fjsnd17137cb2f24" func calculateLove(first: String, second: String) async { guard !first.isEmpty, !second.isEmpty else { errorMessage = "请输入完整姓名" return } isLoading = true errorMessage = nil // 使用URLComponents构建URL,避免字符串拼接的编码问题 guard var components = URLComponents(string: baseURL) else { errorMessage = "URL格式错误" isLoading = false return } components.queryItems = [ URLQueryItem(name: "sname", value: first), URLQueryItem(name: "fname", value: second), URLQueryItem(name: "rapidapi-key", value: apiKey) ] guard let url = components.url else { errorMessage = "无法构建请求URL" isLoading = false return } do { var request = URLRequest(url: url) // 符合rapidapi接口要求的Host头 request.setValue("love-calculator.p.rapidapi.com", forHTTPHeaderField: "X-RapidAPI-Host") let (data, response) = try await URLSession.shared.data(for: request) // 检查HTTP响应状态 guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 else { errorMessage = "请求失败,请重试" isLoading = false return } let decodedData = try JSONDecoder().decode(LoveData.self, from: data) DispatchQueue.main.async { self.loveData = decodedData } } catch { errorMessage = "请求出错:\(error.localizedDescription)" print(error) } DispatchQueue.main.async { self.isLoading = false } } }
3. LoveCalculator View
struct LoveCalculator: View { @State var firstName = "" @State var secondName = "" @State private var isResultPresented = false @StateObject var loveViewModel = LoveViewModel() // 用@StateObject确保生命周期一致 var body: some View { NavigationView { ZStack{ Color(.systemTeal).ignoresSafeArea() VStack(alignment: .center) { Text("Love Calculator") .padding(.top, 100) Text("输入第一个姓名:") .padding() TextField("姓名", text: $firstName) .textFieldStyle(.roundedBorder) .frame(maxWidth: 300) .padding(.bottom) Text("输入第二个姓名:") .padding() TextField("姓名", text: $secondName) .textFieldStyle(.roundedBorder) .frame(maxWidth: 300) .padding(.bottom) // 显示错误提示 if let error = loveViewModel.errorMessage { Text(error) .foregroundColor(.red) .padding(.bottom) } // 点击按钮触发请求,成功后跳转 Button(action: { Task { await loveViewModel.calculateLove(first: firstName, second: secondName) // 仅当数据加载成功时跳转 if loveViewModel.loveData != nil { isResultPresented = true } } }) { if loveViewModel.isLoading { ProgressView() .foregroundColor(.white) } else { Text("CHECK IT!") .foregroundColor(.white) .padding() .background(Color.blue) .cornerRadius(8) } } // 输入为空时禁用按钮 .disabled(firstName.isEmpty || secondName.isEmpty) Spacer() } // 通过isPresented控制导航跳转 NavigationLink(isActive: $isResultPresented) { if let data = loveViewModel.loveData { LoveResults(percentage: data.percentage, description: data.result) } else { Text("暂无数据") } } label: { EmptyView() } } } } }
4. LoveResults View(无需修改)
struct LoveResults: View { var percentage: String var description: String var body: some View { ZStack{ Color(.green).ignoresSafeArea() VStack{ Text("RESULTS :") .padding() Text(percentage) .font(.system(size: 80, weight: .heavy)) .foregroundColor(.red) .padding() Text(description) } } } }
关键修改说明
- ViewModel:
- 将
loveData改为可选类型,取消默认值 - 添加
isLoading和errorMessage状态,用于显示加载提示和错误信息 - 使用
URLComponents构建请求URL,避免姓名含特殊字符时的编码问题 - 完善错误处理逻辑,覆盖空输入、URL构建失败、HTTP响应异常等场景
- 将
- 视图:
- 用
@StateObject替代@ObservedObject,确保ViewModel与视图生命周期同步 - 移除自动触发的
.task,将请求放在按钮点击事件中 - 按钮根据输入状态禁用,避免无效请求
- 实现"先请求再跳转"的逻辑,确保跳转时已有有效数据
- 用
内容的提问来源于stack exchange,提问作者michalbarnat
相关产品推荐
相关产品推荐

