I am trying to run a particular function with background execution at a certain time each morning. Here's what I have figured out...
func getCurrentTime() { //Is called every minuite by a timer
let date = NSDate()
let formatter = NSDateFormatter()
formatter.timeStyle = .ShortStyle
var stringValue = formatter.stringFromDate(date)
if stringValue == "7:00 AM" {
sendPushAlert()//Defined elsewhere in code, executes desired code
}
}
However, that does not run in the background (e.g. when app not open), and seems like a clunky way to do things.
So, How do I run background execution at a certain time each morning?
Or, is there a better way to fetch data to show in a push notification?
Thanks!!!!!!
You can't force iOS to launch your app at a specific time.
If all you want to do is send a notification at a specific time, schedule a local notification. If the user opens your notification then your app will launch.
Related
I have some code I want to run after a particular date/time has passed. For example, if I want the code to run 7 days from now and the user opens the app at any time on day 7 or after the code will run but if they open the app before the beginning of day 7 nothing happens. Timers in the main runloop work but only if the app is still running in the background. I need a method that will work even if the user kills the app.
Your best option is to store it as local data Even though you only want the code to run once, the overhead is so low, the "check" will not impact the speed or feel of the application. Also this will allow you to run additional checks .. If someone deletes the app, for instance, and leaves the local storage behind. If they re-install you could theoretically "remember" that the application has been installed, and said code has already run (until the user clears application data)
Something like:
//Globally set key
struct defaultsKeys {
static let keyDate = "dateKey"
}
// Set the date in local storage
let defaults = UserDefaults.standard
defaults.set("Your Date String", forKey: defaultsKeys.dateKey)
// Get the date from local storage
let defaults = UserDefaults.standard
if let stringDate = defaults.string(forKey: defaultsKeys.dateKey) {
print(stringDate)
// Do your date comparison here
}
Very few lines of code, and even though the check happens every time the application starts .. The overhead is negligible.
You can either set the date you want your app to "remember" on your local storage or web service. Then, when the user opens your app, compare that date to current device time to determine if you should execute your code.
First, save the current time when you want. You can set the key name however you want.
UserDefaults.standard.setValue(Date(), forKey: "rememberTime")
And every time I open the app, You compare the current time with the saved time.
To do so, I created a function that compares time.
extension Date {
func timeAgoSince() -> Bool {
let calendar = Calendar.current
let unitFlags: NSCalendar.Unit = [.day]
let components = (calendar as NSCalendar).components(unitFlags, from: self, to: Date(), options: [])
if let day = components.day, day >= 7 {
// Returns true if more than 7 days have passed.
return true
}
return false
}
}
Recall the previously saved time and use the time comparison function.
let beforeTime: Date = (UserDefaults.standard.object(forKey: "rememberTime") as? Date)!
if beforeTime.timeAgoSince() {
// more than seven days later
...
} else {
...
}
If you have a problem, please leave a comment !
You can use the below sample code:
override func viewDidLoad() {
super.viewDidLoad()
let nextCodeRunDate = Date() + (7 * 24 * 60 * 60) // 7 Days
if let savedDate = UserDefaults.standard.value(forKey: "NEXT_DATE") as? Date {
if Date() > savedDate {
UserDefaults.standard.setValue(nextCodeRunDate, forKey: "NEXT_DATE")
runYourCode()
}
}else {
// First time
UserDefaults.standard.setValue(nextCodeRunDate, forKey: "NEXT_DATE")
runYourCode()
}
}
func runYourCode() {
// Your code
}
in my app I send a daily notification to remind the user to visit the app.
This notification is locally delivered every day at 1pm.
func scheduleNotifications() -> Void {
for notification in notifications {
let content = UNMutableNotificationContent()
content.title = notification.title
let todaysDate = Date()
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "dd"
let currentDay = dateFormatter.string(from: todaysDate)
let currentDayInt = Int(currentDay) ?? 0
var datComp = DateComponents()
datComp.hour = 13
datComp.minute = 00
datComp.day = currentDayInt + 1
let trigger = UNCalendarNotificationTrigger(dateMatching: datComp, repeats: true)
let request = UNNotificationRequest(identifier: notification.id, content: content, trigger: trigger)
UNUserNotificationCenter.current().add(request) { error in
guard error == nil else { return }
print("Scheduling notification with id: \(notification.id) on Day \(datComp.day ?? 00) at \(datComp.hour ?? 00) - \(datComp.minute ?? 00)")
}
}
}
As you can see, I added the "current day + 1" lines because if the user opens the app before 1pm, there is no need to deliver the notification on this day.
So every time the user opens the app, I use UNUserNotificationCenter.current().removeAllPendingNotificationRequests() to remove and reschedule the notification for the next day (by recalling the function above).
My issue:
The notification should repeat every day, which it does, as long as the user opens the app.
But if the user does not open the app on one day, there will be no notification on the following days.
Is there a way to mute notifications for the current day so that I don't have to use this "current day + 1"-thing? Or does anyone have a better idea?
Thank you guys.
I think you misunderstood how repeating notifications and your datComp works here.
For example (let's use today's date: May 27)
datComp.hour = 13
datComp.minute = 00
datComp.day = currentDayInt + 1 // in our example it's 28
let trigger = UNCalendarNotificationTrigger(dateMatching: datComp, repeats: true)
means that your notification will get triggered every month on 28th, all parameters your trigger knows is hour, minute, day and by repeating it, will be triggered on every 28th at 13:00.
So the way your app works now is that you set up monthly notification starting from tomorrow, when you open the app you remove that monthly notification and reschedule it for day later. By opening every day it gives you impression that its daily notification but it's not, it's monthly. That's why if you don't open the app nothing shows up next the day, it will show up next month.
You can check my similar explanation here: (top answer there, maybe it is better worded and easier to understand)
Removing scheduled local notification
and my solution for similar problem i had here:
How to set up daily local notification for tasks but don't show it when user completes the task before
I'm using a UIDatePicker to allow users to choose a time for daily notifications in my app. However, it doesn't keep the value of the chosen time on the actual picker. How would I go about having the UIDatePicker reflect the time that they have chosen?
Setup of UIDatePicker and Notifications page
I don't know how you store your notification time, but you are responsible for setting the date picker to that time. For instance, here I am setting a picker to show the time exactly seven days ago:
let oneWeekAgo = Calendar.current.date(byAdding: .day, value: -7, to: Date())!
datePicker.setDate(oneWeekAgo, animated: true)
Your problem is since you are not saving the UIDatePicker time somewhere other than in the notification itself you are unsure of how to set it back to the UIDatePicker when it comes time to edit it. You could try something like this:
UNUserNotificationCenter.current().getPendingNotificationRequests { requests in
guard let notification = requests.first(where: { $0.identifier == "notInt" }) else { return }
guard let trigger = notification.trigger as? UNCalendarNotificationTrigger else { return }
notificationTime.date = trigger.nextTriggerDate()
}
... getting all pending notification requests, finding the one that matches the identifier you set and get the time from that. notificationTime above is the UIDatePicker.
I developed an application in which I use a singleton for keeping a single instance of an NSDateFormatter.
My date formatter is initialized as below:
timeDateFormatter = NSDateFormatter()
timeDateFormatter.locale = NSLocale(localeIdentifier:EN_US_POSIX_LocaleIdentifier)
timeDateFormatter.dateFormat = StringDateType.DetailSigningHour.rawValue
let EN_US_POSIX_LocaleIdentifier = "en_US_POSIX"
With 24-Hour Time turned off the application runs as it should, but when going to Settings-->General-->Date&Time and turning on 24-Hour Time, then going and tapping on the app icon, the app tries to come up and then immediately exit.
I read that this may be an unknown issue for Apple.
Can you help me with some more information about this?
Update
When the system time was changed from 12-hour format to 24-hour format, my date formatter was messed up.
The good part is that the system is sending a notification (NSCurrentLocaleDidChangeNotification) letting you know that the locale has changed. All what I have done was to add an observer for this notification and to re-initialize my date formatter.
Setting the locale to en_US_POSIX, you force the 12-hour mode, regardless of the user's 24/12-hour mode setting.
I'm going to assume that you're force-unwrapping the result of timeDateFormatter.dateFromString with a wrong date format.
If you do something like this:
let timeDateFormatter = NSDateFormatter()
timeDateFormatter.locale = NSLocale(localeIdentifier:"en_US_POSIX")
timeDateFormatter.dateFormat = "hh:mm"
And force-unwrap the result:
let d = timeDateFormatter.dateFromString("11:42")!
You will get a crash at runtime if the date string is more than 12h:
let d = timeDateFormatter.dateFromString("13:42")! // crash
because "hh:mm" deals with 12h format only.
To use 24h format you should use "HH:mm":
timeDateFormatter.dateFormat = "HH:mm"
And to avoid crashes, avoid force-unwrapping:
if let d = timeDateFormatter.dateFromString("11:42") {
print(d)
} else {
// oops
}
If my diagnostic is wrong, please add details to your question. :)
I do not know that issue, however I always used my formatters this way, maybe this is also convenient to you.
let formatter = NSDateFormatter()
formatter.timeStyle = NSDateFormatterStyle.ShortStyle // here are different values possible, and all are good declared.
formatter.dateStyle = .MediumStyle // Same possible values like in timeStyle.
How can I handle date change event in swift?
I want to create a set of notification when the date changes.
var localNotification: UILocalNotification = UILocalNotification()
localNotification.alertAction = "Testing notifications on iOS8"
localNotification.alertBody = " Woww it works!!"
localNotification.fireDate = date
UIApplication.sharedApplication().scheduleLocalNotification(localNotification)
You can subscribe to UIApplicationSignificantTimeChangeNotification. This one should fire when the day changes.
Unfortunately this one also fires on other occasions. So you should store a static variable with the date the last time this notification fired and then check if it actually changed the day.
I don't know what you're gonna use it for, but in most cases the general significant time change is what one is looking for.
You could do localNotification.fireDate = NSDate(timeIntervalSinceNow: 1) which will give you a notification 1 day from the current date. This could be further customized to your liking depending on how specific you want the timing to be.
EDIT: As was pointed out in the comments, the timeIntervalSinceNow is calculated in seconds, so 1 would be one second, not one day.
EDIT 2: Trying to answer the question that was asked. If you want to create a notification at midnight every day...
First, create an NSCalendar object
var calendar = NSCalendar()
var calendarComponents = NSDateComponents()
calendarComponents.setDay(29)
calendarComponents.setMonth(6)
calendarComponents.setYear(2015)
calendarComponents.setHour(12)
calendarComponents.setSeconds(0)
calendarcomponents.setMinutes(0)
calendar.setTimeZone(NSTimeZone.defaultTimeZone)
var dateToFire = calendar.dateFromComponents(calendarComponents)
Now we can schedule the notification daily.
localNotification.fireDate = dateToFire
localNotification.setTimeZone(NSTimeZone.defaultTimeZone)
localNotification.setRepeatInterval(kcfCalendarUnitDay)
Syntax might not be perfect, I was translating from Obj-C, but you should get the general idea.
Try adding observer for NSNotification.Name.NSCalendarDayChanged
The down side of this that it triggers only when app is in foreground