最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

how to create a callback object in a swiftui view - Stack Overflow

programmeradmin7浏览0评论

I'm new to swiftui and I'm trying to call a method and pass a callback object:

Callback protocol

protocol AsyncJobCallback {
    func onLoadingStarted()
    func onDone<T: Decodable>(_ response: T)
    func onError(_ error: Error?)
}

Main view

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()
        }
    }
}

Job class

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)
                }
            }
        }
    }
}

what is the proper way to pass a callback to a method an change ui depends on callback return?

I'm new to swiftui and I'm trying to call a method and pass a callback object:

Callback protocol

protocol AsyncJobCallback {
    func onLoadingStarted()
    func onDone<T: Decodable>(_ response: T)
    func onError(_ error: Error?)
}

Main view

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()
        }
    }
}

Job class

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)
                }
            }
        }
    }
}

what is the proper way to pass a callback to a method an change ui depends on callback return?

Share Improve this question asked Mar 20 at 16:21 devopsdevops 9,2036 gold badges50 silver badges77 bronze badges
Add a comment  | 

1 Answer 1

Reset to default 1
  • You should use @AppStorage instead of AppPreferences.shared.firstStart. SwiftUI cannot observe changes in the latter (unless AppPreferences is @Observable and firstStart is a stored property).
  • You must show something in the beginning. You cannot wait until onLoadingStarted to display anything. That effectively makes the protocol:
@MainActor
protocol AsyncJobCallback {
    func onDone<T: Decodable>(_ response: T)
    func onError(_ error: (any Error)?)
}

It's unusual that onDone can receive any Decodable type. You probably intended to do this instead:

@MainActor
protocol AsyncJobCallback<Response> {
    associatedtype Response: Decodable
    func onDone(_ response: Response)
    func onError(_ error: (any Error)?)
}

And GuestSignUpAsyncJob would store a AsyncJobCallback<ApiAccessToken>.

In any case, your view can then implement the callback methods like this:

struct ContentView: View, AsyncJobCallback {
    @AppStorage("firstStart") var firstStart = true
    @State var error: (any Error)?
    
    var body: some View {
        
        if firstStart {
            ProgressView()
                .onAppear {
                    ApiClient.shared.requestAccessToken(callback: self)
                }
        } else if let error {
            // display the error in some way...
            Text(verbatim: "\(error)")
        } else {
            MainTabsView()
        }
    }
    
//  func onDone(_ response: ApiAccessToken)
    func onDone<T>(_ response: T) where T : Decodable {
        firstStart = false
    }
    
    func onError(_ error: (any Error)?) {
        self.error = error
    }
}

Finally, instead of using callbacks like this, I strongly suggest that you embrace Swift Concurrency, and rewrite requestAccessToken to have a signature like this:

func requestAccessToken() async throws -> ApiAccessToken

Then you can write:

ProgressView()
    .task {
        do {
            _ = try await requestAccessToken()
            firstStart = false
        } catch {
            self.error = error
        }
    }
发布评论

评论列表(0)

  1. 暂无评论