2016年03月07日更新
已經(jīng)重新提交了Githup,現(xiàn)在項(xiàng)目可以正常使用
首先附上前兩篇的鏈接
LeanCloud實(shí)時(shí)通信模塊iOS端快速接入指南(一)
LeanCloud實(shí)時(shí)通信模塊iOS端快速接入指南(二)
上一節(jié)我們已經(jīng)實(shí)現(xiàn)了一個(gè)相對(duì)完整的聊天砾肺,今天我們來(lái)定制我們聊天里一些細(xì)節(jié)功能。
怎么定制
說(shuō)是定制,其實(shí)就是對(duì)LeanChatLib寫(xiě)好的代碼進(jìn)行改造踏兜,我這里列出幾項(xiàng)項(xiàng)目基本都會(huì)用到的改造袜爪,照著做就能用。如果你有時(shí)間的話顶燕,閱讀LeanChatLib的代碼并搞懂它的邏輯凑保,你幾乎可以進(jìn)行任意的改造。
準(zhǔn)備工作
上次的代碼有一些小BUG涌攻,我們先修復(fù)一下欧引,原因我已經(jīng)更新到上一篇的開(kāi)頭
我們把『登陸』按鈕點(diǎn)擊事件改成如下代碼
- (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];
}];
}];
}
真正的準(zhǔn)備工作
我們繼續(xù)使用我們?cè)诘诙獎(jiǎng)?chuàng)建的工程
在這個(gè)工程里,我們的ChatListViewController繼承了CDChatListVC恳谎,但是我們的很多改造并不是重寫(xiě)方法芝此,而是修改方法中的部分邏輯憋肖,所以,為了方便婚苹,我們把CDChatListVC中除了ViewDidLoad外其他的代碼全都拷貝過(guò)來(lái)岸更。
- 別忘了那些頭文件和成員屬性
而對(duì)于聊天界面我們需要做的界面方面的改動(dòng)很少,關(guān)于業(yè)務(wù)邏輯方面的東西CDChatRoomVC提供了足夠的接口膊升,所以不用做代碼的修改怎炊。
完成后,command+B 編譯一下廓译,沒(méi)有問(wèn)題之后我們開(kāi)始改造工作
解決警告
身為一個(gè)強(qiáng)迫癥患者评肆,看見(jiàn)警告總是要想辦法去掉
代碼拷貝完之后CDChatListVC會(huì)有兩個(gè)警告
第一條是因?yàn)槲覀冊(cè)贗OS 9之后AlertView以及被廢棄,建議使用的是UIAlertController,因此我們定位到這個(gè)警告非区,把 -(BOOL) filterError:(NSError *)error{}
方法寫(xiě)成這樣
- (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;
}
第二條警告是告訴我們把一個(gè)將一個(gè)可變數(shù)組類(lèi)型的指針指向了不可變數(shù)組
定位到錯(cuò)誤瓜挽,將self.conversations = conversations
改成self.conversations.array = conversations
即可
開(kāi)始改造
不接受/顯示某人消息(黑名單)
之前我們說(shuō)過(guò),當(dāng)?shù)谝淮芜M(jìn)入或手動(dòng)刷新聊天界面時(shí),會(huì)拉取最近的聊天,然后刷新TableView律适。這里調(diào)用的就是這個(gè) - (void)refresh
方法,其中有這么兩句
這兩句的意思大家一眼就能看出來(lái),就是把從服務(wù)器獲取到的會(huì)話數(shù)組賦值給TableView的數(shù)據(jù)源淆衷,然后刷新TableView。因此排惨,我們?cè)谶@里做個(gè)手腳吭敢,把要屏蔽的人從數(shù)組里剔除就可以。
比如我們要屏蔽id為79或78的用戶(hù)暮芭,那么我們把這兩句替換為如下代碼
NSMutableArray * array = [[NSMutableArray alloc]initWithArray:conversations];
//將id為"78"或"79"的用戶(hù)聊天從最近聊天移除
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];
運(yùn)行項(xiàng)目鹿驼,發(fā)起和78或者79的聊天,然后再返回辕宏,大功告成
設(shè)置靜音
設(shè)置靜音是對(duì)會(huì)話的muted
屬性做操作畜晰,設(shè)置靜音后,當(dāng)該會(huì)話有新消息時(shí)將不會(huì)推送(如果你設(shè)置了推送的話)瑞筐,并且角標(biāo)會(huì)顯示一個(gè)小紅點(diǎn)凄鼻,而不是具體數(shù)字。
其實(shí)就是微信的免打擾功能聚假。
具體做法為在任何一個(gè)能夠獲取到conversation的地方調(diào)用
[conver muteWithCallback:^(BOOL succeeded, NSError *error) {
}];
取消靜音這么寫(xiě)
[conver unmuteWithCallback:^(BOOL succeeded, NSError *error) {
}];
設(shè)置靜音后的效果如下
標(biāo)簽欄顯示未讀消息數(shù)量
想在標(biāo)簽欄顯示未讀消息數(shù)块蚌,得先知道未讀消息的數(shù)目,我們繼續(xù)看這個(gè)refresh
方法膘格,發(fā)現(xiàn)這么幾句
在回調(diào)方法中已經(jīng)返回了未讀消息的總數(shù)峭范,并且CDChatListDelegate也提供了相應(yīng)的代理方法。
之前我們將ChatListViewController作為自身的代理瘪贱,因此我們實(shí)現(xiàn)這個(gè)代理方法即可纱控。我們給ChatListViewController添加如下方法
- (void)setBadgeWithTotalUnreadCount:(NSInteger)totalUnreadCount
{
if (totalUnreadCount == 0) {
[self.navigationController.tabBarItem setBadgeValue:nil];
}
else {
[self.navigationController.tabBarItem setBadgeValue:[NSString stringWithFormat:@"%ld",(long)totalUnreadCount]];
}
}
- 之前有人問(wèn)過(guò)我怎么給標(biāo)簽欄加這個(gè)數(shù)字角標(biāo)辆毡,網(wǎng)上也有各種定制方法,但其實(shí)tabBarItem自身就有一個(gè)
setBageVaule:
的方法用來(lái)設(shè)置角標(biāo)
頭像切圓角甜害、移動(dòng)角標(biāo)位置
為什么要把這兩項(xiàng)放在這里呢舶掖?
一來(lái)是頭像圓角顯示是現(xiàn)在的主流,二來(lái)默認(rèn)的角標(biāo)是顯示在頭像右上角的尔店,大家都知道圖片切圓角無(wú)非是對(duì)UIImageView的layer做剪裁眨攘,但是因?yàn)樗呀菢?biāo)直接加在了頭像上作為子控件,這樣的話角標(biāo)就無(wú)法顯示嚣州,因此我們還需要把角標(biāo)移個(gè)位置期犬。
LeanChatLib在這里用的是叫做LZConversationCell的類(lèi),但是這里我們不再選擇使用它避诽,因?yàn)樗鼊?chuàng)建控件的方式還蠻奇怪的,繼承改造很麻煩璃谨。再加上在TableView返回cell采用的策略也并不是很好沙庐,因此這里我們選擇重新創(chuàng)建自己的cell。
首先是創(chuàng)建帶Xib的UITableViewCell
然后在cell中完成布局(不一定非要按照LeanChatLib的來(lái))
然后把xib中的控件和代碼關(guān)聯(lián)起來(lái)佳吞,這里注意把最好把控件名字起的和LZConversationCell一樣拱雏,這樣一會(huì)可以省很大力氣。
- 這里解釋一下littleBadgeView和BadgeView,之前說(shuō)過(guò)如何設(shè)置會(huì)話的靜音底扳,當(dāng)新消息來(lái)了之后铸抑,如果會(huì)話是靜音的,那么就顯示littleBadgeView衷模,也就是小紅點(diǎn)鹊汛,如果非靜音,那么就顯示BadgeView阱冶,也就是角標(biāo)刁憋。LeanChatLib把他們兩個(gè)的位置設(shè)置的一樣,但是我們自己做的時(shí)候是可以隨意安排它的位置的木蹬。我這里是跟QQ一樣放在了cell的右側(cè)居中位置至耻。
引入頭文件
#import <JSBadgeView>
然后添加
@property (nonatomic, strong) JSBadgeView *badgeView;
,
我們一會(huì)在代碼中處理它
再次引入頭文件
#import <AVIMConversation.h>
再添加一條成員屬性
@property(nonatomic,strong)AVIMConversation * conversation;
最后頭文件中是這樣的
實(shí)現(xiàn)ConversationCell.m
的awakeFromNib
方法
- (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];
}
然后重寫(xiě)conversation
的set方法
將ChatListViewController中返回cell的代理方法中的圖中框起來(lái)的內(nèi)容剪切(沒(méi)有錯(cuò)镊叁,就是剪切)過(guò)來(lái)尘颓,并把 cell. 都改成 self. 。
并添加以下頭文件
#import <AVIMConversation+Custom.h>
#import <CDChatManager.h>
#import <UIView+XHRemoteImage.h>
#import <CDMessageHelper.h>
#import <NSDate+DateTools.h>
- 到了這一步在ChatListViewController中對(duì)應(yīng)的頭文件已經(jīng)可以刪除了晦譬,而且你會(huì)發(fā)現(xiàn)CDChatListVC引用了兩次
UIView+XHRemoteImage.h
疤苹,雖然沒(méi)啥影響,但看來(lái)LeanCloud的程序員也蠻粗心的....
set方法中的內(nèi)容
- (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的類(lèi)方法并在.m實(shí)現(xiàn)蛔添,代碼如下
+ (instancetype)cellForRowWithTableView:(UITableView *)tableView
{
NSString * className = NSStringFromClass([self class]);
[tableView registerNib:[UINib nibWithNibName:className bundle:nil] forCellReuseIdentifier:className];
return [tableView dequeueReusableCellWithIdentifier:className];
}
最后一步痰催,在ChatViewController中引入我們寫(xiě)好的cell
然后在返回cell的代理方法中這么寫(xiě)
- (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;
}
- 這里回報(bào)一個(gè)警告說(shuō)類(lèi)型不正確兜辞,是因?yàn)長(zhǎng)eanChatLib把參數(shù)類(lèi)型寫(xiě)死寫(xiě)成了LZConversationCell,但是沒(méi)關(guān)系夸溶,這個(gè)代理方法依然可以正常使用..
現(xiàn)在運(yùn)行項(xiàng)目逸吵,查看效果
圖片切成了圓角(我這個(gè)圖片邊角都是白色所以不顯眼。缝裁。扫皱。),角標(biāo)也移到了右側(cè)
同時(shí)我們還整理了一下返回cell的代碼捷绑,讓結(jié)構(gòu)看起來(lái)更好一點(diǎn)
- 這里我們花了很大力氣自己寫(xiě)了一個(gè)Cell用來(lái)展示最近會(huì)話韩脑,其實(shí)聰明的同學(xué)已經(jīng)想到了,這個(gè)conversation其實(shí)就是一個(gè)數(shù)據(jù)模型嘛粹污,因此如果你愿意段多,conversation中的任何屬性你都可以以各種方式展現(xiàn),不必拘泥于原有的形式壮吩。這樣就實(shí)現(xiàn)了最近消息列表的自定制
煩人的狀態(tài)欄
LeanCloud目前的連接狀態(tài)我還沒(méi)有搞的很明白进苍,總之如果你長(zhǎng)時(shí)間沒(méi)有活動(dòng)或程序處于后臺(tái)一段時(shí)間,就會(huì)和服務(wù)器斷開(kāi)連接鸭叙,如果你又有了刷新或者發(fā)消息的操作它就會(huì)重新連接觉啊。但是,這都不是關(guān)鍵沈贝,關(guān)鍵是它那個(gè)狀態(tài)欄是作為T(mén)ableView的HeaderView存在的杠人,因此如果你在HeaderView上加了其它控件會(huì)被這個(gè)狀態(tài)欄覆蓋掉。另外宋下,在我看來(lái)嗡善,一個(gè)聊天動(dòng)不動(dòng)就顯示斷線對(duì)用戶(hù)體驗(yàn)也不友好,因此我們要想辦法干掉這個(gè)狀態(tài)欄学歧。
其實(shí)方法很簡(jiǎn)單滤奈,我們只要在ChatViewController中重寫(xiě)updateStatusView
方法,然后啥也不做就行了撩满。就是這么簡(jiǎn)單蜒程!
解決線程阻塞問(wèn)題
之前說(shuō)過(guò),聊天列表中的頭像昵稱(chēng)等信息是通過(guò)CDChatManager的代理方法返回一個(gè)user對(duì)象獲取的伺帘,一般在項(xiàng)目中我們都會(huì)返回自己后臺(tái)保存的昵稱(chēng)和頭像地址昭躺,但是LeanChatLib這里是這么獲取User的
- 這句話現(xiàn)在已經(jīng)被移到了我們剛才自定義的ConversationCell的SetConversation中
那么在UserFactory中我們就要寫(xiě)一個(gè)網(wǎng)絡(luò)訪問(wèn)請(qǐng)求來(lái)返回,不管你寫(xiě)的是NSSURLSession還是NSURLConnect伪嫁,如果網(wǎng)絡(luò)環(huán)境不好的話领炫,就有可能阻塞線程。因此這里我們需要做一點(diǎn)處理张咳,讓它去異步獲取User帝洪。我們使用最簡(jiǎn)答的方法似舵,利用NSOperationQueue來(lái)實(shí)現(xiàn)
首先給Cell添加NSOperationQueue
@property(nonatomic,strong)NSOperationQueue * operationQueue;
在從xib加載cell的時(shí)候?qū)⑺跏蓟?/p>
- (void)awakeFromNib {
.....原來(lái)的代碼
_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]];
}];
}
然后運(yùn)行代碼,看不出什么差別葱峡。但是在你最近聊天列表內(nèi)容比較多或者網(wǎng)絡(luò)狀況不好的時(shí)候砚哗,不會(huì)出現(xiàn)卡住不動(dòng)的情況。
為聊天室添加功能
之前說(shuō)過(guò)砰奕,聊天室的界面其實(shí)沒(méi)有什么要改動(dòng)的地方蛛芥,我們要做的無(wú)非就是點(diǎn)擊頭像查看對(duì)方詳細(xì)信息,點(diǎn)擊消息或長(zhǎng)按消息出現(xiàn)觸發(fā)相應(yīng)事件军援。對(duì)于這些操作仅淑,LeanChatLib提供的各種代理方法基本足夠了。各種代理方法如下圖所示胸哥,紅框框起來(lái)的是經(jīng)常用到的涯竟。
舉個(gè)例子,我們?cè)贑hatRoomViewController中實(shí)現(xiàn)點(diǎn)擊頭像的代理方法如下
- (void)didSelectedAvatorOnMessage:(id<XHMessageModel>)message atIndexPath:(NSIndexPath *)indexPath
{
NSLog(@"sender is %@",message.sender);
}
可以看到我們打印出了發(fā)送者的id空厌,這樣我們就可以根據(jù)這個(gè)id跳轉(zhuǎn)到對(duì)應(yīng)用戶(hù)的詳情界面去昆禽,是不是很簡(jiǎn)單?
其它的代理方法大家可以一個(gè)一個(gè)去試蝇庭,這里就不在贅述
到了這里,LeanCloud即時(shí)通訊模塊在iOS端的基本應(yīng)用和改造方法已經(jīng)都教給大家了捡硅。為了能夠比較簡(jiǎn)單的接入IM功能哮内,我們大量使用或繼承了LeanChatLib中提供的類(lèi),但是LeanCloud的清晰的后臺(tái)業(yè)務(wù)邏輯和前臺(tái)提供的豐富的接口才是我認(rèn)為它最吸引人的地方壮韭,有興趣的同學(xué)可以自己研究研究北发。
這篇的代碼在這里[https://github.com/RunningChicken-K/LeanCloudDemo_02]
- 如果我的文章對(duì)您有幫助,請(qǐng)點(diǎn)贊或評(píng)論
- 如有不對(duì)喷屋,歡迎指正
- 我們下篇文章見(jiàn)