tableView中的几个NSTimer对象的问题

我有一个TableView,其中每个单元格都由一个NSTimer对象组成。

我设法使其能够在多个计时器运行,更新或删除它们的地方工作。 但是,当我删除正在运行的计时器时,就会出现问题

当我这样做时,计数器列表中的下一个对象(计时器对象)将替换该对象,但是与此相关的UILabel仍引用旧索引,因此UI不再得到更新。

在这一点上,我真的很困,应该删除所有计时器,然后在删除一个计时器之后再次触发它们吗?或者有更好的方法吗?

ViewController类(主要问题在“ playPauseCounter”功能上)

import UIKit
import RealmSwift

class TimerController: UITableViewController,editDataDelegate,settingVCDelegate {

    // MARK: - Properties

    // delegate objects
    var timerArray: [String: Timer]?
    var counterArray: Results<Counter>?
    var counter: Counter?
    var index: String?


    var counters: Results<Counter>?
    var timerDict = [String: Timer]()

    private let reuseIdentifier = "TimerCell"
    private let cellSpacingHeight: CGFloat = 10
    var shared = DatabaseService.shared
    var sharedNotif = NotifService.shared

    // MARK: - Initializers
    override func viewDidLoad() {
        super.viewDidLoad()
        configureNavigationBar()
        tableView.register(TimerCell.self,forCellReuseIdentifier: reuseIdentifier)
//        How to handle when application goes to background and then comes to foreground
        NotificationCenter.default.addobserver(self,selector: #selector(pauseWhenBackground(noti:)),name: UIApplication.didEnterBackgroundNotification,object: nil)
        NotificationCenter.default.addobserver(self,selector: #selector(continueWhenForeground(noti:)),name: UIApplication.willEnterForegroundNotification,object: nil)
        sharedNotif.requestLocalNotification()
        if shared.getcurrentTheme() == true {
            overrideUserInterfaceStyle = .dark
        } else {
            overrideUserInterfaceStyle = .light
        }
        refreshData()
    }

    override func viewDidAppear(_ animated: Bool) {
        refreshData()
        if shared.getcurrentTheme() == true {
            overrideUserInterfaceStyle = .dark
        } else {
            overrideUserInterfaceStyle = .light
        }
        tableView.reloadData()
    }

    // MARK: - UITableView Functions
    override func tableView(_ tableView: UITableView,trailingSwipeactionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeactionsConfiguration? {
        let action = UIContextualaction(style: .normal,title: "Delete") { (_,_,completion) in
            guard let counter = self.counters?[indexPath.section] else { return }
            self.sharedNotif.removeLocalNotificationPending(id: counter.id)
            if let t = self.timerDict[counter.id] {
                t.invalidate()
            }
            self.shared.delete(idx: indexPath.section)
            self.tableView.reloadData()
            completion(true)
        }
        action.image = #imageLiteral(resourceName: "delete")
        action.backgroundColor = .textRed()
        return UISwipeactionsConfiguration(actions: [action])
    }

    override func tableView(_ tableView: UITableView,leadingSwipeactionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeactionsConfiguration? {
        let edit = handleEdit(at: indexPath)
        return UISwipeactionsConfiguration(actions: [edit])
    }

    override func numberOfSections(in tableView: UITableView) -> Int {
        counters = shared.fetchAllCounters()
        guard let counters = counters else { return 0 } // later here load items from the database
        return counters.count
    }

    // There is just one row in every section
    override func tableView(_ tableView: UITableView,numberOfRowsInSection section: Int) -> Int {
        return 1
    }

    // Set the spacing between sections
    override func tableView(_ tableView: UITableView,heightForHeaderInSection section: Int) -> CGFloat {
        return cellSpacingHeight
    }

    override func tableView(_ tableView: UITableView,heightForRowAt indexPath: IndexPath) -> CGFloat {
        return 80
    }

    // Make the background color show through
    override func tableView(_ tableView: UITableView,viewForHeaderInSection section: Int) -> UIView? {
        let headerView = UIView()
        headerView.backgroundColor = UIColor.clear
        return headerView
    }
    override func tableView(_ tableView: UITableView,cellForRowAt indexPath: IndexPath) -> UITableViewCell {

        // swiftlint:disable force_cast
        let cell = tableView.dequeueReusableCell(withIdentifier: reuseIdentifier,for: indexPath) as! TimerCell
        // swiftlint:enabl1e force_cast
        if let counters = counters {
            cell.counter = counters[indexPath.section]
            changeColorsLight(cell: cell,mode: counters[indexPath.section].counterMode)
        }
        cell.delegate = self
        return cell
    }
    func footerAddButton() {
        let footerView = UIView(frame: CGRect(x: 0,y: 0,width: 100,height: 100))
        let btn = UIButton(type: .roundedRect)
        btn.setTitle("ADD",for: .normal)
        btn.setTitleColor(.black,for: .normal)
        btn.titleLabel?.font = UIFont.boldSystemFont(ofSize: 16)
        btn.translatesAutoresizingMaskintoConstraints = false
        btn.layer.cornerRadius = 5
        btn.frame = CGRect(x: 0,width: 50,height: 50)
        footerView.addSubview(btn)
        tableView.tableFooterView = footerView
    }

    //MARK: - Helper Methods
    func refreshData() {
        counters = shared.fetchAllCounters()
        if let counters = counters {
            for counter in counters where timerDict[counter.id] == nil {
                timerDict[counter.id] = Timer()
            }
        }
    }

    func getTimeDifference(_ startDate: Date) -> Int {
        let calendar = Calendar.current
        let components = calendar.dateComponents([.second],from: startDate,to: Date())
        if let secs = components.second {
            return abs(secs)
        }
        return 0
    }

    func configureNavigationBar() {
        navigationController?.navigationBar.barTintColor = UIColor.systemGray
        navigationController?.navigationBar.isTranslucent = false
        navigationItem.title = "Timers"
        navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Settings",style: .done,target: self,action: #selector(goToSettings))
        navigationItem.leftBarButtonItem = UIBarButtonItem(title: "Add",action: #selector(goToNewTimer))
    }

    //MARK: - Event Handlers
    @objc func pauseWhenBackground(noti: Notification) {
        counters = shared.fetchAllCounters()
        guard let counters = counters else { return }
        for counter in counters where counter.counterMode == Mode.running.rawValue {
            guard let idx = counters.index(of: counter) else { return }
            shared.editCounter(idx: counter.id,name: nil,ot: nil,ct: nil,md: nil,timer: Date())
        }
    }

    @objc func continueWhenForeground(noti: Notification) {
        counters = shared.fetchAllCounters()
        guard let counters = counters else { return }
        for counter in counters where counter.counterMode == Mode.running.rawValue {
            if let savedDate = counter.savedTime {
                let diff = getTimeDifference(savedDate)
                if diff > 0 {
                    guard let idx = counters.index(of: counter) else { return }
                    shared.editCounter(idx: counter.id,ct: counter.currentTime - diff,timer: nil)
                }
            }
        }
    }

    @objc func goToSettings() {
        let settingVC = SettingController()
        settingVC.delegateVC = self
        timerArray = timerDict
        counterArray = shared.fetchAllCounters()
        navigationController?.pushViewController(settingVC,animated: true)
    }

    @objc func goToNewTimer() {
        let newTimerVC = TimeController()
        navigationController?.pushViewController(newTimerVC,animated: true)
    }

    func handleEdit(at indexPath: IndexPath) -> UIContextualaction {
        let action = UIContextualaction(style: .normal,title: "Edit") { (_,_) in
            let vc = TimeController()
            vc.delegate = self
            self.counter = self.counters![indexPath.section]
            self.index = self.counters![indexPath.section].id
            self.navigationController?.pushViewController(vc,animated: true)
        }
        action.image = #imageLiteral(resourceName: "edit")
        action.backgroundColor = .rgb(red: 239,blue: 13,green: 155)
        return action
    }
}

extension TimerController: CounterDelegate {

    // transfer seconds to h/m/s
    func updateCounterView(seconds: Int) -> String {
        let arr = secondsToDate(seconds: seconds)
        return String(format: "%02i:%02i:%02i",arr[0],arr[1],arr[2])
    }

    func playPauseCounter(cell: TimerCell) {
        let idx = self.tableView.indexPath(for: cell)
        guard let index = idx?.section else { return }
        guard let counter = counters?[index] else { return }
        let timeLabel = cell.timeLabel

        if counter.counterMode == Mode.notStarted.rawValue ||  counter.counterMode == Mode.paused.rawValue {
            changeColorsLight(cell: cell,mode: Mode.running.rawValue)
            // add local notification when user resumes or starts the timer
            sharedNotif.addLocalNotificationAlert(id: counter.id,name: counter.name,seconds: counter.currentTime)

            cell.backgroundColor = UIColor.rgb(red: 1,blue: 255,green: 123)
            shared.editCounter(idx: counter.id,md: .running,timer: nil)
            timerDict[counter.id] = Timer.scheduledTimer(withTimeInterval: 1,repeats: true,block: { [weak self] (_) in
                if counter.currentTime > 0 {
                    print(counter.currentTime)
                    self?.shared.editCounter(idx: counter.id,ct: counter.currentTime-1,timer: nil)
                    timeLabel.text = self?.updateCounterView(seconds: counter.currentTime)
                } else if counter.currentTime == 0 {
                    // remove all delivered local notifications left
                    changeColorsLight(cell: cell,mode: Mode.ended.rawValue)
                    self?.sharedNotif.removeLocalNotificationsDelivered()
                    self?.timerDict[counter.id]!.invalidate()
                    self?.shared.editCounter(idx: counter.id,ct: counter.currentTime,md: .ended,timer: nil)
                }
            })
            RunLoop.current.add(timerDict[counter.id]!,forMode: .common)
            timerDict[counter.id]!.tolerance = 0.15
        } else if counter.counterMode == Mode.running.rawValue {
            changeColorsLight(cell: cell,mode: Mode.paused.rawValue)
            sharedNotif.removeLocalNotificationPending(id: counter.id)
            timerDict[counter.id]!.invalidate()
            shared.editCounter(idx: counter.id,md: .paused,timer: nil)
        }
    }
    func resetCounter(cell: TimerCell) {
        let idx = self.tableView.indexPath(for: cell)
        guard let index = idx?.section else { return }
        guard let counter = counters?[index] else { return }
        timerDict[counter.id]!.invalidate()
        // remove the previous notification (if exists) before resetting the timer
        sharedNotif.removeLocalNotificationPending(id: counter.id)
        // change currentTime back to original
        shared.editCounter(idx: counter.id,ct: counter.originalTime,md: .notStarted,timer: nil)
        cell.timeLabel.text = String(counter.originalTime)
        changeColorsLight(cell: cell,mode: Mode.notStarted.rawValue)
    }
}

模型类

import Foundation
import RealmSwift

class Counter: Object {
    @objc dynamic var id: String = UUID().uuidString
    @objc dynamic var name: String = ""
    @objc dynamic var originalTime: Int = 0
    @objc dynamic var currentTime: Int = 0
    // Realm database doesnt work properly with objects of type enum
    @objc dynamic var counterMode: Int = 0
    @objc dynamic var savedTime: Date?
}
elai30 回答:tableView中的几个NSTimer对象的问题

对于以后遇到此问题的任何人,我都使用Paulw11提出的提示,并仅创建了一个计时器,而不是在数组中手动​​保存索引,而是在每次cellForRowAt函数获取时将所有当前单元格保存在tableView中。叫。

这是我的解决方案的修改后的代码:

import UIKit
import RealmSwift

class TimerController: UITableViewController,editDataDelegate,settingVCDelegate {

    // MARK: - Properties

    // delegate objects
    var timerArray: [String: Timer]?
    var counterArray: Results<Counter>?
    var counter: Counter?
    var index: String?

    var counters: Results<Counter>?
    var timer = Timer()
    var currentRunning = [TimerCell]()

    private let reuseIdentifier = "TimerCell"
    private let cellSpacingHeight: CGFloat = 10
    var shared = DatabaseService.shared
    var sharedNotif = NotifService.shared

    // MARK: - Initializers
    override func viewDidLoad() {
        super.viewDidLoad()
        shared.deleteAll()

        configureNavigationBar()

        tableView.register(TimerCell.self,forCellReuseIdentifier: reuseIdentifier)

        // How to handle when application goes to background and then comes to foreground
        NotificationCenter.default.addObserver(self,selector: #selector(pauseWhenBackground(noti:)),name: UIApplication.didEnterBackgroundNotification,object: nil)

        NotificationCenter.default.addObserver(self,selector: #selector(continueWhenForeground(noti:)),name: UIApplication.willEnterForegroundNotification,object: nil)

        sharedNotif.requestLocalNotification()

        if shared.getCurrentTheme() == true {
            overrideUserInterfaceStyle = .dark
        } else {
            overrideUserInterfaceStyle = .light
        }
        counters = shared.fetchAllCounters()

        // add this feature so timer will continue working when user drags down the list,and add tolerance to timer
        fireTimer()
    }

    override func viewDidAppear(_ animated: Bool) {
        counters = shared.fetchAllCounters()

        if shared.getCurrentTheme() == true {
            overrideUserInterfaceStyle = .dark
        } else {
            overrideUserInterfaceStyle = .light
        }
        tableView.reloadData()
    }

    // MARK: - UITableView Functions
    override func tableView(_ tableView: UITableView,trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
        let action = UIContextualAction(style: .normal,title: "Delete") { (_,_,completion) in
            guard let counter = self.counters?[indexPath.section] else { return }
            self.sharedNotif.removeLocalNotificationPending(id: counter.id)
            /////////////
            self.shared.delete(idx: indexPath.section)
            self.tableView.reloadData()
            ///////////
            completion(true)
        }
        action.image = #imageLiteral(resourceName: "delete")
        action.backgroundColor = .textRed()
        return UISwipeActionsConfiguration(actions: [action])
    }

    override func tableView(_ tableView: UITableView,leadingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
        let edit = handleEdit(at: indexPath)
        return UISwipeActionsConfiguration(actions: [edit])
    }

    override func numberOfSections(in tableView: UITableView) -> Int {
        print(currentRunning)
        currentRunning = []
        counters = shared.fetchAllCounters()
        guard let counters = counters else { return 0 } // later here load items from the database
        return counters.count
    }

    // There is just one row in every section
    override func tableView(_ tableView: UITableView,numberOfRowsInSection section: Int) -> Int {
        return 1
    }

    // Set the spacing between sections
    override func tableView(_ tableView: UITableView,heightForHeaderInSection section: Int) -> CGFloat {
        return cellSpacingHeight
    }

    override func tableView(_ tableView: UITableView,heightForRowAt indexPath: IndexPath) -> CGFloat {
        return 80
    }

    // Make the background color show through
    override func tableView(_ tableView: UITableView,viewForHeaderInSection section: Int) -> UIView? {
        let headerView = UIView()
        headerView.backgroundColor = UIColor.clear
        return headerView
    }
    override func tableView(_ tableView: UITableView,cellForRowAt indexPath: IndexPath) -> UITableViewCell {

        // swiftlint:disable force_cast
        let cell = tableView.dequeueReusableCell(withIdentifier: reuseIdentifier,for: indexPath) as! TimerCell
        // swiftlint:enabl1e force_cast
        if let counters = counters {
            cell.counter = counters[indexPath.section]
            changeColorsLight(cell: cell,mode: counters[indexPath.section].counterMode)
            if cell.counter?.counterMode == Mode.running.rawValue {
                currentRunning.append(cell)
            }
        }
        cell.delegate = self
        return cell
    }
    func footerAddButton() {
        let footerView = UIView(frame: CGRect(x: 0,y: 0,width: 100,height: 100))
        let btn = UIButton(type: .roundedRect)
        btn.setTitle("ADD",for: .normal)
        btn.setTitleColor(.black,for: .normal)
        btn.titleLabel?.font = UIFont.boldSystemFont(ofSize: 16)
        btn.translatesAutoresizingMaskIntoConstraints = false
        btn.layer.cornerRadius = 5
        btn.frame = CGRect(x: 0,width: 50,height: 50)
        footerView.addSubview(btn)
        tableView.tableFooterView = footerView
    }

    //MARK: - Helper Methods
    func getTimeDifference(_ startDate: Date) -> Int {
        let calendar = Calendar.current
        let components = calendar.dateComponents([.second],from: startDate,to: Date())
        if let secs = components.second {
            return abs(secs)
        }
        return 0
    }

    func configureNavigationBar() {
        navigationController?.navigationBar.barTintColor = UIColor.systemGray
        navigationController?.navigationBar.isTranslucent = false
        navigationItem.title = "Timers"
        navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Settings",style: .done,target: self,action: #selector(goToSettings))
        navigationItem.leftBarButtonItem = UIBarButtonItem(title: "Add",action: #selector(goToNewTimer))
    }

    //MARK: - Event Handlers
    @objc func pauseWhenBackground(noti: Notification) {
        counters = shared.fetchAllCounters()
        guard let counters = counters else { return }
        for counter in counters where counter.counterMode == Mode.running.rawValue {
            guard let idx = counters.index(of: counter) else { return }
            shared.editCounter(idx: counter.id,name: nil,ot: nil,ct: nil,md: nil,timer: Date())
        }
    }

    @objc func continueWhenForeground(noti: Notification) {
        counters = shared.fetchAllCounters()
        guard let counters = counters else { return }
        for counter in counters where counter.counterMode == Mode.running.rawValue {
            if let savedDate = counter.savedTime {
                let diff = getTimeDifference(savedDate)
                if diff > 0 {
                    guard let idx = counters.index(of: counter) else { return }
                    shared.editCounter(idx: counter.id,ct: counter.currentTime - diff,timer: nil)
                }
            }
        }
    }

    @objc func goToSettings() {
        let settingVC = SettingController()
        settingVC.delegateVC = self

        //////////////
        timerArray = [String: Timer]()
        /////////////

        counterArray = shared.fetchAllCounters()
        navigationController?.pushViewController(settingVC,animated: true)
    }

    @objc func goToNewTimer() {
        let newTimerVC = TimeController()
        navigationController?.pushViewController(newTimerVC,animated: true)
    }

    func handleEdit(at indexPath: IndexPath) -> UIContextualAction {
        let action = UIContextualAction(style: .normal,title: "Edit") { (_,_) in
            let vc = TimeController()
            vc.delegate = self
            self.counter = self.counters![indexPath.section]
            self.index = self.counters![indexPath.section].id
            self.navigationController?.pushViewController(vc,animated: true)
        }
        action.image = #imageLiteral(resourceName: "edit")
        action.backgroundColor = .rgb(red: 239,blue: 13,green: 155)
        return action
    }
}

extension TimerController: CounterDelegate {

    // transfer seconds to h/m/s
    func updateCounterView(seconds: Int) -> String {
        let arr = secondsToDate(seconds: seconds)
        return String(format: "%02i:%02i:%02i",arr[0],arr[1],arr[2])
    }

    func playPauseCounter(cell: TimerCell) {
        let idx = self.tableView.indexPath(for: cell)
        guard let index = idx?.section else { return }
        guard let counter = counters?[index] else { return }
        let timeLabel = cell.timeLabel

        if counter.counterMode == Mode.notStarted.rawValue ||  counter.counterMode == Mode.paused.rawValue {
            changeColorsLight(cell: cell,mode: Mode.running.rawValue)
            sharedNotif.addLocalNotificationAlert(id: counter.id,name: counter.name,seconds: counter.currentTime)
            cell.backgroundColor = UIColor.rgb(red: 1,blue: 255,green: 123)
            shared.editCounter(idx: counter.id,md: .running,timer: nil)
            ///////////////
            currentRunning.append(cell)
            /////////////
        } else if counter.counterMode == Mode.running.rawValue {
            changeColorsLight(cell: cell,mode: Mode.paused.rawValue)
            sharedNotif.removeLocalNotificationPending(id: counter.id)
            shared.editCounter(idx: counter.id,ct: counter.currentTime,md: .paused,timer: nil)
            ////////////////////
            guard let indexOf = currentRunning.index(of: cell) else { return }
            currentRunning.remove(at: indexOf)
            ///////////////
        }
    }

    func fireTimer() {
        timer = Timer.scheduledTimer(withTimeInterval: 1,repeats: true,block: { (_) in
            // for each item in the current running list,update it and save it to database
            for cell in self.currentRunning {
                guard let counter = cell.counter else { return }
                let timeLabel = cell.timeLabel
                if counter.currentTime > 0 {
                    self.shared.editCounter(idx: counter.id,ct: counter.currentTime-1,timer: nil)
                    timeLabel.text = self.updateCounterView(seconds: counter.currentTime)
                } else if counter.currentTime == 0 {
                    // remove all delivered local notifications left
//                    changeColorsLight(cell: cell,mode: Mode.ended.rawValue)
                    self.sharedNotif.removeLocalNotificationsDelivered()
                    self.shared.editCounter(idx: counter.id,md: .ended,timer: nil)
                    // here remove this item from the list
                    guard let indexOf = self.currentRunning.index(of: cell) else { return }
                    self.currentRunning.remove(at: indexOf)
                    ///////////////
                }
            }
        })
        RunLoop.current.add(timer,forMode: .common)
        timer.tolerance = 0.15
    }


    func resetCounter(cell: TimerCell) {
        let idx = self.tableView.indexPath(for: cell)
        guard let index = idx?.section else { return }
        guard let counter = counters?[index] else { return }

        // remove this index from the running list
        guard let indexOf = self.currentRunning.index(of: cell) else { return }
        self.currentRunning.remove(at: indexOf)
        //////////

        sharedNotif.removeLocalNotificationPending(id: counter.id)
        shared.editCounter(idx: counter.id,ct: counter.originalTime,md: .notStarted,timer: nil)
        cell.timeLabel.text = String(counter.originalTime)
        changeColorsLight(cell: cell,mode: Mode.notStarted.rawValue)
    }
}
本文链接:https://www.f2er.com/2807763.html

大家都在问