没有 "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 的重新加载(如您的示例)

我不知道它是否有效,但请尝试一下。

  1. 我认为你在从 API 获取数据之前清空了 orders (orders = []) 数组,同时你之前的调用试图在此处重新加载 tableview来了 index out of range .
  2. 按如下方式更改 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 获取数据之后和添加新数据之前清空主队列中的数组

  1. 如果它未能解决您的问题,正如@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()
                }
                })
}