Нет необходимости обновлять пользовательский интерфейс 10 раз в секунду. Поскольку он разряжает аккумулятор устройства намного быстрее, чем необходимо, он должен работать только раз в минуту. Вы можете изменить timeInterval таймера на 1 секунду и запланировать его срабатывание в следующую четную секунду. Чтобы получить следующий четный час и следующую четную минуту, вы можете использовать метод Calendar
func nextDate(after date: Date, matching components: DateComponents, matchingPolicy: Calendar.MatchingPolicy, repeatedTimePolicy: Calendar.RepeatedTimePolicy = .first, direction: Calendar.SearchDirection = .forward) -> Date?
Просто создайте два вычисленных свойства, расширяющих Date, и передайте ноль для минутных или наносекундных компонентов:
extension Date {
var nextHour: Date {
Calendar.current.nextDate(after: self, matching: DateComponents(minute: 0), matchingPolicy: .strict)!
}
var nextSecond: Date {
Calendar.current.nextDate(after: self, matching: DateComponents(nanosecond: 0), matchingPolicy: .strict)!
}
var minute: Int {
Calendar.current.component(.minute, from: self)
}
}
Теперь добавьте свойство в ваш контроллер представления, чтобы сохранить ссылку на дату окончания. Обратите внимание, что нет необходимости объявлять свой таймер как необязательный:
var end: Date?
var timer = Timer()
И создать DateComponentsFormatter для создания локализованного описания оставшегося времени:
extension Formatter {
static let minutesRemaining: DateComponentsFormatter = {
let formatter = DateComponentsFormatter()
formatter.formattingContext = .standalone
formatter.unitsStyle = .short
formatter.allowedUnits = [.minute, .second]
formatter.includesTimeRemainingPhrase = true
return formatter
}()
}
Теперь вы просто настраиваете дату окончания и запланировать срабатывание таймера в следующую четную минуту:
override func viewDidLoad() {
super.viewDidLoad()
// get the current date
let date = Date()
// set the end date
end = date.nextHour
// schedule the timer to fire at the next even second and set its interval to 1 second
timer = .init(fireAt: date.nextSecond, interval: 1, target: self, selector: #selector(updateUI), userInfo: nil, repeats: true)
RunLoop.main.add(timer, forMode: .common)
updateUI()
}
@objc func updateUI() {
if Date().minute == 0 || Date() > end {
end = Date().nextHour
timerLabel.text = "beginning of hour"
print("beginning of hour")
} else {
// update the remaining time (for a perfect sync we need to subtract a second from the current time)
let text = Formatter.minutesRemaining.string(from: Date().addingTimeInterval(-1), to: end) ?? ""
timerLabel.text = text
print(text)
}
}