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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 19:53:15