From 7fe31483ca3035fdf187800ad3e68f57eae88e92 Mon Sep 17 00:00:00 2001 From: Nan Date: Tue, 27 May 2025 11:33:38 -0700 Subject: [PATCH 1/6] clearBadgeCount method does not need to return bool * The `clearBadgeCount` was returning a bool flag to indicate if badge was set within the method. However, nothing is consuming this flag, and with the Apple API to get application icon badge number deprecated (https://developer.apple.com/documentation/uikit/uiapplication/applicationiconbadgenumber?language=objc), the SDK's ability to get accurate badge count at any particular moment is reduced. --- .../OneSignalNotifications/OSNotificationsManager.h | 2 +- .../OneSignalNotifications/OSNotificationsManager.m | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/iOS_SDK/OneSignalSDK/OneSignalNotifications/OSNotificationsManager.h b/iOS_SDK/OneSignalSDK/OneSignalNotifications/OSNotificationsManager.h index 81a5eaafb..a53d03d74 100644 --- a/iOS_SDK/OneSignalSDK/OneSignalNotifications/OSNotificationsManager.h +++ b/iOS_SDK/OneSignalSDK/OneSignalNotifications/OSNotificationsManager.h @@ -114,7 +114,7 @@ NS_SWIFT_NAME(onClick(event:)); + (void)handleWillShowInForegroundForNotification:(OSNotification *_Nonnull)notification completion:(OSNotificationDisplayResponse _Nonnull)completion; + (void)handleNotificationActionWithUrl:(NSString* _Nullable)url actionID:(NSString* _Nonnull)actionID; -+ (BOOL)clearBadgeCount:(BOOL)fromNotifOpened fromClearAll:(BOOL)fromClearAll; ++ (void)clearBadgeCount:(BOOL)fromNotifOpened fromClearAll:(BOOL)fromClearAll; + (BOOL)receiveRemoteNotification:(UIApplication* _Nonnull)application UserInfo:(NSDictionary* _Nonnull)userInfo completionHandler:(void (^_Nonnull)(UIBackgroundFetchResult))completionHandler; + (void)notificationReceived:(NSDictionary* _Nonnull)messageDict wasOpened:(BOOL)opened; diff --git a/iOS_SDK/OneSignalSDK/OneSignalNotifications/OSNotificationsManager.m b/iOS_SDK/OneSignalSDK/OneSignalNotifications/OSNotificationsManager.m index b8cebda98..5ca10d101 100644 --- a/iOS_SDK/OneSignalSDK/OneSignalNotifications/OSNotificationsManager.m +++ b/iOS_SDK/OneSignalSDK/OneSignalNotifications/OSNotificationsManager.m @@ -784,7 +784,7 @@ + (void)displayWebView:(NSURL*)url { openUrlBlock(true); } -+ (BOOL)clearBadgeCount:(BOOL)fromNotifOpened fromClearAll:(BOOL)fromClearAll { ++ (void)clearBadgeCount:(BOOL)fromNotifOpened fromClearAll:(BOOL)fromClearAll { NSNumber *disableBadgeNumber = [[NSBundle mainBundle] objectForInfoDictionaryKey:ONESIGNAL_DISABLE_BADGE_CLEARING]; @@ -796,7 +796,7 @@ + (BOOL)clearBadgeCount:(BOOL)fromNotifOpened fromClearAll:(BOOL)fromClearAll { if (_disableBadgeClearing && !fromClearAll) { // The customer could have manually changed the badge value. We must ensure our cached value will match the current state. [OneSignalUserDefaults.initShared saveIntegerForKey:ONESIGNAL_BADGE_KEY withValue:[UIApplication sharedApplication].applicationIconBadgeNumber]; - return false; + return; } bool wasBadgeSet = [UIApplication sharedApplication].applicationIconBadgeNumber > 0; @@ -806,8 +806,6 @@ + (BOOL)clearBadgeCount:(BOOL)fromNotifOpened fromClearAll:(BOOL)fromClearAll { [[UIApplication sharedApplication] setApplicationIconBadgeNumber:0]; }]; } - - return wasBadgeSet; } + (BOOL)handleIAMPreview:(OSNotification *)notification { From 208ff4e6833f736941f532233ae18f7e1689a2fa Mon Sep 17 00:00:00 2001 From: Nan Date: Tue, 27 May 2025 22:51:06 -0700 Subject: [PATCH 2/6] Centralize method to cache badge count * Use one method to cache badge count and move this method `updateCachedBadgeValue` out of OneSignalExtension so it can be used by other modules. Move it to a badge helpers class in OneSignalCore. --- .../OneSignal.xcodeproj/project.pbxproj | 8 ++++ .../Source/OneSignalBadgeHelpers.h | 32 ++++++++++++++++ .../Source/OneSignalBadgeHelpers.m | 38 +++++++++++++++++++ .../OneSignalCore/Source/OneSignalCore.h | 1 + .../OneSignalExtensionBadgeHandler.h | 1 - .../OneSignalExtensionBadgeHandler.m | 10 +---- .../OSNotification+OneSignal.m | 2 +- .../OSNotificationsManager.m | 2 +- iOS_SDK/OneSignalSDK/Source/OneSignal.m | 2 +- 9 files changed, 84 insertions(+), 12 deletions(-) create mode 100644 iOS_SDK/OneSignalSDK/OneSignalCore/Source/OneSignalBadgeHelpers.h create mode 100644 iOS_SDK/OneSignalSDK/OneSignalCore/Source/OneSignalBadgeHelpers.m diff --git a/iOS_SDK/OneSignalSDK/OneSignal.xcodeproj/project.pbxproj b/iOS_SDK/OneSignalSDK/OneSignal.xcodeproj/project.pbxproj index cabab70a0..ad9d90b77 100644 --- a/iOS_SDK/OneSignalSDK/OneSignal.xcodeproj/project.pbxproj +++ b/iOS_SDK/OneSignalSDK/OneSignal.xcodeproj/project.pbxproj @@ -73,6 +73,8 @@ 3C277D7E2BD76E0000857606 /* OSIdentityModelRepo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C277D7D2BD76E0000857606 /* OSIdentityModelRepo.swift */; }; 3C2C7DC8288F3C020020F9AE /* OSSubscriptionModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C2C7DC7288F3C020020F9AE /* OSSubscriptionModel.swift */; }; 3C2D8A5928B4C4E300BE41F6 /* OSDelta.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C2D8A5828B4C4E300BE41F6 /* OSDelta.swift */; }; + 3C2DB2F12DE6CB5E0006B905 /* OneSignalBadgeHelpers.h in Headers */ = {isa = PBXBuildFile; fileRef = 3C2DB2EF2DE6CB5E0006B905 /* OneSignalBadgeHelpers.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 3C2DB2F22DE6CB5E0006B905 /* OneSignalBadgeHelpers.m in Sources */ = {isa = PBXBuildFile; fileRef = 3C2DB2F02DE6CB5E0006B905 /* OneSignalBadgeHelpers.m */; }; 3C44673E296D099D0039A49E /* OneSignalMobileProvision.m in Sources */ = {isa = PBXBuildFile; fileRef = 912411FD1E73342200E41FD7 /* OneSignalMobileProvision.m */; }; 3C44673F296D09CC0039A49E /* OneSignalMobileProvision.h in Headers */ = {isa = PBXBuildFile; fileRef = 912411FC1E73342200E41FD7 /* OneSignalMobileProvision.h */; settings = {ATTRIBUTES = (Public, ); }; }; 3C448B9D2936ADFD002F96BC /* OSBackgroundTaskHandlerImpl.h in Headers */ = {isa = PBXBuildFile; fileRef = 3C448B9B2936ADFD002F96BC /* OSBackgroundTaskHandlerImpl.h */; }; @@ -1251,6 +1253,8 @@ 3C2C7DC2288E007E0020F9AE /* UnitTests-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "UnitTests-Bridging-Header.h"; sourceTree = ""; }; 3C2C7DC7288F3C020020F9AE /* OSSubscriptionModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OSSubscriptionModel.swift; sourceTree = ""; }; 3C2D8A5828B4C4E300BE41F6 /* OSDelta.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OSDelta.swift; sourceTree = ""; }; + 3C2DB2EF2DE6CB5E0006B905 /* OneSignalBadgeHelpers.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OneSignalBadgeHelpers.h; sourceTree = ""; }; + 3C2DB2F02DE6CB5E0006B905 /* OneSignalBadgeHelpers.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OneSignalBadgeHelpers.m; sourceTree = ""; }; 3C448B9B2936ADFD002F96BC /* OSBackgroundTaskHandlerImpl.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OSBackgroundTaskHandlerImpl.h; sourceTree = ""; }; 3C448B9C2936ADFD002F96BC /* OSBackgroundTaskHandlerImpl.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OSBackgroundTaskHandlerImpl.m; sourceTree = ""; }; 3C448BA12936B474002F96BC /* OSBackgroundTaskManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OSBackgroundTaskManager.swift; sourceTree = ""; }; @@ -2596,6 +2600,8 @@ DE7D182C270273B0002D3A5D /* OSNotification.h */, 454F94F41FAD2E5A00D74CCF /* OSNotification.m */, 454F94F61FAD2EC300D74CCF /* OSNotification+Internal.h */, + 3C2DB2EF2DE6CB5E0006B905 /* OneSignalBadgeHelpers.h */, + 3C2DB2F02DE6CB5E0006B905 /* OneSignalBadgeHelpers.m */, 3CE8CC4C2911ADD1000DB0D3 /* OSDeviceUtils.h */, 3C47A972292642B100312125 /* OneSignalConfigManager.h */, 3C47A973292642B100312125 /* OneSignalConfigManager.m */, @@ -3138,6 +3144,7 @@ DE7D182F270275FF002D3A5D /* OneSignalTrackFirebaseAnalytics.h in Headers */, DEF7845C2912E89200A1F3A5 /* OSObservable.h in Headers */, DE7D1875270375FF002D3A5D /* OSReattemptRequest.h in Headers */, + 3C2DB2F12DE6CB5E0006B905 /* OneSignalBadgeHelpers.h in Headers */, DEF784652912FB2200A1F3A5 /* OSDialogInstanceManager.h in Headers */, DEF78493291479B200A1F3A5 /* OneSignalSelectorHelpers.h in Headers */, DE7D1862270374EE002D3A5D /* OSJSONHandling.h in Headers */, @@ -4427,6 +4434,7 @@ DEF784642912FA5100A1F3A5 /* OSDialogInstanceManager.m in Sources */, DE7D183B27027EFC002D3A5D /* NSURL+OneSignal.m in Sources */, DE7D186B270374EE002D3A5D /* OneSignalRequest.m in Sources */, + 3C2DB2F22DE6CB5E0006B905 /* OneSignalBadgeHelpers.m in Sources */, 3C44673E296D099D0039A49E /* OneSignalMobileProvision.m in Sources */, 3CCF44BF299B17290021964D /* OneSignalWrapper.m in Sources */, DEF78492291479B200A1F3A5 /* OneSignalSelectorHelpers.m in Sources */, diff --git a/iOS_SDK/OneSignalSDK/OneSignalCore/Source/OneSignalBadgeHelpers.h b/iOS_SDK/OneSignalSDK/OneSignalCore/Source/OneSignalBadgeHelpers.h new file mode 100644 index 000000000..81c47a6bc --- /dev/null +++ b/iOS_SDK/OneSignalSDK/OneSignalCore/Source/OneSignalBadgeHelpers.h @@ -0,0 +1,32 @@ +/** + * Modified MIT License + * + * Copyright 2025 OneSignal + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * 1. The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * 2. All copies of substantial portions of the Software may only be used in connection + * with services provided by OneSignal. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#import + +@interface OneSignalBadgeHelpers : NSObject ++ (void)updateCachedBadgeValue:(NSInteger)value; +@end diff --git a/iOS_SDK/OneSignalSDK/OneSignalCore/Source/OneSignalBadgeHelpers.m b/iOS_SDK/OneSignalSDK/OneSignalCore/Source/OneSignalBadgeHelpers.m new file mode 100644 index 000000000..06756b030 --- /dev/null +++ b/iOS_SDK/OneSignalSDK/OneSignalCore/Source/OneSignalBadgeHelpers.m @@ -0,0 +1,38 @@ +/** + * Modified MIT License + * + * Copyright 2025 OneSignal + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * 1. The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * 2. All copies of substantial portions of the Software may only be used in connection + * with services provided by OneSignal. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#import "OneSignalBadgeHelpers.h" +#import "OneSignalUserDefaults.h" +#import "OneSignalCommonDefines.h" + +@implementation OneSignalBadgeHelpers ++ (void)updateCachedBadgeValue:(NSInteger)value { + // Since badge logic can be executed in an extension, we need to use app groups to get + // a shared NSUserDefaults from the app group suite name + [OneSignalUserDefaults.initShared saveIntegerForKey:ONESIGNAL_BADGE_KEY withValue:value]; +} +@end diff --git a/iOS_SDK/OneSignalSDK/OneSignalCore/Source/OneSignalCore.h b/iOS_SDK/OneSignalSDK/OneSignalCore/Source/OneSignalCore.h index c7783d740..f3922e961 100644 --- a/iOS_SDK/OneSignalSDK/OneSignalCore/Source/OneSignalCore.h +++ b/iOS_SDK/OneSignalSDK/OneSignalCore/Source/OneSignalCore.h @@ -58,6 +58,7 @@ #import #import #import +#import // TODO: Testing: Should this class be defined in this file? @interface OneSignalCoreImpl : NSObject diff --git a/iOS_SDK/OneSignalSDK/OneSignalExtension/OneSignalExtensionBadgeHandler.h b/iOS_SDK/OneSignalSDK/OneSignalExtension/OneSignalExtensionBadgeHandler.h index f14ad763a..41571340d 100644 --- a/iOS_SDK/OneSignalSDK/OneSignalExtension/OneSignalExtensionBadgeHandler.h +++ b/iOS_SDK/OneSignalSDK/OneSignalExtension/OneSignalExtensionBadgeHandler.h @@ -31,6 +31,5 @@ @interface OneSignalExtensionBadgeHandler : NSObject + (void)handleBadgeCountWithNotificationRequest:(UNNotificationRequest *)request withNotification:(OSNotification *)notification withMutableNotificationContent:(UNMutableNotificationContent *)replacementContent; -+ (void)updateCachedBadgeValue:(NSInteger)value; + (NSInteger)currentCachedBadgeValue; @end diff --git a/iOS_SDK/OneSignalSDK/OneSignalExtension/OneSignalExtensionBadgeHandler.m b/iOS_SDK/OneSignalSDK/OneSignalExtension/OneSignalExtensionBadgeHandler.m index 0e5097b70..c8e5605e7 100644 --- a/iOS_SDK/OneSignalSDK/OneSignalExtension/OneSignalExtensionBadgeHandler.m +++ b/iOS_SDK/OneSignalSDK/OneSignalExtension/OneSignalExtensionBadgeHandler.m @@ -35,7 +35,7 @@ + (void)handleBadgeCountWithNotificationRequest:(UNNotificationRequest *)request //make sure the OneSignal cached value is updated to this value if (!notification.badgeIncrement) { if (notification.hasBadge) - [OneSignalExtensionBadgeHandler updateCachedBadgeValue:notification.badge]; + [OneSignalBadgeHelpers updateCachedBadgeValue:notification.badge]; return; } @@ -50,17 +50,11 @@ + (void)handleBadgeCountWithNotificationRequest:(UNNotificationRequest *)request replacementContent.badge = @(currentValue); - [OneSignalExtensionBadgeHandler updateCachedBadgeValue:currentValue]; + [OneSignalBadgeHelpers updateCachedBadgeValue:currentValue]; } + (NSInteger)currentCachedBadgeValue { return [OneSignalUserDefaults.initShared getSavedIntegerForKey:ONESIGNAL_BADGE_KEY defaultValue:0]; } -+ (void)updateCachedBadgeValue:(NSInteger)value { - // Since badge logic can be executed in an extension, we need to use app groups to get - // a shared NSUserDefaults from the app group suite name - [OneSignalUserDefaults.initShared saveIntegerForKey:ONESIGNAL_BADGE_KEY withValue:value]; -} - @end diff --git a/iOS_SDK/OneSignalSDK/OneSignalNotifications/OSNotification+OneSignal.m b/iOS_SDK/OneSignalSDK/OneSignalNotifications/OSNotification+OneSignal.m index f28970869..7f2356726 100644 --- a/iOS_SDK/OneSignalSDK/OneSignalNotifications/OSNotification+OneSignal.m +++ b/iOS_SDK/OneSignalSDK/OneSignalNotifications/OSNotification+OneSignal.m @@ -77,7 +77,7 @@ - (void)complete:(OSDisplayableNotification *)notification { */ if (!notification) { NSInteger previousBadgeCount = [UIApplication sharedApplication].applicationIconBadgeNumber; - [OneSignalUserDefaults.initShared saveIntegerForKey:ONESIGNAL_BADGE_KEY withValue:previousBadgeCount]; + [OneSignalBadgeHelpers updateCachedBadgeValue:previousBadgeCount]; } if (_completion) { _completion(notification); diff --git a/iOS_SDK/OneSignalSDK/OneSignalNotifications/OSNotificationsManager.m b/iOS_SDK/OneSignalSDK/OneSignalNotifications/OSNotificationsManager.m index 5ca10d101..d74c0e6b9 100644 --- a/iOS_SDK/OneSignalSDK/OneSignalNotifications/OSNotificationsManager.m +++ b/iOS_SDK/OneSignalSDK/OneSignalNotifications/OSNotificationsManager.m @@ -795,7 +795,7 @@ + (void)clearBadgeCount:(BOOL)fromNotifOpened fromClearAll:(BOOL)fromClearAll { if (_disableBadgeClearing && !fromClearAll) { // The customer could have manually changed the badge value. We must ensure our cached value will match the current state. - [OneSignalUserDefaults.initShared saveIntegerForKey:ONESIGNAL_BADGE_KEY withValue:[UIApplication sharedApplication].applicationIconBadgeNumber]; + [OneSignalBadgeHelpers updateCachedBadgeValue:[UIApplication sharedApplication].applicationIconBadgeNumber]; return; } diff --git a/iOS_SDK/OneSignalSDK/Source/OneSignal.m b/iOS_SDK/OneSignalSDK/Source/OneSignal.m index 6851e2167..b9b2e447d 100755 --- a/iOS_SDK/OneSignalSDK/Source/OneSignal.m +++ b/iOS_SDK/OneSignalSDK/Source/OneSignal.m @@ -809,7 +809,7 @@ + (void)load { We swizzle the 'setApplicationIconBadgeNumber()' to intercept these calls so we always know the latest count */ - (void)onesignalSetApplicationIconBadgeNumber:(NSInteger)badge { - [OneSignalExtensionBadgeHandler updateCachedBadgeValue:badge]; + [OneSignalBadgeHelpers updateCachedBadgeValue:badge]; [self onesignalSetApplicationIconBadgeNumber:badge]; } From 5114281f54d398bcaf5fa3a61b2fe9e4e67c34c4 Mon Sep 17 00:00:00 2001 From: Nan Date: Tue, 27 May 2025 23:16:15 -0700 Subject: [PATCH 3/6] nit: remove unused method from OneSignalExtensionBadgeHandler header * currentCachedBadgeValue is only used within the OneSignalExtensionBadgeHandler class, no need to expose on header. --- .../OneSignalExtension/OneSignalExtensionBadgeHandler.h | 1 - 1 file changed, 1 deletion(-) diff --git a/iOS_SDK/OneSignalSDK/OneSignalExtension/OneSignalExtensionBadgeHandler.h b/iOS_SDK/OneSignalSDK/OneSignalExtension/OneSignalExtensionBadgeHandler.h index 41571340d..9311a7e01 100644 --- a/iOS_SDK/OneSignalSDK/OneSignalExtension/OneSignalExtensionBadgeHandler.h +++ b/iOS_SDK/OneSignalSDK/OneSignalExtension/OneSignalExtensionBadgeHandler.h @@ -31,5 +31,4 @@ @interface OneSignalExtensionBadgeHandler : NSObject + (void)handleBadgeCountWithNotificationRequest:(UNNotificationRequest *)request withNotification:(OSNotification *)notification withMutableNotificationContent:(UNMutableNotificationContent *)replacementContent; -+ (NSInteger)currentCachedBadgeValue; @end From 6d9b74c16f9f6c7247ba8dfdc28db73550515ba7 Mon Sep 17 00:00:00 2001 From: Nan Date: Tue, 27 May 2025 11:26:58 -0700 Subject: [PATCH 4/6] Use new setBadgeCount API on iOS 16+ * The method `applicationIconBadgeNumber` is deprecated in iOS 17 - see https://developer.apple.com/documentation/uikit/uiapplication/applicationiconbadgenumber * Its replacement is `setBadgeCount:withCompletionHandler:` but there is no equivalent getter. * Use this new API on ios 16+ and swizzle it just like we do for applicationIconBadgeNumber --- ...serNotificationCenter+OneSignalNotifications.m | 15 +++++++++++++++ .../OSNotificationsManager.m | 14 +++++++++++--- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/iOS_SDK/OneSignalSDK/OneSignalNotifications/Categories/UNUserNotificationCenter+OneSignalNotifications.m b/iOS_SDK/OneSignalSDK/OneSignalNotifications/Categories/UNUserNotificationCenter+OneSignalNotifications.m index 14bb1bbc1..75a41150d 100644 --- a/iOS_SDK/OneSignalSDK/OneSignalNotifications/Categories/UNUserNotificationCenter+OneSignalNotifications.m +++ b/iOS_SDK/OneSignalSDK/OneSignalNotifications/Categories/UNUserNotificationCenter+OneSignalNotifications.m @@ -97,6 +97,12 @@ + (void)swizzleSelectors { [OneSignalNotificationsUNUserNotificationCenter class], @selector(onesignalGetNotificationSettingsWithCompletionHandler:) ); + injectSelector( + [UNUserNotificationCenter class], + @selector(setBadgeCount:withCompletionHandler:), + [OneSignalNotificationsUNUserNotificationCenter class], + @selector(onesignalSetBadgeCount:withCompletionHandler:) + ); } + (void)registerDelegate { @@ -167,6 +173,15 @@ - (void)onesignalGetNotificationSettingsWithCompletionHandler:(void(^)(UNNotific [self onesignalGetNotificationSettingsWithCompletionHandler:wrapperBlock]; } +/** + In order for the badge count to be consistent even in situations where the developer manually sets the badge number, + we swizzle the 'setBadgeCount()' method for ios 16+ to intercept these calls so we always know the latest count. This is especially + necessary as there is no equivalent "getBadgeCount" method available. + */ +- (void)onesignalSetBadgeCount:(NSInteger)badge withCompletionHandler:(void(^)(NSError *error))completionHandler { + [OneSignalBadgeHelpers updateCachedBadgeValue:badge]; + [self onesignalSetBadgeCount:badge withCompletionHandler:completionHandler]; +} // A Set to keep track of which classes we have already swizzled so we only // swizzle each one once. If we swizzled more than once then this will create diff --git a/iOS_SDK/OneSignalSDK/OneSignalNotifications/OSNotificationsManager.m b/iOS_SDK/OneSignalSDK/OneSignalNotifications/OSNotificationsManager.m index d74c0e6b9..310834770 100644 --- a/iOS_SDK/OneSignalSDK/OneSignalNotifications/OSNotificationsManager.m +++ b/iOS_SDK/OneSignalSDK/OneSignalNotifications/OSNotificationsManager.m @@ -802,9 +802,17 @@ + (void)clearBadgeCount:(BOOL)fromNotifOpened fromClearAll:(BOOL)fromClearAll { bool wasBadgeSet = [UIApplication sharedApplication].applicationIconBadgeNumber > 0; if (fromNotifOpened || wasBadgeSet) { - [OneSignalCoreHelper runOnMainThread:^{ - [[UIApplication sharedApplication] setApplicationIconBadgeNumber:0]; - }]; + if (@available(iOS 16.0, *)) { + [[UNUserNotificationCenter currentNotificationCenter] setBadgeCount:0 withCompletionHandler:^(NSError * _Nullable error) { + if (error) { + [OneSignalLog onesignalLog:ONE_S_LL_ERROR message:[NSString stringWithFormat:@"clearBadgeCount encountered error setting badge count: %@", error]]; + } + }]; + } else { + [OneSignalCoreHelper runOnMainThread:^{ + [[UIApplication sharedApplication] setApplicationIconBadgeNumber:0]; + }]; + } } } From 0f1ae550ebc08fe5e659b42a0f05abaa90336251 Mon Sep 17 00:00:00 2001 From: Nan Date: Wed, 28 May 2025 00:43:29 -0700 Subject: [PATCH 5/6] Remove all usages of deprecated applicationIconBadgeNumber * applicationIconBadgeNumber is deprecated and reports of apps hanging have been submitted, let's move off it. * In clearBadgeCount, we read if badge was > 0 before setting badge to 0. Since we can't read accurately, let's just always set to 0, which is what the old logic effectively did anyways. --- .../OSNotificationsManager.m | 29 +++++++++---------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/iOS_SDK/OneSignalSDK/OneSignalNotifications/OSNotificationsManager.m b/iOS_SDK/OneSignalSDK/OneSignalNotifications/OSNotificationsManager.m index 310834770..4a72345b6 100644 --- a/iOS_SDK/OneSignalSDK/OneSignalNotifications/OSNotificationsManager.m +++ b/iOS_SDK/OneSignalSDK/OneSignalNotifications/OSNotificationsManager.m @@ -794,25 +794,22 @@ + (void)clearBadgeCount:(BOOL)fromNotifOpened fromClearAll:(BOOL)fromClearAll { _disableBadgeClearing = NO; if (_disableBadgeClearing && !fromClearAll) { - // The customer could have manually changed the badge value. We must ensure our cached value will match the current state. - [OneSignalBadgeHelpers updateCachedBadgeValue:[UIApplication sharedApplication].applicationIconBadgeNumber]; + // The developer could have manually changed the badge value but we cannot read the current state. + // We swizzle badge count setters, which should be sufficient to ensure the cached value matches the current state. + [OneSignalLog onesignalLog:ONE_S_LL_DEBUG message:@"clearBadgeCount called but badge clearing is disabled, not updating cached badge count"]; return; } - bool wasBadgeSet = [UIApplication sharedApplication].applicationIconBadgeNumber > 0; - - if (fromNotifOpened || wasBadgeSet) { - if (@available(iOS 16.0, *)) { - [[UNUserNotificationCenter currentNotificationCenter] setBadgeCount:0 withCompletionHandler:^(NSError * _Nullable error) { - if (error) { - [OneSignalLog onesignalLog:ONE_S_LL_ERROR message:[NSString stringWithFormat:@"clearBadgeCount encountered error setting badge count: %@", error]]; - } - }]; - } else { - [OneSignalCoreHelper runOnMainThread:^{ - [[UIApplication sharedApplication] setApplicationIconBadgeNumber:0]; - }]; - } + if (@available(iOS 16.0, *)) { + [[UNUserNotificationCenter currentNotificationCenter] setBadgeCount:0 withCompletionHandler:^(NSError * _Nullable error) { + if (error) { + [OneSignalLog onesignalLog:ONE_S_LL_ERROR message:[NSString stringWithFormat:@"clearBadgeCount encountered error setting badge count: %@", error]]; + } + }]; + } else { + [OneSignalCoreHelper runOnMainThread:^{ + [[UIApplication sharedApplication] setApplicationIconBadgeNumber:0]; + }]; } } From 1fe4fc1a7eb1180d75223c6d8fb09b0a6523a4fa Mon Sep 17 00:00:00 2001 From: Nan Date: Wed, 28 May 2025 01:24:07 -0700 Subject: [PATCH 6/6] Keep a previous badge count as well as current * When a notification foreground display is cancelled, the badge count in that notification (if any) does not apply. We have to revert our cached badge count back to the previous. We store the previous as we cannot use a getter for badge count anymore. --- .../Source/OneSignalBadgeHelpers.h | 2 +- .../Source/OneSignalBadgeHelpers.m | 18 ++++++++++++++++-- .../Source/OneSignalCommonDefines.h | 2 ++ .../OneSignalExtensionBadgeHandler.m | 4 ++-- ...NotificationCenter+OneSignalNotifications.m | 2 +- .../OSNotification+OneSignal.m | 4 ++-- iOS_SDK/OneSignalSDK/Source/OneSignal.m | 2 +- 7 files changed, 25 insertions(+), 9 deletions(-) diff --git a/iOS_SDK/OneSignalSDK/OneSignalCore/Source/OneSignalBadgeHelpers.h b/iOS_SDK/OneSignalSDK/OneSignalCore/Source/OneSignalBadgeHelpers.h index 81c47a6bc..c3487b9f7 100644 --- a/iOS_SDK/OneSignalSDK/OneSignalCore/Source/OneSignalBadgeHelpers.h +++ b/iOS_SDK/OneSignalSDK/OneSignalCore/Source/OneSignalBadgeHelpers.h @@ -28,5 +28,5 @@ #import @interface OneSignalBadgeHelpers : NSObject -+ (void)updateCachedBadgeValue:(NSInteger)value; ++ (void)updateCachedBadgeValue:(NSInteger)value usePreviousBadgeCount:(BOOL)usePrevious; @end diff --git a/iOS_SDK/OneSignalSDK/OneSignalCore/Source/OneSignalBadgeHelpers.m b/iOS_SDK/OneSignalSDK/OneSignalCore/Source/OneSignalBadgeHelpers.m index 06756b030..68eaada3d 100644 --- a/iOS_SDK/OneSignalSDK/OneSignalCore/Source/OneSignalBadgeHelpers.m +++ b/iOS_SDK/OneSignalSDK/OneSignalCore/Source/OneSignalBadgeHelpers.m @@ -30,9 +30,23 @@ #import "OneSignalCommonDefines.h" @implementation OneSignalBadgeHelpers -+ (void)updateCachedBadgeValue:(NSInteger)value { + +/** + Store a previous badge count so that we can revert to this cached value when a notification display is cancelled. + When `usePreviousBadgeCount` is `true`, the `value` passed to this method will be unused. + */ ++ (void)updateCachedBadgeValue:(NSInteger)value usePreviousBadgeCount:(BOOL)usePrevious { // Since badge logic can be executed in an extension, we need to use app groups to get // a shared NSUserDefaults from the app group suite name - [OneSignalUserDefaults.initShared saveIntegerForKey:ONESIGNAL_BADGE_KEY withValue:value]; + if (usePrevious) { + // Keep the PREVIOUS_ONESIGNAL_BADGE_KEY value unchanged + NSInteger previousBadgeCount = [OneSignalUserDefaults.initShared getSavedIntegerForKey:PREVIOUS_ONESIGNAL_BADGE_KEY defaultValue:0]; + [OneSignalUserDefaults.initShared saveIntegerForKey:ONESIGNAL_BADGE_KEY withValue:previousBadgeCount]; + } else { + NSInteger previousBadgeCount = [OneSignalUserDefaults.initShared getSavedIntegerForKey:ONESIGNAL_BADGE_KEY defaultValue:0]; + [OneSignalUserDefaults.initShared saveIntegerForKey:PREVIOUS_ONESIGNAL_BADGE_KEY withValue:previousBadgeCount]; + [OneSignalUserDefaults.initShared saveIntegerForKey:ONESIGNAL_BADGE_KEY withValue:value]; + } } + @end diff --git a/iOS_SDK/OneSignalSDK/OneSignalCore/Source/OneSignalCommonDefines.h b/iOS_SDK/OneSignalSDK/OneSignalCore/Source/OneSignalCommonDefines.h index bdcb97615..c6bf94c79 100644 --- a/iOS_SDK/OneSignalSDK/OneSignalCore/Source/OneSignalCommonDefines.h +++ b/iOS_SDK/OneSignalSDK/OneSignalCore/Source/OneSignalCommonDefines.h @@ -159,6 +159,8 @@ #define ONESIGNAL_DISABLE_BADGE_CLEARING @"OneSignal_disable_badge_clearing" #define ONESIGNAL_APP_GROUP_NAME_KEY @"OneSignal_app_groups_key" #define ONESIGNAL_BADGE_KEY @"onesignalBadgeCount" +/// Store the previous badge count to read for a cancelled notification display event +#define PREVIOUS_ONESIGNAL_BADGE_KEY @"previousOnesignalBadgeCount" // Firebase #define ONESIGNAL_FB_ENABLE_FIREBASE @"OS_ENABLE_FIREBASE_ANALYTICS" diff --git a/iOS_SDK/OneSignalSDK/OneSignalExtension/OneSignalExtensionBadgeHandler.m b/iOS_SDK/OneSignalSDK/OneSignalExtension/OneSignalExtensionBadgeHandler.m index c8e5605e7..cfe1134a2 100644 --- a/iOS_SDK/OneSignalSDK/OneSignalExtension/OneSignalExtensionBadgeHandler.m +++ b/iOS_SDK/OneSignalSDK/OneSignalExtension/OneSignalExtensionBadgeHandler.m @@ -35,7 +35,7 @@ + (void)handleBadgeCountWithNotificationRequest:(UNNotificationRequest *)request //make sure the OneSignal cached value is updated to this value if (!notification.badgeIncrement) { if (notification.hasBadge) - [OneSignalBadgeHelpers updateCachedBadgeValue:notification.badge]; + [OneSignalBadgeHelpers updateCachedBadgeValue:notification.badge usePreviousBadgeCount:false]; return; } @@ -50,7 +50,7 @@ + (void)handleBadgeCountWithNotificationRequest:(UNNotificationRequest *)request replacementContent.badge = @(currentValue); - [OneSignalBadgeHelpers updateCachedBadgeValue:currentValue]; + [OneSignalBadgeHelpers updateCachedBadgeValue:currentValue usePreviousBadgeCount:false]; } + (NSInteger)currentCachedBadgeValue { diff --git a/iOS_SDK/OneSignalSDK/OneSignalNotifications/Categories/UNUserNotificationCenter+OneSignalNotifications.m b/iOS_SDK/OneSignalSDK/OneSignalNotifications/Categories/UNUserNotificationCenter+OneSignalNotifications.m index 75a41150d..e093bb469 100644 --- a/iOS_SDK/OneSignalSDK/OneSignalNotifications/Categories/UNUserNotificationCenter+OneSignalNotifications.m +++ b/iOS_SDK/OneSignalSDK/OneSignalNotifications/Categories/UNUserNotificationCenter+OneSignalNotifications.m @@ -179,7 +179,7 @@ - (void)onesignalGetNotificationSettingsWithCompletionHandler:(void(^)(UNNotific necessary as there is no equivalent "getBadgeCount" method available. */ - (void)onesignalSetBadgeCount:(NSInteger)badge withCompletionHandler:(void(^)(NSError *error))completionHandler { - [OneSignalBadgeHelpers updateCachedBadgeValue:badge]; + [OneSignalBadgeHelpers updateCachedBadgeValue:badge usePreviousBadgeCount:false]; [self onesignalSetBadgeCount:badge withCompletionHandler:completionHandler]; } diff --git a/iOS_SDK/OneSignalSDK/OneSignalNotifications/OSNotification+OneSignal.m b/iOS_SDK/OneSignalSDK/OneSignalNotifications/OSNotification+OneSignal.m index 7f2356726..b2f9cefb1 100644 --- a/iOS_SDK/OneSignalSDK/OneSignalNotifications/OSNotification+OneSignal.m +++ b/iOS_SDK/OneSignalSDK/OneSignalNotifications/OSNotification+OneSignal.m @@ -76,8 +76,8 @@ - (void)complete:(OSDisplayableNotification *)notification { reset the badge count to the value prior to receipt of this notif */ if (!notification) { - NSInteger previousBadgeCount = [UIApplication sharedApplication].applicationIconBadgeNumber; - [OneSignalBadgeHelpers updateCachedBadgeValue:previousBadgeCount]; + // The badge count value of -99 will not be used, the previous badge count will be set + [OneSignalBadgeHelpers updateCachedBadgeValue:-99 usePreviousBadgeCount:true]; } if (_completion) { _completion(notification); diff --git a/iOS_SDK/OneSignalSDK/Source/OneSignal.m b/iOS_SDK/OneSignalSDK/Source/OneSignal.m index b9b2e447d..10e7157ce 100755 --- a/iOS_SDK/OneSignalSDK/Source/OneSignal.m +++ b/iOS_SDK/OneSignalSDK/Source/OneSignal.m @@ -809,7 +809,7 @@ + (void)load { We swizzle the 'setApplicationIconBadgeNumber()' to intercept these calls so we always know the latest count */ - (void)onesignalSetApplicationIconBadgeNumber:(NSInteger)badge { - [OneSignalBadgeHelpers updateCachedBadgeValue:badge]; + [OneSignalBadgeHelpers updateCachedBadgeValue:badge usePreviousBadgeCount:false]; [self onesignalSetApplicationIconBadgeNumber:badge]; }