CleverTap / CTNotificationContent

A Notification Content Extension class to display custom content interfaces for iOS 10 push notifications
MIT License
14 stars 7 forks source link

Initial Integration: Carousel Images Not Showing #37

Open maurusrv opened 9 months ago

maurusrv commented 9 months ago

After following the steps indicated in the docs and in the repos, Single Message is working with the image, but the default carousel has issues. Specifically, the notification shows up, the options for Back, Next, View in App shows up after tap and hold, but the images does not show. And clicking either of the actions only suspends the notification and goes to the url we are using for redirection.

image

Below are the details about the issue we encountered when integrating CleverTap's Rich Push Notifications Feature.

Thank you.

Observations

  1. Aside from the notification being blank, when clicking back, next or view app, it just suspends notification.
  2. We haven't used the custom key-value pairs, but the question is, is it necessary?
  3. What exactly is the needed change on MainInterface.storyboard?
  4. only willPresentNotification & didRegisterForRemoteNotificationsWithDeviceToken is being called during testing.
  5. There's a bit recent issue in this repo that is somewhat similar. But even if we made sure that the test image is jpg and less than 40KB, it still does not show.
  6. We are testing this in a test CT project.

Configuration

Package Version
Test Devices iOS Version 17.2.1 (iPhone 14), 15.8 (iPhone 6s)
Minimum Target Deployment 11.0
XCode 15.2
CleverTap-iOS-SDK 5.2.1
clevertap-react-native 1.2.1
CTNotificationContent 0.2.5
CTNotificationService 0.1.5

Pod File

...
platform :ios, '11.0'

pod "CleverTap-iOS-SDK", '5.2.1'
...
target 'NotificationService' do
  pod 'CTNotificationService', :modular_headers => true  
end

target 'NotificationContent' do
  pod "CTNotificationContent"
end

NotificationService

NofificationService.h

#import <CTNotificationService/CTNotificationService.h>

@interface NotificationService : CTNotificationServiceExtension

@end

NotificationService.m

#import "NotificationService.h"
#import "CleverTap.h"

@interface NotificationService ()

@property (nonatomic, strong) void (^contentHandler)(UNNotificationContent *contentToDeliver);
@property (nonatomic, strong) UNMutableNotificationContent *bestAttemptContent;

@end

@implementation NotificationService

- (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent * _Nonnull))contentHandler {
    self.contentHandler = contentHandler;
    self.bestAttemptContent = [request.content mutableCopy];

    // Modify the notification content here...
    self.bestAttemptContent.title = [NSString stringWithFormat:@"%@ [modified]", self.bestAttemptContent.title];
//    
   self.contentHandler(self.bestAttemptContent);
    #if DEBUG
        NSLog(@"AppDelegate: didReceiveRemoteNotificationRequest!");
    #endif

    [[CleverTap sharedInstance] recordNotificationViewedEventWithData:request.content.userInfo];
    [super didReceiveNotificationRequest:request withContentHandler:contentHandler];
}

- (void)serviceExtensionTimeWillExpire {
    // Called just before the extension will be terminated by the system.
    // Use this as an opportunity to deliver your "best attempt" at modified content, otherwise the original push payload will be used.
    self.contentHandler(self.bestAttemptContent);
}

@end

Info.plist

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>NSExtension</key>
    <dict>
        <key>NSExtensionPointIdentifier</key>
        <string>com.apple.usernotifications.service</string>
        <key>NSExtensionPrincipalClass</key>
        <string>CTNotificationServiceExtension</string>
    </dict>
    <key>CleverTapAccountID</key>
    <string>$(CT_ACCOUNT_ID)</string>
    <key>CleverTapRegion</key>
    <string>sg1</string>
    <key>CleverTapToken</key>
    <string>$(CT_TOKEN)</string>
</dict>
</plist>

NotificationContent

NotificationViewController.h

#import <UIKit/UIKit.h>
#import <CTNotificationContent/CTNotificationContent.h>

@interface NotificationViewController : CTNotificationViewController

@end

NotificationViewController.m

#import "NotificationViewController.h"
#import <UserNotifications/UserNotifications.h>
#import <UserNotificationsUI/UserNotificationsUI.h>

@interface NotificationViewController () <UNNotificationContentExtension>

@property IBOutlet UILabel *label;

@end

@implementation NotificationViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any required interface initialization here.
}

- (void)didReceiveNotification:(UNNotification *)notification {
    self.label.text = notification.request.content.body;
    #if DEBUG
        NSLog(@"did receive notification: %@", notification.request.content.body);
    #endif
}

// optional: implement to get user event type data
- (void)userDidPerformAction:(NSString *)action withProperties:(NSDictionary *)properties {
    #if DEBUG
        NSLog(@"user did perform action: %@ with props: %@", action , properties);
    #endif
}

// optional: implement to get notification response
- (void)userDidReceiveNotificationResponse:(UNNotificationResponse *)response {
    #if DEBUG
        NSLog(@"user did receive notification response: %@:", response);
    #endif
}

@end

Info.plist

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Application App Transport Security Settings</key>
    <dict>
        <key>Allow Arbitrary Loads</key>
        <true/>
    </dict>
    <key>NSExtension</key>
    <dict>
        <key>NSExtensionAttributes</key>
        <dict>
            <key>UNNotificationExtensionCategory</key>
            <string>CTNotification</string>
            <key>UNNotificationExtensionUserInteractionEnabled</key>
            <string>1</string>
            <key>UNNotificationExtensionDefaultContentHidden</key>
            <true/>
            <key>UNNotificationExtensionInitialContentSizeRatio</key>
            <real>0.1</real>
            <!-- <key>UNNotificationExtensionInitialContentSizeRatio</key>
            <real>1</real> -->
        </dict>
        <key>NSExtensionMainStoryboard</key>
        <string>MainInterface</string>
        <key>NSExtensionPointIdentifier</key>
        <string>com.apple.usernotifications.content-extension</string>
    </dict>
</dict>
</plist>

AppDelegate.m

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
  [self initializeClevertap];

  #if DEBUG
    NSLog(@"DEBUG: %d", DEBUG);
    [CleverTap setDebugLevel:CleverTapLogDebug];
    #ifdef FB_SONARKIT_ENABLED
      InitializeFlipper(application);
    #endif
  #endif

  if(lOptions == nil) {
    lOptions = launchOptions;
  }

  RCTBridge *bridge = [[RCTBridge alloc] initWithDelegate:self launchOptions:launchOptions];
  RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge
                                                   moduleName:@"Booky"
                                            initialProperties:nil];

   if (@available(iOS 13.0, *)) {
      rootView.backgroundColor = [UIColor systemBackgroundColor];
  } else {
      rootView.backgroundColor = [UIColor whiteColor];
  }

  self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
  UIViewController *rootViewController = [UIViewController new];
  rootViewController.view = rootView;
  self.window.rootViewController = rootViewController;
  [self.window makeKeyAndVisible];
  [RNBootSplash initWithStoryboard:@"BootSplash" rootView:rootView];
  if ([FIRApp defaultApp] == nil) {
    [FIRApp configure];
  }
  [[UNUserNotificationCenter currentNotificationCenter] setDelegate:self];

  return YES;
}
...
- (void) initializeClevertap {
  [CleverTap autoIntegrate]; // integrate CleverTap SDK using the autoIntegrate option
  [self registerPush]; // register for APN
  [[CleverTapReactManager sharedInstance] applicationDidLaunchWithOptions:lOptions];
}
...
- (void)registerPush {    
    UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];

    UNNotificationAction *action1 = [UNNotificationAction actionWithIdentifier:@"action_1" title:@"Back" options:UNNotificationActionOptionNone];
    UNNotificationAction *action2 = [UNNotificationAction actionWithIdentifier:@"action_2" title:@"Next" options:UNNotificationActionOptionNone];
    UNNotificationAction *action3 = [UNNotificationAction actionWithIdentifier:@"action_3" title:@"View In App" options:UNNotificationActionOptionNone];
    UNNotificationCategory *cat = [UNNotificationCategory categoryWithIdentifier:@"CTNotification" actions:@[action1, action2, action3] intentIdentifiers:@[] options:UNNotificationCategoryOptionNone];
    [center setNotificationCategories:[NSSet setWithObjects:cat, nil]];

    [center requestAuthorizationWithOptions:(UNAuthorizationOptionSound | UNAuthorizationOptionAlert | UNAuthorizationOptionBadge) completionHandler:^(BOOL granted, NSError * _Nullable error){
        if( !error ){
            dispatch_async(dispatch_get_main_queue(), ^(void) {
                [[UIApplication sharedApplication] registerForRemoteNotifications];
            });
        }
    }];
}
...
/** For iOS 10 and above - Foreground, NOTE: Also works for iOS 8 **/
- (void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler{    
    /**
      Use this method to perform the tasks associated with your app's custom actions. When the user responds to a notification, the system calls this method with the results. You use this method to perform the task associated with that action, if at all. At the end of your implementation, you must call the completionHandler block to let the system know that you are done processing the notification.

      You specify your app's notification types and custom actions using UNNotificationCategory and UNNotificationAction objects.
      You create these objects at initialization time and register them with the user notification center. Even if you register custom actions, the action in the response parameter might indicate that the user dismissed the notification without performing any of your actions.

      If you do not implement this method, your app never responds to custom actions.

      see https://developer.apple.com/reference/usernotifications/unusernotificationcenterdelegate/1649501-usernotificationcenter?language=objc
      **/

    NSLog(@"%@: will present notification: %@", self.description, notification.request.content.userInfo);

    /** NOTE: As of CT SDK Version 5.2.1, this triggers Push Impressions / Impressions on Campaign Stat page, not Notification Viewed as indicated by name! **/

    [[CleverTap sharedInstance] recordNotificationViewedEventWithData:notification.request.content.userInfo];

    // if you wish CleverTap to record the notification open and fire any deep links contained in the payload, NOTE: This triggers Notification Clicked automatically
    // [[CleverTap sharedInstance]handleNotificationWithData:notification.request.content.userInfo openDeepLinksInForeground: NO];
    completionHandler(UNAuthorizationOptionSound | UNAuthorizationOptionAlert | UNAuthorizationOptionBadge);
}
...
/** NOTE: This is the only one triggered in testing the happy path, other than willPresentNotification **/
- (void) application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken{
  #if DEBUG
    NSLog(@"%@: registered for remote notifications: %@", self.description, deviceToken.description);
  #endif
}
AishwaryaNanna commented 9 months ago

Hi @maurusrv ,

We have checked with our sample app and that works fine, Can you please share payload and sample app zipped so that we can debug the issue further ?

Thanks!