实现两个不同APP之间的聊天功能原创
金蝶云社区-honey缘木鱼
honey缘木鱼
1人赞赏了该文章 1586次浏览 未经作者许可,禁止转载编辑于2019年04月17日 10:14:46

以前做过聊天的项目,群聊和单聊都是在一个项目中进行的,这次的项目需求是在不同的APP中实现即时通讯,想着很复杂的样子,没想到实现起来那么容易。

一.选择第三方框架(环信?,容云?)

上次做聊天功能时用的容云,这次本想着用下环信,看环信API时,真心感到累!不能浪费太长时间,于是最后的决定还是容云吧!

二.注册获得App Key和下载SDK

  1. 注册登录后,在容云的控制台获得App Key。


    2.选择自己需要的功能去官网下载

    下载SDK

三.SDK 集成(手动集成)

1.把下载IMLib和IMKit 手动拖到自己的项目中。


2.添加系统库依赖


注:苹果在 XCode10 和 iOS12 中移除了 libstdc++ 这个库,由 libc++ 这个库取而代之。


  1. 在 Xcode 项目 Build Settings -> Other Linker Flags 中,增加 "-ObjC"。

  2. 在 Xcode 项目 Build Settings->Search Paths 中SDK路径是否正确。


四.IMLib和IMKit 的使用

  1. 初始化
    在AppDelegate.m文件中引入头文件#import <RongIMKit/RongIMKit.h>

  //初始化融云SDK appKey即是在容云控制台获得的
    [[RCIM sharedRCIM] initWithAppKey:@“3argexb6**ve”];

2.连接融云服务器

[[RCIM sharedRCIM] connectWithToken:@"YourTestUserToken"  success:^(NSString *userId) {    NSLog(@"登陆成功。当前登录的用户ID:%@", userId);
} error:^(RCConnectErrorCode status) {  
  NSLog(@"登陆的错误码为:%d", status);
} tokenIncorrect:^{  
  //token过期或者不正确。
    //如果设置了token有效期并且token过期,请重新请求您的服务器获取新的token
    //如果没有设置token有效期却提示token错误,请检查您客户端和服务器的appkey是否匹配,还有检查您获取token的流程。
    NSLog(@"token错误");
}];

注:
(1).如何获取token?


必须在服务器端请求 Token,因为获取 Token 时需要提供 App Key 和 App Secret。如果在客户端请求 Token,假如 App 代码一旦被反编译,则会导致 App Key和App Secret 泄露。作为前端的我们需要Token来做测试,容云已经想到这一问题,为我们提供API调试,获取token


(2).登录完成后,记得回到主线程

// 在主线程更新UIdispatch_sync(dispatch_get_main_queue(), ^(){         
      // 就是这里进行页面跳转啊,刷新UI
            });
  1. 会话列表
    直接用RCConversationListViewController 或者继承它创建一个控制器,即可快速启动和使用会话列表界面。

- (void)viewDidLoad {
    [super viewDidLoad];  
    //设置需要显示哪些类型的会话
    [self setDisplayConversationTypes:@[@(ConversationType_PRIVATE),
    @(ConversationType_GROUP)]];  
    //设置需要将哪些类型的会话在会话列表中聚合显示
    [self setCollectionConversationType:@[@(ConversationType_PRIVATE),
    @(ConversationType_GROUP)]];
}

注:
您需要设置在会话列表界面显示哪些类型的会话,以及将哪些类型的会话在会话列表中聚合显示。
聚合显示指的是此类型所有会话,在会话列表中聚合显示成一条会话,点击进去会显示此类型的具体会话列表。

/*!
 点击Cell头像的回调

 @param model   会话Cell的数据模型
 */- (void)didTapCellPortrait:(RCConversationModel *)model;/*!
 长按Cell头像的回调

 @param model   会话Cell的数据模型
 */- (void)didLongPressCellPortrait:(RCConversationModel *)model;

4.会话页面
融云 IMKit 中已经实现了完整的会话页面,包含发送、接收、更新等 UI,并覆盖常用的 IM 交互场景,您直接使用或继承 RCConversationViewController,即可快速启动和使用会话页面。创建一个 ChatViewController控制器 继承 RCConversationViewController,点击会话列表时进入会话页面方法

/**
 *重写RCConversationListViewController的onSelectedTableRow事件
 *
 *  @param conversationModelType 数据模型类型
 *  @param model                 数据模型
 *  @param indexPath             索引
 */-(void)onSelectedTableRow:(RCConversationModelType)conversationModelType conversationModel:(RCConversationModel *)model atIndexPath:(NSIndexPath *)indexPath
{
    ChatViewController *conversationVC = [[ChatViewController alloc]init];
    conversationVC.conversationType =model.conversationType;
    conversationVC.targetId = model.targetId;
     conversationVC.title = model.conversationTitle;
    conversationVC.hidesBottomBarWhenPushed = YES;
    [self.navigationController pushViewController:conversationVC animated:YES];
}

注:
(1).用户信息如何传给容云?会话列表上的用户头像和昵称如何显示?


userInfoDataSource 和 groupMemberDataSource两个代理会为我们完美的解决显示用户信息的问题。
设置代理:

[RCIM sharedRCIM].userInfoDataSource =self;
[RCIM sharedRCIM].groupMemberDataSource =self;

实现代理的方法:

/**
 *此方法中要提供给融云用户的信息,建议缓存到本地,然后改方法每次从您的缓存返回
 */- (void)getUserInfoWithUserId:(NSString *)userId completion:(void(^)(RCUserInfo* userInfo))completion
{    //此处为了演示写了一个用户信息
    if ([@"123456" isEqual:userId]) {
        RCUserInfo *user = [[RCUserInfo alloc]init];
        user.userId = @"123456";
        user.extra = @"全国总店";
        user.name = @"dddd";
        user.portraitUri = @"https://kxlj.oss-cn-hangzhou.aliyuncs.com/diary-pic/15547206992190";        return completion(user);
    }else if([@"222222" isEqual:userId]) {
        RCUserInfo *user = [[RCUserInfo alloc]init];
        user.userId = @"222222";
        user.name = @"用户3";
        user.portraitUri = @"https://kxlj.oss-cn-hangzhou.aliyuncs.com/diary-pic/15547206992190";        return completion(user);
    }
}

当需要显示用户信息的时候,IMKit自己会走这个方法去查询用户信息,然后显示在适当的位置。

(2).点击头像的方法?

/*!
 点击Cell中头像的回调

 @param userId  点击头像对应的用户ID
 */- (void)didTapCellPortrait:(NSString *)userId;/*!
 长按Cell中头像的回调

 @param userId  头像对应的用户ID
 */- (void)didLongPressCellPortrait:(NSString *)userId;
  1. 消息缓存
    IMKit是如何缓存的呢?怎么才能在断网的时候显示聊天记录呢?我们所担心的这些,云容都已经想到方法,在此不得不佩服容云开发者的强大。

[RCIM sharedRCIM].enablePersistentUserInfoCache = YES;

在AppDelegate里面如果设置enablePersistentUserInfoCache设置为NO,则SDK在需要显示用户信息时,会调用用户信息提供者获取用户信息并缓存到Cache,此Cache在App生命周期结束时会被移除,下次启动时会再次通过用户信息提供者获取信息。
如果设置为YES,则会将获取到的用户信息持久化存储在本地,App下次启动时Cache会仍然有效。

/*!
 获取SDK中缓存的用户信息 
 @param userId  用户ID 
 @return      SDK中缓存的用户信息
 */- (RCUserInfo *)getUserInfoCache:(NSString *)userId;

在我们服务端获取token成功之后我们会去连接融云的服务器,断网的时候当然我们不能获取到token了。
我们需要在获取token成功之后缓存一下这个token,

[NSUserDefaults standardUserDefaults] setObject:token forKey:KRongCloudToken];

再次登录的时候,如果获取token失败了,那在登录失败的方法中用缓存的token连接融云服务器,根据UserId拿到用户信息。

  1. 消息推送
    根据官网推送说明在容云控制台上传自己生成的推送证书。
    在代码中请求远程推送权限

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; /**
     * 推送处理1
     */
    if ([application
         respondsToSelector:@selector(registerUserNotificationSettings:)]) {   
              //注册推送, iOS 8
        UIUserNotificationSettings *settings = [UIUserNotificationSettings
                                                settingsForTypes:(UIUserNotificationTypeBadge |                                                                  UIUserNotificationTypeSound |                                                                  UIUserNotificationTypeAlert)
                                           categories:nil];
        [application registerUserNotificationSettings:settings];
    } else {   
         UIRemoteNotificationType myTypes = UIRemoteNotificationTypeBadge |UIRemoteNotificationTypeAlert |        UIRemoteNotificationTypeSound;
        [application registerForRemoteNotificationTypes:myTypes];
    }
  [[NSNotificationCenter defaultCenter]
     addObserver:self
     selector:@selector(didReceiveMessageNotification:)
     name:RCKitDispatchMessageNotification
     object:nil];

需要将 didRegisterForRemoteNotificationsWithDeviceToken: 获取到的 deviceToken ,转为 NSString 类型,并去掉其中的空格和尖括号,传给 SDK。
SDK 会将此 deviceToken 保存,在 Connect 时会自动上传到融云服务器,并与用户 ID 绑定,用于远程推送。

/**
 *  将得到的devicetoken 传给融云用于离线状态接收push ,您的app后台要上传推送证书
 *
 *  @param application <#application description#>
 *  @param deviceToken <#deviceToken description#>
 */- (void)application:(UIApplication *)application
didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {  
  NSString *token =
    [[[[deviceToken description] stringByReplacingOccurrencesOfString:@"<"
                                                           withString:@""]
      stringByReplacingOccurrencesOfString:@">"
      withString:@""]
     stringByReplacingOccurrencesOfString:@" "
     withString:@""];
    
    [[RCIMClient sharedRCIMClient] setDeviceToken:token];
}

五.功能拓展

  1. 两个APP端之间如何聊天?

我们只需要申请一套 App Key / App Secrect,提供给两个 App 使用即可。上线前,您需要在开发者平台上填写这两个应用的包名(Bundle Identifier)即可。
在测试中我们只需要在API调试中,申请两个Token,一个是目标的userId,一个是自己。也同样在以下方法中实现用户信息的刷新。

 设置  [[RCIM sharedRCIM] setUserInfoDataSource:self];
 - (void)getUserInfoWithUserId:(NSString *)userId 
 completion:(void(^)(RCUserInfo* userInfo))completion{}
  1. 聊天列表头布局显示,设计如下图


    在聊天列表上面显示

因为我们自己创建的控制器是集成RCConversationListViewController的,所以我们可以当成tableView的tableHeaderView。

   self.conversationListTableView.tableHeaderView = self.tableHeaderView; 
   //设置tableView头部
    self.conversationListTableView.tableFooterView = [UIView new];
    //这样就不会显示多余的分割线啦
    self.conversationListTableView.backgroundColor = [UIColor whiteColor];  
    self.conversationListTableView.layoutMargins = UIEdgeInsetsZero;   
    self.conversationListTableView.separatorInset = UIEdgeInsetsZero;   
    self.topCellBackgroundColor = [UIColor blueColor]; // 置顶的消息背景颜色,

3.会话列表页,cell的左滑显示 置顶,表为未读,删除
直接用tableView的代理方法

- (NSArray<UITableViewRowAction *> *)tableView:(UITableView *)tableView editActionsForRowAtIndexPath:(NSIndexPath *)indexPath {
    RCConversationModel *model = self.conversationListDataSource[indexPath.row];    UITableViewRowAction *deleteRoWAction = [UITableViewRowAction rowActionWithStyle:UITableViewRowActionStyleDefault title:@"删除" handler:^(UITableViewRowAction *action, NSIndexPath *indexPath) {
        
        [[RCIMClient sharedRCIMClient] removeConversation:ConversationType_PRIVATE targetId:model.targetId];
        [self refreshConversationTableViewIfNeeded];
        
    }];    UITableViewRowAction *readRoWAction;
       if (model.unreadMessageCount > 0) {
        readRoWAction = [UITableViewRowAction rowActionWithStyle:UITableViewRowActionStyleNormal title:@"标为已读" handler:^(UITableViewRowAction *action, NSIndexPath *indexPath) {
            [[RCIMClient sharedRCIMClient] clearMessagesUnreadStatus:ConversationType_PRIVATE targetId:model.targetId];
            
            [self refreshConversationTableViewIfNeeded];
        }];
    } else {
        readRoWAction = [UITableViewRowAction rowActionWithStyle:UITableViewRowActionStyleNormal title:@"标为未读" handler:^(UITableViewRowAction *action, NSIndexPath *indexPath) {
            [[RCIMClient sharedRCIMClient] setMessageReceivedStatus:model.lastestMessageId receivedStatus:ReceivedStatus_UNREAD];
            [self refreshConversationTableViewIfNeeded];
            // 刷新tableView
        }];
    }    UITableViewRowAction *topRoWAction; 
       if (model.isTop) {
        topRoWAction = [UITableViewRowAction rowActionWithStyle:UITableViewRowActionStyleNormal title:@"取消置顶" handler:^(UITableViewRowAction *action, NSIndexPath *indexPath) {
            [[RCIMClient sharedRCIMClient] setConversationToTop:1 targetId:model.targetId isTop:NO];
            [self refreshConversationTableViewIfNeeded];
        }];
        topRoWAction.backgroundColor = [UIColor redColor];
    } else {
        topRoWAction = [UITableViewRowAction rowActionWithStyle:UITableViewRowActionStyleNormal title:@"置顶" handler:^(UITableViewRowAction *action, NSIndexPath *indexPath) {
            [[RCIMClient sharedRCIMClient] setConversationToTop:1 targetId:model.targetId isTop:YES];
            [self refreshConversationTableViewIfNeeded];
        }];
        topRoWAction.backgroundColor =  [UIColor redColor];
    }    return @[deleteRoWAction, readRoWAction, topRoWAction];
}
  1. 设置聊天的头像为圆形

[RCIMsharedRCIM].globalConversationAvatarStyle =RC_USER_AVATAR_CYCLE;
//设置头像是圆形还是方形,默认是方形,聊天列表
[RCIMsharedRCIM].globalConversationPortraitSize =CGSizeMake(50,50);
//设置头像大小,默认是46*46,设置时必须大于36*36,否则无效

5.获取对话列表

 NSArray *array = [NSArray arrayWithObjects:[NSNumber numberWithInteger:ConversationType_PRIVATE]
 ,[NSNumber numberWithInteger:ConversationType_DISCUSSION],nil]; 
 NSArray *listArray = [[RCIMClient sharedRCIMClient] getConversationList:array];

六.遇到的问题

在聊天列表页,总是自己的信息不能显示出来,但在另一个APP上信息显示正常,并且没有发现两者什么不同,表示很郁闷,于是自己用了一个简单粗暴的方法,更新SDK中的用户信息缓存。

  //刷新融云对应用户信息
     RCUserInfo *user = [[RCUserInfo alloc]init];
     user.userId = @"666666";
     user.name = @"用户2";
     user.portraitUri = @"https://ss0.baidu.com/73t1bjeh1BF3odCf/it/u=1756054607,4047938258&fm=96&s=94D712D20AA1875519EB37BE0300C008";
    [[RCIM sharedRCIM]refreshUserInfoCache:user withUserId:userId];


demo地址


因为项目目前需求为单聊,以后会有群聊,聊天室,后续加上此功能。



本篇独发金蝶云社区

赞 1