我有一个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?
}