Текстовая метка в ячейке табличного представления не обновляется таймером обратного отсчета - PullRequest
0 голосов
/ 11 июля 2019

У меня есть таблица с базовой функцией cellforrow. Я также создал функцию таймера для моего списка задач.

Я пытаюсь создать приложение с запущенным таймером обратного отсчета в каждой ячейке таблицы, используя стандартные ячейки субтитров. Имя таймера отображается в текстовом поле, а запущенный таймер отображается в субтитре.

Идея состоит в том, чтобы рассчитать оставшееся время для задачи, когда пользователь устанавливает конкретную дату крайнего срока для выбранной задачи.

Мне удалось реализовать эту функцию успешно, но проблема у меня заключается в том, что, когда я задаю для тега detailtext ячейки (субтитра) оставшееся время (которое отображает оставшиеся дни / часы / минуты / секунды), оно не обновляется.

Подводя итог, я хочу создать функцию таймера обратного отсчета для задачи, которая будет показывать обратный отсчет каждую секунду. Я хочу, чтобы он загружался в viewdidload.

мой таймер правильный? или не в том месте. Я подумал, что было бы целесообразно, чтобы таймер обновил всю таблицу тогда, если бы это была функция inn cellforRow.

Я попробовал таймер в функции cellforrow, но без радости. Затем я сделал отдельную функцию для таймера, чтобы обновить табличное представление с помощью reloadData () внутри него. Эта функция вызывается в viewDidLoad. обратный отсчет и все работает, но мой ярлык просто не обновится, если я не нажму на ячейку или не добавлю новую задачу todo.

override func viewDidLoad() {
    super.viewDidLoad()

    func startTimer() {
        self.countDownTimer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(reloadTableForTimer), userInfo: nil, repeats: true)
    }
}



@objc func reloadTableForTimer() {
    DispatchQueue.main.async { self.tableView.reloadData() }
}






override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
    if item.done == true {
                cell.detailTextLabel?.text = "Complete!"
            }else {

                var deadlineDatesAll = item.deadlineDate

                if deadlineDatesAll == "" {
                    cell.detailTextLabel?.text = "NO DEADLINE SET"
                } else {
                    // insert code for data disection and then to timer to display each countdown:
                    retrieveDateComponants(deadlineDateChosen: deadlineDatesAll.description)
                    var dataComponants:Array = retrieveDateComponants(deadlineDateChosen: deadlineDatesAll.description)

                    var returnedDueDateString = updateTime(yearExtract: dataComponants[0], monthExtract: dataComponants[1], dayExtract: dataComponants[2], hourExtract: dataComponants[3], minExtract: dataComponants[4], secsExtract: dataComponants[5])
                    cell.detailTextLabel?.text = "Due In: \(returnedDueDateString)"
                }

            }
        cell.accessoryType = item.done == true ? .checkmark : .none
    }else {
        cell.textLabel?.text = "No Item Found"
    }
    return cell
}


//UPDATE countdowns function:
@objc func updateTime(yearExtract:String, monthExtract:String, dayExtract:String,hourExtract:String,minExtract:String, secsExtract:String) -> (String) {

        var deadlineTimeDueString : String = ""

        let futureDate: Date = {
            var future = DateComponents(
                year: Int(yearExtract),
                month: Int(monthExtract),
                day: Int(dayExtract),
                hour: Int(hourExtract),
                minute: Int(minExtract),
                second: Int(secsExtract)            )
            return Calendar.current.date(from: future)!
        }()

        var countdownComp: DateComponents {
            return Calendar.current.dateComponents([.day, .hour, .minute, .second], from: Date(), to: futureDate)
        }

        let countdown = countdownComp //only compute once per call
        let days = countdown.day!
        let hours = countdown.hour!
        let minutes = countdown.minute!
        let seconds = countdown.second!

        print((String(format: "OUTPUT TEST: %02d:%02d:%02d:%02d", days, hours, minutes, seconds)))

        if seconds < 0 {
            deadlineTimeDueString = String("OVERDUE")
        } else if seconds > 0 {
            deadlineTimeDueString = String(format: "%02d Days, %02d Hours, %02d Mins & %02d Secs", days, hours, minutes, seconds)
        }
        return deadlineTimeDueString
    }


Все работает хорошо, за исключением того, что время обратного отсчета не обновляет метку, если я не нажму вручную на другую или ту же ячейку. Оставшееся время работает и обновляется каждую секунду.

любая помощь будет оценена Спасибо МФК

1 Ответ

0 голосов
/ 12 июля 2019

Я мог бы предложить разрешить ячейкам обновлять свои собственные метки, но чтобы они обновлялись одновременно, иметь уведомление контроллера о том, когда ячейки должны быть обновлены.

Итак, вы можете создать уведомление для своего таймера:

extension Notification.Name {
    static let timerFired = Notification.Name(rawValue: Bundle.main.bundleIdentifier! + ".timer")
}

Затем контроллер представления разместит это уведомление:

class ViewController: UITableViewController {
    var todos = ...

    private weak var timer: Timer?

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)

        timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in
            NotificationCenter.default.post(name: .timerFired, object: nil)
        }
    }

    override func viewDidDisappear(_ animated: Bool) {
        super.viewDidDisappear(animated)
        timer?.invalidate()
    }
}

UITableViewDataSource может затем передать «сделать» в клетку:

// MARK: - UITableViewDataSource

extension ViewController {
    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return todos.count
    }

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "ToDoCell", for: indexPath) as! ToDoCell
        cell.configure(for: todos[indexPath.row])
        return cell
    }
}

Затем ячейка может связать все это вместе, соблюдая уведомление и соответствующим образом обновляя текст:

class ToDoCell: UITableViewCell {
    private var todo: ToDo?
    private var token: NSObjectProtocol?

    private static let formatter: DateComponentsFormatter = {
        let formatter = DateComponentsFormatter()
        formatter.unitsStyle = .full
        formatter.allowedUnits = [.day, .hour, .minute, .second]
        formatter.maximumUnitCount = 2
        return formatter
    }()

    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        addObservers()
    }

    required init?(coder: NSCoder) {
        super.init(coder: coder)
        addObservers()
    }

    deinit {
        removeObservers()
    }

    func configure(for todo: ToDo) {
        self.todo = todo
        textLabel?.text = todo.name
        if todo.done {
            removeObservers()
            detailTextLabel?.textColor = .black
            detailTextLabel?.text = "Done!"
            accessoryType = .checkmark
        } else {
            addObservers()
            updateTime()
            accessoryType = .none
        }
    }
}

private extension ToDoCell {
    func addObservers() {
        guard token == nil else { return }

        token = NotificationCenter.default.addObserver(forName: .timerFired, object: nil, queue: .main) { [weak self] _ in
            self?.updateTime()
        }
    }

    func removeObservers() {
        if let token = token {
            NotificationCenter.default.removeObserver(token)
        }
    }

    func updateTime() {
        guard let date = todo?.deadline else {
            detailTextLabel?.text = "No deadline specified"
            detailTextLabel?.textColor = .black
            return
        }

        let now = Date()
        if date < now {
            detailTextLabel?.text = "Past due " + (ToDoCell.formatter.string(from: date, to: now) ?? "")
            detailTextLabel?.textColor = .red
        } else {
            detailTextLabel?.text = ToDoCell.formatter.string(from: now, to: date)
            detailTextLabel?.textColor = .black
        }
    }
}

Обратите внимание, я использую DateComponentsFormatter с maximumUnitCount из 2, поэтому я не вижу раздражающего уровня детализации. Поэтому вместо «7 дней, 12 часов, 42 минуты, 8 секунд» я просто посмотрю «7 дней, 12 часов», но он покажет, где могут быть значимы секунды (например, менее чем через 1 час):

enter image description here

Но детали менее важны, чем общий принцип разработки, заключающийся в том, что большая часть кода, специфичного для ячейки, включается в подкласс UITableViewCell, а не помещается в контроллер представления.

...