2017-04-15 26 views
0

私は目覚まし時計アプリケーションを持っています。ユーザーはアラームを追加できます。再利用可能なセルとアレイの問題

Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[__NSArrayI objectAtIndex:]: index 8 beyond bounds [0 .. 7]' 

I:私はあまりにも多くのアラームを追加し、スクロールダウンしている場合、私は私がUISwitch(のみ湖底セル)を使用しようとした場合

enter image description here

は私がアプリをクラッシュし、エラーを取得することがわかりますその配列内の問題を考える。それを解決するには?あなたはランタイムエラーを取得すると

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

そのセルのスイッチとやり取りするとアプリケーションがクラッシュする –

+1

セルタグを使用しないでください。行が並べ替えられたり削除されたりすると問題が発生することがわかっています。より良いアプローチについては、http://stackoverflow.com/questions/28659845/swift-how-to-get-the-indexpath-row-when-a-button-in-a-cell-is-tapped/38941510#38941510を参照してください。 – Paulw11

答えて

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に作ります。普遍的に有用な機能のようです。

関連する問題