Search code examples
swiftasynchronousescapingclosuresapi-design

How to load UI with escaping closures and async calls?


I've written a function called 'configureLabels()' that is supposed to make a 'GET' request and retrieve a value which is then supposed to be set as the text for a label. The request is async so I thought I would be able to use an escaping closure to update the UI when the request is finished being made. I'm relatively new to coding, so I am not sure what I've done wrong. I'd really appreciate anyone's help in figuring this out.

This is the code containing the 'configureLabels()' method:

import UIKit

import SwiftyJSON

class ItemDetailViewController: UIViewController {

@IBOutlet weak var numberOfFiberGrams: UILabel!

var ndbnoResults = [JSON]()
var ndbno = ""

let requestManager = RequestManager()

override func viewDidLoad() {
    super.viewDidLoad()
    // Do any additional setup after loading the view.
    configureLabels()
}

override func didReceiveMemoryWarning() {
    super.didReceiveMemoryWarning()
    // Dispose of any resources that can be recreated.
}


func configureLabels() {

    requestManager.ndbnoRequest(ndbno: ndbno) { (results) in
        let json = JSON(results)
        let fiber = json["food"]["nutrients"][7].dictionaryValue
        for (key, value) in fiber {
            if key == "value" {
                self.numberOfFiberGrams.text = "\(value.stringValue)"
            } else {
                self.numberOfFiberGrams.text = "Fail"
            }
        }
    }
}

/*
// MARK: - Navigation

// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    // Get the new view controller using segue.destinationViewController.
    // Pass the selected object to the new view controller.
}
*/

}

And here is the code containing the function that 'configureLabels()' calls:

    func ndbnoRequest(ndbno: String, apiKey: String, completionHandler: @escaping (_ results: JSON?) -> Void) {
    Alamofire.request("https://api.nal.usda.gov/ndb/V2/reports?ndbno=\(ndbno)&type=f&format=json&api_key=\(apiKey)", method: .get).validate().responseJSON { response in
        switch response.result {
        case .success(let value):
            let json = JSON(value)
            completionHandler(json)
            print("successful ndbno request")
        case .failure(let error):
            completionHandler(nil)
            print(error)
        }
    }
}

Solution

  • Your code looks ok only issue I have find with your code is you are not calling the completionHandler in failure part, You need to always call completion block so it will gave you idea have you got response or not as of your completionHandler argument is type of [JSON] as of your not having response in failure part you are not calling completionHandler in it. What you can do is make it optional and call completionHandler with nil argument in case of failure.

    func ndbnoRequest(ndbno: String, completionHandler: @escaping (_ results: [JSON]?) -> Void) {
        let parameters = ["api_key": "tIgopGnvNSP7YJOQ17lGVwazeYI1TVhXNBA2Et9W", "format": "json", "ndbno": "\(ndbno)"]
        Alamofire.request("https://api.nal.usda.gov/ndb/reports/V2", method: .get, parameters: parameters).responseJSON { response in
            switch response.result {
            case .success(let value):
                let json = JSON(value)
                let ndbnoResults = json["foods"].arrayValue
                completionHandler(ndbnoResults)
                print("successful ndbno request")
            case .failure(let error):
                completionHandler(nil)
                print("error with ndbno request")
            }
        }        
    }
    

    Now call it this way and wrapped the optional in completion block so you can confirm you get response.

    requestManager.ndbnoRequest(ndbno: ndbno) { (results) in
        if let result = results {
            let json = JSON(result)
            let fiber = json["food"]["nutrients"][7].dictionaryValue
            for (key, value) in fiber {
                if key == "value" {
                    self.numberOfFiberGrams.text = "\(value.stringValue)"
                } else {
                    self.numberOfFiberGrams.text = "Fail"
                }
            }
        }
        else {
            print("Problem to get response")
        }
    }