My app is supposed to perform an action whenever the time changes (every minute, not every second). The way that I am achieving this is by creating a Timer object, like so:
var timer = Timer.scheduledTimer(timeInterval: 60, target: self, selector: #selector(ViewController.update), userInfo: nil, repeats: true)
Although this way works it doesn't look very professional because the app doesn't perform the update exactly when the time changes and instead depends on the precise moment the object was created. For example, if the Timer object was created at 10:10:30, then the update will only be performed 30 seconds after the minute changes.
Is there a way that I can know that the minute of the time on the phone changes?
Thanks in advance!
You can schedule the first timer's fire at a specific time, and that time could be the next round minute. Here is an example (copy-and-paste to a playgroung):
import PlaygroundSupport
import Foundation
let cal = NSCalendar.current
var comps = cal.dateComponents([.era, .year, .month, .day, .hour, .minute], from: Date())
comps.minute = comps.minute! + 1
let nextMinute = cal.date(from: comps)
let timer = Timer(fire: nextMinute!, interval: 60, repeats: true) { _ in
print("Minute change")
}
RunLoop.current.add(timer, forMode: .defaultRunLoopMode)
PlaygroundPage.current.needsIndefiniteExecution = true
Note that the timer will not fire if the app is in the background.
Related
I'm trying to update a countdown timer on my UI that counts down in minutes, so I need to update it every start of a new minute, which is not the same as updating every minute. My current solution is initializing a timer that calls the updateUI() function every tenth of a second, which is far from ideal:
timer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector:#selector(TimesViewController.updateUI), userInfo: nil, repeats: true)
Is there a way to call that function every start of a new minute?
Create timer that checks system date in each second. For example;
Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { (_) in
let second = Calendar.current.component(.second, from: Date())
if second == 0 {
// new minute
updateUI()
}
}
Let's say I have a view controller with code that I want to execute at a certain time, say 2:00pm. What I want to happen is that if the user opens the view controller at 1:58pm, that the code will wait and continuously check the time, executing itself at exactly 2:00pm. Is there a way to do this in Swift 4, perhaps with a timer?
You can go around this way to achieve the result:
override func viewDidLoad() {
super.viewDidLoad()
let dateFormatter = DateFormatter()
/*The accepted DateFormat*/
dateFormatter.dateFormat = "MM-dd-yyyy HH:mm"
/*My Date String to run the code at*/
let dateString = "12-06-2017 15:41"
let date = dateFormatter.date(from: dateString)
/*Now find the time differnce from now*/
let secondsFromNowToFinish = date?.timeIntervalSinceNow
DispatchQueue.main.asyncAfter(deadline: .now() + secondsFromNowToFinish!, execute: {
print ("Hello Future.")
})
}
Tested and running.
References:
DateFormatter
Convert Date String to Date
Happy coding. Hope it helps. Please remove the forced unwrappings.
You can figure out the number of seconds between your future date and now and use either GCD or Timer to setup your future event:
let futureDate = ISO8601DateFormatter().date(from: "2018-1-1T00:00:00Z" )!
DispatchQueue.main.asyncAfter(deadline: .now() + futureDate.timeIntervalSinceNow) {
print("Its the future!")
}
or
let futureDate = ISO8601DateFormatter().date(from: "2018-1-1T00:00:00Z" )!
let timer = Timer.scheduledTimer(withTimeInterval: futureDate.timeIntervalSinceNow, repeats: false) {
print("Its the future!")
}
Timer is easier to cancel and reschedule:
timer.invalidate()
You can add a timer with a fire date to the runloop:
{
let dateOfExecution = Date(timeIntervalSince1970: <number of seconds from 1970-01-01-00:00:00 to your date of execution>)
let timer = Timer(fireAt: dateOfExecution, interval: 0, target: self, selector: #selector(codeToExecute), userInfo: nil, repeats: false)
RunLoop.main.add(timer, forMode: RunLoopMode.commonModes)
}
#objc func codeToExecute() {
<Your code to run>
}
See Apple docs: https://developer.apple.com/documentation/foundation/timer/1415700-init
Read about DispatchQueue
Example:
// Delay 2 seconds
DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) {
// your code
}
Note: Your application must be active.
Also you may have a look at Background modes tutorial
I have a countdown timer to a specific date. It looks pretty good, and updates each second to show the countdown. Here is the code I'm using to create the timer:
func scheduleTimeLabelsUpdateTimer() {
var components = Calendar.current.dateComponents([.day, .hour, .minute, .second], from: Date())
components.second! += 1
let nextSecondDate = Calendar.current.date(from: components)!
let timer = Timer(fireAt: nextSecondDate, interval: 1, target: self, selector: #selector(updateTimeLabels), userInfo: nil, repeats: true)
RunLoop.main.add(timer, forMode: .commonModes)
}
However, I'd like it to update each second, on the second, so that the clock updates at the same time as each second passes by. Right now it updates each second from when this method is called, which is in viewDidLoad().
For example if the countdown is set for midnight, I want it to hit zero exactly at midnight. Right now it may hit zero slightly after midnight, depending on how far into the second it was when the user opened this screen.
EDIT: This is how the countdown is displayed to the user. updateTimeLabels() just sets the text for each of those labels based on the amount of time left until that date. I would like each of the labels to be updated exactly on each second. This way the countdown will "hit zero" exactly on time. Notice how right now, the number of seconds hits zero, and then the system clock on the status bar updates. I would like these to happen at the same time.
This code, which I found somewhere on Stack Overflow many months ago, is being called in updateTimeLabels() to calculate the remaining time:
public func timeOffset(from date: Date) -> (days: Int, hours: Int, minutes: Int, seconds: Int) {
// Number of seconds between times
var delta = Double(self.seconds(from: date))
// Calculate and subtract whole days
let days = floor(delta / 86400)
delta -= days * 86400
// Caluclate and subtract whole hours
let hours = floor(delta / 3600).truncatingRemainder(dividingBy: 24)
delta -= hours * 3600
// Calculate and subtract whole minutes
let minutes = floor(delta / 60.0).truncatingRemainder(dividingBy: 60)
delta -= minutes * 60
// What's left is seconds
let seconds = delta.truncatingRemainder(dividingBy: 60)
return (Int(days), Int(hours), Int(minutes), Int(seconds))
}
In your code, you never use nanosecond when you init your component
So the timer will always hit at the round second number.
I test your code below print the Date() in the selector
I'm not sure if this is what you are talking about "hit zero", hope this would help
Since a Timer is scheduled on the run loop and other things are happening in the run loop, it isn't particularly accurate; it will fire after the specified interval has passed, but not necessarily exactly when the interval has passed.
Rather than trying to schedule the timer for the desired time, you should run your timer faster than 1 second, say at 0.5 seconds and update your time remaining label each time the timer fires. This will give a smoother update to the display:
var timer: Timer?
func scheduleTimeLabelsUpdateTimer() {
self.timer = Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true) { (timer) in
self.updateTimeLabels()
}
}
UPDATE
Don't do all of that math; iOS has nice libraries built in to do all of this for you; Simply create your target date and use DateComponents to work it out for you.
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var label: UILabel!
#IBOutlet weak var countDownLabel: UILabel!
let dateFormatter = DateFormatter()
var targetTime: Date!
var calendar = Calendar.current
var timer: Timer?
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
var components = DateComponents()
components.setValue(3, for: .month)
components.setValue(3, for: .day)
components.setValue(2017, for: .year)
components.setValue(0, for: .hour)
components.setValue(0, for: .minute)
components.setValue(1, for: .second)
self.targetTime = calendar.date(from: components)
self.timer = Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true, block: { (timer) in
self.updateLabel()
})
self.dateFormatter.timeStyle = .long
self.dateFormatter.dateStyle = .short
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func updateLabel() {
let now = Date()
self.label.text = self.dateFormatter.string(from: now)
let components = calendar.dateComponents([.day,.hour,.minute,.second], from: now, to: self.targetTime)
self.countDownLabel.text = String(format: "%d days %d hours, %d minutes %d seconds", components.day!, components.hour!, components.minute!,components.second!)
}
}
I'd like to let my app detect date even when iOS app is suspended(neither foreground nor background).
Can app use timer or function which get date in the suspended state?
This question may be simple question, but I couldn't find answer on the web.
Please let me know it is possible or not.
[Edit]
I want to run specified code at tomorrow midnight whenever app is any status.
Thanks in Advance!
This is OS X programming, but maybe you can adapt it.
So setup a timer:
var timer:NSTimer? = NSTimer(timeInterval:30.0, target: self, selector: "timeCheck:", userInfo: nil, repeats: true)
NSRunLoop.currentRunLoop().addTimer(timer!, forMode: NSDefaultRunLoopMode)
NSRunLoop.currentRunLoop().addTimer(timer!, forMode: NSEventTrackingRunLoopMode)
This sets up a timer to fire every 30 seconds. Then what happens when the timer fires:
func timeCheck(timer:UnsafePointer<NSTimer>)
{
let date = NSDate()
let calendar = NSCalendar.currentCalendar()
let components = calendar.components(.CalendarUnitYear | .CalendarUnitMonth | .CalendarUnitDay , fromDate: date)
let year = components.year
let month = components.month
let day = components.day
// Do something with this
println("Today is \(day), \(month), \(year)")
}
This prints:
Today is 24, 12, 2014
Every 30 seconds.
I have experience with NSTimer to run it once per minute, like
NSTimer.scheduledTimerWithTimeInterval(60.0, target: self, selector: Selector("everyMinute"), userInfo: nil, repeats: true)
Is it possible to use NSTimer, some other class, or some other control, to run some method every minute but on first second of minute ?
I do have some idea how to implement it on my own, but I am first checking is this already exist ?
One way would be to figure out the current next minute using an NSCalendar and schedule the timer to start from that, manually scheduling on the runLoop
let date = NSDate()
let calendar = NSCalendar.currentCalendar()
let components = calendar.components(NSCalendarUnit.CalendarUnitEra|NSCalendarUnit.CalendarUnitYear|NSCalendarUnit.CalendarUnitMonth|NSCalendarUnit.CalendarUnitDay|NSCalendarUnit.CalendarUnitHour|NSCalendarUnit.CalendarUnitMinute, fromDate: date)
components.minute += 1
components.second = 1
let nextMinuteDate = calendar.dateFromComponents(components)
let timer = NSTimer(fireDate: nextMinuteDate!, interval: 60, target: self, selector: Selector("everyMinute"), userInfo: nil, repeats: true)
NSRunLoop.mainRunLoop().addTimer(timer, forMode: NSDefaultRunLoopMode)
The accepted answer, updated for Swift 5:
let date = Date()
let calendar = Calendar.current
var components = calendar.dateComponents([.era, .year, .month, .day, .hour, .minute], from: date)
guard let minute = components.minute else { return }
components.second = 0
components.minute = minute + 1
guard let nextMinute = calendar.date(from: components) else { return }
let timer = Timer(fire: nextMinute, interval: 60, repeats: true) { [weak self] timer in
self?.everyMinute()
}
RunLoop.main.add(timer, forMode: .default)
The NSDate method timeIntervalSinceReferenceDate returns the number of seconds since January 1, 2001, at 12:00 a.m. GMT. It includes fractions of a second
If you call that, divide by 60, take the floor value, then multiply by 60, it should give you the time interval of the current "round minute". Add 60 to that, and you get the time interval for the next "round minute. Add 1 to THAT, and you get a time interval for one second after the next "round minute".
The code might look something like this:
NSTimeInterval now = [NSDate timeIntervalSinceReferenceDate];
NSTimeInterval nextMinute = floor(now/60)*60 + 61 //time interval for next minute, plus 1 second
NSTimeInterval delay = nextMinute - now;
//Delay now contains the number of seconds until the next "round minute", plus 1 second.
dispatch_after(
dispatch_time(DISPATCH_TIME_NOW,
(int64_t)(delay * NSEC_PER_SEC)),
dispatch_get_main_queue(), ^
{
//Replace the code below with whatever target/userInfo you need.
[NSTimer scheduledTimerWithTimeInterval: 60
target: self
userInfo: nil;
repeats: YES];
}
);
Edit: Actually, the "First second" of a minute is the zero'th second, so you should probably change the +61 in the code above that calculated nextMinute to "+60", not "+61"
A much simpler approach:
func everyMinute() {
// some code you want done every minute, in the first second, or VERY close to it
let delaySeconds = 60 - NSDate.timeIntervalSinceReferenceDate.truncatingRemainder(dividingBy: 60)
Timer.scheduledTimer(withTimeInterval: delaySeconds, repeats: false) { timer in
self.everyMinute()
}
}