Reputation: 969
I'm trying to reload my tableview every second. what I have now reload tableview objects but since I'm clearing Order
array before reloading, it crashes due to index out of 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
}
here is how I get data from 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 here is if the range is out of 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)
}
Upvotes: 5
Views: 4643
Reputation: 615
You are getting index out of Range
because every 4 second you are appending info data to the orders array and it is not initialize.
You can try this..
override func viewDidLoad() {
super.viewDidLoad()
// table stuff
tableview.dataSource = self
tableview.delegate = self
// update orders
startTimer()
}
Timer portion is:
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()
}
Upvotes: 2
Reputation: 400
just use callback closure
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()
}
})
}
Upvotes: 1
Reputation: 4375
You are getting indexOutOfRange
because you are modifying the tableview dataSource
array every 4sec based on api response. At the same time you are trying to access the data from the same array.
Change : 1:
Create 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()
}
}
Change 2 :
Don't simply trigger the api for every 4 seconds. Schedule the timer once you got the response of the api.
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()
}
Upvotes: 4
Reputation: 2096
I don't know whether it works or nor, however give it a try.
orders = []
) array before you get data from API, meanwhile your previous call tries to reload tableview here comes index out of range
.Change your 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()
}
** Empty your array in main queue most importantly just after getting data from API and just before appending new data's
if it fails to solve your problem, As @o15a3d4l11s2 said make sure GetOrders()
function is called only after getting response for 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)
}
Upvotes: 7
Reputation: 4059
Here is a proposal for the logic to refresh the orders and keep away from out of bound exceptions - let getOrders()
to schedule the next call to itself only when it is finished. 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 of this logic is that one second after the orders are really loaded, only then the next getOrders will be called.
Please note that it might be needed to wrap the reload of the table (as in your example) with DispatchQueue.main.async
Upvotes: 6
Reputation: 67
I would suggest you to reload the tableView in a completionBlock rather than the way you are calling
Swift 3
let say you are getting 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 received your data then go ahead a clear your Array , load the newData then reload the tableView in main thread as you are doing 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
Upvotes: 3
Reputation: 13063
Maybe it is because you reload the data on the main sync. You are not showing your full function GetOrder(). If you are using dispatch there without waiting for completion, and using the main sync to reload the data, the data will try to reload when the orders are not even downloaded.
Are you working with dispatch groups? When will the data be reloaded? Are you sure you only reload the data when you have all the data?
If this is the case show us your full code and I will try to add dispatch groups their that will wait for completion.
Upvotes: 1
Reputation: 333
Maybe the problem is that you`re trying to fill the TableView before you have data to do so. If thats the case, you need to set a local variable to 0 and when you call GetOrders in the ViewDidLoad you set it to orders.count. Then you use this variable in numberOfRowsInSection, like that :
var orders = [Order]()
var aux = 0
override func viewDidLoad(){
GetOrders()
aux = Orders.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSectionsection: Int) -> Int {
return aux
}
Upvotes: 2