Return Value from Completion Handler - Swift

Return value from completion handler - Swift

Add a handler to your loadImage func:

Swift 3

 func loadImage(_ urlString: String, handler:@escaping (_ image:UIImage?)-> Void)
{

let imageURL: URL = URL(string: urlString)!

URLSession.shared.dataTask(with: imageURL) { (data, _, _) in
if let data = data{
handler(UIImage(data: data))
}
}.resume()
}

Call func like this:

loadImage("SomeURL") { (image) -> Void in
if let image = image{
DispatchQueue.main.async {
self.imageView.image = image
}
}
}

Swift 2.3

func loadImage(urlString: String, handler: (image:UIImage?)-> Void)
{

let imageURL: NSURL = NSURL(string: urlString)!

NSURLSession.sharedSession().dataTaskWithURL(imageURL) { (data, _, _) in
if let data = data{
handler(image: UIImage(data: data))
}
}.resume()
}

Call func like this:

  loadImage("someURL") { (image) -> Void in
if let image = image{
dispatch_async(dispatch_get_main_queue()) {
self.imageView.image = image
}
}
}

how to use the return value in a completion handler?

You're returning the value into the productToString function but not doing anything else with it.

func productToString(num: Int,num2: Int,completion: (Int)->String){

let result = num * num2

completion(result) // <--- Your return value ends up here
}

If you want to print the result you have to return it again out of the productToString function.

func productToString(num: Int,num2: Int,completion: (Int)->String) -> String {

let result = num * num2

return completion(result)
}

Sidenote: The empty brackets that are printed are an empty tuple which is equivalent to Void in Swift.

How to display in a View a returned value from a completion handler in Swift?

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

Completion handler swift 3 return a variable from function

Two issues:

  • The completion handler passes a HistoryKey instance and has no return value so the signature must be the other way round.
  • The call of the completion handler must be inside the completion block of the data task.

To be able to parse the received data outside the completion block return the data on success

enum ConnectionResult {
case success(Data)
case failure(Error)
}

private func getHistoryKeys(searchterm: String, completion: @escaping (ConnectionResult) -> ()) {
let url = PubmedAPI.createEsearchURL(searchString: searchterm)
let task = session.dataTask(with: url) { (data, response, error) in
if let error = error {
completion(.failure(error))
} else {
completion(.success(data!))
}
}
task.resume()
}

and call it

getHistoryKeys(searchterm: String) { connectionResult in 
switch connectionResult {
case .success(let data):
let myParser = XMLParser(data: data)
myParser.delegate = self
myParser.parse()
// get the parsed data from the delegate methods

case .failure(let error): print(error)
}
}

swift - how to return from a within a completion handler closure of a system function?

You can't do in this way; getNotificationSettings is asynchronous, so you should pass a closure in the method and call right after the switch.
Something like this:

static func isNotificationNotDetermined(completion: (Bool) -> Void) {

UNUserNotificationCenter.current().getNotificationSettings { (notificationSettings) in
var isNotDetermined = false
switch notificationSettings.authorizationStatus {
case .notDetermined:
isNotDetermined = true

case .authorized:
isNotDetermined = false

case .denied:
isNotDetermined = false

}

// call the completion and pass the result as parameter
completion(isNotDetermined)
}

}

Then you will call this method like this:

    YourClass.isNotificationNotDetermined { isNotDetermined in
// do your stuff
}

How can I chain completion handlers if one method also has a return value?

You can create a wrapper for your task and return that instead.

protocol Task {
func cancel()
}

class URLSessionTaskWrapper: Task {
private var completion: ((Result<(Data, HTTPURLResponse), Error>) -> Void)?

var wrapped: URLSessionTask?

init(_ completion: @escaping (Result<(Data, HTTPURLResponse), Error>) -> Void) {
self.completion = completion
}

func complete(with result: Result<(Data, HTTPURLResponse), Error>) {
completion?(result)
}

func cancel() {
preventFurtherCompletions()
wrapped?.cancel()
}

private func preventFurtherCompletions() {
completion = nil
}
}

Your entire playground would become

protocol TokenLoader {
func load(_ key: String, completion: @escaping (String?) -> Void)
}

protocol Client {
func dispatch(_ request: URLRequest, completion: @escaping (Result<(Data, HTTPURLResponse), Error>) -> Void) -> Task
}

class AuthTokenLoader: TokenLoader {
func load(_ key: String, completion: @escaping (String?) -> Void) {
print("was called")
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
completion("some.access.token")
}
}
}

protocol Task {
func cancel()
}

class URLSessionTaskWrapper: Task {
private var completion: ((Result<(Data, HTTPURLResponse), Error>) -> Void)?

var wrapped: URLSessionTask?

init(_ completion: @escaping (Result<(Data, HTTPURLResponse), Error>) -> Void) {
self.completion = completion
}

func complete(with result: Result<(Data, HTTPURLResponse), Error>) {
completion?(result)
}

func cancel() {
preventFurtherCompletions()
wrapped?.cancel()
}

private func preventFurtherCompletions() {
completion = nil
}
}

class Networking: Client {

private let loader: TokenLoader

init(loader: TokenLoader) {
self.loader = loader
}

func dispatch(_ request: URLRequest, completion: @escaping (Result<(Data, HTTPURLResponse), Error>) -> Void) -> Task {
let task = URLSessionTaskWrapper(completion)

loader.load("token") { token in

task.wrapped = URLSession.shared.dataTask(with: request, completionHandler: { data, response, error in
if let error = error {
task.complete(with: .failure(error))
} else if let data = data, let response = response as? HTTPURLResponse {
task.complete(with: .success((data, response)))
}
})

task.wrapped?.resume()
}

return task
}
}

let loader = AuthTokenLoader()
let client = Networking(loader: loader)

let request = URL(string: "https://jsonplaceholder.typicode.com/todos/1")!
client.dispatch(.init(url: request), completion: { print($0) })

Swift Combine Completion Handler with return of values

You're trying to combine Combine with old asynchronous code. You can do it with Future, check out more about it in this apple article:

Future { promise in
signinModel.login { success in

if success == true {
promise(Result.success(()))
}
else {
promise(Result.failure(Error.unknown))
}

}
}
.flatMap { _ in
// repeat request if login succeed
request(ofType: type, from: endpoint, body: body)
}.eraseToAnyPublisher()

But this should be done when you cannot modify the asynchronous method or most of your codebase uses it.

In your case it looks like you can rewrite login to Combine. I can't build your code, so there might be errors in mine too, but you should get the idea:

func login() -> AnyPublisher<Void, Error> {

self.loginState = .loading

let preparedBody = APIPrepper.prepBody(parametersDict: ["username": self.credentials.username, "password": self.credentials.password])

return service.request(ofType: UserLogin.self, from: .login, body: preparedBody)
.handleEvents(receiveCompletion: { res in
if case let .failure(error) = res {
(self.banner.message,
self.banner.stateIdentifier,
self.banner.type,
self.banner.show) = (error.errorMessage, error.statusCode, "error", true)
self.loginState = .failed(stateIdentifier: error.statusCode, errorMessage: error.errorMessage)
}
})
.flatMap { loginResult in
if loginResult.token != nil {
self.loginState = .success
self.token.token = loginResult.token!

_ = KeychainStorage.saveCredentials(self.credentials)
_ = KeychainStorage.saveAPIToken(self.token)

return Just(Void()).eraseToAnyPublisher()
} else {
(self.banner.message, self.banner.stateIdentifier, self.banner.type, self.banner.show) = ("ERROR",
"TOKEN",
"error",
true)
self.loginState = .failed(stateIdentifier: "TOKEN", errorMessage: "ERROR")
return Fail(error: Error.unknown).eraseToAnyPublisher()
}
}
.eraseToAnyPublisher()
}

And then call it like this:

signinModel.login()
.flatMap { _ in
request(ofType: type, from: endpoint, body: body)
}.eraseToAnyPublisher()


Related Topics



Leave a reply



Submit