没有 "Index out of Range" swift 的实时重新加载 UITableview
RealTime Reload UITableview without "Index out of Range" swift
我试图每秒重新加载我的表格视图。我现在拥有的是重新加载 tableview 对象,但是由于我在重新加载之前清除了 Order
数组,它因索引超出范围而崩溃。
这是我当前的代码
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
}
这是我从 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()
}
如果范围超出索引
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)
}
可能问题是您在有数据之前试图填充 TableView。如果是这种情况,您需要将局部变量设置为 0,并且在 ViewDidLoad 中调用 GetOrders 时将其设置为 orders.count。然后你在 numberOfRowsInSection 中使用这个变量,就像那样:
var orders = [Order]()
var aux = 0
override func viewDidLoad(){
GetOrders()
aux = Orders.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSectionsection: Int) -> Int {
return aux
}
可能是因为你在主同步上重新加载了数据。您没有显示完整的函数 GetOrder()。如果您在那里使用 dispatch 而不等待完成,并使用主同步重新加载数据,数据将在甚至没有下载订单时尝试重新加载。
您在与调度组合作吗?什么时候会重新加载数据?您确定只有在拥有所有数据后才重新加载数据吗?
如果是这种情况,请向我们展示您的完整代码,我将尝试添加等待完成的调度组。
我建议您在 completionBlock 中重新加载 tableView,而不是您调用的方式
Swift 3
假设您正在通过 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()
}
由于您使用的是 Alomofire,因此您可以进行相应的更改
getDataFromJson(url: "http://....", parameter: "....", completion: { response in
print(response)
//你 100% 确定你收到了你的数据然后继续清除你的 Array ,加载 newData 然后在你正在做的主线程中重新加载 tableView
订单 = []
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()
}
})
完成块应该可以解决您的问题
这是一个关于刷新订单并避免越界异常的逻辑的建议 - 让 getOrders()
仅在完成时安排对自身的下一次调用。这是一个例子:
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)
}
}
这个逻辑的想法是,在真正加载订单后一秒,才会调用下一个 getOrders。
请注意,可能需要用 DispatchQueue.main.async
包装 table 的重新加载(如您的示例)
我不知道它是否有效,但请尝试一下。
- 我认为你在从 API 获取数据之前清空了 orders (
orders = []
) 数组,同时你之前的调用试图在此处重新加载 tableview来了 index out of range
.
按如下方式更改 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()
}
** 最重要的是在从 API 获取数据之后和添加新数据之前清空主队列中的数组
如果它未能解决您的问题,正如@o15a3d4l11s2 所说,确保 GetOrders()
函数仅在获得上一次调用的响应后调用
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)
}
您得到 index out of Range
是因为您每 4 秒就将信息数据附加到订单数组并且它没有初始化。
你可以试试这个..
override func viewDidLoad() {
super.viewDidLoad()
// table stuff
tableview.dataSource = self
tableview.delegate = self
// update orders
startTimer()
}
计时器部分是:
func startTimer() {
let mySelector = #selector(self.GetOrders)
var timer = Timer.scheduledTimer(timeInterval: 4, target: self, selector: #selector(mySelector), userInfo: nil, repeats: true)
timer.fire()
}
订购方式:
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()
}
您得到 indexOutOfRange
是因为您根据 api 响应每 4 秒 修改 tableview dataSource
数组。同时您正在尝试访问同一数组中的数据。
变化:1:
为 tableView DataSource
创建单独的数组,为 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()
}
}
改变 2 :
不要简单地每 4 秒触发一次 api。收到 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()
}
只使用回调闭包
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()
}
})
}
我试图每秒重新加载我的表格视图。我现在拥有的是重新加载 tableview 对象,但是由于我在重新加载之前清除了 Order
数组,它因索引超出范围而崩溃。
这是我当前的代码
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
}
这是我从 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()
}
如果范围超出索引
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)
}
可能问题是您在有数据之前试图填充 TableView。如果是这种情况,您需要将局部变量设置为 0,并且在 ViewDidLoad 中调用 GetOrders 时将其设置为 orders.count。然后你在 numberOfRowsInSection 中使用这个变量,就像那样:
var orders = [Order]()
var aux = 0
override func viewDidLoad(){
GetOrders()
aux = Orders.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSectionsection: Int) -> Int {
return aux
}
可能是因为你在主同步上重新加载了数据。您没有显示完整的函数 GetOrder()。如果您在那里使用 dispatch 而不等待完成,并使用主同步重新加载数据,数据将在甚至没有下载订单时尝试重新加载。
您在与调度组合作吗?什么时候会重新加载数据?您确定只有在拥有所有数据后才重新加载数据吗?
如果是这种情况,请向我们展示您的完整代码,我将尝试添加等待完成的调度组。
我建议您在 completionBlock 中重新加载 tableView,而不是您调用的方式
Swift 3
假设您正在通过 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()
}
由于您使用的是 Alomofire,因此您可以进行相应的更改
getDataFromJson(url: "http://....", parameter: "....", completion: { response in
print(response)
//你 100% 确定你收到了你的数据然后继续清除你的 Array ,加载 newData 然后在你正在做的主线程中重新加载 tableView 订单 = []
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()
}
})
完成块应该可以解决您的问题
这是一个关于刷新订单并避免越界异常的逻辑的建议 - 让 getOrders()
仅在完成时安排对自身的下一次调用。这是一个例子:
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)
}
}
这个逻辑的想法是,在真正加载订单后一秒,才会调用下一个 getOrders。
请注意,可能需要用 DispatchQueue.main.async
我不知道它是否有效,但请尝试一下。
- 我认为你在从 API 获取数据之前清空了 orders (
orders = []
) 数组,同时你之前的调用试图在此处重新加载 tableview来了index out of range
. 按如下方式更改 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() }
** 最重要的是在从 API 获取数据之后和添加新数据之前清空主队列中的数组
如果它未能解决您的问题,正如@o15a3d4l11s2 所说,确保
GetOrders()
函数仅在获得上一次调用的响应后调用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) }
您得到 index out of Range
是因为您每 4 秒就将信息数据附加到订单数组并且它没有初始化。
你可以试试这个..
override func viewDidLoad() {
super.viewDidLoad()
// table stuff
tableview.dataSource = self
tableview.delegate = self
// update orders
startTimer()
}
计时器部分是:
func startTimer() {
let mySelector = #selector(self.GetOrders)
var timer = Timer.scheduledTimer(timeInterval: 4, target: self, selector: #selector(mySelector), userInfo: nil, repeats: true)
timer.fire()
}
订购方式:
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()
}
您得到 indexOutOfRange
是因为您根据 api 响应每 4 秒 修改 tableview dataSource
数组。同时您正在尝试访问同一数组中的数据。
变化:1:
为 tableView DataSource
创建单独的数组,为 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()
}
}
改变 2 :
不要简单地每 4 秒触发一次 api。收到 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()
}
只使用回调闭包
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()
}
})
}