Jhonnytunes
Jhonnytunes

Reputation: 113

Why is Swift @escaping closure not working?

I'm trying to show a list of Customers when the app(iOS) main view loads. The data is returned from an API request in JSON format which I confirmed I'm getting in the debugger. I'm having issues getting the response from the main view because the fetch method is getting called after the view load method finished. As I read, I should add a completion handler to the fetch method and pass it as an escaping enclosure so the main method waits for the fetch method. I follow this guide and read it several times and still can't get where the error is. What am I missing?

This

  1. This is the SwiftUI view where I'm calling the search method:
    struct CustomerList: View {
    
    @State var customerList = searchCustomer(criteria: "jose")
    
    
    
    var body: some View {
        List(self.customerList, id: \.id){ customer in
            CellRow(customer: customer)
        
        }
     }
    }
  1. This is the search function(Is actually a function, not part of any class or struct) called in the view load event
    func searchCustomer(criteria: String) -> [Customer] {
    
    //API call full url
    let fullURL = FTP_API_URL + criteria
    var customerList = [Customer]()
    
    if criteria == ""{
        return customerList
    }
    
    getJsonFromAPI(url: fullURL, fetchCompletionHandler: {customers, error in
        
        if let jsonCustomers = customers{
            customerList = jsonCustomers.customers
        }
    })
    return customerList
  }
  1. The fetch async function(Is actually a function, not part of any class or struct)
func getJsonFromAPI(url: String, fetchCompletionHandler : @escaping (Customers?, Error?)-> Void){
    
    let username = decryptData(data: FTP_API_USERNAME)
    let password = decryptData(data: FTP_API_PASSWORD)
    
    let loginData = String(format: "%@:%@", username, password).data(using: String.Encoding.utf8)!
    let base64LoginData = loginData.base64EncodedString()
    
    
    // create the request
    let url = URL(string: url)!
    var request = URLRequest(url: url)
    request.httpMethod = "GET"
    request.setValue("Basic \(base64LoginData)", forHTTPHeaderField: "Authorization")
    
    //making the request
    let task = URLSession.shared.dataTask(with: request) { data, response, error in
        
        guard let data = data, error == nil else {
            print("\(error)")
            return
            
            
        }
        
        //if let httpStatus = response as? HTTPURLResponse {
        // check status code returned by the http server
        //print("status code = \(httpStatus.statusCode)")
        // process result
        //        }
        
        let jsonDecoder = JSONDecoder()
        
        do{
            
            let results = try jsonDecoder.decode(Customers.self, from: data)
            
            //            for result in results.customers{
            //                print(result.cedulaOrPassport)
            //            }
            
            fetchCompletionHandler(results, nil)
            
        }
        catch{
            print(error)
            fetchCompletionHandler(nil, error)
        }
        
    }
    task.resume()
    
    
}

Thanks in advance.

Upvotes: 1

Views: 1721

Answers (1)

New Dev
New Dev

Reputation: 49590

return customerList in searchCustomer happens synchronously when the data (that's obtained asynchronously from getJsonFromAPI) isn't yet available. So, you're assigning and empty [Customer] array to @State var customerList.

In any case, you can't directly assign an asynchronously-obtained value to a property.

Instead, change searchCustomer to also accept a completion handler, and use .onAppear to invoke it and assign the value from within a completion handler (just like you with getJsonFromAPI):

func searchCustomer(criteria: String, 
                    completionHandler: @escaping ([Customer]) -> Void) -> Void {
    
    //API call full url
    let fullURL = FTP_API_URL + criteria
    var customerList = [Customer]()
    
    if criteria == "" {
        completionHandler(customerList)
    }
    
    getJsonFromAPI(url: fullURL, fetchCompletionHandler: {customers, error in
        
        if let jsonCustomers = customers{
            customerList = jsonCustomers.customers
            completionHandler(customerList)
        }
    })
  }
struct CustomerList: View {
    
    @State var customerList = []

    var body: some View {
        List(self.customerList, id: \.id){ customer in
            CellRow(customer: customer)
        }
        .onAppear() {
           searchCustomer(criteria: "jose") { customers in
              customerList = customers
           }
        }
    }
}

Upvotes: 1

Related Questions