我有闹钟应用程序。用户可以添加警报。如果我添加太多警报并向下滚动,我会看到
如果我尝试使用UISwitch
(只是buttom单元格),我会崩溃应用程序并获得错误:
Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[__NSArrayI objectAtIndex:]: index 8 beyond bounds [0 .. 7]'
我认为数组里面的问题。如何解决?
import UIKit
class MainAlarmViewController: UITableViewController{
var alarmDelegate: AlarmApplicationDelegate = AppDelegate()
var alarmScheduler: AlarmSchedulerDelegate = Scheduler()
var alarmModel: Alarms = Alarms()
override func viewDidLoad() {
super.viewDidLoad()
tableView.allowsSelectionDuringEditing = true
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
alarmModel = Alarms()
tableView.reloadData()
//dynamically append the edit button
if alarmModel.count != 0 {
self.navigationItem.leftBarButtonItem = editButtonItem
}
else {
self.navigationItem.leftBarButtonItem = nil
}
//unschedule all the notifications, faster than calling the cancelAllNotifications func
//UIApplication.shared.scheduledLocalNotifications = nil
let cells = tableView.visibleCells
if !cells.isEmpty {
for i in 0..<cells.count {
if alarmModel.alarms[i].enabled {
(cells[i].accessoryView as! UISwitch).setOn(true, animated: false)
cells[i].backgroundColor = UIColor.white
cells[i].textLabel?.alpha = 1.0
cells[i].detailTextLabel?.alpha = 1.0
}
else {
(cells[i].accessoryView as! UISwitch).setOn(false, animated: false)
cells[i].backgroundColor = UIColor.groupTableViewBackground
cells[i].textLabel?.alpha = 0.5
cells[i].detailTextLabel?.alpha = 0.5
}
}
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
// MARK: - Table view data source
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 90
}
override func numberOfSections(in tableView: UITableView) -> Int {
// Return the number of sections.
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// Return the number of rows in the section.
if alarmModel.count == 0 {
tableView.separatorStyle = UITableViewCellSeparatorStyle.none
}
else {
tableView.separatorStyle = UITableViewCellSeparatorStyle.singleLine
}
return alarmModel.count
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if isEditing {
performSegue(withIdentifier: Id.editSegueIdentifier, sender: SegueInfo(curCellIndex: indexPath.row, isEditMode: true, label: alarmModel.alarms[indexPath.row].label, mediaLabel: alarmModel.alarms[indexPath.row].mediaLabel, mediaID: alarmModel.alarms[indexPath.row].mediaID, repeatWeekdays: alarmModel.alarms[indexPath.row].repeatWeekdays, enabled: alarmModel.alarms[indexPath.row].enabled, dateTime: alarmModel.alarms[indexPath.row].date))
}
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCell(withIdentifier: Id.alarmCellIdentifier)
if (cell == nil) {
cell = UITableViewCell(style: UITableViewCellStyle.subtitle, reuseIdentifier: Id.alarmCellIdentifier)
}
//cell text
cell!.selectionStyle = .none
cell!.tag = indexPath.row
let alarm: Alarm = alarmModel.alarms[indexPath.row]
let amAttr: [String : Any] = [NSFontAttributeName : UIFont.systemFont(ofSize: 20.0)]
let str = NSMutableAttributedString(string: alarm.formattedTime, attributes: amAttr)
let timeAttr: [String : Any] = [NSFontAttributeName : UIFont.systemFont(ofSize: 45.0)]
str.addAttributes(timeAttr, range: NSMakeRange(0, str.length-2))
cell!.textLabel?.attributedText = str
cell!.detailTextLabel?.text = alarm.label
//append switch button
let sw = UISwitch(frame: CGRect())
sw.transform = CGAffineTransform(scaleX: 0.9, y: 0.9);
//tag is used to indicate which row had been touched
sw.tag = indexPath.row
sw.addTarget(self, action: #selector(MainAlarmViewController.switchTapped(_:)), for: UIControlEvents.touchUpInside)
if alarm.enabled {
sw.setOn(true, animated: false)
}
cell!.accessoryView = sw
//delete empty seperator line
tableView.tableFooterView = UIView(frame: CGRect.zero)
return cell!
}
@IBAction func switchTapped(_ sender: UISwitch) {
let index = sender.tag
alarmModel.alarms[index].enabled = sender.isOn
if sender.isOn {
print("switch on")
sender.superview?.backgroundColor = UIColor.white
alarmScheduler.setNotificationWithDate(alarmModel.alarms[index].date, onWeekdaysForNotify: alarmModel.alarms[index].repeatWeekdays, snoozeEnabled: alarmModel.alarms[index].snoozeEnabled, onSnooze: false, soundName: alarmModel.alarms[index].mediaLabel, index: index)
let cells = tableView.visibleCells
if !cells.isEmpty {
cells[index].textLabel?.alpha = 1.0
cells[index].detailTextLabel?.alpha = 1.0
}
}
else {
print("switch off")
sender.superview?.backgroundColor = UIColor.groupTableViewBackground
let cells = tableView.visibleCells
if !cells.isEmpty {
cells[index].textLabel?.alpha = 0.5
cells[index].detailTextLabel?.alpha = 0.5
}
alarmScheduler.reSchedule()
}
}
// Override to support editing the table view.
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
let index = indexPath.row
alarmModel.alarms.remove(at: index)
let cells = tableView.visibleCells
for cell in cells {
let sw = cell.accessoryView as! UISwitch
//adjust saved index when row deleted
if sw.tag > index {
sw.tag -= 1
}
}
if alarmModel.count == 0 {
self.navigationItem.leftBarButtonItem = nil
}
// Delete the row from the data source
tableView.deleteRows(at: [indexPath], with: .fade)
alarmScheduler.reSchedule()
}
}
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Get the new view controller using segue.destinationViewController.
// Pass the selected object to the new view controller.
let dist = segue.destination as! UINavigationController
let addEditController = dist.topViewController as! AlarmAddEditViewController
if segue.identifier == Id.addSegueIdentifier {
addEditController.navigationItem.title = "Add Alarm"
addEditController.segueInfo = SegueInfo(curCellIndex: alarmModel.count, isEditMode: false, label: "Alarm", mediaLabel: "bell", mediaID: "", repeatWeekdays: [], enabled: false, dateTime: Date())
}
else if segue.identifier == Id.editSegueIdentifier {
addEditController.navigationItem.title = "Edit Alarm"
addEditController.segueInfo = sender as! SegueInfo
}
}
@IBAction func unwindFromAddEditAlarmView(_ segue: UIStoryboardSegue) {
isEditing = false
}
}
答案 0 :(得分:0)
当您收到运行时错误时,应该在问题中包含引发错误的行。
标签是一种非常脆弱的方法,可以找出被挖掘的细胞。 (标签是一种非常脆弱的方式来做任何事情。除了使用标签之外,几乎总是找到视图或找出有关视图的信息的更好方法。)
我创建了一个UITableView的简单扩展,它允许您向表格视图询问单元格中任何视图的IndexPath。编写一个按钮IBAction很容易,该按钮使用扩展来确定敲击了哪个单元格。我建议重新编写代码以使用此方法
扩展名:
public extension UITableView {
/**
This method returns the indexPath of the cell that contains the specified view
- Parameter view: The view to find.
- Returns: The indexPath of the cell containing the view, or nil if it can't be found
*/
func indexPathForView(_ view: UIView) -> IndexPath? {
let origin = view.bounds.origin
let viewOrigin = self.convert(origin, from: view)
let indexPath = self.indexPathForRow(at: viewOrigin)
return indexPath
}
}
使用它的IBAction:
@IBAction func buttonTapped(_ button: UIButton) {
if let indexPath = self.tableView.indexPathForView(button) {
print("Button tapped at indexPath \(indexPath)")
}
else {
print("Button indexPath not found")
}
}
整个项目可在Github上找到:
TableViewExtension project on GitHub
我不知道为什么Apple没有将indexPathForView
函数构建到UITableView
之内。这似乎是一个普遍有用的功能。