Reputation:
I Want My swift code to count down to the nearest top of the hour. So if the time is 146 the user code should count down 14 minutes. Right now My code below counts down to a spefic day and time. I just want it to count down to the nearest hour when the app is running.
import UIKit
class ViewController: UIViewController {
@IBOutlet var timerLabel: UILabel!
var timer: Timer!
override func viewDidLoad() {
super.viewDidLoad()
timer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(UpdateTime), userInfo: nil, repeats: true)
}
@objc func UpdateTime() {
let userCalendar = Calendar.current
// Set Current Date
let date = Date()
let components = userCalendar.dateComponents([.hour, .minute, .month, .year, .day], from: date)
let currentDate = userCalendar.date(from: components)!
// Set Event Date
var eventDateComponents = DateComponents()
eventDateComponents.year = 2021
eventDateComponents.month = 01
eventDateComponents.day = 01
eventDateComponents.hour = 01
eventDateComponents.minute = 00
eventDateComponents.timeZone = TimeZone(abbreviation: "GMT")
let eventDate = userCalendar.date(from: eventDateComponents)!
let timeLeft = userCalendar.dateComponents([.day, .hour, .minute, ], from: currentDate, to: eventDate)
timerLabel.text = "\(timeLeft.day!)d \(timeLeft.hour!)h \(timeLeft.minute!)m "
endEvent(currentdate: currentDate, eventdate: eventDate)
}
func endEvent(currentdate: Date, eventdate: Date) {
if currentdate >= eventdate {
timerLabel.text = "Happy New Year!"
// Stop Timer
timer.invalidate()
}
}
}
edit/update:
My goal in my swift code is when the top of the hour is reached. After trying to implement @Leo's answer it prints "Top of Hour" and it does the problem is that It only does it one time. As long as the app is open I want it to print "Top of Hour" at every hour. So I need to reset the end date which is what I tried to do at
let date = Date() end = date.nextHour
That does not let the code compile. So I have to reset the end var to the next hour.
Upvotes: 0
Views: 202
Reputation: 236305
No need to update the user interface 10 times per second. As it is it will drain the device's battery much faster than needed while it should only run once a minute. You can change your timer timeInterval to 1 second and schedule it to fire at the next even second. To get the next even hour and the next even minute you can use Calendar method
func nextDate(after date: Date, matching components: DateComponents, matchingPolicy: Calendar.MatchingPolicy, repeatedTimePolicy: Calendar.RepeatedTimePolicy = .first, direction: Calendar.SearchDirection = .forward) -> Date?
Just create two computed properties extending Date and pass zero for minute or nanosecond components:
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)
}
}
Now add a property to your view controller to keep a reference of the end date. Note that there is no need to declare your timer as optional:
var end: Date?
var timer = Timer()
And create a DateComponentsFormatter to create a localized description of the remaining time:
extension Formatter {
static let minutesRemaining: DateComponentsFormatter = {
let formatter = DateComponentsFormatter()
formatter.formattingContext = .standalone
formatter.unitsStyle = .short
formatter.allowedUnits = [.minute, .second]
formatter.includesTimeRemainingPhrase = true
return formatter
}()
}
Now you just setup the end date and to schedule your timer to fire at the next even minute:
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)
}
}
Upvotes: 0