Run a function every day or every hour in Swift - ios

I can do it manually with the following code:
var myDate:NSDateComponents = NSDateComponents()
myDate.year = 2015
myDate.month = 04
myDate.day = 20
myDate.hour = 12
myDate.minute = 38
myDate.timeZone = NSTimeZone.systemTimeZone()
var calendar:NSCalendar = NSCalendar(calendarIdentifier: NSCalendarIdentifierGregorian)!
var date:NSDate = calendar.dateFromComponents(myDate)!
var notification:UILocalNotification = UILocalNotification()
notification.category = "First Category"
notification.alertBody = "Hi, I'm a notification"
notification.fireDate = date
UIApplication.sharedApplication().scheduleLocalNotification(notification)
But how can I run it every hour or every day? Any idea?

First: add an extension to the Date class:
extension Date {
func currentTimeMillis() -> Int64 {
return Int64(self.timeIntervalSince1970 * 1000)
}
}
then call this function in the viewDidLoad():
func run24HoursTimer() {
let currentDate = Date()
let waitingDateTimeInterval:Int64 = UserDefaults.standard.value(forKey: "waiting_date") as? Int64 ?? 0
let currentDateTimeInterval = currentDate.currentTimeMillis()
let dateDiffrence = currentDateTimeInterval - waitingDateTimeInterval
if dateDiffrence > 24*60*60*1000 {
// Call the function that you want to be repeated every 24 hours here:
UserDefaults.standard.setValue(currentDateTimeInterval, forKey: "waiting_date")
UserDefaults.standard.synchronize()
}
}

There is a separate property on a local notification called repeatInterval. See reference
notification.repeatInterval = .Day
Also keep in mind to register in application delegate (didFinishLaunchingWithOptions method) for local notification (alert asking for permission will be presented for the first time). In Swift this will be (an example):
if UIApplication.instancesRespondToSelector(Selector("registerUserNotificationSettings:"))
{
application.registerUserNotificationSettings(UIUserNotificationSettings(forTypes: [.Sound, .Alert, .Badge], categories: nil))
}
I would also recommend setting time zone for the notification, could be like this (example):
notification.timeZone = NSTimeZone.localTimeZone()
Not sure about "run function every...". This will create a notification fired with the specified repeat interval. I found this tutorial helpful.

Use This :-
1). save your daily time in user defaults
2). set notification on time for next day
3). check in app delegate if time is passed or not
4). if it is passed then set next day notification
5). if you change time update user defaults
let trigger = UNCalendarNotificationTrigger(dateMatching: dateComponents, repeats: true)
let request = UNNotificationRequest(identifier: indentifier, content: content, trigger: trigger)
UNUserNotificationCenter.current().add(request, withCompletionHandler: {
(errorObject) in
if let error = errorObject{
print("Error \(error.localizedDescription) in notification \(indentifier)")
}
})

You mean something like this?
let timer = NSTimer(timeInterval: 3600, target: self, selector: "test", userInfo: nil, repeats: false)
func test() {
// your code here will run every hour
}
Put all that code in one class. Much more info at #selector() in Swift?

//Swift >=3 selector syntax
let timer = Timer.scheduledTimer(timeInterval: 3600, target: self, selector: #selector(self.test), userInfo: nil, repeats: true)
func test() {
// your code here will run every hour
}
Note: Time Interval is in seconds
Reference : How can I use NSTimer in Swift?

Related

Execute a func when the device time change

I'm trying to execute a func when the device clock change.
This func will return the next course of a student, all the courses are in a array.
if I understand correctly we can not execute a func when the clock of the device change.
I read some topics where people say to do a timer of 60s or other but if the user launch the app a 08:05:07 the func will execute with 7s of late.
I thought to use a do while but I think it will use the CPU a lot and so the battery too. no ?
Does anyone have an idea ?
If you’re just saying that you want to fire a timer at some specific future Date, you should just calculate the amount of time between now and then (using timeIntervalSince), and then use that.
For example, it’s currently "2019-01-20 17:11:59 +0000”, but if I want it to fire at 17:15, you can do:
weak var timer: Timer?
func startTimer() {
let futureDate = ISO8601DateFormatter().date(from: "2019-01-20T17:15:00Z")!
let elapsed = futureDate.timeIntervalSince(Date()) // will be roughly 180.56 in this example at this moment of time
timer?.invalidate() // invalidate prior timer, if any
timer = Timer.scheduledTimer(withTimeInterval: elapsed, repeats: false) { [weak self] _ in
// whatever you want to do at 17:15
}
}
Clearly, however you come up with futureDate will be different in your case, but it illustrates the idea. Just calculate the elapsed time between the future target date and now, and use that for the timer.
Now, if you’re really worried about changes to clock, in iOS you might observe significantTimeChangeNotification, e.g.,
NotificationCenter.default.addObserver(forName: UIApplication.significantTimeChangeNotification, object: nil, queue: .main) { [weak self] _ in
// do something, maybe `invalidate` existing timer and create new one
self?.startTimer()
}
I thought to use a do while but I think it will use the CPU a lot and so the battery too. no ?
Yes, spinning in a loop, waiting for some time to elapse, is always a bad idea. Generally you’d just set a timer.
This func will return the next course of a student, all the courses are in a array.
This begs the question whether the app will be running in the foreground or not. If you want to notify the user at some future time, whether they’re running the app right now or not, consider "user notifications”. E.g. request permission for notification:
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { granted, _ in
if !granted {
// warn the user that they won't be notified after the user leaves the app unless they grant permission for notifications
DispatchQueue.main.async {
let alert = UIAlertController(title: nil, message: "We need permission to notify you of your class", preferredStyle: .alert)
if let url = URL(string: UIApplication.openSettingsURLString) {
alert.addAction(UIAlertAction(title: "Settings", style: .default) { _ in
UIApplication.shared.open(url)
})
}
alert.addAction(UIAlertAction(title: "Cancel", style: .default))
self.present(alert, animated: true)
}
}
}
And then, assuming that permissions have been granted, then schedule a notification:
let content = UNMutableNotificationContent()
content.title = "ClassTime"
content.body = "Time to go to math class"
let components = Calendar.current.dateComponents([.year, .month, .day, .hour, .minute], from: futureDate)
let trigger = UNCalendarNotificationTrigger(dateMatching: components, repeats: false)
let request = UNNotificationRequest(identifier: "Math 101", content: content, trigger: trigger)
UNUserNotificationCenter.current().add(request)
I did this
private var secondesActuelles : Int = 0
private var timer = Timer()
private func récuperLesSecondes(date : Date) -> Int {
let date = date
let calendar = Calendar.current
let second = calendar.component(.second, from: date)
return second
}
override func viewDidLoad() {
super.viewDidLoad()
secondesActuelles = récuperLesSecondes(date: Date())
timer = Timer.scheduledTimer(withTimeInterval: TimeInterval(60 - secondesActuelles), repeats: false, block: { (timer) in
print("l'heure a changé")
self.secondesActuelles = self.récuperLesSecondes(date: Date())
print("secondesActuelles : \(self.secondesActuelles)")
})
}
It work once. but after the timeInterval don't change.

Having a block of code wait to run at a certain time in iOS

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

Calendar Based Local Notification Not Working - Swift 3

I have setup a local notification system so that I can fire a notification at a certain time every day. This time is determined by the user and I store it as a string. I will break down all the steps I have done in the code to follow but basically, my problem is that the notification won't fire.
Step 1:
In this step I setup an alert to ask permission to send a notification:
let center = UNUserNotificationCenter.current()
let options: UNAuthorizationOptions = [.alert, .badge, .sound]
center.requestAuthorization(options: options) { (granted, error) in
if granted {
application.registerForRemoteNotifications()
}
}
Step 2:
In this step I setup the function I call to send a notification:
static func sendNotification(stringDate: String) {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "HH:mm"
let content = UNMutableNotificationContent()
content.title = "Title"
content.body = "Detail"
content.badge = 1
if let date = dateFormatter.date(from: stringDate) {
let calendar = Calendar.current
let hour = calendar.component(.hour, from: date)
let minute = calendar.component(.minute, from: date)
var dateComponents = DateComponents()
dateComponents.hour = hour
dateComponents.minute = minute
let trigger = UNCalendarNotificationTrigger(dateMatching: dateComponents, repeats: true)
let request = UNNotificationRequest(identifier: "dateDone", content: content, trigger: trigger)
UNUserNotificationCenter.current().add(request, withCompletionHandler: nil)
}
}
Step 3:
I then call this function in my app delegate file like so:
func applicationDidEnterBackground(_ application: UIApplication) {
let defaults = UserDefaults.standard
if defaults.object(forKey: "currentUser") != nil {
if User.current.desiredTimeForNews.characters.contains("A") {
let stringToBeConverted = User.current.desiredTimeForNews.replacingOccurrences(of: "AM", with: "")
HelperFunctions.sendNotification(stringDate: stringToBeConverted)
} else {
let stringToBeConverted = User.current.desiredTimeForNews.replacingOccurrences(of: "PM", with: "")
HelperFunctions.sendNotification(stringDate: stringToBeConverted)
}
}
}
IMPORTANT: As you can see within my function I take in a "stringDate" parameter. This variable has a string value of my date. I then convert this to a date value. Through using breakpoints and print statements I have seen that all my values including the date, hour, minute, etc are all NOT nil.
Overall, my problem is that the notification is never sent. However I know for a fact that it is called as I have used breakpoints to prove that. Any help would be appreciated!
For me personally, I ran it on a real device and it worked. Although it should work on a simulator it didn't for me. So I would try and run it on a real device before looking at anything else!
The code seems to be ok. Should work on the device and also on the simulator. My guess is that the trigger time just happens at a different time, then what you would expect.
Insert this check below after your setup to see the details:
UNUserNotificationCenter.current()
.getPendingNotificationRequests(completionHandler: { requests in
for (index, request) in requests.enumerated() {
print("notification: \(index) \(request.identifier) \(request.trigger)")
}
})
note 1:Why do you have this line in the code?
application.registerForRemoteNotifications()
note 2:You should check if the requestAuthorization went through ok. With something like this:
UNUserNotificationCenter.current().getNotificationSettings { (settings) in
guard (settings.authorizationStatus == .authorized )
else { print("notifsetup not authorized");return }
guard (settings.soundSetting == .enabled) else {
print("notifsetup sound not enabled");return }
}

how to run a function at certain time using swift

In my app, i have implemented functionality to check app version using bundleVersion String. Now, i want to run this function everyday at 8:00 a.m. This is kiosk based app which does not go into background. So, app would be active all the time.
I am using UILocalnotification to schedule a notification for that time. Now, my app has other UILocalnotification as well. I am not sure how can i identify notifications in app delegate didReceiveLocalNotification() method.
My method to schedule notification is below
func scheduleNotification() {
//UIApplication.sharedApplication().cancelAllLocalNotifications()
let notif = UILocalNotification()
let calendar = NSCalendar.currentCalendar()
let date = NSDate()
var calendarComponents = NSDateComponents()
calendarComponents = calendar.components([.Day,.Month,.Year], fromDate: date)
let day = calendarComponents.day
let month = calendarComponents.month
let year = calendarComponents.year
calendarComponents.day = day
calendarComponents.month = month
calendarComponents.year = year
calendarComponents.hour = 8
calendarComponents.second = 0
calendarComponents.minute = 0
calendar.timeZone = NSTimeZone.systemTimeZone()
let dateToFire = calendar.dateFromComponents(calendarComponents)
notif.fireDate = dateToFire
notif.timeZone = NSTimeZone.systemTimeZone()
notif.repeatInterval = NSCalendarUnit.NSWeekdayCalendarUnit
UIApplication.sharedApplication().scheduleLocalNotification(notif)
}
Any idea would be appreciated.
Following method could help you to execute any task at regular interval , i used this method to call webservice at regular interval to provide searching functionality :
let debounceTimer : NSTimer?
func test() {
if let timer = debounceTimer {
timer.invalidate()
}
debounceTimer = NSTimer(timeInterval: 0.3, target: self, selector: Selector("putmethodnamewhichneedstocall"), userInfo: nil, repeats: false)
NSRunLoop.currentRunLoop().addTimer(debounceTimer!, forMode: "NSDefaultRunLoopMode")
}
For specific time daily Refer to this SO link :Repeating local notification daily at a set time with swift
Hope it helps.

swift NSTimer in Background

I have come across a lot of issues with how to handle NSTimer in background here on stack or somewhere else. I've tried one of all the options that actually made sense .. to stop the timer when the application goes to background with
NSNotificationCenter.defaultCenter().addObserver(self, selector: "appDidEnterBackground", name: UIApplicationDidEnterBackgroundNotification, object: nil)
and
NSNotificationCenter.defaultCenter().addObserver(self, selector: "appDidBecomeActive", name: UIApplicationWillEnterForegroundNotification, object: nil)
At first I thought that my problem is solved, I just saved the time when the app did enter background and calculated the difference when the app entered foreground .. but later I noticed that the time is actually postponed by 3, 4 , 5 seconds .. that it actually is not the same .. I've compared it to the stopwatch on another device.
Is there REALLY any SOLID solution to running an NSTimer in background?
You shouldn't be messing with any adjustments based upon when it enters background or resumes, but rather just save the time that you are counting from or to (depending upon whether you are counting up or down). Then when the app starts up again, you just use that from/to time when reconstructing the timer.
Likewise, make sure your timer handler is not dependent upon the exact timing that the handling selector is called (e.g. do not do anything like seconds++ or anything like that because it may not be called precisely when you hope it will), but always go back to that from/to time.
Here is an example of a count-down timer, which illustrates that we don't "count" anything. Nor do we care about the time elapsed between appDidEnterBackground and appDidBecomeActive. Just save the stop time and then the timer handler just compares the target stopTime and the current time, and shows the elapsed time however you'd like.
For example:
import UIKit
import UserNotifications
private let stopTimeKey = "stopTimeKey"
class ViewController: UIViewController {
#IBOutlet weak var datePicker: UIDatePicker!
#IBOutlet weak var timerLabel: UILabel!
private weak var timer: Timer?
private var stopTime: Date?
let dateComponentsFormatter: DateComponentsFormatter = {
let formatter = DateComponentsFormatter()
formatter.allowedUnits = [.hour, .minute, .second]
formatter.unitsStyle = .positional
formatter.zeroFormattingBehavior = .pad
return formatter
}()
override func viewDidLoad() {
super.viewDidLoad()
registerForLocalNotifications()
stopTime = UserDefaults.standard.object(forKey: stopTimeKey) as? Date
if let time = stopTime {
if time > Date() {
startTimer(time, includeNotification: false)
} else {
notifyTimerCompleted()
}
}
}
#IBAction func didTapStartButton(_ sender: Any) {
let time = datePicker.date
if time > Date() {
startTimer(time)
} else {
timerLabel.text = "timer date must be in future"
}
}
}
// MARK: Timer stuff
private extension ViewController {
func registerForLocalNotifications() {
if #available(iOS 10, *) {
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge]) { granted, error in
guard granted, error == nil else {
// display error
print(error ?? "Unknown error")
return
}
}
} else {
let types: UIUserNotificationType = [.alert, .sound, .badge]
let settings = UIUserNotificationSettings(types: types, categories: nil)
UIApplication.shared.registerUserNotificationSettings(settings)
}
}
func startTimer(_ stopTime: Date, includeNotification: Bool = true) {
// save `stopTime` in case app is terminated
UserDefaults.standard.set(stopTime, forKey: stopTimeKey)
self.stopTime = stopTime
// start Timer
timer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(handleTimer(_:)), userInfo: nil, repeats: true)
guard includeNotification else { return }
// start local notification (so we're notified if timer expires while app is not running)
if #available(iOS 10, *) {
let content = UNMutableNotificationContent()
content.title = "Timer expired"
content.body = "Whoo, hoo!"
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: stopTime.timeIntervalSinceNow, repeats: false)
let notification = UNNotificationRequest(identifier: "timer", content: content, trigger: trigger)
UNUserNotificationCenter.current().add(notification)
} else {
let notification = UILocalNotification()
notification.fireDate = stopTime
notification.alertBody = "Timer finished!"
UIApplication.shared.scheduleLocalNotification(notification)
}
}
func stopTimer() {
timer?.invalidate()
}
// I'm going to use `DateComponentsFormatter` to update the
// label. Update it any way you want, but the key is that
// we're just using the scheduled stop time and the current
// time, but we're not counting anything. If you don't want to
// use `DateComponentsFormatter`, I'd suggest considering
// `Calendar` method `dateComponents(_:from:to:)` to
// get the number of hours, minutes, seconds, etc. between two
// dates.
#objc func handleTimer(_ timer: Timer) {
let now = Date()
if stopTime! > now {
timerLabel.text = dateComponentsFormatter.string(from: now, to: stopTime!)
} else {
stopTimer()
notifyTimerCompleted()
}
}
func notifyTimerCompleted() {
timerLabel.text = "Timer done!"
}
}
By the way, the above also illustrates the use of a local notification (in case the timer expires while the app isn't currently running).
For Swift 2 rendition, see previous revision of this answer.
Unfortunately, there is no reliable way to periodically run some actions while in background. You can make use of background fetches, however the OS doesn't guarantee you that those will be periodically executed.
While in background your application is suspended, and thus no code is executed, excepting the above mentioned background fetches.

Resources