UITableView 索引超出范围

Posted

技术标签:

【中文标题】UITableView 索引超出范围【英文标题】:UITableView Index Out of Range 【发布时间】:2017-12-09 14:53:38 【问题描述】:

我在使用标签应用程序时遇到了崩溃问题,我真的想不出解决方案。当应用程序第一次执行时,当我选择第三个选项卡项(天气预报)时,一切正常,表数据正在加载其中的所有数据并且一切正常显示,用户可以毫无问题地进行交互。当我选择另一个选项卡项目然后返回到天气预报的项目时,会发生崩溃问题。我担心的是有时它会崩溃,但有时它不会……这是我真的无法理解的,为什么它会连续工作一次甚至几次,有时它会在第一次尝试时崩溃……我想在某些时候viewdidAppear 加载时的视图生命周期,我的数组没有及时填充,因此 TableView 试图访问一个不存在并崩溃的数组元素。这是我尝试使用线程和 dispatchQueue 进行管理但仍然出现问题..

有什么想法吗?非常感谢您的帮助。

有时我会收到以下错误消息:

2017-12-09 14:19:11.431048+0100 Le Baluchon[19344:5162393] *** 由于未捕获的异常“NSInternalInconsistencyException”而终止应用程序,原因:“无效更新:第 0 节中的行数无效。更新后现有节中包含的行数 (0) 必须等于更新前该节中包含的行数 (4),加上或减去从该节中插入或删除的行数(0 插入, 0 已删除)加或减移入或移出该部分的行数(0 移入,0 移出)

但很多时候,我在 VC 中的第 73 行旁边收到此错误消息:

线程 1:致命错误:索引超出范围

有什么想法吗?

这是我的 VIewController:

class WeatherViewController: UIViewController, UITableViewDataSource, UITableViewDelegate 

@IBOutlet weak var weatherView: UITableView!
@IBOutlet weak var weatherDataLoading: UIActivityIndicatorView!

let error = Notification.Name(rawValue: "Error")
var selectedRow: Int?
var count: Int = 0
var names = [String]()
var temps = [String]()
var dates = [String]()
var climates = [String]()
var forecastDates = [String]()
var forecastClimates = [String]()

override func viewDidLoad() 
    super.viewDidLoad()

    self.weatherView.dataSource = self
    self.weatherView.delegate = self
    NotificationCenter.default.addObserver(self, selector: #selector(displayErrorAlert), name: error, object: nil)


override func viewDidAppear(_ animated: Bool) 
    super.viewDidAppear(animated)

    WeatherRequest.getWeatherCast(completion:  (names, temps, dates, climates, forecastdates, forecastclimates) in
        self.names = names
        self.temps = temps
        self.dates = dates
        self.climates = climates
        self.forecastDates = forecastdates
        self.forecastClimates = forecastclimates
        self.weatherView.reloadData()
        self.weatherDataLoading.isHidden = true
    )


@objc func displayErrorAlert() 

    let alertVC = UIAlertController(title: "Error", message: "Couldn't retrieve data from servor", preferredStyle: .alert)
    alertVC.addAction(UIAlertAction(title: "OK", style: .cancel, handler: nil))
    self.present(alertVC, animated: true, completion: nil)



func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int 
    return names.count


func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell 

    let cell = tableView.dequeueReusableCell(withIdentifier: "table_cell", for: indexPath) as! WeatherViewCell

    cell.separatorInset = UIEdgeInsets.zero

    cell.firstStackView.constant = tableView.frame.height / 4
    cell.cityName.text = names[indexPath.item]
    cell.cityDate.text = dates[indexPath.item]
    cell.cityClimate.text = climates[indexPath.item]
    cell.cityTemp.text = temps[indexPath.item]

    cell.firstForecastedClimate.text = forecastClimates[count]
    cell.firstForecastedDate.text = forecastDates[count]
    count += 1
    cell.secondForecastedClimate.text = forecastClimates[count]
    cell.secondForecastedDate.text = forecastDates[count]
    count += 1
    cell.thirdForecastedClimate.text = forecastClimates[count]
    cell.thirdForecastedDate.text = forecastDates[count]
    cell.isExpanded = false

    count += 8

    return cell


func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) 

    guard let cell = tableView.cellForRow(at: indexPath) as? WeatherViewCell
        else  return 

    selectedRow = indexPath.item

    cell.contentView.backgroundColor = UIColor.darkGray
    cell.contentView.alpha = 0.5

    cell.isExpanded = !cell.isExpanded

    self.weatherView.beginUpdates()
    self.weatherView.endUpdates()


func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) 

    guard let cell = tableView.cellForRow(at: indexPath) as? WeatherViewCell
        else  return 

    self.weatherView.beginUpdates()
    cell.contentView.backgroundColor = nil
    cell.contentView.alpha = 1
    cell.isExpanded = false
    self.weatherView.endUpdates()


override func viewDidDisappear(_ animated: Bool) 
    super.viewDidDisappear(animated)

    guard let selectedRow = selectedRow else  return 
    guard let cell = weatherView.cellForRow(at: [0,selectedRow]) as? WeatherViewCell
        else  return 
    cell.isExpanded = false
    weatherView.beginUpdates()
    cell.contentView.backgroundColor = nil
    cell.contentView.alpha = 1
    weatherView.endUpdates()
    clear()


func clear() 
    count = 0
    names = [String]()
    temps = [String]()
    dates = [String]()
    climates = [String]()
    forecastDates = [String]()
    forecastClimates = [String]()


这是我从 API Yahoo Weather 获取数据的类。

class WeatherRequest 

static func getWeatherCast(completion: @escaping ([String], [String], [String], [String], [String], [String]) -> ()) 

DispatchQueue.global(qos: .userInteractive).async 

var names = [String]()
var temps = [String]()
var dates = [String]()
var climates = [String]()

var forecastDates = [String]()
var forecastClimates = [String]()

    let url = URL(string: "https://query.yahooapis.com/v1/public/yql?q=select%20*%20from%20weather.forecast%20where%20woeid%20in%20(select%20woeid%20from%20geo.places(1)%20where%20text%20in(%22Miami%22%2C%20%22new%20york%22%2C%20%22chicago%22%2C%20%22philadelphia%22))%20and%20u%3D'c'&format=json&env=store%3A%2F%2Fdatatables.org%2Falltableswithkeys")

    let task = URLSession.shared.dataTask(with: url!)  (data, response, error) in

        guard let myData = data else  return 

        do 
            let decoder = JSONDecoder()
            decoder.dateDecodingStrategy = .iso8601
            let root = try decoder.decode(Root.self, from: myData)
            for channel in root.channels 
                dates.append(channel.item.condition.date)
                temps.append(channel.item.condition.temp + " °C")
                climates.append(channel.item.condition.text)
            

            for channel in root.channels 
                for forecast in channel.item.forecast 
                    forecastDates.append(forecast.date)
                    forecastClimates.append(forecast.text)
                
            
            names = ["Miami", "New York", "Chicago", "Philadelphia"]
            DispatchQueue.main.async 
            completion(names, temps, dates, climates, forecastDates, forecastClimates)
            

         catch let fetchError 
            print(fetchError)
        
    
    task.resume()



【问题讨论】:

你有一个属性count,你在cellForRowAt中操作,但是cellForRowAt的调用没有特定的顺序,对于同一个索引路径可能会被多次调用,所以count 很有可能超过数组的大小。与其拥有多个相关的数组,不如拥有一个结构体数组,将检索到的数据放入其中。此外,当您获取新数据时,您不会先从数组属性中清除旧数据 感谢您的回答,我想它解释了致命错误消息。我使用了一个结构数组,其中包含从 API 获取的所有数据并与 indexPath 一起使用来显示我需要的内容,因此我不再需要 count 变量。目前看来效果很好! 【参考方案1】:

错误清楚地解释了它。当调用开始更新和结束更新时,会像这样检查行数。更新后的行数应等于更新前的行数加上或减去添加或删除的行数。在您的情况下,api 返回 4 个值,但是当您更改选项卡并返回时,计数更改为 0(此处的 api 响应延迟)。因此,在更新 4 之前和更新 0 之后,但不调用删除单元格。有关详细信息,请阅读 beginupdates 的描述,它有明确的记录。

【讨论】:

感谢您的回复和建议。我会看看它。我按照上面保罗的回答所做的似乎已经解决了所有的崩溃问题。

以上是关于UITableView 索引超出范围的主要内容,如果未能解决你的问题,请参考以下文章

在 UITableView 中嵌入不同数量的项目的 UICollectionViews 索引超出范围

在后台删除的数据导致 UITableView 中的索引超出范围崩溃

如何解决 uiTableview 滚动应用程序崩溃,它会在 swift 中引发数组索引超出范围异常?

UICollectionView 给出“索引超出范围”错误

致命错误:索引超出范围

从firebase删除时数组索引超出范围