Search code examples
iosswiftnetwork-programming

Swift 5: Display Data Retrieved in Closure to Table View


I've successfully obtained some data from my API in a closure within a function. I need to display the data to the table on my storyboard.

I know the reason why it's not displaying is because the data is saved within the closure and I can't access it from the outside unless I have a completion handler. I've read a bunch of other questions here on Stack Overflow but I can't really understand it. I've tried reloading the table as well, but it just returns an error of:

'Unexpectedly found nil'.

Claim Properties

class ClaimProperties {
    var id: Int
    var date: String
    var status: String
    
    init(id: Int, date: String, status: String) {
        self.id = id
        self.date = date
        self.status = status
    }
}

DashboardController

struct Claims: Decodable {
    let id: Int
    let submission_date: String
    let status: String
    init(json: [String:Any]) {
        id = json["id"] as? Int ?? -1
        submission_date = json["submission_date"] as? String ?? ""
        status = json["status"] as? String ?? ""
    }
}

class DashboardController: UIViewController, GIDSignInUIDelegate {
    
    @IBOutlet weak var tableView: UITableView!
    var tempArray: [ClaimProperties] = []
    
    override func viewDidLoad() {
        super.viewDidLoad()
        tableView.delegate = self
        tableView.dataSource = self
    }

    // getTokenFromAPI() gets called form AppDelegate.swift once the user logs in via Google API

    func getTokenFromAPI(usersAppToken:String) {
        guard let urlString = URL(string: "https://claim.ademo.work/claims/") else { return }
        var requestAPI = URLRequest(url: urlString)

        requestAPI.httpMethod = "GET"
        requestAPI.addValue("application/json", forHTTPHeaderField: "Content-Type")
        requestAPI.addValue("application/json", forHTTPHeaderField: "Accept")
        requestAPI.setValue("Bearer \(usersAppToken)", forHTTPHeaderField: "Authorization")

        URLSession.shared.dataTask(with: requestAPI) {
            (data, response, error) in
            if let data = data {
                do {

                    let json = try JSONDecoder().decode([Claims].self, from: data)
                    
                    for n in 0..<json.count {
                        self.tempArray.append(ClaimProperties(id: json[n].id, date: json[n].submission_date, status: json[n].status))
                    }

                    // This is the data I'm trying to display -> tempArray
                    
                } catch let error {
                    print("Localized Error: \(error.localizedDescription)")
                    print("Error: \(error)")
                }
            }
        }.resume()
    }
}

extension DashboardController: UITableViewDataSource, UITableViewDelegate {
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return tempArray.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "ClaimCell", for: indexPath) as! AllMyClaimsTable
        cell.idField.text = String(tempArray[indexPath.section].id)
        cell.dateField.text = tempArray[indexPath.section].date
        cell.statusField.text = tempArray[indexPath.section].status
        return cell
    }
}

In summary, what I'm trying to do is just to display API data to my table view.


Solution

  • Solution:

    After searching around and amending my code, below is the working solution:

    Dashboard Controller

    import UIKit
    
    struct Claims: Decodable {
        let id: Int
        let submission_date: String
        let status: String
        
        init(json: [String:Any]) {
            id = json["id"] as? Int ?? -1
            submission_date = json["submission_date"] as? String ?? ""
            status = json["status"] as? String ?? ""
        }
    }
    
    class DashboardController: UIViewController {
        
        @IBOutlet weak var dashboardMyClaim: UITableView!
        var myClaimArray: [PropertyExistingClaim] = []
        var idToken = ""
        var email = ""
        var appToken = ""
        
        override func viewDidLoad() {
            super.viewDidLoad()
            idToken = AppDelegate.originalAppDelegate.userIdToken
            email = AppDelegate.originalAppDelegate.userEmail
            tableSettings()
            getAppToken()
        }
        
        func tableSettings() {
            dashboardMyClaim.delegate = self
            dashboardMyClaim.dataSource = self
        }
        
        // Get user's App Token from Google API
    
        func getAppToken() {
            guard let urlString = URL(string: "https://claim.ademo.work/sessions/") else { return }
            var requestAPI = URLRequest(url: urlString)
    
            let parameters: [String: Any] = ["email":email, "platform":"web", "id_token":idToken]
            requestAPI.httpMethod = "POST"
            requestAPI.setValue("Application/json", forHTTPHeaderField: "Content-Type")
            guard let httpBody = try? JSONSerialization.data(withJSONObject: parameters, options: []) else {return}
            requestAPI.httpBody = httpBody
            requestAPI.timeoutInterval = 20
            URLSession.shared.dataTask(with: requestAPI) { (data, response, error) in
                if let data = data {
                    do {
                        let jsonResult = try JSONDecoder().decode(SignIn.self, from: data)
                        self.getClaimFromAPI(usersAppToken: jsonResult.app_token)
                    } catch {
                        print(error)
                    }
                }
            }.resume()
        }
        
        // Use the App Token to GET claims from the database / API
    
        func getClaimFromAPI(usersAppToken:String) {
            guard let urlString = URL(string: "https://claim.ademo.work/claims/") else { return }
            var requestAPI = URLRequest(url: urlString)
    
            requestAPI.httpMethod = "GET"
            requestAPI.addValue("application/json", forHTTPHeaderField: "Content-Type")
            requestAPI.addValue("application/json", forHTTPHeaderField: "Accept")
            requestAPI.setValue("Bearer \(usersAppToken)", forHTTPHeaderField: "Authorization")
    
            URLSession.shared.dataTask(with: requestAPI) { [weak self] (data, response, error) in
                if let data = data {
                    do {
                        let json = try JSONDecoder().decode([Claims].self, from: data)
    
                        for n in 0..<json.count {
                            self!.myClaimArray.append(PropertyExistingClaim(id: json[n].id, date: json[n].submission_date, desc: "-", amount: "-", amountMyr: "-", currency: "-", status: json[n].status))
                        }
                        DispatchQueue.main.async {
                            self!.dashboardMyClaim.reloadData()
                        }
                        
                    } catch let error {
                        print("Localized Error: \(error.localizedDescription)")
                        print("Error: \(error)")
                    }
    
                }
            }.resume()
        }
        
        override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
            if segue.identifier == "goToNewClaimDashboard" {
                let destinationVC = segue.destination as? NewClaimDashboardController
            }
        }
    }
    
    extension DashboardController: UITableViewDelegate, UITableViewDataSource {
        func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
            myClaimArray.count
        }
        
        func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
            let cell = dashboardMyClaim.dequeueReusableCell(withIdentifier: "DashboardMyClaim", for: indexPath) as! TableDashboardMyClaim
            cell.dateField.text = "Date: \(myClaimArray[indexPath.row].date)"
            cell.descriptionField.text = "Description: \(myClaimArray[indexPath.row].desc)"
            cell.amountMyrField.text = "RM \(myClaimArray[indexPath.row].amountMyr)"
            cell.idField.text = "#\(myClaimArray[indexPath.row].id)"
            cell.statusField.text = "\(myClaimArray[indexPath.row].status)"
            return cell
        }
        
        func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
            return 120.0
        }
        
    }
    

    I think adding this has helped quite abit:

    DispatchQueue.main.async {
        self!.dashboardMyClaim.reloadData()
    }
    

    Special thanks to all those that has helped me on this question. I really appreciate it! :)