0

I have searched a lot to try to find a specific solution to this problem. I have an app that uses the former UILocalNotification system to send local notifications, and this still works fine under iOS 10.3.3. However, I have tried to convert this to the new UNNotification system because the former system is deprecated. No matter whether I try a UNCalendarNotificationTrigger or a UNTimeIntervalNotificationTrigger, the delegate does not receive a call. Here is the code for the triggering viewcontroller.

if (isItTime){
    NSCalendar *currentCalendar = [NSCalendar calendarWithIdentifier:NSCalendarIdentifierGregorian];
    [currentCalendar setTimeZone:[NSTimeZone localTimeZone]];
    NSDateComponents *components = [currentCalendar components:NSCalendarUnitYear | NSCalendarUnitMonth | NSCalendarUnitDay | NSCalendarUnitHour | NSCalendarUnitMinute | NSCalendarUnitSecond | NSCalendarUnitTimeZone fromDate:[now dateByAddingTimeInterval:30]];
   // components.second = 0;
    NSLog(@"trigger components: %@", components);
    UNCalendarNotificationTrigger* trigger = [UNCalendarNotificationTrigger
                                             triggerWithDateMatchingComponents:components repeats:NO];
    UNNotificationRequest *request = [UNNotificationRequest requestWithIdentifier:@"invite" content:content trigger:trigger];
    [center addNotificationRequest:request withCompletionHandler:^(NSError * _Nullable error) {
        if (error != nil) {
            NSLog(@"Something went wrong: %@",error);
        }
    }];
    [self.currentLocalNotificationRequests addObject:request];
    return  request;
}else{
    return nil;
}

And here is the code for the delegate (appdelegate) didFinishLaunching:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
UNUserNotificationCenter* center = [UNUserNotificationCenter currentNotificationCenter];
center.delegate = self;
[center requestAuthorizationWithOptions:(UNAuthorizationOptionAlert + UNAuthorizationOptionSound)
                      completionHandler:^(BOOL granted, NSError * _Nullable error) {
                          // Enable or disable features based on authorization.
                          NSLog(@"_prefix:set($class $method $line)");
                          NSUserDefaults *storage = [NSUserDefaults standardUserDefaults];
                          if(granted == YES){
                              [storage setBool:YES forKey:@"permission granted"];
                              [storage setBool:YES forKey:@"alert permission granted"];
                              [storage setBool:YES forKey:@"sound permission granted"];
                          }else{
                              NSLog(@"No permission granted");
                              [storage setBool:NO forKey:@"permission granted"];
                          };
                      }];

Code for the appdelegate to get the notifications:

    - (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)(void))completionHandler{
    NSLog(@"appdelegate - center didReceiveNotificationResponse");
    NSString *actionIdentifier = response.actionIdentifier;
    UNNotification *notification = response.notification;
    if([actionIdentifier isEqual:@"com.apple.UNNotificationDefaultActionIdentifier"] || [actionIdentifier isEqual:@"com.apple.UNNotificationDismissActionIdentifier"]){
    }else{

        BOOL accept = [actionIdentifier isEqual:@"ACCEPT_IDENTIFIER"];
        BOOL stop = [actionIdentifier isEqual:@"DECLINE_IDENTIFIER"];
        BOOL doNotDisturb = [actionIdentifier isEqual:@"DO_NOT_DISTURB_IDENTIFIER"];

        if (accept){NSLog(@"accept");
            [self handleAcceptActionWithNotification:notification];
        }
        else if (stop){NSLog(@"stop");
            [self handleDeclineActionWithNotification:notification];
        }
        else if(doNotDisturb) {NSLog(@"do not disturb");
            [self handleDoNotDisturbActionWithNotification:notification];
        };
    }
    completionHandler();
}
- (void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions options))completionHandler{
    NSLog(@"appdelegate willPresentNotification");
    UNNotificationRequest * request = notification.request;
    NSString * actionIdentifier = request.identifier;
    if([actionIdentifier isEqualToString:UNNotificationDismissActionIdentifier] || [actionIdentifier isEqualToString:UNNotificationDefaultActionIdentifier]){
    }else{
        if([actionIdentifier isEqualToString:@"invite"]){
            NSLog(@"app delegate notification received while in foreground");
        }
    }
    completionHandler(UNNotificationPresentationOptionAlert + UNNotificationPresentationOptionSound);

}

Here is the NSLog of the triggering code:

<NSDateComponents: 0x146d51c0>
TimeZone: America/Chicago (CDT) offset -18000 (Daylight)
Calendar Year: 2017
Month: 10
Leap month: no
Day: 29
Hour: 14
Minute: 3
Second: 4

It is very clear that appdelegate methods are not being called by the system (I did put the app in background before the notification time, so the didReceiveNotification method should have been called.

If anyone can help, I would appreciate it!

Also, this code in viewDidLoad:

    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(receiveNotificationFromAppDelegate:)
                                                 name:kAppDelegateNotification
                                               object:nil];

    [[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(applicationBecameActive) name:UIApplicationDidBecomeActiveNotification object:nil];
    self.currentLocalNotificationRequests = [[NSMutableArray alloc]init];
/*
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(checkDndIndicator) name:UIApplicationDidBecomeActiveNotification object:nil];
 */
    UNNotificationAction *acceptAction = [UNNotificationAction actionWithIdentifier:@"ACCEPT_IDENTIFIER" title:NSLocalizedString(@"Continue notifications", nil) options:UNNotificationActionOptionAuthenticationRequired];
    UNNotificationAction *declineAction = [UNNotificationAction actionWithIdentifier:@"DECLINE_IDENTIFIER" title:NSLocalizedString(@"Stop notifications", nil) options:UNNotificationActionOptionAuthenticationRequired];
    UNNotificationAction *doNotDisturbAction = [UNNotificationAction actionWithIdentifier:@"DO_NOT_DISTURB_IDENTIFIER" title:NSLocalizedString(@"Start Do Not Disturb", nil) options:UNNotificationActionOptionAuthenticationRequired];
    NSArray *actions = [NSArray arrayWithObjects:acceptAction, declineAction, doNotDisturbAction, nil];
    // NSArray *intentIdentifiers = [NSArray arrayWithObjects:@"none", nil];
    UNNotificationCategory *invite = [UNNotificationCategory categoryWithIdentifier:@"com.nelsoncapes.localNotification" actions:actions intentIdentifiers: @[] options:UNNotificationCategoryOptionNone];
    NSSet *categories = [NSSet setWithObjects:invite, nil];
    [center setNotificationCategories:categories];
    [center requestAuthorizationWithOptions:(UNAuthorizationOptionAlert + UNAuthorizationOptionSound)
                          completionHandler:^(BOOL granted, NSError * _Nullable error) {
                              // Enable or disable features based on authorization.
                              NSLog(@"request granted");
                          }];

And code to start the trigger process:

    -(UNNotificationRequest *)startLocalNotification:(NSDate *)fireDate :
(NSMutableDictionary *)userInfo{
    NSUserDefaults *storage = [NSUserDefaults standardUserDefaults];
    UNUserNotificationCenter* center = [UNUserNotificationCenter currentNotificationCenter];
    [center removeAllPendingNotificationRequests];
    UNMutableNotificationContent* content = [[UNMutableNotificationContent alloc] init];
    content.title = NSLocalizedString(@"TimeChime Alert", nil);
    content.body = NSLocalizedString(@"Click to Stop or Change Timer",nil);
    content.categoryIdentifier = @"com.nelsoncapes.localNotification";
Nelson Capes
  • 411
  • 3
  • 12

2 Answers2

0

It turns out that this problem is obscure but the fix is easy. Unfortunately, I didn't carefully follow the first rule of coding: RTFD.

Apple's documentation for UNNotificationRequest> identifier states:

"If you use the same identifier when scheduling a new notification, the system removes the previously scheduled notification with that identifier and replaces it with the new one."

My code was using the same identifier for each UNNotificationRequest. Although the calendar notification date for each request was different, the system only kept the latest-dated request. In my case, the trigger would have fired after 1 hour, and I expected it to fire after 15 minutes. This is why I never saw a notification on the device and why I never saw a breakpoint in the delegate's didReceiveNotificationResponse method.

The fix is very easy. Just supply a unique identifier in the following code. After that, the code works.

UNNotificationRequest *request = [UNNotificationRequest requestWithIdentifier:@"invite" content:content trigger:trigger];

Replace @"invite" with a unique identifier for each request.

Nelson Capes
  • 411
  • 3
  • 12
0

I was not getting my notification because I did not specify the required date components to pass to my UNCalendarNotificationTrigger.

This does not work:

NSDateComponents *dateComponents = [calendar components:(NSCalendarUnitYear | NSCalendarUnitMonth | NSCalendarUnitDay | NSCalendarUnitHour) fromDate:[[NSDate date] dateByAddingTimeInterval:5]];

This works:

NSDateComponents *dateComponents = [calendar components:(NSCalendarUnitYear | NSCalendarUnitMonth | NSCalendarUnitDay | NSCalendarUnitHour | NSCalendarUnitMinute | NSCalendarUnitSecond | NSCalendarUnitNanosecond) fromDate:[[NSDate date] dateByAddingTimeInterval:5]];