Custom push notification swizzling for iOS

By default, Leanplum's iOS SDK uses method swizzling to collect push tokens. If your iOS app has multiple different SDKs installed that attempt to swizzle the push methods, it can cause problems where some push notification methods are not called.

To avoid issues collecting push tokens, you can disable automatic method swizzling for push notification methods and call the Leanplum push notification methods manually.

Here are the steps to call the push methods without swizzling:

  1. Disable swizzling by putting a boolean flag for LeanplumSwizzlingEnabled in your app Info.plist to NO.
  2. Ensure the following methods are present in your AppDelegate.m file. Make sure to call the corresponding method on the Leanplum object.

iOS SDK 4.0.0 and newer

We have introduced changes in the push notifications handling from SDK 4.0.0. Read more about the changes and push notification methods here.

Sample Implemention

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary<UIApplicationLaunchOptionsKey,id> *)launchOptions
{
    [[UNUserNotificationCenter currentNotificationCenter] setDelegate:self];
    [Leanplum start];
    [Leanplum applicationDidFinishLaunchingWithOptions:launchOptions];
    
    return result;
}

- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler
{
    [Leanplum didReceiveRemoteNotification:userInfo];
    completionHandler(UIBackgroundFetchResultNewData);
}

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

- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error
{
    [Leanplum didFailToRegisterForRemoteNotificationsWithError:error];
}

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

-(void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions options))completionHandler
{
    [Leanplum willPresentNotification:notification];
    completionHandler(UNNotificationPresentationOptionNone);
}
@main
class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate {

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        // Override point for customization after application launch.
        UNUserNotificationCenter.current().delegate = self
        Leanplum.start()
        Leanplum.applicationDidFinishLaunching(options: launchOptions ?? [:])
        
        return true
    }
    
    func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
        Leanplum.didRegisterForRemoteNotifications(withDeviceToken: deviceToken)
    }
    
    func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {
        Leanplum.didFailToRegisterForRemoteNotificationsWithError(error)
    }
    
    func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
        Leanplum.didReceiveRemoteNotification(userInfo)
        completionHandler(.newData)
    }

    func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
        Leanplum.didReceive(response)
        completionHandler()
    }
    
    func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
        Leanplum.willPresent(notification)
        completionHandler([])
    }
}

iOS SDK 3.2.1 and older

// At minimum:
+ (void)application:(UIApplication *) didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)token;
+ (void)application:(UIApplication *) didFailToRegisterForRemoteNotificationsWithError:(NSError *)error;
+ (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler;

// Optional:
+ (void)application:(UIApplication *) didRegisterUserNotificationSettings:(UIUserNotificationSettings*) notificationSettings;
+ (void)application:(UIApplication *) didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)(void))completionHandler API_AVAILABLE(ios(10.0));
+ (void)application:(UIApplication *) didReceiveLocalNotification:(UILocalNotification *)localNotification;
// At minimum:
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data)
func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error)

func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void)


// Optional:
func application(_ application: UIApplication, didRegister notificationSettings: UIUserNotificationSettings)
func application(_ application: UIApplication, 
didReceiveRemoteNotification userInfo: [AnyHashable : Any])
func application(_ application: UIApplication, 
               didReceive notification: UILocalNotification)

Here’s a sample implementation:

- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler
{
    // Needs to be called if swizzling is disabled in Info.plist otherwise it won’t affect SDK if swizzling is enabled.
  
      void (^leanplumCompletionHandler)(LeanplumUIBackgroundFetchResult) =  ^(LeanplumUIBackgroundFetchResult result) {
        UIBackgroundFetchResult r = result;
        completionHandler(r);
    };
  
    [Leanplum didReceiveRemoteNotification:userInfo 
fetchCompletionHandler:leanplumCompletionHandler];
}

- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken
{
    // Needs to be called if swizzling is disabled in Info.plist otherwise it won’t affect SDK if swizzling is enabled.
    [Leanplum didRegisterForRemoteNotificationsWithDeviceToken:deviceToken];
}

- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error
{
    // Needs to be called if swizzling is disabled in Info.plist otherwise it won’t affect SDK if swizzling is enabled.
    [Leanplum didFailToRegisterForRemoteNotificationsWithError:error];
}
// Implement didReceiveRemoteNotification with completion handler.
// Implementing (didReceiveRemoteNotification userInfo: [AnyHashable : Any]) could lead to action not working when the app is not already running
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
  
    let leanplumCompletionHandler: LeanplumFetchCompletionBlock = { (lpBlock:LeanplumUIBackgroundFetchResult) in
     let result:UIBackgroundFetchResult = UIBackgroundFetchResult.init(rawValue: lpBlock) ?? .noData
                                                   
     completionHandler(result)
    }
  
    Leanplum.didReceiveRemoteNotification(userInfo, fetchCompletionHandler: leanplumCompletionHandler)
}

func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
    // Needs to be called if swizzling is disabled in Info.plist otherwise it won’t affect SDK if swizzling is enabled.
    Leanplum.didRegisterForRemoteNotifications(withDeviceToken: deviceToken)
}

func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {
    // Needs to be called if swizzling is disabled in Info.plist otherwise it won’t affect SDK if swizzling is enabled.
    Leanplum.didFailToRegisterForRemoteNotificationsWithError(error)
}

If you are using UNUserNotificationCenter, you need to call the Leanplum didReceive method and willPresent, so the notification is handled:

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

-(void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions options))completionHandler
{
    [[LPPushNotificationsManager sharedManager].handler willPresentNotification:notification withCompletionHandler:completionHandler];
}
@available(iOS 10.0, *)
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
   Leanplum.didReceive(response, withCompletionHandler: completionHandler)
}

func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
   LPPushNotificationsManager.shared().handler.willPresent(notification, withCompletionHandler: completionHandler)
}