如何在IOS中处理多个远程通知点击

Posted

技术标签:

【中文标题】如何在IOS中处理多个远程通知点击【英文标题】:How to handle multiple remote-notification click in IOS 【发布时间】:2016-02-22 08:50:41 【问题描述】:

我正在尝试根据notification click 打开不同的视图控制器,但是当收到push notification 时,它会自动在后台运行控制器代码,因此当我通过来自notification centre 的应用程序图标/通知打开应用程序时,它会加载view controller 立即,但当收到多个通知时,它会加载第一个通知控制器,而不管点击了哪个通知。

假设我收到了标题为“晚上”、“早上”和 “Night”,应该在“Evening”时打开“Evening View Controller” 通知被点击,但当我去时它会加载“夜景控制器” 返回它加载“Morning View Controller”,最后它加载“Evening 查看控制器”。在通知到来之前,我在主控制器上 然后我将应用移到后台。

当我点击“早上”notification 时,它现在什么都不做。

之前我尝试使用 addObserver 但结果是一样的,所以我转向应用程序委托。

这是应用委托代码

@interface AppDelegate ()

@end


@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 

    [[UIApplication sharedApplication]setMinimumBackgroundFetchInterval:UIApplicationBackgroundFetchIntervalMinimum];

    // Override point for customization after application launch.
    // Register for Push Notitications, if running ios 8 or More

    if ([application respondsToSelector:@selector(registerUserNotificationSettings:)]) 

        UIUserNotificationType userNotificationTypes = (UIUserNotificationTypeAlert |
                                                        UIUserNotificationTypeBadge |
                                                        UIUserNotificationTypeSound);
        UIUserNotificationSettings *settings = [UIUserNotificationSettings settingsForTypes:userNotificationTypes categories:nil];
        [application registerUserNotificationSettings:settings];
        [application registerForRemoteNotifications];

     else 
        // Register for Push Notifications before iOS 8

        [application registerForRemoteNotificationTypes:(UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeAlert |  UIRemoteNotificationTypeSound)];
    

    [application setStatusBarHidden:YES];

    return YES;


// Handle remote notification registration.
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken 

    NSLog(@"Device Token %@",[self stringWithDeviceToken:deviceToken]);

    [[NSUserDefaults standardUserDefaults]setObject:[self stringWithDeviceToken:deviceToken] forKey:@"registration_id"];
    [[NSUserDefaults standardUserDefaults] synchronize];


- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)err 

    NSLog(@"Error in registration. Error: %@", err);



- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler 

    NSDictionary *alertInfo = (NSDictionary *) userInfo[@"aps"][@"alert"];
    NSLog(@"%@",alertInfo);

    NSString *alertID = [userInfo valueForKey:@"alertid"];


    if (application.applicationState == UIApplicationStateBackground) 

        [UIApplication sharedApplication].applicationIconBadgeNumber = [UIApplication sharedApplication].applicationIconBadgeNumber + 1;

        NSLog(@"Background Mode");
        NSString *title = alertInfo[@"title"];

        [self openViewController:title];
    

    if(application.applicationState == UIApplicationStateActive)

        NSLog(@"Active Mode");
           

    completionHandler(UIBackgroundFetchResultNewData);
    [self performSelector:@selector(sendAckRequest:) withObject:alertID];



- (void)openViewController:(NSString *)notificationTitle

    NSDictionary * userDict = [[NSUserDefaults standardUserDefaults] objectForKey:@"loginUser"];

    if([notificationTitle isEqualToString:@"Exercise"])

    UINavigationController *navigationController = (UINavigationController *)self.window.rootViewController;

    UIStoryboard *mainStoryboard = [UIStoryboard storyboardWithName:@"Main" bundle: nil];

    Excercise *excerciseController = (Excercise*)[mainStoryboard instantiateViewControllerWithIdentifier: @"Excercise"];       
    [navigationController pushViewController:excerciseController animated:YES];
    //[navigationController presentViewController:excerciseController animated:YES completion:nil];

    else if([notificationTitle isEqualToString:@"Weight"])

        UINavigationController *navigationController = (UINavigationController *)self.window.rootViewController;

        UIStoryboard *mainStoryboard = [UIStoryboard storyboardWithName:@"Main" bundle: nil];

        Weight *weightController = (Weight*)[mainStoryboard instantiateViewControllerWithIdentifier: @"Weight"];           
       [navigationController pushViewController:weightController animated:YES];

        //[navigationController presentViewController:weightController animated:YES completion:nil];

    else if([notificationTitle isEqualToString:@"MCQ"])

        UINavigationController *navigationController = (UINavigationController *)self.window.rootViewController;

        UIStoryboard *mainStoryboard = [UIStoryboard storyboardWithName:@"Main" bundle: nil];

        QuestionOfTheDay *questionController = (QuestionOfTheDay*)[mainStoryboard instantiateViewControllerWithIdentifier: @"QuestionOfTheDay"];
        questionController.self.dictUser = userDict;
        [navigationController pushViewController:questionController animated:YES]
    


-(void)sendAckRequest:(NSString *)alertID 

    AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
    manager.requestSerializer = [AFHTTPRequestSerializer serializer];
    manager.responseSerializer = [AFHTTPResponseSerializer serializer];

    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];

    NSString *userID =[[defaults objectForKey:@"loginUser"]objectForKey:@"UserId"];
    NSString *serverRegistrationID = [defaults objectForKey:@"server_registration_id"];

    NSDateFormatter *dateFormatter = [[NSDateFormatter alloc]init];

    [dateFormatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ss"];
    NSString *currentDateString = [dateFormatter stringFromDate:[NSDate date]];

    //NSDate *currentDate = [dateFormatter dateFromString:currentDateString];

    //NSDictionary *parameter = @@"ReminderDateTime":currentDateString;

    NSString *url = [NSString stringWithFormat:@"%@%@/%@/%@/?ReminderDateTime=%@",sendNotificationAck,userID,serverRegistrationID,alertID,currentDateString];


    NSLog(@"url: %@",url);

    [manager GET:url parameters:nil success:^(AFHTTPRequestOperation * _Nonnull operation, id  _Nonnull responseObject) 

        if(operation.response.statusCode == 200)
            NSLog(@"Notification Acknowledged");
        else
            NSLog(@"Notification failed to acknowledge");

     failure:^(AFHTTPRequestOperation * _Nullable operation, NSError * _Nonnull error) 

        NSLog(@"error: %@",[error localizedDescription]);
    ];


- (NSString *)stringWithDeviceToken:(NSData *)deviceToken 

    const char *data = [deviceToken bytes];

    NSMutableString *token = [NSMutableString string];

    for (int i = 0; i < [deviceToken length]; i++) 

        [token appendFormat:@"%02.2hhX", data[i]];
    

    return token;


- (void)applicationDidBecomeActive:(UIApplication *)application 
    // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.

    if([UIApplication sharedApplication].applicationIconBadgeNumber!=0)
    [UIApplication sharedApplication].applicationIconBadgeNumber = [UIApplication sharedApplication].applicationIconBadgeNumber - 1;


@end

【问题讨论】:

你可以使用布尔值来处理它。 【参考方案1】:

首先,您的Remote Notification 后台模式已启用,因此通知将在后台处理。现在的错误是,每当收到通知时,您都会将 viewController 推送到 viewController 堆栈,这就是为什么您会看到三个 viewController 堆叠在一起的原因。

其次,didReceiveRemoteNotification 可以在不同的状态下被调用/处理。 StateBackground 如果应用程序在后台,StateInactive 如果用户点击通知中心的通知,StateForeground 如果应用程序在前台。您在StateBackground 中添加了仅推送视图控制器的检查,这就是为什么您在点击通知时看不到任何更改的原因。

【讨论】:

这是否意味着如果我在StateInactive 中添加导航代码,它会起作用吗? @dichen 添加 StateInactive 检查将在您点击通知时处理这种情况。但要小心处理多次,一次用于后台,一次用于非活动。您可能需要一个标志来检查目标 viewController 是否已显示。 我会检查一下,但早些时候我使用的是StateBackground,当点击通知时它没有运行。我会尝试StateInactive 并让你知道。我应该使用直接视图控制器代码还是添加观察者并尝试? 对不起,我没有遵循这个,“我应该使用直接视图控制器代码还是添加观察者并尝试?” 我应该在StateInActiveaddObserver 内的应用程序委托中使用导航控制器而不是导航控制器【参考方案2】:

为此,需要检查didReceiveRemoteNotification 方法中的应用程序状态。这是代码:

- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler 

 //recieved notification
    if ( application.applicationState == UIApplicationStateActive )
        // app was already in the foreground
        UIViewController* topViewController = [self.navigationController topViewController];
        if ([topViewController isKindOfClass:[HomeVC class]]) 
            [[NSNotificationCenter defaultCenter] postNotificationName:@"getUpdatedServiceData" object:nil];
        
    

在 HomeVC 中您需要创建通知 getUpdatedServiceData 并在此方法中推送视图控制器。

*注 -

使用此方法为您的应用处理传入的远程通知。 与 application:didReceiveRemoteNotification: 方法不同,它是 仅当您的应用程序在前台运行时调用,系统 当您的应用在前台运行时调用此方法或 背景。此外,如果您启用了远程通知 后台模式,系统启动您的应用程序(或从 暂停状态)并在推送时将其置于后台状态 通知到达。但是系统不会自动 如果用户强制退出它,则启动您的应用程序。在这种情况下, 用户必须在系统之前重新启动您的应用程序或重新启动设备 尝试再次自动启动您的应用。

谢谢。

【讨论】:

我们在后台收到推送时遇到问题。 在前台,我们显示的是UIAlertView,所以这对我们来说不是问题【参考方案3】:

我没有尝试过,但我想这个应用程序委托方法会解决你的问题。

application:handleActionWithIdentifier:forRemoteNotification:withResponseInfo:completionHandler:

当您的应用已通过从远程通知中选择一个动作被激活时调用此方法。

userInfo 字典参数包含与远程通知相关的信息。

更多信息参见苹果文档

https://developer.apple.com/library/ios//documentation/UIKit/Reference/UIApplicationDelegate_Protocol/index.html#//apple_ref/occ/intfm/UIApplicationDelegate/application:handleActionWithIdentifier:forRemoteNotification:withResponseInfo:completionHandler:

【讨论】:

此方法从 8.0 开始可用,我们的最低版本是 7.0【参考方案4】:

当您在后台处理通知时肯定会发生此问题,因此当您尝试从通知中心启动应用时,您的应用已经准备好显示以前收到的通知。

所以要解决你的问题,你应该

    检查并决定最新的或点击的通知,或者在 - (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 中显示的有意义的通知,具体取决于应用状态,这些委托会被调用以获取通知

    在应用启动时检查已显示的 viewController。如果是,请删除它们并显示最新或点击的通知。

    UINavigationController *navigationController = (UINavigationController *)self.window.rootViewController;
    
    UIViewController* topViewController = [navigationController visibleViewController];
    
    if ([topViewController isKindOfClass:[Excercise class]])
    
        [topViewController dismissViewControllerAnimated:NO completion:nil];
    
    

// 之后启动相关的通知VC。

【讨论】:

【参考方案5】:

您可以使用不同的应用状态。

    func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject], fetchCompletionHandler completionHandler: (UIBackgroundFetchResult) -> Void)
    
        if ( application.applicationState == UIApplicationState.Active)
        
            // App is foreground and notification is recieved,
            // Show a alert.
        
        else if( application.applicationState == UIApplicationState.Background)
        
            // App is in background and notification is received, 
            // You can fetch required data here don't do anything with UI.
        
        else if( application.applicationState == UIApplicationState.Inactive)
        
           // App came in foreground by used clicking on notification, 
           // Use userinfo for redirecting to specific view controller.
        

            

【讨论】:

以上是关于如何在IOS中处理多个远程通知点击的主要内容,如果未能解决你的问题,请参考以下文章

如何检测用户点击的 iOS 远程通知?

如何在 iOS 应用中检索和处理远程推送通知内容

Xamarin 推送通知,在应用程序激活后处理远程通知

当应用程序处于状态后台或被杀死时,如何在不点击通知的情况下存储 iOS 远程通知?

如何部分启用/禁用远程推送通知客户端?

远程通知中的自定义声音 iOS 10,swift 3