У меня есть TableView, где каждая ячейка состоит из объекта NSTimer.
Мне удалось заставить его работать, где я могу запустить несколько таймеров, обновить их или удалить их. Но проблема возникает, когда я удаляю работающий таймер,
, когда я делаю это, следующий объект в моем списке счетчиков (объекты таймера) заменяет этот, но связанная с ним UILabel все еще ссылается на старый индекс и, следовательно, пользовательский интерфейс больше не обновляется.
В этот момент я действительно застрял, должен ли я сделать недействительными все таймеры и запустить их снова () после удаления одного или есть лучший способ сделать это?
Класс 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, y: 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", style: .done, target: self, 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, name: nil, ot: nil, ct: counter.currentTime - diff, md: nil, 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, name: nil, ot: nil, ct: nil, 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, name: nil, ot: nil, ct: counter.currentTime-1, md: nil, 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, name: nil, ot: nil, 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, name: nil, ot: nil, ct: counter.currentTime, 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, name: nil, ot: nil, 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?
}