简体   繁体   中英

iOS with Swift: App crashes on UITableView scroll down event

I am writing an iOS app in Swift that puts data from an API into a TableView. The goal is that the user can scroll down a continuous list of blog posts. What is happening is that data is filling the first screen's amount of cells fine, but when the user scrolls down, the app crashes as one of the cell properties, cellImageView becomes nil and is unwrapped (as force unwrap is specified in the cell class's code):

class BlogPostEntryCell: UITableViewCell {
    //MARK: Properites
    @IBOutlet weak var titleLabel: UILabel!
    @IBOutlet weak var bodyTextView: UITextView!
    @IBOutlet weak var initialsImageView: UIImageView!   
}

Here is the code that causes the crash:

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

    apiCaller.getAPIDataThroughAsyncCall(id: nextCell, entry: { cellContent in
        DispatchQueue.main.async(execute: {
            cell.cellLabel.text = cellContent.labelText
            cell.cellTextView.text = cellContent.textViewText
            // App crashes at the below line
            cell.initialsImageView = self.createPicture(initials: cellContent.pictureText)
        })
    })

    nextCell += 1

    return cell
}

The interesting thing is that if do this:

    cell.cellLabel?.text = cellContent.labelText
    cell.cellTextView?.text = cellContent.textViewText
    // App does NOT crash at the below line
    cell.initialsImageView? = self.createPicture(initials: cellContent.pictureText)

, the app doesn't crash but the data just repeats over and over again as the user scrolls.

I've tried the solution to a similar problem here: Why does data returned from coredata become nil after tableview scroll in swift? , but that did not work. However, I do believe this has to do with the asynchronous API calls.

I am thinking that possible solutions may include population an array of API data before generating the cells, but I would like some insight on the source of this problem before a rethink the architecture of the code.

You need to make an API call in your viewDidLoad to know how many element are there and record their ID in an array. Then in your cellForRow function, you can get the id in this way

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

    apiCaller.getAPIDataThroughAsyncCall(id: array[indexPath.row], entry: { cellContent in
        DispatchQueue.main.async(execute: {
            ...
        })
    })
    return cell
}

The reason your nextCell wont work is that, this cellForRow function is called every time when your table view is trying to display a cell that is not currently on your screen. Say you know you have 10 element and your screen can only display 5 of them. When you scroll to bottom, your nextCell will become 10 and everything works fine as now. But now when you scroll up, the table view have to prepare the previous 5 again. So now your nextCell becomes 15, which you don't have corresponding element in your API call.

It is bad practice to call any API in cellForRowAt indexPath: IndexPath, it produces crashes as you see, have a look at MVVM. It is like wining a war without having to go to battle and I refer to this instance where you call API in cellForRowAt indexPath: IndexPath and the outlets are not initialised.

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