SwiftUI视图中创建回调对象并通过回调更新UI的正确方法
SwiftUI中正确传递回调并更新UI的解决方案
我是SwiftUI新手,尝试调用方法并传递回调对象,已定义AsyncJobCallback协议、MainView视图及GuestSignUpAsyncJob类,想请教如何正确传递回调并根据回调返回结果更新UI。
现有代码
回调协议定义
protocol AsyncJobCallback { func onLoadingStarted() func onDone<T: Decodable>(_ response: T) func onError(_ error: Error?) }
主视图代码
struct MainView: View { var body: some View { if AppPreferences.shared.firstStart{ var callback = AsyncJobCallback() // .... ApiClient.shared.requestAccessToken(callback: callback) // request api access token // - onLoadingStarted: show loading view // - onDone: set firstStart to false, show MainTabsView // - onError: show error message } else { MainTabsView() } } }
任务类代码
class GuestSignUpAsyncJob { let callback: AsyncJobCallback init(callback: AsyncJobCallback) { self.callback = callback } func signUpAsGuest() { DispatchQueue.main.async { self.callback.onLoadingStarted() } var body = ApiUserGuestSignUp( name: "Guest" ) let endpoint = ApiPath.GUEST_REGISTRATION ApiClient.shared.postRequest(endpoint: endpoint, body: body) { data, response, error in if let error = error { DispatchQueue.main.async { self.callback.onError(error) } return } guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 201 else { DispatchQueue.main.async { // TODO create exception self.callback.onError(nil) } return } if let data = data { do { let token = try JSONDecoder().decode(ApiAccessToken.self, from: data) DispatchQueue.main.async { self.callback.onDone(token) } } catch { DispatchQueue.main.async { self.callback.onError(error) } } } else { DispatchQueue.main.async { // TODO create exception self.callback.onError(nil) } } } } }
解决方案
1. 创建遵循协议的回调管理类
协议无法直接实例化,需要创建具体类实现AsyncJobCallback,同时用ObservableObject和@Published标记状态变量,让SwiftUI能感知状态变化:
import SwiftUI class GuestSignUpCallback: ObservableObject, AsyncJobCallback { @Published var isLoading = false @Published var requestError: Error? @Published var receivedToken: ApiAccessToken? func onLoadingStarted() { isLoading = true requestError = nil receivedToken = nil } func onDone<T: Decodable>(_ response: T) { isLoading = false if let token = response as? ApiAccessToken { receivedToken = token // 更新全局配置,触发视图切换 AppPreferences.shared.firstStart = false } } func onError(_ error: Error?) { isLoading = false requestError = error } }
2. 修改主视图,集成回调并更新UI
在MainView中使用@StateObject持有回调实例,监听状态变化来更新UI:
struct MainView: View { @StateObject private var signUpCallback = GuestSignUpCallback() @State private var showErrorAlert = false var body: some View { Group { if AppPreferences.shared.firstStart { VStack(spacing: 20) { if signUpCallback.isLoading { ProgressView("正在注册...") } else { Button("游客身份进入") { let job = GuestSignUpAsyncJob(callback: signUpCallback) job.signUpAsGuest() } .buttonStyle(.borderedProminent) } } .alert(isPresented: $showErrorAlert) { Alert( title: Text("注册失败"), message: Text(signUpCallback.requestError?.localizedDescription ?? "未知错误"), dismissButton: .default(Text("重试")) ) } // 监听错误状态,触发弹窗 .onChange(of: signUpCallback.requestError) { error in showErrorAlert = error != nil } } else { MainTabsView() } } } }
关键说明
- 协议实例化问题:原代码中直接创建
AsyncJobCallback()实例是错误的,必须通过具体类实现协议。 - 状态驱动UI:SwiftUI依赖状态变化刷新视图,所以用
@Published标记需要监听的变量,结合ObservableObject让视图感知变化。 - 主线程更新:原任务类中已经用
DispatchQueue.main.async确保回调在主线程执行,符合SwiftUI更新UI的线程要求。 - 视图自动切换:当
AppPreferences.shared.firstStart被设为false后,MainView的body会重新计算,自动切换到MainTabsView。
内容的提问来源于stack exchange,提问作者devops
相关产品推荐
相关产品推荐

