2017-04-15 80 views
0

我的鬧鐘應用。用戶可以添加警報。如果我添加了過多的報警和向下滾動我會看到麻煩可重複使用的細胞和陣列

enter image description here

如果我試圖用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

當我與該單元的開關交互時,應用程序崩潰 –

+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

我不知道蘋果爲什麼不將indexPathForView函數構建成UITableView。這似乎是一個普遍有用的功能。