简体   繁体   English

可重复使用的单元和阵列出现问题

[英]Trouble with reusable cells and array

I have Alarm Clock App. 我有闹钟应用程序。 User can add alarms. 用户可以添加警报。 If I add too much alarms and scroll down I will see that 如果我添加太多警报并向下滚动,我会看到

在此处输入图片说明

If i tried to use UISwitch (only buttom cell) I will crash the app and get Error: 如果我尝试使用UISwitch (仅适用于buttom单元格),则会使应用程序崩溃并出现错误:

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

I think that the problem inside of array. 我认为数组内部的问题。 How to resolve it? 怎么解决呢?

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
    }

}

When you get a runtime error, you should include the line that throws the error as part of your question. 遇到运行时错误时,应在问题中包括引发错误的行。

Tags are a pretty fragile way to figure out what cell was tapped. 标签是一种非常脆弱的方法,可以确定被窃听的单元格。 (Tags are a pretty fragile way to do anything. There's almost always a better way to find views or figure out information about a view than using tags.) (标签是做任何事情的一种非常脆弱的方法。几乎总是比使用标签更好的方法来查找视图或找出有关视图的信息。)

I created a simple extension to UITableView that lets you ask the table view for the IndexPath of any view in a cell. 我为UITableView创建了一个简单的扩展,使您可以向表视图询问单元格中任何视图的IndexPath。 It's easy to write a button IBAction that uses the extension to figure out which cell was tapped. 编写一个按钮IBAction很容易,该按钮使用扩展名来找出被点击的单元格。 I suggest reworking your code to use this approach 我建议您修改代码以使用这种方法

The extension: 扩展名:

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 bounds = view.bounds
        let center = CGPoint(x: bounds.midX, y: bounds.midY)
        let viewCenter = self.convert(center, from: view)
        let indexPath = self.indexPathForRow(at: viewCenter)
        return indexPath
    }
}

And the IBAction that uses it: 以及使用它的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")
  }
}

The whole project is available on Github: 整个项目在Github上可用:

TableViewExtension project on GitHub GitHub上的TableViewExtension项目

I don't know why Apple doesn't build something like the indexPathForView function into UITableView . 我不知道为什么苹果没有在UITableView构建诸如indexPathForView函数之类的东西。 It seems like a universally useful function. 它似乎是一个通用的功能。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM