I have a class that has these two methods:
private func send(method: String, path: String, code: Array<Int>, headers: HTTPHeaders, completionHandler: @escaping (Int) -> Void) {
let url: String = "\(self.credentials.url)/\(path)"
AF.request(url, method: HTTPMethod(rawValue: method), headers: headers)
.authenticate(with: self.request_credentials)
.response { response in
let status_code: Int = response.response!.statusCode
completionHandler(status_code as Int)
}
}
And
func list_files(path: String) {
let headers: HTTPHeaders = [
"Depth": "1"
]
send(method: "PROPFIND", path: path, code: [207, 301], headers: headers) { status_code in
self.status_code = String(status_code)
}
}
So both of these functions use completion handlers because of the fact that Alamofire uses them in its process of making HTTP requests.
I understood that I have to use completion handlers this way to handle this async data.
My current issue is that now I have to display this data (let's say self.status_code
) in a view but I have no idea how to do this. When I display it like this :
struct ContentView: View {
var body: some View {
let auth = Authentication(username: "****",
domain: "****",
password: "****",
port: ****,
proto: "****",
path:"****")
let commands = Commands(credentials: auth.get_credentials())
let _ = commands.list_files(path: "/")
Text(commands.status_code)
.padding()
}
}
It will display the initialization value of status_code (which is 0) instead of the updated value by list_files method. I know this is because of the asynchronous behavior of these completion handlers and the value is not yet updated when I display it.
But here's the question: how can I manage to properly display this updated value to the user?
I'm probably doing lots of things wrong here and I don't mind receiving a completely different solution since I'm willing to follow the best practices.
Thank you.
Below is a Playground with a complete example. I'll walk through some of the important things to note.
First I simplified the "send" method since I don't have all the types and things from your original example. This send will wait 3 seconds then call the completion handler with whatever message you give it.
Inside the view, when the button is pushed, we call "send". Then, in the completion handler you'll notice:
DispatchQueue.main.async {
message = msg
}
I don't know what thread the Timer
in send is going to use to call my completion handler. But UI updates need to happen on the main thread, so the DispatchQueue.main...
construct will ensure that the UI update (setting message
to msg
) will happen on the main thread.
import UIKit
import SwiftUI
import PlaygroundSupport
func send(message: String, completionHandler: @escaping (String) -> Void) {
Timer.scheduledTimer(withTimeInterval: 3.0, repeats: false) { timer in
completionHandler(message)
timer.invalidate()
}
}
struct ContentView: View {
@State var message : String = ""
var body: some View {
print("Building view")
return VStack {
TextField("blah", text: $message)
Button("Push Me", action: {
send(message: "Message Received") { msg in
DispatchQueue.main.async {
message = msg
}
}
})
}.frame(width: 320, height: 480, alignment: .center)
}
}
let myView = ContentView()
let host = UIHostingController(rootView: myView)
PlaygroundSupport.PlaygroundPage.current.liveView = host