LeanCloud实时通信模块iOS端快速接入指南(三)

2016年03月07日更新

已经重新提交了Githup,现在项目可以正常使用


首先附上前两篇的链接
LeanCloud实时通信模块iOS端快速接入指南(一)
LeanCloud实时通信模块iOS端快速接入指南(二)

上一节我们已经实现了一个相对完整的聊天,今天我们来定制我们聊天里一些细节功能。


怎么定制

说是定制,其实就是对LeanChatLib写好的代码进行改造,我这里列出几项项目基本都会用到的改造,照着做就能用。如果你有时间的话,阅读LeanChatLib的代码并搞懂它的逻辑,你几乎可以进行任意的改造。

准备工作

上次的代码有一些小BUG,我们先修复一下,原因我已经更新到上一篇的开头
我们把『登陆』按钮点击事件改成如下代码
- (IBAction)loginBtnClicked:(id)sender {
[[CDChatManager manager]closeWithCallback:^(BOOL succeeded, NSError *error) {
[[CDChatManager manager]openWithClientId:_textField.text callback:^(BOOL succeeded, NSError *error) {
ChatListViewController * chatList = [[ChatListViewController alloc]init];
[self.navigationController pushViewController:chatList animated:YES];
}];

 }];
}
真正的准备工作

我们继续使用我们在第二篇创建的工程
在这个工程里,我们的ChatListViewController继承了CDChatListVC,但是我们的很多改造并不是重写方法,而是修改方法中的部分逻辑,所以,为了方便,我们把CDChatListVC中除了ViewDidLoad外其他的代码全都拷贝过来。

  • 别忘了那些头文件和成员属性

而对于聊天界面我们需要做的界面方面的改动很少,关于业务逻辑方面的东西CDChatRoomVC提供了足够的接口,所以不用做代码的修改。

完成后,command+B 编译一下,没有问题之后我们开始改造工作

解决警告

身为一个强迫症患者,看见警告总是要想办法去掉
代码拷贝完之后CDChatListVC会有两个警告

Paste_Image.png

第一条是因为我们在IOS 9之后AlertView以及被废弃,建议使用的是UIAlertController,因此我们定位到这个警告,把 -(BOOL) filterError:(NSError *)error{} 方法写成这样

- (BOOL)filterError:(NSError *)error {
if (error) {
    UIAlertController * alert = [UIAlertController alertControllerWithTitle:nil message:[NSString stringWithFormat:@"%@", error]  preferredStyle:UIAlertControllerStyleAlert];
    [alert addAction:[UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleCancel handler:nil]];
    
    [self presentViewController:alert animated:YES completion:nil];
    return NO;
}
return YES;

}

第二条警告是告诉我们把一个将一个可变数组类型的指针指向了不可变数组
定位到错误,将self.conversations = conversations改成self.conversations.array = conversations即可

开始改造

不接受/显示某人消息(黑名单)

之前我们说过,当第一次进入或手动刷新聊天界面时,会拉取最近的聊天,然后刷新TableView。这里调用的就是这个 - (void)refresh方法,其中有这么两句

Paste_Image.png

这两句的意思大家一眼就能看出来,就是把从服务器获取到的会话数组赋值给TableView的数据源,然后刷新TableView。因此,我们在这里做个手脚,把要屏蔽的人从数组里剔除就可以。
比如我们要屏蔽id为79或78的用户,那么我们把这两句替换为如下代码

NSMutableArray * array = [[NSMutableArray alloc]initWithArray:conversations];
   //将id为"78"或"79"的用户聊天从最近聊天移除
for (AVIMConversation * conver in conversations) {
      for (id member in conver.members) {
            if ([member isEqual:@"78"] || [member isEqual:@"79"]) {
                 [array removeObject:conver];
            }
       }
 } 
self.conversations= array;
[self.tableView reloadData];

运行项目,发起和78或者79的聊天,然后再返回,大功告成

和78聊天
返回最近聊天列表,和78的会话并没有出现在这里
设置静音

设置静音是对会话的muted属性做操作,设置静音后,当该会话有新消息时将不会推送(如果你设置了推送的话),并且角标会显示一个小红点,而不是具体数字。
其实就是微信的免打扰功能。
具体做法为在任何一个能够获取到conversation的地方调用

[conver muteWithCallback:^(BOOL succeeded, NSError *error) {
        }];

取消静音这么写

[conver unmuteWithCallback:^(BOOL succeeded, NSError *error) {
        }];

设置静音后的效果如下

设置静音
标签栏显示未读消息数量

想在标签栏显示未读消息数,得先知道未读消息的数目,我们继续看这个refresh方法,发现这么几句

回调方法中已经返回了未读消息数目

在回调方法中已经返回了未读消息的总数,并且CDChatListDelegate也提供了相应的代理方法。
之前我们将ChatListViewController作为自身的代理,因此我们实现这个代理方法即可。我们给ChatListViewController添加如下方法
- (void)setBadgeWithTotalUnreadCount:(NSInteger)totalUnreadCount
{
if (totalUnreadCount == 0) {
[self.navigationController.tabBarItem setBadgeValue:nil];
}
else {
[self.navigationController.tabBarItem setBadgeValue:[NSString stringWithFormat:@"%ld",(long)totalUnreadCount]];
}
}

  • 之前有人问过我怎么给标签栏加这个数字角标,网上也有各种定制方法,但其实tabBarItem自身就有一个 setBageVaule:的方法用来设置角标
角标设置成功
头像切圆角、移动角标位置

为什么要把这两项放在这里呢?
一来是头像圆角显示是现在的主流,二来默认的角标是显示在头像右上角的,大家都知道图片切圆角无非是对UIImageView的layer做剪裁,但是因为它把角标直接加在了头像上作为子控件,这样的话角标就无法显示,因此我们还需要把角标移个位置。

LeanChatLib在这里用的是叫做LZConversationCell的类,但是这里我们不再选择使用它,因为它创建控件的方式还蛮奇怪的,继承改造很麻烦。再加上在TableView返回cell采用的策略也并不是很好,因此这里我们选择重新创建自己的cell。

首先是创建带Xib的UITableViewCell

创建cell

然后在cell中完成布局(不一定非要按照LeanChatLib的来)

cell的布局

然后把xib中的控件和代码关联起来,这里注意把最好把控件名字起的和LZConversationCell一样,这样一会可以省很大力气。

  • 这里解释一下littleBadgeView和BadgeView,之前说过如何设置会话的静音,当新消息来了之后,如果会话是静音的,那么就显示littleBadgeView,也就是小红点,如果非静音,那么就显示BadgeView,也就是角标。LeanChatLib把他们两个的位置设置的一样,但是我们自己做的时候是可以随意安排它的位置的。我这里是跟QQ一样放在了cell的右侧居中位置。

引入头文件
#import <JSBadgeView>
然后添加
@property (nonatomic, strong) JSBadgeView *badgeView;
我们一会在代码中处理它

再次引入头文件
#import <AVIMConversation.h>
再添加一条成员属性
@property(nonatomic,strong)AVIMConversation * conversation;
最后头文件中是这样的

头文件

实现ConversationCell.mawakeFromNib方法
- (void)awakeFromNib {
// Initialization code
self.avatarImageView.layer.cornerRadius = 22.5;
self.avatarImageView.clipsToBounds = YES;
self.litteBadgeView.hidden = YES;
_badgeView = [[JSBadgeView alloc]initWithParentView:self.timestampLabel alignment:JSBadgeViewAlignmentBottomCenter];
}
然后重写conversation的set方法
将ChatListViewController中返回cell的代理方法中的图中框起来的内容剪切(没有错,就是剪切)过来,并把 cell. 都改成 self. 。
并添加以下头文件
#import <AVIMConversation+Custom.h>
#import <CDChatManager.h>
#import <UIView+XHRemoteImage.h>
#import <CDMessageHelper.h>
#import <NSDate+DateTools.h>

  • 到了这一步在ChatListViewController中对应的头文件已经可以删除了,而且你会发现CDChatListVC引用了两次 UIView+XHRemoteImage.h,虽然没啥影响,但看来LeanCloud的程序员也蛮粗心的....
需要剪切的代码

set方法中的内容

- (void)setConversation:(AVIMConversation *)conversation
{
if (conversation.type == CDConvTypeSingle) {
    id <CDUserModel> user = [[CDChatManager manager].userDelegate getUserById:conversation.otherId];
    self.nameLabel.text = user.username;
    [self.avatarImageView setImageWithURL:[NSURL URLWithString:user.avatarUrl]];
}
else {
    [self.avatarImageView setImage:conversation.icon];
    self.nameLabel.text = conversation.displayName;
}
if (conversation.lastMessage) {
    self.messageTextLabel.attributedText = [[CDMessageHelper helper] attributedStringWithMessage:conversation.lastMessage conversation:conversation];
    self.timestampLabel.text = [[NSDate dateWithTimeIntervalSince1970:conversation.lastMessage.sendTimestamp / 1000] timeAgoSinceNow];
}
if (conversation.unreadCount > 0) {
    if (conversation.muted) {
        self.litteBadgeView.hidden = NO;
    } else {
        self.badgeView.badgeText = [NSString stringWithFormat:@"%ld", conversation.unreadCount];
    }
}

}

然后在.h添加返回cell的类方法并在.m实现,代码如下
+ (instancetype)cellForRowWithTableView:(UITableView *)tableView
{
NSString * className = NSStringFromClass([self class]);

[tableView registerNib:[UINib nibWithNibName:className bundle:nil] forCellReuseIdentifier:className];
return [tableView dequeueReusableCellWithIdentifier:className];
}

最后一步,在ChatViewController中引入我们写好的cell
然后在返回cell的代理方法中这么写
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
ConversationCell *cell = [ConversationCell cellForRowWithTableView:tableView];
AVIMConversation *conversation = [self.conversations objectAtIndex:indexPath.row];
cell.conversation = conversation;
if ([self.chatListDelegate respondsToSelector:@selector(configureCell:atIndexPath:withConversation:)]) {
[self.chatListDelegate configureCell:cell atIndexPath:indexPath withConversation:conversation];
}
return cell;
}

  • 这里回报一个警告说类型不正确,是因为LeanChatLib把参数类型写死写成了LZConversationCell,但是没关系,这个代理方法依然可以正常使用..

现在运行项目,查看效果

Paste_Image.png

图片切成了圆角(我这个图片边角都是白色所以不显眼。。。),角标也移到了右侧
同时我们还整理了一下返回cell的代码,让结构看起来更好一点

  • 这里我们花了很大力气自己写了一个Cell用来展示最近会话,其实聪明的同学已经想到了,这个conversation其实就是一个数据模型嘛,因此如果你愿意,conversation中的任何属性你都可以以各种方式展现,不必拘泥于原有的形式。这样就实现了最近消息列表的自定制
烦人的状态栏

LeanCloud目前的连接状态我还没有搞的很明白,总之如果你长时间没有活动或程序处于后台一段时间,就会和服务器断开连接,如果你又有了刷新或者发消息的操作它就会重新连接。但是,这都不是关键,关键是它那个状态栏是作为TableView的HeaderView存在的,因此如果你在HeaderView上加了其它控件会被这个状态栏覆盖掉。另外,在我看来,一个聊天动不动就显示断线对用户体验也不友好,因此我们要想办法干掉这个状态栏。
其实方法很简单,我们只要在ChatViewController中重写updateStatusView方法,然后啥也不做就行了。就是这么简单!

去掉状态栏
解决线程阻塞问题

之前说过,聊天列表中的头像昵称等信息是通过CDChatManager的代理方法返回一个user对象获取的,一般在项目中我们都会返回自己后台保存的昵称和头像地址,但是LeanChatLib这里是这么获取User的


获取User
  • 这句话现在已经被移到了我们刚才自定义的ConversationCell的SetConversation中

那么在UserFactory中我们就要写一个网络访问请求来返回,不管你写的是NSSURLSession还是NSURLConnect,如果网络环境不好的话,就有可能阻塞线程。因此这里我们需要做一点处理,让它去异步获取User。我们使用最简答的方法,利用NSOperationQueue来实现
首先给Cell添加NSOperationQueue
@property(nonatomic,strong)NSOperationQueue * operationQueue;
在从xib加载cell的时候将它初始化

- (void)awakeFromNib {
.....原来的代码
_operationQueue = [[NSOperationQueue alloc]init];
}

然后把上面截图里的几句话改成这样

 if (conversation.type == CDConvTypeSingle) {
    [self.operationQueue addOperationWithBlock:^{
        id <CDUserModel> user = [[CDChatManager manager].userDelegate getUserById:conversation.otherId];
        self.nameLabel.text = user.username;
        [self.avatarImageView setImageWithURL:[NSURL URLWithString:user.avatarUrl]];
    }];

}

然后运行代码,看不出什么差别。但是在你最近聊天列表内容比较多或者网络状况不好的时候,不会出现卡住不动的情况。

为聊天室添加功能

之前说过,聊天室的界面其实没有什么要改动的地方,我们要做的无非就是点击头像查看对方详细信息,点击消息或长按消息出现触发相应事件。对于这些操作,LeanChatLib提供的各种代理方法基本足够了。各种代理方法如下图所示,红框框起来的是经常用到的。

各种代理方法

举个例子,我们在ChatRoomViewController中实现点击头像的代理方法如下

- (void)didSelectedAvatorOnMessage:(id<XHMessageModel>)message     atIndexPath:(NSIndexPath *)indexPath
{
NSLog(@"sender is %@",message.sender);
}
Paste_Image.png

可以看到我们打印出了发送者的id,这样我们就可以根据这个id跳转到对应用户的详情界面去,是不是很简单?
其它的代理方法大家可以一个一个去试,这里就不在赘述


到了这里,LeanCloud即时通讯模块在iOS端的基本应用和改造方法已经都教给大家了。为了能够比较简单的接入IM功能,我们大量使用或继承了LeanChatLib中提供的类,但是LeanCloud的清晰的后台业务逻辑和前台提供的丰富的接口才是我认为它最吸引人的地方,有兴趣的同学可以自己研究研究。
这篇的代码在这里[https://github.com/RunningChicken-K/LeanCloudDemo_02]

  • 如果我的文章对您有帮助,请点赞或评论
  • 如有不对,欢迎指正
  • 我们下篇文章见
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,607评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,047评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,496评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,405评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,400评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,479评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,883评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,535评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,743评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,544评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,612评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,309评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,881评论 3 306
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,891评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,136评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,783评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,316评论 2 342

推荐阅读更多精彩内容