Bubu
Bubu

Reputation: 15

Refresher on Table - Index out of range error

I've implemented the following code which works fine, it displays correct data, refreshed on pull. But when spamming the refresher several times quickly, it eventually leads to a fatal error - index out of range.

It is driving me crazy, could someone see what's wrong with my code?

    override func viewDidLoad()
{
    super.viewDidLoad()

    nameArray.removeAll(keepingCapacity: false)
    addressArray.removeAll(keepingCapacity: false)


    refresher = UIRefreshControl()
    refresher.attributedTitle = NSAttributedString(string: "Reloading")
    refresher.addTarget(self, action: #selector(downloadJsonWithURL), for: UIControlEvents.valueChanged)
    tableView.addSubview(refresher)
    self.downloadJsonWithURL()

}


func downloadJsonWithURL()
{
    // Emtpy arrays to avoid index out of range crash
    nameArray.removeAll(keepingCapacity: false)
    addressArray.removeAll(keepingCapacity: false)

    let url = NSURL(string: urlString)

    URLSession.shared.dataTask(with: (url as URL?)!, completionHandler: {(data, response, error) -> Void in


        if let jsonObj = try? JSONSerialization.jsonObject(with: data!, options: .allowFragments) as? NSDictionary {

            if let projectArray = jsonObj!.value(forKey: "project") as? NSArray {
                for project in projectArray{
                    if let projectDict = project as? NSDictionary {
                        if let name = projectDict.value(forKey: "societe") {
                            self.nameArray.append(name as! String)
                        }
                    }
                    if let projectDict = project as? NSDictionary {
                        if let name = projectDict.value(forKey: "address") {
                            self.addressArray.append(name as! String)
                        }
                    }
                }
            }

            OperationQueue.main.addOperation(
                {
                    self.tableView.reloadData()
                    self.refresher.endRefreshing()
            })
        }
    }).resume()

}

// Count length of array and create number of cells accordingly
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
    return nameArray.count
}

// Create cell data from arrays
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
    let cell = tableView.dequeueReusableCell(withIdentifier: "cell") as! TableViewCell
    cell.projectDesc.text = nameArray[indexPath.row].uppercased()
    cell.projectAddress.text = addressArray[indexPath.row]
    return cell
}

Upvotes: 1

Views: 300

Answers (2)

Priyal
Priyal

Reputation: 877

Problem is you are removing objects from the array before receiving the response. It creates a scenario wherein your numberOfRowsInSection returns a different value and when cellForRowAtIndexPath is called it has a different (Zero) items in array, which crashes the app because of index out of range issue. This is typically a problem of multithreading.

To overcome this issue, empty your array only when you successfully receive data from API call. For this you need to add your code of removeAll elements just above for project in projectArray{.

Which makes your code like :

override func viewDidLoad()
{
    super.viewDidLoad()

    nameArray.removeAll(keepingCapacity: false)
    addressArray.removeAll(keepingCapacity: false)

    refresher = UIRefreshControl()
    refresher.attributedTitle = NSAttributedString(string: "Reloading")
    refresher.addTarget(self, action: #selector(downloadJsonWithURL), for: UIControlEvents.valueChanged)
    tableView.addSubview(refresher)
    self.downloadJsonWithURL()
}

func downloadJsonWithURL()
{
    let url = NSURL(string: urlString)

    URLSession.shared.dataTask(with: (url as URL?)!, completionHandler: {(data, response, error) -> Void in


        if let jsonObj = try? JSONSerialization.jsonObject(with: data!, options: .allowFragments) as? NSDictionary {

            if let projectArray = jsonObj!.value(forKey: "project") as? NSArray {
                nameArray.removeAll(keepingCapacity: false)
                addressArray.removeAll(keepingCapacity: false)

                for project in projectArray{
                    if let projectDict = project as? NSDictionary {
                        if let name = projectDict.value(forKey: "societe") {
                            self.nameArray.append(name as! String)
                        }
                    }
                    if let projectDict = project as? NSDictionary {
                        if let name = projectDict.value(forKey: "address") {
                            self.addressArray.append(name as! String)
                        }
                    }
                }
            }

            OperationQueue.main.addOperation(
                {
                    self.tableView.reloadData()
                    self.refresher.endRefreshing()
            })
        }
    }).resume()
}

Upvotes: 1

Tristan Beaton
Tristan Beaton

Reputation: 1762

Can you try if this will fix it? Because this seems to be where it is crashing.

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

    if nameArray.count > indexPath.row && addressArray.count > indexPath.row {
        cell.projectDesc.text = nameArray[indexPath.row].uppercased()
        cell.projectAddress.text = addressArray[indexPath.row]
    }
    return cell
}

Adding if refresher.isRefreshing will prevent this data request being called numerous times.

func downloadJsonWithURL() {

    // If already refreshing don't run the code below
    if refresher.isRefreshing {

        // Emtpy arrays to avoid index out of range crash
        nameArray.removeAll(keepingCapacity: false)
        addressArray.removeAll(keepingCapacity: false)

        let url = NSURL(string: urlString)

        URLSession.shared.dataTask(with: (url as URL?)!, completionHandler: {(data, response, error) -> Void in


            if let jsonObj = try? JSONSerialization.jsonObject(with: data!, options: .allowFragments) as? NSDictionary {

                if let projectArray = jsonObj!.value(forKey: "project") as? NSArray {
                    for project in projectArray{
                        if let projectDict = project as? NSDictionary {
                            if let name = projectDict.value(forKey: "societe") {
                                self.nameArray.append(name as! String)
                            }
                        }
                        if let projectDict = project as? NSDictionary {
                            if let name = projectDict.value(forKey: "address") {
                                self.addressArray.append(name as! String)
                            }
                        }
                    }
                }

                OperationQueue.main.addOperation(
                    {
                        self.tableView.reloadData()
                        self.refresher.endRefreshing()
                })
            }
        }).resume()
    }
}

Upvotes: 1

Related Questions