Reputation: 113
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
struct CustomerList: View {
@State var customerList = searchCustomer(criteria: "jose")
var body: some View {
List(self.customerList, id: \.id){ customer in
CellRow(customer: customer)
}
}
}
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
}
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
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