在 iOS 上实现通知快捷回复

前提条件

请先阅读苹果推送文档

解决方案

  1. 引入依赖库

    #import <UserNotifications/UserNotifications.h>
    
  2. 添加通知快捷按钮

    - (void)addNotificationCategory {
        UNNotificationAction *ignoreAction = [UNNotificationAction actionWithIdentifier:@"ignore"
                                                                                  title:@"忽略"
                                                                                options:UNNotificationActionOptionNone];
        UNNotificationAction *replayAction = [UNTextInputNotificationAction actionWithIdentifier:@"reply"
                                                                                           title:@"回复"
                                                                                         options:UNNotificationActionOptionNone
                                                                            textInputButtonTitle:@"发送"
                                                                            textInputPlaceholder:@"请输入回复信息"];
        UNNotificationCategory *category = [UNNotificationCategory categoryWithIdentifier:@"message"
                                                                                  actions:@[ignoreAction, replayAction]
                                                                        intentIdentifiers:@[]
                                                                                  options:UNNotificationCategoryOptionNone];
        [[UNUserNotificationCenter currentNotificationCenter] setNotificationCategories:[NSSet setWithObject:category]];
    }
    
  3. 监听和处理通知代理

    [UNUserNotificationCenter currentNotificationCenter].delegate = self;
    - (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)(void))completionHandler {
        NSString *categoryIdentifier = response.notification.request.content.categoryIdentifier;
        UNNotificationContent *content = response.notification.request.content;
        if ([categoryIdentifier isEqualToString:@"message"]) {
            if ([response.actionIdentifier isEqualToString:@"reply"]) {
                NSDictionary *userInfo = content.userInfo;
                NSString *userId = [self getUserIdFromRemotePushInfo:userInfo];
                
                UNTextInputNotificationResponse *tResponse = (UNTextInputNotificationResponse *)response;
                NSString *userText = tResponse.userText;
                
                [self replyMessage:userId content:userText completion:^(BOOL success) {
                    dispatch_async(dispatch_get_main_queue(), ^{
                        completionHandler();
                    });
                }];
                return;
            }
        }
        completionHandler();
    }
    
  4. 获取消息会话用户id,可以参考融云推送 payload

    - (nullable NSString *)getUserIdFromRemotePushInfo:(NSDictionary *)info {
        NSDictionary *rc = info[@"rc"];
        if (![rc isKindOfClass:[NSDictionary class]]) return nil;
        NSString *userId = rc[@"tId"];
        if (![userId isKindOfClass:[NSString class]]) return nil;
        return userId;
    }
    
  5. 回复消息的实现方式有两种,一种是直接用 IM 建立连接后发送;另一种是调用服务器端接口发送。相对来说第二种方式简单安全,第一种实现起来更复杂、易出错。

    客户端方式,以下使用了 IMKit 中的方法:

    - (void)replyMessage:(NSString *)toUserId content:(NSString *)content completion:(void(^)(BOOL))completion {
        NSString *token = [[NSUserDefaults standardUserDefaults] stringForKey:@"IM_Token"];
        if (!token.length || !toUserId || !content) return !completion?:completion(NO);
        [RCIM.sharedRCIM initWithAppKey:AppKey option:nil];
        RCTextMessage *message = [RCTextMessage messageWithContent:content];
        if ([[RCIM sharedRCIM] getConnectionStatus] == ConnectionStatus_Connected) {
            [[RCIM sharedRCIM] sendMessage:ConversationType_PRIVATE targetId:toUserId content:message pushContent:nil pushData:nil success:^(long messageId) {
                !completion?:completion(YES);
            } error:^(RCErrorCode nErrorCode, long messageId) {
                !completion?:completion(NO);
            }];
            return;
        }
        [[RCIM sharedRCIM] connectWithToken:token dbOpened:^(RCDBErrorCode code) {
        } success:^(NSString *userId) {
            [[RCIM sharedRCIM] sendMessage:ConversationType_PRIVATE targetId:toUserId content:message pushContent:nil pushData:nil success:^(long messageId) {
                !completion?:completion(YES);
            } error:^(RCErrorCode nErrorCode, long messageId) {
                !completion?:completion(NO);
            }];
        } error:^(RCConnectErrorCode errorCode) {
            !completion?:completion(NO);
        }];
    }
    

    服务端方式,服务器发送实现请参考文档:

    - (void)replyMessage:(NSString *)toUserId content:(NSString *)content completion:(void(^)(BOOL))completion {
        [network sendMessage:toUserId content:content completion:^(int code){}];
    }
    
  6. 消息发送。为了支持 Category 事件的触发,需要发送消息时配置推送中的 category 属性,安卓和iOS都需要设置

        RCMessage *message = [[RCMessage alloc] initWithType:self.conversationType targetId:self.targetId direction:MessageDirection_SEND content:messageContent];
        message.messagePushConfig.iOSConfig.category = @"message";
        [[RCIM sharedRCIM] sendMessage:message pushContent:nil pushData:nil successBlock:^(RCMessage *successMessage) {
            
        } errorBlock:^(RCErrorCode nErrorCode, RCMessage *errorMessage) {
            
        }];
    

    注意:Category 事件触发后,只是调用的部分代码,如果在 AppDelegate 中其它地方初始化和连接时,判断下当前 App 的活跃状态:

    if ([[UIApplication sharedApplication] applicationState] == UIApplicationStateActive) {
        [RCIM.sharedRCIM initWithAppKey:AppKey];
    }