Skip to content

Commit

Permalink
ios: add default chained notification delegate support
Browse files Browse the repository at this point in the history
  • Loading branch information
arnaud-roland committed May 30, 2024
1 parent 6228fc2 commit e4ca119
Show file tree
Hide file tree
Showing 3 changed files with 201 additions and 1 deletion.
43 changes: 43 additions & 0 deletions ios/BatchBridgeNotificationCenterDelegate.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
//
// Copyright © Batch.com. All rights reserved.
//

#import <Foundation/Foundation.h>
#import <UserNotifications/UserNotifications.h>

NS_ASSUME_NONNULL_BEGIN

// Batch's bridge UNUserNotificationCenterDelegate
// Handles:
// - Forwarding calls to another delegate (chaining, rather than swizzling)
// - Giving notification callbacks to Batch
// - Enabling/Disabling foreground notifications
@interface BatchBridgeNotificationCenterDelegate : NSObject <UNUserNotificationCenterDelegate>

/// Shared singleton BatchUNUserNotificationCenterDelegate.
/// Using this allows you to set the instance as UNUserNotificationCenter's delegate without having to retain it yourself.
/// The shared instance is lazily loaded.
@property (class, retain, readonly, nonnull) BatchBridgeNotificationCenterDelegate* sharedInstance;

/// Registers this class' sharedInstance as UNUserNotificationCenter's delegate, and stores the previous one as a property
+ (void)registerAsDelegate;

/// Should iOS display notifications even if the app is in foreground?
/// Default: true
@property (assign) BOOL showForegroundNotifications;

/// Should Batch use the chained delegate's completionHandler responses or force its own, while still calling the chained delegate.
/// This is useful if you want Batch to enforce its "showForegroundNotifications" setting while still informing the chained delegate.
/// Default: true, but the plugin will automatically set that to false when calling "setShowForegroundNotification" from JavaScript.
@property (assign) BOOL shouldUseChainedCompletionHandlerResponse;

/// Previous delegate
@property (weak, nullable) id<UNUserNotificationCenterDelegate> previousDelegate;

/// Should this class automatically register itself as UNUserNotificationCenterDelegate when the app is launched? Default: true
/// This value needs to be changed before `[RNBatch start]` be called.
@property (class, assign) BOOL automaticallyRegister;

@end

NS_ASSUME_NONNULL_END
151 changes: 151 additions & 0 deletions ios/BatchBridgeNotificationCenterDelegate.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
//
// Copyright © Batch.com. All rights reserved.
//

#import "BatchBridgeNotificationCenterDelegate.h"

#import <Batch/BatchPush.h>

@implementation BatchBridgeNotificationCenterDelegate
{
__weak __nullable id<UNUserNotificationCenterDelegate> _previousDelegate;
}

static BOOL _batBridgeNotifDelegateShouldAutomaticallyRegister = true;


+ (BatchBridgeNotificationCenterDelegate *)sharedInstance
{
static BatchBridgeNotificationCenterDelegate *sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[BatchBridgeNotificationCenterDelegate alloc] init];
});

return sharedInstance;
}

+ (void)registerAsDelegate
{
UNUserNotificationCenter *notifCenter = [UNUserNotificationCenter currentNotificationCenter];
BatchBridgeNotificationCenterDelegate *instance = [self sharedInstance];
instance.previousDelegate = notifCenter.delegate;
notifCenter.delegate = instance;
}

+ (BOOL)automaticallyRegister
{
return _batBridgeNotifDelegateShouldAutomaticallyRegister;
}

+ (void)setAutomaticallyRegister:(BOOL)automaticallyRegister
{
_batBridgeNotifDelegateShouldAutomaticallyRegister = automaticallyRegister;
}

- (nullable id<UNUserNotificationCenterDelegate>)previousDelegate
{
return _previousDelegate;
}

- (void)setPreviousDelegate:(nullable id<UNUserNotificationCenterDelegate>)delegate
{
// Do not register ourserlves as previous delegate to avoid
// an infinite loop
if (delegate == self || [delegate isKindOfClass:[self class]]) {
_previousDelegate = nil;
} else {
_previousDelegate = delegate;
}
}

- (instancetype)init
{
self = [super init];
if (self) {
_showForegroundNotifications = true;
_shouldUseChainedCompletionHandlerResponse = true;
}
return self;
}

- (void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler
{
[BatchPush handleUserNotificationCenter:center willPresentNotification:notification willShowSystemForegroundAlert:self.showForegroundNotifications];

id<UNUserNotificationCenterDelegate> chainDelegate = self.previousDelegate;
// It's the chain delegate's responsibility to call the completionHandler
if ([chainDelegate respondsToSelector:@selector(userNotificationCenter:willPresentNotification:withCompletionHandler:)]) {
//returnType (^blockName)(parameterTypes) = ^returnType(parameters) {...};
void (^chainCompletionHandler)(UNNotificationPresentationOptions);

if (self.shouldUseChainedCompletionHandlerResponse) {
// Set iOS' completion handler as the one we give to the method, as we don't want to override the result
chainCompletionHandler = completionHandler;
} else {
// Set ourselves as the chained completion handler so we can wait for the implementation but rewrite the response
chainCompletionHandler = ^(UNNotificationPresentationOptions ignored) {
[self performPresentCompletionHandler:completionHandler];
};
}

[chainDelegate userNotificationCenter:center
willPresentNotification:notification
withCompletionHandler:chainCompletionHandler];
} else {
[self performPresentCompletionHandler:completionHandler];
}
}

- (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)(void))completionHandler
{
[BatchPush handleUserNotificationCenter:center didReceiveNotificationResponse:response];

id<UNUserNotificationCenterDelegate> chainDelegate = self.previousDelegate;
// It's the chain delegate's responsibility to call the completionHandler
if ([chainDelegate respondsToSelector:@selector(userNotificationCenter:didReceiveNotificationResponse:withCompletionHandler:)]) {
[chainDelegate userNotificationCenter:center
didReceiveNotificationResponse:response
withCompletionHandler:completionHandler];
} else {
if (completionHandler) {
completionHandler();
}
}

}

- (void)userNotificationCenter:(UNUserNotificationCenter *)center openSettingsForNotification:(UNNotification *)notification
{
if (@available(iOS 12.0, *)) {
id<UNUserNotificationCenterDelegate> chainDelegate = self.previousDelegate;
if ([chainDelegate respondsToSelector:@selector(userNotificationCenter:openSettingsForNotification:)]) {
[self.previousDelegate userNotificationCenter:center
openSettingsForNotification:notification];
}
}
}

/// Call iOS back on the "present" completion handler with Batch controlled presentation options
- (void)performPresentCompletionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler {
UNNotificationPresentationOptions options = UNNotificationPresentationOptionNone;
if (self.showForegroundNotifications) {
options = UNNotificationPresentationOptionBadge | UNNotificationPresentationOptionSound;

#ifdef __IPHONE_14_0
if (@available(iOS 14.0, *)) {
options = options | UNNotificationPresentationOptionList | UNNotificationPresentationOptionBanner;
} else {
options = options | UNNotificationPresentationOptionAlert;
}
#else
options = options | UNNotificationPresentationOptionAlert;
#endif
}

if (completionHandler) {
completionHandler(options);
};
}

@end
8 changes: 7 additions & 1 deletion ios/RNBatch.m
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
# import "RNBatch.h"
# import "RNBatchOpenedNotificationObserver.h"
# import "RNBatchEventDispatcher.h"
# import "BatchBridgeNotificationCenterDelegate.h"

static RNBatchEventDispatcher* dispatcher = nil;

Expand Down Expand Up @@ -63,6 +64,9 @@ + (void)start

NSString *batchAPIKey = [info objectForKey:@"BatchAPIKey"];
[BatchSDK startWithAPIKey:batchAPIKey];
if (BatchBridgeNotificationCenterDelegate.automaticallyRegister) {
[BatchBridgeNotificationCenterDelegate registerAsDelegate];
}
dispatcher = [[RNBatchEventDispatcher alloc] init];
[BatchEventDispatcher addDispatcher:dispatcher];
}
Expand Down Expand Up @@ -166,7 +170,9 @@ -(void)stopObserving {

RCT_EXPORT_METHOD(push_setShowForegroundNotification:(BOOL) enabled)
{
[BatchUNUserNotificationCenterDelegate sharedInstance].showForegroundNotifications = enabled;
BatchBridgeNotificationCenterDelegate *delegate = [BatchBridgeNotificationCenterDelegate sharedInstance];
delegate.showForegroundNotifications = enabled;
delegate.shouldUseChainedCompletionHandlerResponse = false;
}

RCT_EXPORT_METHOD(push_clearBadge)
Expand Down

0 comments on commit e4ca119

Please sign in to comment.