Skip to content

Commit e4ca119

Browse files
committed
ios: add default chained notification delegate support
1 parent 6228fc2 commit e4ca119

File tree

3 files changed

+201
-1
lines changed

3 files changed

+201
-1
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
//
2+
// Copyright © Batch.com. All rights reserved.
3+
//
4+
5+
#import <Foundation/Foundation.h>
6+
#import <UserNotifications/UserNotifications.h>
7+
8+
NS_ASSUME_NONNULL_BEGIN
9+
10+
// Batch's bridge UNUserNotificationCenterDelegate
11+
// Handles:
12+
// - Forwarding calls to another delegate (chaining, rather than swizzling)
13+
// - Giving notification callbacks to Batch
14+
// - Enabling/Disabling foreground notifications
15+
@interface BatchBridgeNotificationCenterDelegate : NSObject <UNUserNotificationCenterDelegate>
16+
17+
/// Shared singleton BatchUNUserNotificationCenterDelegate.
18+
/// Using this allows you to set the instance as UNUserNotificationCenter's delegate without having to retain it yourself.
19+
/// The shared instance is lazily loaded.
20+
@property (class, retain, readonly, nonnull) BatchBridgeNotificationCenterDelegate* sharedInstance;
21+
22+
/// Registers this class' sharedInstance as UNUserNotificationCenter's delegate, and stores the previous one as a property
23+
+ (void)registerAsDelegate;
24+
25+
/// Should iOS display notifications even if the app is in foreground?
26+
/// Default: true
27+
@property (assign) BOOL showForegroundNotifications;
28+
29+
/// Should Batch use the chained delegate's completionHandler responses or force its own, while still calling the chained delegate.
30+
/// This is useful if you want Batch to enforce its "showForegroundNotifications" setting while still informing the chained delegate.
31+
/// Default: true, but the plugin will automatically set that to false when calling "setShowForegroundNotification" from JavaScript.
32+
@property (assign) BOOL shouldUseChainedCompletionHandlerResponse;
33+
34+
/// Previous delegate
35+
@property (weak, nullable) id<UNUserNotificationCenterDelegate> previousDelegate;
36+
37+
/// Should this class automatically register itself as UNUserNotificationCenterDelegate when the app is launched? Default: true
38+
/// This value needs to be changed before `[RNBatch start]` be called.
39+
@property (class, assign) BOOL automaticallyRegister;
40+
41+
@end
42+
43+
NS_ASSUME_NONNULL_END
+151
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
//
2+
// Copyright © Batch.com. All rights reserved.
3+
//
4+
5+
#import "BatchBridgeNotificationCenterDelegate.h"
6+
7+
#import <Batch/BatchPush.h>
8+
9+
@implementation BatchBridgeNotificationCenterDelegate
10+
{
11+
__weak __nullable id<UNUserNotificationCenterDelegate> _previousDelegate;
12+
}
13+
14+
static BOOL _batBridgeNotifDelegateShouldAutomaticallyRegister = true;
15+
16+
17+
+ (BatchBridgeNotificationCenterDelegate *)sharedInstance
18+
{
19+
static BatchBridgeNotificationCenterDelegate *sharedInstance = nil;
20+
static dispatch_once_t onceToken;
21+
dispatch_once(&onceToken, ^{
22+
sharedInstance = [[BatchBridgeNotificationCenterDelegate alloc] init];
23+
});
24+
25+
return sharedInstance;
26+
}
27+
28+
+ (void)registerAsDelegate
29+
{
30+
UNUserNotificationCenter *notifCenter = [UNUserNotificationCenter currentNotificationCenter];
31+
BatchBridgeNotificationCenterDelegate *instance = [self sharedInstance];
32+
instance.previousDelegate = notifCenter.delegate;
33+
notifCenter.delegate = instance;
34+
}
35+
36+
+ (BOOL)automaticallyRegister
37+
{
38+
return _batBridgeNotifDelegateShouldAutomaticallyRegister;
39+
}
40+
41+
+ (void)setAutomaticallyRegister:(BOOL)automaticallyRegister
42+
{
43+
_batBridgeNotifDelegateShouldAutomaticallyRegister = automaticallyRegister;
44+
}
45+
46+
- (nullable id<UNUserNotificationCenterDelegate>)previousDelegate
47+
{
48+
return _previousDelegate;
49+
}
50+
51+
- (void)setPreviousDelegate:(nullable id<UNUserNotificationCenterDelegate>)delegate
52+
{
53+
// Do not register ourserlves as previous delegate to avoid
54+
// an infinite loop
55+
if (delegate == self || [delegate isKindOfClass:[self class]]) {
56+
_previousDelegate = nil;
57+
} else {
58+
_previousDelegate = delegate;
59+
}
60+
}
61+
62+
- (instancetype)init
63+
{
64+
self = [super init];
65+
if (self) {
66+
_showForegroundNotifications = true;
67+
_shouldUseChainedCompletionHandlerResponse = true;
68+
}
69+
return self;
70+
}
71+
72+
- (void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler
73+
{
74+
[BatchPush handleUserNotificationCenter:center willPresentNotification:notification willShowSystemForegroundAlert:self.showForegroundNotifications];
75+
76+
id<UNUserNotificationCenterDelegate> chainDelegate = self.previousDelegate;
77+
// It's the chain delegate's responsibility to call the completionHandler
78+
if ([chainDelegate respondsToSelector:@selector(userNotificationCenter:willPresentNotification:withCompletionHandler:)]) {
79+
//returnType (^blockName)(parameterTypes) = ^returnType(parameters) {...};
80+
void (^chainCompletionHandler)(UNNotificationPresentationOptions);
81+
82+
if (self.shouldUseChainedCompletionHandlerResponse) {
83+
// Set iOS' completion handler as the one we give to the method, as we don't want to override the result
84+
chainCompletionHandler = completionHandler;
85+
} else {
86+
// Set ourselves as the chained completion handler so we can wait for the implementation but rewrite the response
87+
chainCompletionHandler = ^(UNNotificationPresentationOptions ignored) {
88+
[self performPresentCompletionHandler:completionHandler];
89+
};
90+
}
91+
92+
[chainDelegate userNotificationCenter:center
93+
willPresentNotification:notification
94+
withCompletionHandler:chainCompletionHandler];
95+
} else {
96+
[self performPresentCompletionHandler:completionHandler];
97+
}
98+
}
99+
100+
- (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)(void))completionHandler
101+
{
102+
[BatchPush handleUserNotificationCenter:center didReceiveNotificationResponse:response];
103+
104+
id<UNUserNotificationCenterDelegate> chainDelegate = self.previousDelegate;
105+
// It's the chain delegate's responsibility to call the completionHandler
106+
if ([chainDelegate respondsToSelector:@selector(userNotificationCenter:didReceiveNotificationResponse:withCompletionHandler:)]) {
107+
[chainDelegate userNotificationCenter:center
108+
didReceiveNotificationResponse:response
109+
withCompletionHandler:completionHandler];
110+
} else {
111+
if (completionHandler) {
112+
completionHandler();
113+
}
114+
}
115+
116+
}
117+
118+
- (void)userNotificationCenter:(UNUserNotificationCenter *)center openSettingsForNotification:(UNNotification *)notification
119+
{
120+
if (@available(iOS 12.0, *)) {
121+
id<UNUserNotificationCenterDelegate> chainDelegate = self.previousDelegate;
122+
if ([chainDelegate respondsToSelector:@selector(userNotificationCenter:openSettingsForNotification:)]) {
123+
[self.previousDelegate userNotificationCenter:center
124+
openSettingsForNotification:notification];
125+
}
126+
}
127+
}
128+
129+
/// Call iOS back on the "present" completion handler with Batch controlled presentation options
130+
- (void)performPresentCompletionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler {
131+
UNNotificationPresentationOptions options = UNNotificationPresentationOptionNone;
132+
if (self.showForegroundNotifications) {
133+
options = UNNotificationPresentationOptionBadge | UNNotificationPresentationOptionSound;
134+
135+
#ifdef __IPHONE_14_0
136+
if (@available(iOS 14.0, *)) {
137+
options = options | UNNotificationPresentationOptionList | UNNotificationPresentationOptionBanner;
138+
} else {
139+
options = options | UNNotificationPresentationOptionAlert;
140+
}
141+
#else
142+
options = options | UNNotificationPresentationOptionAlert;
143+
#endif
144+
}
145+
146+
if (completionHandler) {
147+
completionHandler(options);
148+
};
149+
}
150+
151+
@end

ios/RNBatch.m

+7-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
# import "RNBatch.h"
33
# import "RNBatchOpenedNotificationObserver.h"
44
# import "RNBatchEventDispatcher.h"
5+
# import "BatchBridgeNotificationCenterDelegate.h"
56

67
static RNBatchEventDispatcher* dispatcher = nil;
78

@@ -63,6 +64,9 @@ + (void)start
6364

6465
NSString *batchAPIKey = [info objectForKey:@"BatchAPIKey"];
6566
[BatchSDK startWithAPIKey:batchAPIKey];
67+
if (BatchBridgeNotificationCenterDelegate.automaticallyRegister) {
68+
[BatchBridgeNotificationCenterDelegate registerAsDelegate];
69+
}
6670
dispatcher = [[RNBatchEventDispatcher alloc] init];
6771
[BatchEventDispatcher addDispatcher:dispatcher];
6872
}
@@ -166,7 +170,9 @@ -(void)stopObserving {
166170

167171
RCT_EXPORT_METHOD(push_setShowForegroundNotification:(BOOL) enabled)
168172
{
169-
[BatchUNUserNotificationCenterDelegate sharedInstance].showForegroundNotifications = enabled;
173+
BatchBridgeNotificationCenterDelegate *delegate = [BatchBridgeNotificationCenterDelegate sharedInstance];
174+
delegate.showForegroundNotifications = enabled;
175+
delegate.shouldUseChainedCompletionHandlerResponse = false;
170176
}
171177

172178
RCT_EXPORT_METHOD(push_clearBadge)

0 commit comments

Comments
 (0)