From 543b490cd9bb94143d5558106b4fce26588f3c8e Mon Sep 17 00:00:00 2001 From: Kasten Date: Mon, 28 Nov 2016 03:04:10 -0800 Subject: [PATCH] Fixes for local notification AppDelegate selectors not firing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fixed local notification selectors not firing when app is in focus - userNotificationCenter:willPresentNotification:withCompletionHandler was not calling legacy selectors as it should. * Fixed issue where local notifications created with presentLocalNotificationNow would not fire their AppDelegate selector. * Removed deprecated OSUserNotificationCenterDelegate class - Due to very low usage and complexly to maintain, it’s being removed before a major release version. --- iOS_SDK/OneSignal/OneSignal.h | 18 +-- iOS_SDK/OneSignal/OneSignal.m | 19 +-- .../UNUserNotificationCenter+OneSignal.m | 108 ++++++++++-------- 3 files changed, 62 insertions(+), 83 deletions(-) diff --git a/iOS_SDK/OneSignal/OneSignal.h b/iOS_SDK/OneSignal/OneSignal.h index 6fe379b4a..4e42aa5bf 100755 --- a/iOS_SDK/OneSignal/OneSignal.h +++ b/iOS_SDK/OneSignal/OneSignal.h @@ -52,12 +52,6 @@ #define XC8_AVAILABLE 1 #import -@protocol OSUserNotificationCenterDelegate -@optional -- (void)userNotificationCenter:(id)center willPresentNotification:(id)notification withCompletionHandler:(void (^)(NSUInteger options))completionHandler __deprecated_msg("Can use your own delegate as normal."); -- (void)userNotificationCenter:(id)center didReceiveNotificationResponse:(id)response withCompletionHandler:(void (^)())completionHandler __deprecated_msg("Can use your own delegate as normal."); -@end - #endif /* The action type associated to an OSNotificationAction object */ @@ -80,9 +74,9 @@ typedef NS_ENUM(NSUInteger, OSNotificationDisplayType) { -/* iOS 10+ +/* Used as value type for `kOSSettingsKeyInFocusDisplayOption` - for setting the display option of a notification received while the app was in focus + for setting the display option of a notification received while the app was in focus. */ typedef OSNotificationDisplayType OSInFocusDisplayOption; @@ -239,7 +233,7 @@ typedef NS_ENUM(NSUInteger, ONE_S_LOG_LEVEL) { + (id)initWithLaunchOptions:(NSDictionary*)launchOptions appId:(NSString*)appId handleNotificationReceived:(OSHandleNotificationReceivedBlock)erceivedCallback handleNotificationAction:(OSHandleNotificationActionBlock)actionCallback settings:(NSDictionary*)settings; + (NSString*)app_id; - + // Only use if you passed FALSE to autoRegister + (void)registerForPushNotifications; @@ -280,10 +274,4 @@ typedef NS_ENUM(NSUInteger, ONE_S_LOG_LEVEL) { // Optional method that sends us the user's email as an anonymized hash so that we can better target and personalize notifications sent to that user across their devices. + (void)syncHashedEmail:(NSString*)email; -// - iOS 10 features currently only available on XCode 8 & iOS 10.0+ -#if XC8_AVAILABLE -+ (void)setNotificationCenterDelegate:(id)delegate __deprecated_msg("Can use your own delegate as normal."); -+ (id)notificationCenterDelegate __deprecated_msg("Can use your own delegate as normal."); -#endif - @end diff --git a/iOS_SDK/OneSignal/OneSignal.m b/iOS_SDK/OneSignal/OneSignal.m index 84adcc9e9..00294ee65 100755 --- a/iOS_SDK/OneSignal/OneSignal.m +++ b/iOS_SDK/OneSignal/OneSignal.m @@ -860,7 +860,7 @@ + (void)notificationOpened:(NSDictionary*)messageDict isActive:(BOOL)isActive { // App is active and a notification was received without inApp display. Display type is none or notification // Call Received Block - [OneSignalHelper handleNotificationReceived:[[[NSUserDefaults standardUserDefaults] objectForKey:@"ONESIGNAL_ALERT_OPTION"] intValue]]; + [OneSignalHelper handleNotificationReceived:iaaoption]; // Notify backend that user opened the notifiation NSString* messageId = [customDict objectForKey:@"i"]; @@ -1092,23 +1092,6 @@ + (void)processLocalActionBasedNotification:(UILocalNotification*) notification } -#if XC8_AVAILABLE -static id notificationCenterDelegate; - -+ (void) setNotificationCenterDelegate:(id)delegate { - if (!NSClassFromString(@"UNNotification")) { - onesignal_Log(ONE_S_LL_ERROR, @"Cannot assign delegate. Please make sure you are running on iOS 10+."); - return; - } - notificationCenterDelegate = delegate; -} - -+ (id)notificationCenterDelegate { - return notificationCenterDelegate; -} - -#endif - + (void)syncHashedEmail:(NSString *)email { if(mUserId == nil) { diff --git a/iOS_SDK/OneSignal/UNUserNotificationCenter+OneSignal.m b/iOS_SDK/OneSignal/UNUserNotificationCenter+OneSignal.m index 2ca083b38..42ea22dc8 100644 --- a/iOS_SDK/OneSignal/UNUserNotificationCenter+OneSignal.m +++ b/iOS_SDK/OneSignal/UNUserNotificationCenter+OneSignal.m @@ -50,9 +50,12 @@ + (void)notificationOpened:(NSDictionary*)messageDict isActive:(BOOL)isActive; // - userNotificationCenter:willPresentNotification:withCompletionHandler: // - Reads kOSSettingsKeyInFocusDisplayOption to respect it's setting. // - userNotificationCenter:didReceiveNotificationResponse:withCompletionHandler: -// - Used to process opening a notifications. -// - The presents of this selector tells iOS to no longer fire `application:didReceiveRemoteNotification:fetchCompletionHandler:`. -// We call this to maintain existing behavior. +// - Used to process opening notifications. +// +// NOTE: On iOS 10, when a UNUserNotificationCenterDelegate is set, UIApplicationDelegate notification selectors no longer fire. +// However, this class maintains firing of UIApplicationDelegate selectors if the app did not setup it's own UNUserNotificationCenterDelegate. +// This ensures we don't produce any side effects to standard iOS API selectors. +// The `callLegacyAppDeletegateSelector` selector below takes care of this backwards compatibility handling. @implementation swizzleUNUserNotif @@ -81,18 +84,13 @@ - (void) setOneSignalUNDelegate:(id)delegate { } // Apple's docs - Called when a notification is delivered to a foreground app. +// NOTE: iOS behavior - Calling completionHandler with 0 means userNotificationCenter:didReceiveNotificationResponse:withCompletionHandler: does not trigger. +// - callLegacyAppDeletegateSelector is called from here due to this case. - (void)onesignalUserNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions options))completionHandler { [OneSignal onesignal_Log:ONE_S_LL_VERBOSE message:@"onesignalUserNotificationCenter:willPresentNotification:withCompletionHandler: Fired!"]; - // Depercated - [OneSignal notificationCenterDelegate] - Now handled by swizzling. - // Proxy to user if listening to delegate and overrides the method. - if ([[OneSignal notificationCenterDelegate] respondsToSelector:@selector(userNotificationCenter:willPresentNotification:withCompletionHandler:)]) { - [[OneSignal notificationCenterDelegate] userNotificationCenter:center willPresentNotification:notification withCompletionHandler:completionHandler]; - return; - } - // Set the completionHandler options based on the ONESIGNAL_ALERT_OPTION value. if (![[NSUserDefaults standardUserDefaults] objectForKey:@"ONESIGNAL_ALERT_OPTION"]) { [[NSUserDefaults standardUserDefaults] setObject:@(OSNotificationDisplayTypeInAppAlert) forKey:@"ONESIGNAL_ALERT_OPTION"]; @@ -108,11 +106,19 @@ - (void)onesignalUserNotificationCenter:(UNUserNotificationCenter *)center default: break; } - // Call notificationOpened if no alert (MSB not set) [OneSignal notificationOpened:notification.request.content.userInfo isActive:YES]; + // Call orginal selector if one was set. if ([self respondsToSelector:@selector(onesignalUserNotificationCenter:willPresentNotification:withCompletionHandler:)]) [self onesignalUserNotificationCenter:center willPresentNotification:notification withCompletionHandler:completionHandler]; + // Or call a legacy AppDelegate selector + else { + [swizzleUNUserNotif callLegacyAppDeletegateSelector:notification + isTextReply:false + actionIdentifier:nil + userText:nil + withCompletionHandler:^() {}]; + } // Calling completionHandler for the following reasons: // App dev may have not implented userNotificationCenter:willPresentNotification. @@ -129,15 +135,20 @@ - (void)onesignalUserNotificationCenter:(UNUserNotificationCenter *)center [swizzleUNUserNotif processiOS10Open:response]; - // For depercated OSUserNotificationCenterDelegate - [swizzleUNUserNotif tunnelToDelegate:center response:response handler:completionHandler]; - // Call orginal selector if one was set. if ([self respondsToSelector:@selector(onesignalUserNotificationCenter:didReceiveNotificationResponse:withCompletionHandler:)]) [self onesignalUserNotificationCenter:center didReceiveNotificationResponse:response withCompletionHandler:completionHandler]; - // Or call a legacy selector AppDelegate selector - else if (![swizzleUNUserNotif isDismissEvent:response]) // iOS 9 did not have a dismiss event - [swizzleUNUserNotif callLegacyAppDeletegateSelector:response withCompletionHandler:completionHandler]; + // Or call a legacy AppDelegate selector + // - If not a dismiss event as their isn't a iOS 9 selector for it. + else if (![swizzleUNUserNotif isDismissEvent:response]) { + BOOL isTextReply = [response isKindOfClass:NSClassFromString(@"UNTextInputNotificationResponse")]; + NSString* userText = isTextReply ? [response valueForKey:@"userText"] : nil; + [swizzleUNUserNotif callLegacyAppDeletegateSelector:response.notification + isTextReply:isTextReply + actionIdentifier:response.actionIdentifier + userText:userText + withCompletionHandler:completionHandler]; + } else completionHandler(); } @@ -163,63 +174,60 @@ + (void) processiOS10Open:(UNNotificationResponse *)response { [OneSignal notificationOpened:userInfo isActive:isActive]; } -// Depercated - [OneSignal notificationCenterDelegate] - Now handled by swizzling. -+ (BOOL)tunnelToDelegate:(id)center response:(id)response handler:(void (^)())handler { - if ([[OneSignal notificationCenterDelegate] respondsToSelector:@selector(userNotificationCenter:didReceiveNotificationResponse:withCompletionHandler:)]) { - [[OneSignal notificationCenterDelegate] userNotificationCenter:center didReceiveNotificationResponse:response withCompletionHandler:handler]; - return true; - } - - return false; -} - // Calls depercated pre-iOS 10 selector if one is set on the AppDelegate. // Even though they are deperated in iOS 10 they should still be called in iOS 10 +// As long as they didn't setup their own UNUserNotificationCenterDelegate // - application:didReceiveLocalNotification: // - application:didReceiveRemoteNotification:fetchCompletionHandler: // - application:handleActionWithIdentifier:forLocalNotification:withResponseInfo:completionHandler: // - application:handleActionWithIdentifier:forRemoteNotification:withResponseInfo:completionHandler: // - application:handleActionWithIdentifier:forLocalNotification:completionHandler: // - application:handleActionWithIdentifier:forRemoteNotification:completionHandler: -+ (void)callLegacyAppDeletegateSelector:(UNNotificationResponse *)response ++ (void)callLegacyAppDeletegateSelector:(UNNotification *)notification + isTextReply:(BOOL)isTextReply + actionIdentifier:(NSString*)actionIdentifier + userText:(NSString*)userText withCompletionHandler:(void(^)())completionHandler { + [OneSignal onesignal_Log:ONE_S_LL_VERBOSE message:@"callLegacyAppDeletegateSelector:withCompletionHandler: Fired!"]; + UIApplication *sharedApp = [UIApplication sharedApplication]; - BOOL isTextReply = [response isKindOfClass:NSClassFromString(@"UNTextInputNotificationResponse")]; - BOOL isLegacyLocalNotif = [response.notification.request.trigger isKindOfClass:NSClassFromString(@"UNLegacyNotificationTrigger")]; - BOOL isCustomAction = ![@"com.apple.UNNotificationDefaultActionIdentifier" isEqualToString:response.actionIdentifier]; - BOOL isRemote = [response.notification.request.trigger isKindOfClass:NSClassFromString(@"UNPushNotificationTrigger")]; + // trigger is nil when UIApplication.presentLocalNotificationNow: is used. + // However it will be UNLegacyNotificationTrigger when UIApplication.scheduleLocalNotification: is used + BOOL isLegacyLocalNotif = !notification.request.trigger || [notification.request.trigger isKindOfClass:NSClassFromString(@"UNLegacyNotificationTrigger")]; + BOOL isCustomAction = actionIdentifier && ![@"com.apple.UNNotificationDefaultActionIdentifier" isEqualToString:actionIdentifier]; + BOOL isRemote = [notification.request.trigger isKindOfClass:NSClassFromString(@"UNPushNotificationTrigger")]; if (isLegacyLocalNotif) { UILocalNotification *localNotif = [NSClassFromString(@"UIConcreteLocalNotification") alloc]; - localNotif.alertBody = response.notification.request.content.body; - localNotif.alertTitle = response.notification.request.content.title; - localNotif.applicationIconBadgeNumber = [response.notification.request.content.badge integerValue]; - NSString* soundName = [response.notification.request.content.sound valueForKey:@"_toneFileName"]; + localNotif.alertBody = notification.request.content.body; + localNotif.alertTitle = notification.request.content.title; + localNotif.applicationIconBadgeNumber = [notification.request.content.badge integerValue]; + NSString* soundName = [notification.request.content.sound valueForKey:@"_toneFileName"]; if (!soundName) soundName = @"UILocalNotificationDefaultSoundName"; localNotif.soundName = soundName; - localNotif.alertLaunchImage = response.notification.request.content.launchImageName; - localNotif.userInfo = response.notification.request.content.userInfo; - localNotif.category = response.notification.request.content.categoryIdentifier; + localNotif.alertLaunchImage = notification.request.content.launchImageName; + localNotif.userInfo = notification.request.content.userInfo; + localNotif.category = notification.request.content.categoryIdentifier; localNotif.hasAction = true; // Defaults to true, UNLocalNotification doesn't seem to have a flag for this. - localNotif.fireDate = response.notification.date; - localNotif.timeZone = [response.notification.request.trigger valueForKey:@"_timeZone"]; - localNotif.repeatInterval = (NSCalendarUnit)[response.notification.request.trigger valueForKey:@"_repeatInterval"]; - localNotif.repeatCalendar = [response.notification.request.trigger valueForKey:@"_repeatCalendar"]; + localNotif.fireDate = notification.date; + localNotif.timeZone = [notification.request.trigger valueForKey:@"_timeZone"]; + localNotif.repeatInterval = (NSCalendarUnit)[notification.request.trigger valueForKey:@"_repeatInterval"]; + localNotif.repeatCalendar = [notification.request.trigger valueForKey:@"_repeatCalendar"]; // localNotif.region = // localNotif.regionTriggersOnce = if (isTextReply && [sharedApp.delegate respondsToSelector:@selector(application:handleActionWithIdentifier:forLocalNotification:withResponseInfo:completionHandler:)]) { - NSDictionary* dict = @{UIUserNotificationActionResponseTypedTextKey: [response valueForKey:@"userText"]}; - [sharedApp.delegate application:sharedApp handleActionWithIdentifier:response.actionIdentifier forLocalNotification:localNotif withResponseInfo:dict completionHandler:^() { + NSDictionary* dict = @{UIUserNotificationActionResponseTypedTextKey: userText}; + [sharedApp.delegate application:sharedApp handleActionWithIdentifier:actionIdentifier forLocalNotification:localNotif withResponseInfo:dict completionHandler:^() { completionHandler(); }]; } else if (isCustomAction && [sharedApp.delegate respondsToSelector:@selector(application:handleActionWithIdentifier:forLocalNotification:completionHandler:)]) - [sharedApp.delegate application:sharedApp handleActionWithIdentifier:response.actionIdentifier forLocalNotification:localNotif completionHandler:^() { + [sharedApp.delegate application:sharedApp handleActionWithIdentifier:actionIdentifier forLocalNotification:localNotif completionHandler:^() { completionHandler(); }]; else if ([sharedApp.delegate respondsToSelector:@selector(application:didReceiveLocalNotification:)]) { @@ -230,18 +238,18 @@ + (void)callLegacyAppDeletegateSelector:(UNNotificationResponse *)response completionHandler(); } else if (isRemote) { - NSDictionary* remoteUserInfo = response.notification.request.content.userInfo; + NSDictionary* remoteUserInfo = notification.request.content.userInfo; if (isTextReply && [sharedApp.delegate respondsToSelector:@selector(application:handleActionWithIdentifier:forRemoteNotification:withResponseInfo:completionHandler:)]) { - NSDictionary* responseInfo = @{UIUserNotificationActionResponseTypedTextKey: [response valueForKey:@"userText"]}; - [sharedApp.delegate application:sharedApp handleActionWithIdentifier:response.actionIdentifier forRemoteNotification:remoteUserInfo withResponseInfo:responseInfo completionHandler:^() { + NSDictionary* responseInfo = @{UIUserNotificationActionResponseTypedTextKey: userText}; + [sharedApp.delegate application:sharedApp handleActionWithIdentifier:actionIdentifier forRemoteNotification:remoteUserInfo withResponseInfo:responseInfo completionHandler:^() { completionHandler(); }]; } else if (isCustomAction && [sharedApp.delegate respondsToSelector:@selector(application:handleActionWithIdentifier:forRemoteNotification:completionHandler:)]) - [sharedApp.delegate application:sharedApp handleActionWithIdentifier:response.actionIdentifier forRemoteNotification:remoteUserInfo completionHandler:^() { + [sharedApp.delegate application:sharedApp handleActionWithIdentifier:actionIdentifier forRemoteNotification:remoteUserInfo completionHandler:^() { completionHandler(); }]; else if ([sharedApp.delegate respondsToSelector:@selector(application:didReceiveRemoteNotification:fetchCompletionHandler:)]) {