Daily Local Notifications Are Not Working
Look at the comments within the code
import SwiftUI
//struct and class should start with an uppercase
struct NotificationView: View {
//Central location for Notification code including the delegate
// A call to the notificationManager just like the line of code below has to be included in
// application(_:willFinishLaunchingWithOptions:) or
// application(_:didFinishLaunchingWithOptions:)
//https://developer.apple.com/documentation/usernotifications/unusernotificationcenterdelegate
//https://www.hackingwithswift.com/quick-start/swiftui/how-to-add-an-appdelegate-to-a-swiftui-app
let notificationManager: NotificationManager = NotificationManager.shared
var body: some View {
VStack {
VStack {
Button("Request Permission") {
//Call a func here don't define it
notificationManager.requestAuthorization()
}
.frame(width: 200, height: 60, alignment: .center)
.foregroundColor(.black)
.background(Color.blue)
.cornerRadius(10.0)
.padding()
Button("Add Notifications For Morning") {
//Unique date components
var dateComponents = DateComponents()
dateComponents.hour = 6
dateComponents.minute = 30
//Reusable method
self.notificationManager.scheduleTriggerNotification(title: "Morning Time", body: "Wake Up And Be Productive!", categoryIdentifier: "reminder", dateComponents: dateComponents, repeats: true)
}
.padding()
Button("Add Notifications For Middle Of The Day") {
var dateComponents = DateComponents()
dateComponents.hour = 12
dateComponents.minute = 30
//Reusable method
self.notificationManager.scheduleTriggerNotification(title: "Middle Of The Day", body: "Did you have your daily run?", categoryIdentifier: "reminder", dateComponents: dateComponents, repeats: true)
}
.padding()
Button("Add Notifications For Night") {
var dateComponents = DateComponents()
dateComponents.hour = 20
dateComponents.minute = 51
//Reusable method
self.notificationManager.scheduleTriggerNotification(title: "Night Time", body: "Time to sleep", categoryIdentifier: "reminder", dateComponents: dateComponents, repeats: true)
}
.foregroundColor(.blue)
.padding()
Button("Print Notifications") {
//Reusable method
self.notificationManager.printNotifications()
}
.foregroundColor(.blue)
.padding()
Button("Delete Notifications") {
//Reusable method
self.notificationManager.deleteNotifications()
}
.foregroundColor(.blue)
.padding()
}
}
}
}
//You need a central location for the notification code because
// it is needed in more than 1 spot. At launch in the AppDelegate
// and wherever you schedule your notifications
class NotificationManager: NSObject, UNUserNotificationCenterDelegate{
//Singleton is requierd because of delegate
static let shared: NotificationManager = NotificationManager()
let notificationCenter = UNUserNotificationCenter.current()
private override init(){
super.init()
//This assigns the delegate
notificationCenter.delegate = self
}
func requestAuthorization() {
print(#function)
notificationCenter.requestAuthorization(options: [.alert, .badge, .sound]) { (granted, error) in
if granted {
print("Access Granted!")
} else {
print("Access Not Granted")
}
}
}
func deleteNotifications(){
print(#function)
notificationCenter.removeAllPendingNotificationRequests()
}
///This is just a reusable form of all the copy and paste you did in your buttons. If you have to copy and paste make it reusable.
func scheduleTriggerNotification(title: String, body: String, categoryIdentifier: String, dateComponents : DateComponents, repeats: Bool) {
print(#function)
let content = UNMutableNotificationContent()
content.title = title
content.body = body
content.categoryIdentifier = categoryIdentifier
content.sound = UNNotificationSound.default
let trigger = UNCalendarNotificationTrigger(dateMatching: dateComponents, repeats: repeats)
let request = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: trigger)
notificationCenter.add(request)
}
///Prints to console schduled notifications
func printNotifications(){
print(#function)
notificationCenter.getPendingNotificationRequests { request in
for req in request{
if req.trigger is UNCalendarNotificationTrigger{
print((req.trigger as! UNCalendarNotificationTrigger).nextTriggerDate()?.description ?? "invalid next trigger date")
}
}
}
}
//MARK: UNUserNotificationCenterDelegate
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
completionHandler(.banner)
}
}
struct NotificationView_Previews: PreviewProvider {
static var previews: some View {
NotificationView()
}
}
How to send a localNotification at a specific time everyday, even if that time has passed?
Updated @Paulw11 's answer to Swift 3.0 and wrapped up in a function:
/// Set up the local notification for everyday
/// - parameter hour: The hour in 24 of the day to trigger the notification
class func setUpLocalNotification(hour: Int, minute: Int) {
// have to use NSCalendar for the components
let calendar = NSCalendar(identifier: .gregorian)!;
var dateFire = Date()
// if today's date is passed, use tomorrow
var fireComponents = calendar.components( [NSCalendar.Unit.day, NSCalendar.Unit.month, NSCalendar.Unit.year, NSCalendar.Unit.hour, NSCalendar.Unit.minute], from:dateFire)
if (fireComponents.hour! > hour
|| (fireComponents.hour == hour && fireComponents.minute! >= minute) ) {
dateFire = dateFire.addingTimeInterval(86400) // Use tomorrow's date
fireComponents = calendar.components( [NSCalendar.Unit.day, NSCalendar.Unit.month, NSCalendar.Unit.year, NSCalendar.Unit.hour, NSCalendar.Unit.minute], from:dateFire);
}
// set up the time
fireComponents.hour = hour
fireComponents.minute = minute
// schedule local notification
dateFire = calendar.date(from: fireComponents)!
let localNotification = UILocalNotification()
localNotification.fireDate = dateFire
localNotification.alertBody = "Record Today Numerily. Be completely honest: how is your day so far?"
localNotification.repeatInterval = NSCalendar.Unit.day
localNotification.soundName = UILocalNotificationDefaultSoundName;
UIApplication.shared.scheduleLocalNotification(localNotification);
}
UNNotificationRequest to send local notification daily at a specific time
You can use UNCalendarNotificationTrigger
for creating a notification that fires repeatedly using UNUserNotificationCenter
. You can do something like this. The trick is to only have the time component in the Trigger date.
let center = UNUserNotificationCenter.current()
let content = UNMutableNotificationContent()
content.title = "Attention!"
content.body = "Your daily alert is ready for you!"
content.sound = UNNotificationSound.default
let identifier = "com.yourdomain.notificationIdentifier"
var triggerDate = DateComponents()
triggerDate.hour = 18
triggerDate.minute = 30
let trigger = UNCalendarNotificationTrigger(dateMatching: triggerDate, repeats: true)
let request = UNNotificationRequest(identifier: identifier, content: content, trigger: trigger)
center.add(request, withCompletionHandler: { (error) in
if let error = error {
// Something went wrong
print("Error : \(error.localizedDescription)")
} else {
// Something went right
print("Success")
}
})
How to set up daily local notification for tasks but don't show it when user completes the task before
I ended up using multiple notifications for each weekday but set it up in little different way:
First set up daily reminder using weekday Int as identifier
func setWeekdayReminder(weekday: Int) {
let center = UNUserNotificationCenter.current()
let content = UNMutableNotificationContent()
content.title = "Daily reminder"
content.body = "You still have some tasks to complete today."
content.sound = UNNotificationSound.default
var dateComponents = DateComponents()
dateComponents.hour = 18
dateComponents.minute = 35
dateComponents.weekday = weekday
let trigger = UNCalendarNotificationTrigger(dateMatching: dateComponents, repeats: true
let request = UNNotificationRequest(identifier: String(weekday), content: content, trigger: trigger)
center.add(request) { (error) in
if let error = error {
print("Notification Error: ", error)
}
}
}
then i made a function to check if there is any missing day after users launches app (except today, so i won't request todays notification again even after removing it earlier when user completes the task)
func checkDailyReminder() {
let currentWeekday = Calendar.current.component(.weekday, from: Date())
center.getPendingNotificationRequests { (requests) in
var weekdayArray : [Int] = []
for each in requests {
weekdayArray.append(Int(each.identifier)!)
}
for number in 1...7 {
if weekdayArray.contains(number) {
print("weekdayArray contains weekday \(number)")
} else {
print("weekdayArray doesnt contain weekday \(number)")
if number != currentWeekday {
self.setWeekdayReminder(weekday: number)
}
}
}
}
}
Of course it's kind of a hack and when user completes the task and somehow won't go back for a week and open it again on the same weekday then he won't get notification that day but it works for rest of the time.
How Can I Repeat Local Notifications for Evey Hours in Every Day?
let interval:TimeInterval = 60.0 // 1 minute = 60 seconds
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: interval, repeats: true)
Use UNTimeIntervalNotificationTrigger
instead of UNCalendarNotificationTrigger
because UNCalendarNotificationTrigger
object is used when you want to schedule the delivery of a local notification at the specified date and time and UNTimeIntervalNotificationTrigger
object is used when you want to schedule the delivery of a local notification after the specified number of seconds elapse.
https://developer.apple.com/documentation/usernotifications/untimeintervalnotificationtrigger
iPhone : Daily local notifications
You just need to properly create a NSDate
object to be your fire date (time). Instead of using [NSDate dateByAddingTimeInterval: 20]
, use something like this:
NSCalendar *calendar = [NSCalendar currentCalendar];
NSDateComponents *components = [[NSDateComponents alloc] init];
[components setDay: 3];
[components setMonth: 7];
[components setYear: 2012];
[components setHour: 6];
[components setMinute: 0];
[components setSecond: 0];
[calendar setTimeZone: [NSTimeZone defaultTimeZone]];
NSDate *dateToFire = [calendar dateFromComponents:components];
Here are the Apple NSDateComponents API docs
And then when you add the date to the notification, set the repeat interval to one day:
[localNotification setFireDate: dateToFire];
[localNotification setTimeZone: [NSTimeZone defaultTimeZone]];
[localNotification setRepeatInterval: kCFCalendarUnitDay];
As with all date related code, make sure to test how this works during the switch to daylight savings time, if your time zone uses daylight savings time.
Related Topics
Autolayout Link Two Uilabels to Have the Same Font Size
Error: Error Domain=Nsurlerrordomain Code=-1001 "The Request Timed Out."
How to Get Data from Firebase in Descending Order of Value
Save and Load from Keychain | Swift
Uitapgesturerecognizer Tap on Self.View But Ignore Subviews
How to Run the iOS 7.1 Simulator in Xcode 7.0 Beta 2
iOS Enterprise Ota Distribution Unable to Download Application
Xctest and Asynchronous Testing in Xcode 6
Pixel Array to Uiimage in Swift
Hide Keyboard for Text Field in Swift Programming Language
Removing Duplicates from Array of Custom Objects Swift
Passing Parameters to a Method Called by Nstimer in Swift
Uibezierpath Triangle with Rounded Edges
Custom Font Sizing in Xcode 6 Size Classes Not Working Properly with Custom Fonts
Change Language of Alert in Banner of Push Notification