Push Message

📘

Supported SDK Version

This feature is supported in iOS SDK version 2.28.0 and above.

Set up APNs

To use push messages in an iOS app, you need to integrate the Hackle workspace with APNs. For more details, please refer to Apple Push Notification Service Setup.

Integrate with Hackle SDK

Pass APNs Token

Complete the following setup to enable Hackle to deliver push messages to devices where the iOS app is installed.

class AppDelegate: NSObject, UIApplicationDelegate {
  func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
    return true
  }
}
#import <UIKit/UIKit.h>

@interface AppDelegate : UIResponder <UIApplicationDelegate>

@end
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
  return YES;
}

For SwiftUI, register the AppDelegate in SwiftUI as follows:

import SwiftUI

@main
struct sampleApp: App {
  ...
  @UIApplicationDelegateAdaptor(AppDelegate.self) var delegate
  ...
}

Add the following function to the AppDelegate:

import Hackle

class AppDelegate: NSObject, UIApplicationDelegate {
  ...
  func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    let authOptions: UNAuthorizationOptions = [.alert, .badge, .sound]
    UNUserNotificationCenter.current().requestAuthorization(
      options: authOptions,
      completionHandler: { _, _ in }
    )
    UNUserNotificationCenter.current().delegate = self
    application.registerForRemoteNotifications()
    
    Hackle.initialize(sdkKey: YOUR_APP_SDK_KEY)
    return true
  }
  
  func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
    Hackle.app()?.setPushToken(deviceToken)
  }
  ...
}
#import <UIKit/UIKit.h>
#import <UserNotifications/UserNotifications.h>
@import Hackle;

@interface AppDelegate : UIResponder <UIApplicationDelegate, UNUserNotificationCenterDelegate>

@end
#import "AppDelegate.h"
  
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
  UNUserNotificationCenter* center = [UNUserNotificationCenter currentNotificationCenter];
  [center requestAuthorizationWithOptions:(UNAuthorizationOptionAlert + UNAuthorizationOptionSound) completionHandler:^(BOOL granted, NSError * _Nullable error) {}];
  center.delegate = self;
  [[UIApplication sharedApplication] registerForRemoteNotifications];
  
  [Hackle initializeWithSdkKey:@"YOUR_APP_SDK_KEY" config:[HackleConfig DEFAULT]];
  return YES;
}

- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
  [[Hackle app] setPushToken:deviceToken];
}

Display Push Messages

In the Signing & Capabilities tab of the Xcode project settings, click + Capability as shown below.

Add Push Notifications.

To display push messages sent from Hackle, configure as follows:

import Hackle

extension AppDelegate: UNUserNotificationCenterDelegate {
  // Foreground push message
  func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
    if Hackle.userNotificationCenter(center: center, willPresent: notification, withCompletionHandler: completionHandler) {
      // Succefully processed notification
      // Automatically consumed completion handler
      return
    } else {
      // Received not hackle notification or error
      print("Do something")
      if #available(iOS 14.0, *) {
        completionHandler([.list, .banner])
      } else {
        completionHandler([.alert])
      }
    }
  }
  
  // Converted push message
  public func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
    if Hackle.userNotificationCenter(center: center, didReceive: response, withCompletionHandler: completionHandler) {
      // Automatically consumed completion handler
      return
    } else {
      // Received non hackle notification or error
      print("Do something")
      completionHandler()
    }
  }
}
// Foreground push message
-(void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions options))completionHandler {
  if ([Hackle userNotificationCenterWithCenter:center willPresent:notification withCompletionHandler:completionHandler]) {
    // Succefully processed notification
    // Automatically consumed completion handler
    return;
  } else {
    // Received not hackle notification or error
    NSLog(@"Do something");
    completionHandler(UNNotificationPresentationOptionList | UNNotificationPresentationOptionBanner);
  } 
}

// Converted push message
-(void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void(^)(void))completionHandler {
  if ([Hackle userNotificationCenterWithCenter:center didReceive:response withCompletionHandler:completionHandler]) {
    // Automatically consumed completion handler
    return;
  } else {
    // Received non hackle notification or error
    NSLog(@"Do something");
    completionHandler();
  }
}

Test Push Messages

Check Token

Verify the token set on the iOS device by following the User Identifier Verification Guide.

Test

Verify the push message on the iOS device by referring to the Push Message Test Sending Guide.

(Advanced) Deep Link Navigation

Hackle push messages support deep link navigation when clicked. If the app opens through a push message, you can check the opened deep link information with the following setup.

For more details on iOS deep linking, please refer to the iOS Deep Linking Guide.

import SwiftUI

@main
struct sampleApp: App {
  ...
  var body: some Scene {
    WindowGroup {
      ContentView()
        .onOpenURL(perform: { url in
          // Handle opened url
          print("\(url.absoluteString) opened.")
        })
    }
  }
  ...
}
class AppDelegate: NSObject, UIApplicationDelegate {
  ...
  func application(_ application: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:] ) -> Bool {
    // Handle opened url
    print("\(url.absoluteString) opened.")
  }
  ...
}

🚧

Since deep link handling in SwiftUI and Storyboard is independent, configure it according to your iOS app project.

(Advanced) Display Push Messages with Images (Rich Push Notification)

To display push messages with images in an iOS app, you need to add a Notification Service Extension and complete the following setup.

For more details on iOS Rich Push Notification, check Rich Push Notification.

Xcode Setup

In the Xcode project, select File > New > Target... tab and choose Notification Service Extension as shown below.

Enter an appropriate name and click Finish.

CocoaPods Setup

In the Podfile, configure the previously added Extension as follows:

use_frameworks!

target 'sampleapp' do
  pod 'Hackle', '~> 2.28.0'
end

target 'NotificationServiceExtension' do
  pod 'Hackle', '~> 2.28.0'
end

Swift Package Manager Setup

Add the Hackle framework to the added Extension.

Display Push Messages

import UserNotifications
import Hackle

class NotificationService: UNNotificationServiceExtension {
  var contentHandler: ((UNNotificationContent) -> Void)?
  var bestAttemptContent: UNMutableNotificationContent?
  
  override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
    self.contentHandler = contentHandler
    self.bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)

    if (Hackle.populateNotificationContent(request: request, withContentHandler: contentHandler)) {
      // Succefully processed notification
      // Automatically consumed content handler
      return
    } else {
      // Received non hackle notification or error
      if let bestAttemptContent = bestAttemptContent {
        // Modify the notification content here...
        bestAttemptContent.title = "\(bestAttemptContent.title) [modified]"
        contentHandler(bestAttemptContent)
      }
    }
  }
  
  override func 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.
    if let contentHandler = self.contentHandler, let bestAttemptContent = self.bestAttemptContent {
      contentHandler(bestAttemptContent)
    }
  }
}
#import <UserNotifications/UserNotifications.h>
@import Hackle;

@interface NotificationService : UNNotificationServiceExtension

@end
#import "NotificationService.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];
    
  if ([Hackle populateNotificationContentWithRequest:request withContentHandler:contentHandler]) {
    // Succefully processed notification
    // Automatically consumed content handler
    return;
  } else {
    // Received non hackle notification or error
    self.bestAttemptContent.title = [NSString stringWithFormat:@"%@ [modified]", self.bestAttemptContent.title];
    self.contentHandler(self.bestAttemptContent);
  }
}

- (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