RealTime Reload UITableview without a "pointer out of range" swift

I try to reload my tableview every second. what my tableview objects now reloads, but since I clear the Order array before reloading, it crashes due to an index outside the range.

This is my current code.

  var orders = [Order]() override func viewDidLoad() { super.viewDidLoad() // table stuff tableview.dataSource = self tableview.delegate = self // update orders var timer = Timer.scheduledTimer(timeInterval: 4, target: self, selector: "GetOrders", userInfo: nil, repeats: true) GetOrders() } func numberOfSections(in tableView: UITableView) -> Int { if orders.count > 0 { self.tableview.backgroundView = nil self.tableview.separatorStyle = .singleLine return 1 } let rect = CGRect(x: 0, y: 0, width: self.tableview.bounds.size.width, height: self.tableview.bounds.size.height) let noDataLabel: UILabel = UILabel(frame: rect) noDataLabel.text = "no orders" noDataLabel.textColor = UIColor.white noDataLabel.textAlignment = NSTextAlignment.center self.tableview.backgroundView = noDataLabel self.tableview.separatorStyle = .none return 0 } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return orders.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "OrderCell", for: indexPath) as! OrderCell let entry = orders[indexPath.row] cell.DateLab.text = entry.date cell.shopNameLab.text = entry.shopname cell.shopAddLab.text = entry.shopaddress cell.nameClientLab.text = entry.clientName cell.clientAddLab.text = entry.ClientAddress cell.costLab.text = entry.Cost cell.perefTimeLab.text = entry.PerferTime cell.Shopimage.hnk_setImage(from: URL(string: entry.Logo)) return cell } 

This is how I get data from the API:

 func GetOrders (){ orders = [] // get data by Alamofire let info = Order(shopname: shopname, shopaddress: shopaddr, clientName: cleintName,ClientAddress: clientAddres, PerferTime: time, Cost: subtotal , date : time , Logo : logoString ,id : id) self.orders.append(info) // some if statements DispatchQueue.main.async { self.tableview.reloadData() } 

And so if the range is outside the index

  func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { tableView.deselectRow(at: indexPath, animated: true) let order = orders[indexPath.row] guard orders.count > indexPath.row else { print("Index out of range") return } let storyboard = UIStoryboard(name: "Main", bundle: nil) var viewController = storyboard.instantiateViewController(withIdentifier: "viewControllerIdentifer") as! OrderDetailsController viewController.passedValue = order.id self.present(viewController, animated: true , completion: nil) } 
+5
source share
8 answers

You get indexOutOfRange because you change the tableview dataSource every 4 sec based on api response. At the same time, you are trying to access data from the same array.

Edit: 1:

Create a separate array for tableview dataSource and one for api response.

 func getOrders() { var tempOrdersList = [Order]() // get data by Alamofire let info = Order(shopname: shopname, shopaddress: shopaddr, clientName: cleintName,ClientAddress: clientAddres, PerferTime: time,Cost: subtotal , date : time , Logo : logoString ,id : id) tempOrdersList.append(info) // some if statements DispatchQueue.main.async { //Assign temporary array to class variable, which you are using as tableview data source. self.orders = tempOrdersList self.tableview.reloadData() } } 

Edit 2:

Don't just run api every 4 seconds. Schedule a timer as soon as you get an api response.

 func getOrders() { var tempOrdersList = [Order]() // get data by Alamofire let info = Order(shopname: shopname, shopaddress: shopaddr, clientName: cleintName,ClientAddress: clientAddres, PerferTime: time,Cost: subtotal , date : time , Logo : logoString ,id : id) tempOrdersList.append(info) // some if statements DispatchQueue.main.async { //Assign temporary array to class variable, which you are using as tableview data source. self.orders = tempOrdersList self.tableview.reloadData() timer = Timer.scheduledTimer(timeInterval: 4, target: self, selector: #selector(self. getOrder), userInfo: nil, repeats: false) } } // Invalidate the timer at the view controller deinit deinit { timer.invalidate() } 
+3
source

I don't know if this works or not, but give it a try.

  • I think that you are freeing an array of orders ( orders = [] ) before receiving data from the API, and your previous call is trying to reload the tableview, but index out of range .
  • Change the GetOrders () function as follows

     func GetOrders (){ //orders = [] remove this line // get data by Alamofire // some if statements DispatchQueue.main.async { //empty your array in main queue most importantly just after getting data from API and just before appending new data's orders = [] let info = Order(shopname: shopname, shopaddress: shopaddr, clientName: cleintName,ClientAddress: clientAddres, PerferTime: time, Cost: subtotal , date : time , Logo : logoString ,id : id) self.orders.append(info) self.tableview.reloadData() } 

** Free the array in the main queue, most importantly, immediately after receiving data from the API and before adding new data

  1. if it cannot solve your problem, as @ o15a3d4l11s2 said, make sure that the GetOrders() function is called only after receiving a response to the previous call

      override func viewDidLoad() { super.viewDidLoad() tableview.dataSource = self tableview.delegate = self GetOrders() } func GetOrders (){ //orders = [] remove this line // get data by Alamofire // some if statements DispatchQueue.main.async { //empty your array in main queue most importantly just after getting data from API and just before appending new data's orders = [] let info = Order(shopname: shopname, shopaddress: shopaddr, clientName: cleintName,ClientAddress: clientAddres, PerferTime: time, Cost: subtotal , date : time , Logo : logoString ,id : id) self.orders.append(info) self.tableview.reloadData() Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(self. getOrder), userInfo: nil, repeats: false) } 
+7
source

Here's a suggestion that the logic update orders and stay away from related exceptions - let getOrders() pay the next call to itself only when it is complete. Here is an example:

 func getOrders() { asyncLoadOrders(onComplete: { loadedOrders self.orders = loadedOrders self.tableView.reloadData() Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(self. getOrder), userInfo: nil, repeats: false) } } 

The idea behind this logic is that one second after the orders are actually loaded, only then will the next getOrders be called.

Note that you may need to reload the table (as in your example) using DispatchQueue.main.async

+6
source

I would advise you to reload the tableView in the completion window, and not in how you call

Swift 3

let's say you get data through NSURLsession

 func getDataFromJson(url: String, parameter: String, completion: @escaping (_ success: [String : AnyObject]) -> Void) { //@escaping...If a closure is passed as an argument to a function and it is invoked after the function returns, the closure is @escaping. var request = URLRequest(url: URL(string: url)!) request.httpMethod = "POST" let postString = parameter request.httpBody = postString.data(using: .utf8) let task = URLSession.shared.dataTask(with: request) { Data, response, error in guard let data = Data, error == nil else { // check for fundamental networking error print("error=\(error)") return } if let httpStatus = response as? HTTPURLResponse, httpStatus.statusCode != 200 { // check for http errors print("statusCode should be 200, but is \(httpStatus.statusCode)") print(response!) return } let responseString = try! JSONSerialization.jsonObject(with: data, options: .allowFragments) as! [String : AnyObject] completion(responseString) } task.resume() 

}

Since you are using Alomofire, you can change accordingly

  getDataFromJson(url: "http://....", parameter: "....", completion: { response in print(response) 

// You are 100% sure that you got your data, then clear your array, load newData and reload the tableView in the main thread, as you do orders = []

  let info = Order(shopname: shopname, shopaddress: shopaddr, clientName: cleintName,ClientAddress: clientAddres, PerferTime: time, Cost: subtotal , date : time , Logo : logoString ,id : id) self.orders.append(info) DispatchQueue.main.async { self.tableview.reloadData() } }) 

Completion blocks should solve your problem

+3
source

Perhaps the problem is that you are trying to populate the TableView before you have data for this. If so, you need to set the local variable to 0, and when you call GetOrders in ViewDidLoad, you set it to order.count. Then you use this variable in numberOfRowsInSection, for example:

  var orders = [Order]() var aux = 0 override func viewDidLoad(){ GetOrders() aux = Orders.count } func tableView(_ tableView: UITableView, numberOfRowsInSectionsection: Int) -> Int { return aux } 
+2
source

You get index out of Range , because every 4 seconds you add information data to the order array, and it is not initialized.

You can try this ..

 override func viewDidLoad() { super.viewDidLoad() // table stuff tableview.dataSource = self tableview.delegate = self // update orders startTimer() } 

Timer Part:

 func startTimer() { let mySelector = #selector(self.GetOrders) var timer = Timer.scheduledTimer(timeInterval: 4, target: self, selector: #selector(mySelector), userInfo: nil, repeats: true) timer.fire() } 

order method:

  func GetOrders(){ orders.removeAll() // get data by Alamofire let info = Order(shopname: shopname, shopaddress: shopaddr, clientName: cleintName,ClientAddress: clientAddres, PerferTime: time, Cost: subtotal , date : time , Logo : logoString ,id : id) self.orders.append(info) // some if statements DispatchQueue.main.async { self.tableview.reloadData() } 
+2
source

Perhaps this is because you are reloading data in the main synchronization. You are not showing your full GetOrder () function. If you use sending there, without waiting for completion, and using basic synchronization to reload data, the data will try to reload when orders are not even loaded .

Do you work with dispatch groups? When will the data be reloaded? Are you sure you only reload the data when you have all the data?

If so, show us your full code, and I will try to add sending groups that will wait for completion.

+1
source

just use close callback

 func getOrders() { Alamofire.request("https://api.ivi.ru/mobileapi//geocheck/whoami/v6?app_version=5881&user_agent=ios").validate().responseJSON(completionHandler: { response in //handle response guard everythingsOkay else { return } DispatchQueue.main.async { self.tableView.reloadData() } }) } 
+1
source

Source: https://habr.com/ru/post/1268984/


All Articles