Ivan Cantarino Ivan Cantarino - 19 days ago 9
iOS Question

fatal error: Index out of range when refreshing table view

I've this weird app crash when pulling to refresh occurs.

My code goes as it follows:

var posts: [Posts] = []

override func viewDidLoad() {
super.viewDidLoad()

// refreshControl -> pull to refresh handler
let refreshControl = UIRefreshControl()
refreshControl.addTarget(self,
action: #selector(Main_TVC.getData),
for: UIControlEvents.valueChanged)
self.refreshControl = refreshControl

getData()
}

override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}

override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return posts.count
}

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

cell.titleLabel.text = posts[indexPath.row].postTitle
cell.bodyLabel.text = posts[indexPath.row].postBody

return cell
}

func getData() {
self.posts.removeAll()

// queries the backend and fills the data - sorry for code omission

refresh()

}


func refresh() {
self.tableView.reloadData()
self.refreshControl?.endRefreshing()
}


The app runs properly and even when I pull down to refresh, everything runs perfectly, but if do a long pull down to refresh, like pulling down almost hitting the bottom of the screen, the app crashes and prompts the following error:

fatal error: Index out of range

on the line

cell.titleLabel.text = posts[indexPath.row].postTitle

If I print the post count and the indexPath as follows:

print("posts.count = (posts.count)")
print("indexPath.row = (indexPath.row)")

When I'm pulling down in the normal way, it prints the correct data, but if I pull down like a long pull, through the whole screen if prompts this when it crashes

posts.count = 0

indexPath.row = 2

This kind of thing has never happen to me using the refreshControl the way I'm using here.

Hope my information is understandable, mostly on the long pull to refresh issue.

Answer

Your problem is that the first thing you do in getData is remove all of the posts from self.posts, but you do not (presumably since code is missing) reload the table view, so now the number of posts in the array (0) and the number of posts that the tableview *thinks is in the array (not zero) is different, so you get an array bounds crash.

Calling reloadData after self.posts.removeAll() would fix the issue but result in the tableview 'flashing' as it redrew empty and then redrew with the new data.

Since you haven't shown the full code for getData I can't provide the exact code you need, but it should be something like:

func getData() { 
     fetchPostsWithCompletion() {
         if let tempPosts = dataFromNetwork {
             self.posts = tempPosts
         } else {
             self.posts.removeAll()
         }
         self.refresh()        // Dispatch this on the main queue if your completion handler is not already on the main queue
    }
}

This way you don't manipulate the backing array until you have the new data.