Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions iOS_SDK/OneSignalSDK/OneSignal.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -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 */; };
Expand Down Expand Up @@ -1251,6 +1253,8 @@
3C2C7DC2288E007E0020F9AE /* UnitTests-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "UnitTests-Bridging-Header.h"; sourceTree = "<group>"; };
3C2C7DC7288F3C020020F9AE /* OSSubscriptionModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OSSubscriptionModel.swift; sourceTree = "<group>"; };
3C2D8A5828B4C4E300BE41F6 /* OSDelta.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OSDelta.swift; sourceTree = "<group>"; };
3C2DB2EF2DE6CB5E0006B905 /* OneSignalBadgeHelpers.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OneSignalBadgeHelpers.h; sourceTree = "<group>"; };
3C2DB2F02DE6CB5E0006B905 /* OneSignalBadgeHelpers.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OneSignalBadgeHelpers.m; sourceTree = "<group>"; };
3C448B9B2936ADFD002F96BC /* OSBackgroundTaskHandlerImpl.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OSBackgroundTaskHandlerImpl.h; sourceTree = "<group>"; };
3C448B9C2936ADFD002F96BC /* OSBackgroundTaskHandlerImpl.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OSBackgroundTaskHandlerImpl.m; sourceTree = "<group>"; };
3C448BA12936B474002F96BC /* OSBackgroundTaskManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OSBackgroundTaskManager.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -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 */,
Expand Down Expand Up @@ -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 */,
Expand Down Expand Up @@ -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 */,
Expand Down
32 changes: 32 additions & 0 deletions iOS_SDK/OneSignalSDK/OneSignalCore/Source/OneSignalBadgeHelpers.h
Original file line number Diff line number Diff line change
@@ -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 <Foundation/Foundation.h>

@interface OneSignalBadgeHelpers : NSObject
+ (void)updateCachedBadgeValue:(NSInteger)value usePreviousBadgeCount:(BOOL)usePrevious;
@end
52 changes: 52 additions & 0 deletions iOS_SDK/OneSignalSDK/OneSignalCore/Source/OneSignalBadgeHelpers.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/**
* 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

/**
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
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
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
1 change: 1 addition & 0 deletions iOS_SDK/OneSignalSDK/OneSignalCore/Source/OneSignalCore.h
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
#import <OneSignalCore/OSLocation.h>
#import <OneSignalCore/OSBundleUtils.h>
#import <OneSignalCore/OneSignalClientError.h>
#import <OneSignalCore/OneSignalBadgeHelpers.h>

// TODO: Testing: Should this class be defined in this file?
@interface OneSignalCoreImpl : NSObject
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,4 @@

@interface OneSignalExtensionBadgeHandler : NSObject
+ (void)handleBadgeCountWithNotificationRequest:(UNNotificationRequest *)request withNotification:(OSNotification *)notification withMutableNotificationContent:(UNMutableNotificationContent *)replacementContent;
+ (void)updateCachedBadgeValue:(NSInteger)value;
+ (NSInteger)currentCachedBadgeValue;
@end
Original file line number Diff line number Diff line change
Expand Up @@ -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 usePreviousBadgeCount:false];

return;
}
Expand All @@ -50,17 +50,11 @@ + (void)handleBadgeCountWithNotificationRequest:(UNNotificationRequest *)request

replacementContent.badge = @(currentValue);

[OneSignalExtensionBadgeHandler updateCachedBadgeValue:currentValue];
[OneSignalBadgeHelpers updateCachedBadgeValue:currentValue usePreviousBadgeCount:false];
}

+ (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
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,12 @@ + (void)swizzleSelectors {
[OneSignalNotificationsUNUserNotificationCenter class],
@selector(onesignalGetNotificationSettingsWithCompletionHandler:)
);
injectSelector(
[UNUserNotificationCenter class],
@selector(setBadgeCount:withCompletionHandler:),
[OneSignalNotificationsUNUserNotificationCenter class],
@selector(onesignalSetBadgeCount:withCompletionHandler:)
);
}

+ (void)registerDelegate {
Expand Down Expand Up @@ -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 usePreviousBadgeCount:false];
[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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
[OneSignalUserDefaults.initShared saveIntegerForKey:ONESIGNAL_BADGE_KEY withValue: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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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];

Expand All @@ -794,20 +794,23 @@ + (BOOL)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.
[OneSignalUserDefaults.initShared saveIntegerForKey:ONESIGNAL_BADGE_KEY withValue:[UIApplication sharedApplication].applicationIconBadgeNumber];
return false;
// 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];
}];
}

return wasBadgeSet;
}

+ (BOOL)handleIAMPreview:(OSNotification *)notification {
Expand Down
2 changes: 1 addition & 1 deletion iOS_SDK/OneSignalSDK/Source/OneSignal.m
Original file line number Diff line number Diff line change
Expand Up @@ -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 usePreviousBadgeCount:false];
[self onesignalSetApplicationIconBadgeNumber:badge];
}

Expand Down
Loading