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 中的索引超出范围崩溃