Skip to content

Commit f1489cc

Browse files
authored
Merge pull request #1451 from OneSignal/fix/clearing_notifs_when_swiping_notif_center
Fix clearing notifications when the application becomes active instead of when it enters foreground
2 parents f0552dd + bb60c99 commit f1489cc

File tree

12 files changed

+560
-19
lines changed

12 files changed

+560
-19
lines changed

iOS_SDK/OneSignalSDK/OneSignal.xcodeproj/project.pbxproj

Lines changed: 288 additions & 1 deletion
Large diffs are not rendered by default.

iOS_SDK/OneSignalSDK/OneSignal.xcodeproj/xcshareddata/xcschemes/UnitTestApp.xcscheme

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,20 @@
4848
ReferencedContainer = "container:OneSignal.xcodeproj">
4949
</BuildableReference>
5050
</BuildActionEntry>
51+
<BuildActionEntry
52+
buildForTesting = "YES"
53+
buildForRunning = "NO"
54+
buildForProfiling = "NO"
55+
buildForArchiving = "NO"
56+
buildForAnalyzing = "NO">
57+
<BuildableReference
58+
BuildableIdentifier = "primary"
59+
BlueprintIdentifier = "DEBA2A192C20E35E00E234DB"
60+
BuildableName = "OneSignalNotificationsTests.xctest"
61+
BlueprintName = "OneSignalNotificationsTests"
62+
ReferencedContainer = "container:OneSignal.xcodeproj">
63+
</BuildableReference>
64+
</BuildActionEntry>
5165
</BuildActionEntries>
5266
</BuildAction>
5367
<TestAction
@@ -121,6 +135,13 @@
121135
BlueprintName = "OneSignalCoreTests"
122136
ReferencedContainer = "container:OneSignal.xcodeproj">
123137
</BuildableReference>
138+
<BuildableReference
139+
BuildableIdentifier = "primary"
140+
BlueprintIdentifier = "DEBA2A192C20E35E00E234DB"
141+
BuildableName = "OneSignalNotificationsTests.xctest"
142+
BlueprintName = "OneSignalNotificationsTests"
143+
ReferencedContainer = "container:OneSignal.xcodeproj">
144+
</BuildableReference>
124145
</CodeCoverageTargets>
125146
<Testables>
126147
<TestableReference
@@ -163,6 +184,16 @@
163184
ReferencedContainer = "container:OneSignal.xcodeproj">
164185
</BuildableReference>
165186
</TestableReference>
187+
<TestableReference
188+
skipped = "NO">
189+
<BuildableReference
190+
BuildableIdentifier = "primary"
191+
BlueprintIdentifier = "DEBA2A192C20E35E00E234DB"
192+
BuildableName = "OneSignalNotificationsTests.xctest"
193+
BlueprintName = "OneSignalNotificationsTests"
194+
ReferencedContainer = "container:OneSignal.xcodeproj">
195+
</BuildableReference>
196+
</TestableReference>
166197
</Testables>
167198
</TestAction>
168199
<LaunchAction
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/*
2+
Modified MIT License
3+
Copyright 2024 OneSignal
4+
Permission is hereby granted, free of charge, to any person obtaining a copy
5+
of this software and associated documentation files (the "Software"), to deal
6+
in the Software without restriction, including without limitation the rights
7+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8+
copies of the Software, and to permit persons to whom the Software is
9+
furnished to do so, subject to the following conditions:
10+
1. The above copyright notice and this permission notice shall be included in
11+
all copies or substantial portions of the Software.
12+
2. All copies of substantial portions of the Software may only be used in connection
13+
with services provided by OneSignal.
14+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20+
THE SOFTWARE.
21+
*/
22+
23+
@interface OSBundleUtils : NSObject
24+
+ (BOOL)isAppUsingUIScene;
25+
@end
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*
2+
Modified MIT License
3+
Copyright 2024 OneSignal
4+
Permission is hereby granted, free of charge, to any person obtaining a copy
5+
of this software and associated documentation files (the "Software"), to deal
6+
in the Software without restriction, including without limitation the rights
7+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8+
copies of the Software, and to permit persons to whom the Software is
9+
furnished to do so, subject to the following conditions:
10+
1. The above copyright notice and this permission notice shall be included in
11+
all copies or substantial portions of the Software.
12+
2. All copies of substantial portions of the Software may only be used in connection
13+
with services provided by OneSignal.
14+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20+
THE SOFTWARE.
21+
*/
22+
23+
#import <Foundation/Foundation.h>
24+
#import "OSBundleUtils.h"
25+
@implementation OSBundleUtils
26+
27+
+ (BOOL)isAppUsingUIScene {
28+
if (@available(iOS 13.0, *)) {
29+
return [[NSBundle mainBundle] objectForInfoDictionaryKey:@"UIApplicationSceneManifest"] != nil;
30+
}
31+
return NO;
32+
}
33+
34+
@end

iOS_SDK/OneSignalSDK/OneSignalCore/Source/OneSignalCore.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@
5656
#import <OneSignalCore/OneSignalWrapper.h>
5757
#import <OneSignalCore/OSInAppMessages.h>
5858
#import <OneSignalCore/OSLocation.h>
59-
59+
#import <OneSignalCore/OSBundleUtils.h>
6060
// TODO: Testing: Should this class be defined in this file?
6161
@interface OneSignalCoreImpl : NSObject
6262

iOS_SDK/OneSignalSDK/OneSignalCoreMocks/OneSignalCoreMocks.swift

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,4 +51,48 @@ public class OneSignalCoreMocks: NSObject {
5151
let expectation = XCTestExpectation(description: "Wait for \(seconds) seconds")
5252
_ = XCTWaiter.wait(for: [expectation], timeout: seconds)
5353
}
54+
55+
@objc public static func backgroundApp() {
56+
if (OSBundleUtils.isAppUsingUIScene()) {
57+
if #available(iOS 13.0, *) {
58+
NotificationCenter.default.post(name: UIScene.willDeactivateNotification, object: nil)
59+
NotificationCenter.default.post(name: UIScene.didEnterBackgroundNotification, object: nil)
60+
}
61+
} else {
62+
NotificationCenter.default.post(name: UIApplication.willResignActiveNotification, object: nil)
63+
NotificationCenter.default.post(name: UIApplication.didEnterBackgroundNotification, object: nil)
64+
}
65+
}
66+
67+
@objc public static func foregroundApp() {
68+
if (OSBundleUtils.isAppUsingUIScene()) {
69+
if #available(iOS 13.0, *) {
70+
NotificationCenter.default.post(name: UIScene.willEnterForegroundNotification, object: nil)
71+
NotificationCenter.default.post(name: UIScene.didActivateNotification, object: nil)
72+
}
73+
} else {
74+
NotificationCenter.default.post(name: UIApplication.willEnterForegroundNotification, object: nil)
75+
NotificationCenter.default.post(name: UIApplication.didBecomeActiveNotification, object: nil)
76+
}
77+
}
78+
79+
@objc public static func resignActive() {
80+
if (OSBundleUtils.isAppUsingUIScene()) {
81+
if #available(iOS 13.0, *) {
82+
NotificationCenter.default.post(name: UIScene.willDeactivateNotification, object: nil)
83+
}
84+
} else {
85+
NotificationCenter.default.post(name: UIApplication.willResignActiveNotification, object: nil)
86+
}
87+
}
88+
89+
@objc public static func becomeActive() {
90+
if (OSBundleUtils.isAppUsingUIScene()) {
91+
if #available(iOS 13.0, *) {
92+
NotificationCenter.default.post(name: UIScene.didActivateNotification, object: nil)
93+
}
94+
} else {
95+
NotificationCenter.default.post(name: UIApplication.didBecomeActiveNotification, object: nil)
96+
}
97+
}
5498
}

iOS_SDK/OneSignalSDK/OneSignalNotifications/OSNotificationsManager.m

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,9 +251,38 @@ + (void)start {
251251
@selector(onesignalSetApplicationIconBadgeNumber:)
252252
);
253253
[OneSignalNotificationsUNUserNotificationCenter setup];
254+
255+
[self registerLifecycleObserver];
256+
254257
}
255258
#pragma clang diagnostic pop
256259

260+
+ (void)registerLifecycleObserver {
261+
// Replacing swizzled lifecycle selectors with notification center observers for scene based Apps
262+
if ([OSBundleUtils isAppUsingUIScene]) {
263+
[self registerLifecycleObserverAsUIScene];
264+
} else {
265+
[self registerLifecycleObserverAsUIApplication];
266+
}
267+
}
268+
269+
+ (void)registerLifecycleObserverAsUIScene {
270+
if (@available(iOS 13.0, *)) {
271+
[OneSignalLog onesignalLog:ONE_S_LL_VERBOSE message:@"OSNotificationManager registering for Scene Lifecycle notifications"];
272+
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(willEnterForeground) name:@"UISceneWillEnterForegroundNotification" object:nil];
273+
}
274+
}
275+
276+
+ (void)registerLifecycleObserverAsUIApplication {
277+
[OneSignalLog onesignalLog:ONE_S_LL_VERBOSE message:@"OSNotificationManager registering for Application Lifecycle notifications"];
278+
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(willEnterForeground) name:UIApplicationWillEnterForegroundNotification object:nil];
279+
}
280+
281+
+ (void)willEnterForeground {
282+
[OSNotificationsManager clearBadgeCount:false fromClearAll:false];
283+
[OSNotificationsManager sendNotificationTypesUpdateToDelegate];
284+
}
285+
257286
+ (void)resetLocals {
258287
_lastMessageReceived = nil;
259288
_lastMessageIdFromAction = nil;
@@ -967,6 +996,10 @@ + (UNNotificationRequest*)prepareUNNotificationRequest:(OSNotification*)notifica
967996
return [UNNotificationRequest requestWithIdentifier:identifier content:content trigger:trigger];
968997
}
969998

999+
- (void)dealloc {
1000+
[OneSignalLog onesignalLog:ONE_S_LL_VERBOSE message:@"OSNotificationsManager observer deallocated"];
1001+
[[NSNotificationCenter defaultCenter] removeObserver:self];
1002+
}
9701003

9711004
@end
9721005

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
/*
2+
Modified MIT License
3+
Copyright 2024 OneSignal
4+
Permission is hereby granted, free of charge, to any person obtaining a copy
5+
of this software and associated documentation files (the "Software"), to deal
6+
in the Software without restriction, including without limitation the rights
7+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8+
copies of the Software, and to permit persons to whom the Software is
9+
furnished to do so, subject to the following conditions:
10+
1. The above copyright notice and this permission notice shall be included in
11+
all copies or substantial portions of the Software.
12+
2. All copies of substantial portions of the Software may only be used in connection
13+
with services provided by OneSignal.
14+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20+
THE SOFTWARE.
21+
*/
22+
23+
import XCTest
24+
import OneSignalNotifications
25+
import OneSignalCoreMocks
26+
import UIKit
27+
28+
final class OneSignalNotificationsTests: XCTestCase {
29+
30+
var notifTypes: Int32 = 0
31+
var token: String = ""
32+
33+
override func setUpWithError() throws {
34+
// Put setup code here. This method is called before the invocation of each test method in the class.
35+
self.notifTypes = 0
36+
self.token = ""
37+
}
38+
39+
override func tearDownWithError() throws {
40+
// Put teardown code here. This method is called after the invocation of each test method in the class.
41+
}
42+
43+
func testClearBadgesWhenAppEntersForeground() throws {
44+
// NotificationManager Start to register lifecycle listener
45+
OSNotificationsManager.start()
46+
// Set badge count > 0
47+
UIApplication.shared.applicationIconBadgeNumber = 1
48+
// Then background the app
49+
OneSignalCoreMocks.backgroundApp()
50+
// Foreground the app
51+
OneSignalCoreMocks.foregroundApp()
52+
// Ensure that badge count == 0
53+
XCTAssertEqual(UIApplication.shared.applicationIconBadgeNumber, 0)
54+
}
55+
56+
func testDontclearBadgesWhenAppBecomesActive() throws {
57+
// NotificationManager Start to register lifecycle listener
58+
OSNotificationsManager.start()
59+
// Set badge count > 0
60+
UIApplication.shared.applicationIconBadgeNumber = 1
61+
// Then resign active
62+
OneSignalCoreMocks.resignActive()
63+
// App becomes active the app
64+
OneSignalCoreMocks.becomeActive()
65+
// Ensure that badge count == 1
66+
XCTAssertEqual(UIApplication.shared.applicationIconBadgeNumber, 1)
67+
}
68+
69+
func testUpdateNotificationTypesOnAppEntersForeground() throws {
70+
// NotificationManager Start to register lifecycle listener
71+
OSNotificationsManager.start()
72+
73+
OSNotificationsManager.delegate = self
74+
75+
XCTAssertEqual(self.notifTypes, 0)
76+
77+
// Then background the app
78+
OneSignalCoreMocks.backgroundApp()
79+
80+
// Foreground the app for within 30 seconds
81+
OneSignalCoreMocks.foregroundApp()
82+
83+
// Ensure that the delegate is updated with the new notification type
84+
XCTAssertEqual(self.notifTypes, ERROR_PUSH_NEVER_PROMPTED)
85+
}
86+
87+
88+
}
89+
90+
extension OneSignalNotificationsTests: OneSignalNotificationsDelegate {
91+
public func setNotificationTypes(_ notificationTypes: Int32) {
92+
self.notifTypes = notificationTypes
93+
}
94+
95+
public func setPushToken(_ pushToken: String) {
96+
self.token = pushToken
97+
}
98+
}

iOS_SDK/OneSignalSDK/Source/OneSignalLifecycleObserver.m

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ +(OneSignalLifecycleObserver*) sharedInstance {
4949

5050
+ (void)registerLifecycleObserver {
5151
// Replacing swizzled lifecycle selectors with notification center observers for scene based Apps
52-
if ([UIApplication isAppUsingUIScene]) {
52+
if ([OSBundleUtils isAppUsingUIScene]) {
5353
[self registerLifecycleObserverAsUIScene];
5454
} else {
5555
[self registerLifecycleObserverAsUIApplication];

iOS_SDK/OneSignalSDK/Source/OneSignalTracker.m

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -77,12 +77,12 @@ + (void)onFocus:(BOOL)toBackground {
7777
if (toBackground) {
7878
[self applicationBackgrounded];
7979
} else {
80-
[self applicationForegrounded];
80+
[self applicationBecameActive];
8181
}
8282
}
8383

84-
+ (void)applicationForegrounded {
85-
[OneSignalLog onesignalLog:ONE_S_LL_DEBUG message:@"Application Foregrounded started"];
84+
+ (void)applicationBecameActive {
85+
[OneSignalLog onesignalLog:ONE_S_LL_DEBUG message:@"Application Active started"];
8686
[OSFocusTimeProcessorFactory cancelFocusCall];
8787

8888
if (OSSessionManager.sharedSessionManager.appEntryState != NOTIFICATION_CLICK)
@@ -94,14 +94,10 @@ + (void)applicationForegrounded {
9494
if ([OneSignal shouldStartNewSession])
9595
[OneSignal startNewSession:NO];
9696
else {
97-
// This checks if notification permissions changed when app was backgrounded
98-
[OSNotificationsManager sendNotificationTypesUpdateToDelegate];
9997
[[OSSessionManager sharedSessionManager] attemptSessionUpgrade];
10098
// TODO: Here it used to call receivedInAppMessageJson with nil, this method no longer exists
10199
// [OneSignal receivedInAppMessageJson:nil];
102100
}
103-
104-
[OSNotificationsManager clearBadgeCount:false fromClearAll:false];
105101
}
106102

107103
+ (void)applicationBackgrounded {

0 commit comments

Comments
 (0)