简体   繁体   中英

Swift 3: Preload data before putting it into UITableView

I have an app that will fetch exactly 100 strings from an API and place them into a UITableView. I wish to first preload the data into an array and then, once the array is fully populated with the 100 entries, load the data into the table.

Due to the asynchronous API call, it seems like I am unable to load data into the array before the table view starts populating its cells. Mainly, I am having difficulty getting the data out of the closure in the first place.

This is the API call defined in an APIAgent class:

func getAPIData(_ requestType: String, completionHandler: @escaping (Data) -> ()) {
    let requestURL: URL = URL(string : baseURL + requestType)!
    let currentSession = URLSession.shared
    let task = currentSession.dataTask(with: requestURL) { (data, response, error) in
        completionHandler(data!)
    }
    task.resume()
}

This is how the UITableView uses it:

protocol AsyncHelper {
    func getData(data: Any)
}

class TableViewController: UITableViewController, AsyncHelper {
    var dataEntries: [String] = []

    func getData(data: Data) {
        let entry: String = String(describing: data)
        dataEntries.append(entry) 
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        for i in 1...100 {
            apiAgent.getAPIData("entry" + String(i), entry: { entry in
                self.getData(data: entry)
            })
        }
    }

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "EntryCell", for: indexPath) as! EntryCell

        let entry: String = dataEntries[indexPath.row] // index out of range error

        DispatchQueue.main.async {
            // add Strings to cell here
        }

        return cell
    }
}

So it appears that the cells are being generated before data gets populated into the dataEntries array. How do I prevent the UITableView from generating the cells until dataEntries is populated.

Try this :

override func viewDidLoad() {
        super.viewDidLoad()
        tblView.delegate = nil
        tblView.dataSource = nil
        for i in 1...100 {
            apiAgent.getAPIData("entry" + String(i), entry: { entry in
                self.getData(data: entry)
                tblView.delegate = self
                tblView.dataSource = self
                tblView.reloadData()
            })
        }
    }

If you are going to use a closure you won't need a protocol. You could change your networking code to:

var songData = [Data]

func getAPIData(_ requestType: String, completionHandler: @escaping ([Data]) -> ()) {
let requestURL: URL = URL(string : baseURL + requestType)!
let currentSession = URLSession.shared
let task = currentSession.dataTask(with: requestURL) { (data, response, error) in
    songData.append(data!)

    if (songData.count == 100) {
        completionHandler(songData)
    }
}
task.resume()
}

This will make sure that your getData() and tableView.reloadData() will only be called once all 100 of your data elements have been loaded.

FYI - tableView.reloadData() will reload pretty much everything that has to deal with your table view. Your numberOfRows , numberOfSections , and cellForRow will all be called again. This will create the tableView over again using the updated dataEntries values

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM