前言
最近公司要做IM, 先后試了環(huán)信和極光的IM, 當(dāng)然UI也是用他們的, 后來(lái)溝通不符合公司的要求就不用了, 然后用朋友公司搭建的服務(wù)器, 隨之而來(lái)的界面也就要自己寫(xiě)(想用環(huán)信Demo來(lái)著, 想想還是自己寫(xiě)吧, 畢竟這是一次挑戰(zhàn)) 不廢話,直接上項(xiàng)目:FMChatUI 歡迎前來(lái)評(píng)論交流, 感覺(jué)還可以就star?? 下面就開(kāi)始 這篇先講一下整體的一個(gè)思路(項(xiàng)目中的類的作用)
Controller里只有控制器, 顯示消息的
整個(gè)項(xiàng)目用的第三方庫(kù)是轉(zhuǎn)碼MP3的時(shí)候用的 lame
消息的Model類:
IMBaseItem
包含消息的頭像名字時(shí)間等等信息, 擁有著IMMessageBody
屬性 IMMessageBody
包含了正文的類型,是文本還是圖片, 視頻的鏈接, 本地緩存的名字等等
IMBaseItemTool
是為了將服務(wù)器返回的數(shù)據(jù)包裝成模型寫(xiě)的一個(gè)類, 不侵入到IMBaseItem
里
下面看看Tools里的
看文件名應(yīng)該就能知道啥作用了??
整個(gè)的文件數(shù)就這么多了
說(shuō)說(shuō)控制器的吧
控制器里的代碼主要就是加載數(shù)據(jù),顯示數(shù)據(jù),沒(méi)有復(fù)雜的邏輯
剛進(jìn)入這個(gè)控制器的時(shí)候就是加載數(shù)據(jù), 加載本地的還是服務(wù)器的取決實(shí)現(xiàn)的內(nèi)容,可以直接加載本地的,我的demo里就是這么寫(xiě)的,demo里是采用KVO刷新列表的,其實(shí)當(dāng)時(shí)我是想,這個(gè)數(shù)組改變就刷新列表,但是當(dāng)控制器擁有這個(gè)數(shù)組時(shí),加入模型的時(shí)候, KVO并不會(huì)執(zhí)行,簡(jiǎn)單說(shuō)一下KVO監(jiān)聽(tīng)數(shù)組的變化刷新列表的小技巧:
KVO監(jiān)聽(tīng)數(shù)組
- 將數(shù)組(既然觀察數(shù)組要變化,當(dāng)然是可變數(shù)組了)作為一個(gè)屬性
@property (nonatomic, strong) NSMutableArray *dataArray;
- 注冊(cè)觀察者和實(shí)現(xiàn)觀察者方法,如果是UITableviewController,可以這樣寫(xiě)
[self addObserver:self forKeyPath:@"dataArray" options:NSKeyValueObservingOptionNew context:NULL];
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
NSLog(@"keypath ==%@ object ==%@" ,keyPath, object );
}
- 使用下面這樣的方法來(lái)給數(shù)組添加數(shù)據(jù)或刪除數(shù)據(jù)
[[self mutableArrayValueForKey:@"dataArray"] addObject:@"adfasdf"];
- 移除觀察者
-(void)dealloc{
[self removeObserver:self forKeyPath:@"dataArray" context:NULL];
}
這樣就管插入就好了,會(huì)自動(dòng)刷新,不過(guò)這個(gè)在IM里似乎有一點(diǎn)小問(wèn)題,插入歷史數(shù)據(jù)的時(shí)候, 還是定位在插入前最頂部的那一行, 我這里是才用了保存一個(gè)值, 當(dāng)你需要插入的時(shí)候 這個(gè)值指定,刷新完之后,列表滾動(dòng)到某一行
連續(xù)播放未讀語(yǔ)音
demo里可以連續(xù)播放未讀的語(yǔ)音
具體點(diǎn)擊了哪一行,用代理回調(diào)的
上代碼先:
#pragma mark - 語(yǔ)音Cell點(diǎn)擊的回調(diào)
- (void)IMBaseCellClickAudioCell:(IMBaseCell *)cell{
NSIndexPath *indexPath = [self.tableView indexPathForCell:cell];
[self playAudioWithIndexPath:indexPath autoNext:YES];
}
/**
* 準(zhǔn)備播放
*
* @param indexPath 播放的哪一行
* @param next 是否播放下一行未讀
*/
- (void)playAudioWithIndexPath:(NSIndexPath *)indexPath autoNext:(BOOL)next{
IMBaseItem *message = [self.reloadTable mutableArrayValueForKey:@"messages"][indexPath.row];
WeakSelf
if (message.messageBody.locationPath && message.messageBody.locationPath.length > 0) {
message.isPlaying = YES;
[self reloadDataWithMessage:message isInser:NO];
[[IMAudioTool shareAudioTool] playWithFileName:message.messageBody.locationPath returnBeforeFileName:^(NSString *beforeFileName) {
[weakSelf cancelAudioPlayWithFileName:beforeFileName];
} withFinishBlock:^(BOOL isFinish) {
message.isPlaying = !isFinish;
message.readState = IMMessageReaded;
[weakSelf reloadDataWithMessage:message isInser:NO];
if (next) {
if ([weakSelf getNextIndexPathForUnReadAudioWithCurrentIndexPath:indexPath]) {
[weakSelf playAudioWithIndexPath:[weakSelf getNextIndexPathForUnReadAudioWithCurrentIndexPath:indexPath] autoNext:YES];
}
}
}];
} else {
NSString *fileName = [NSString stringWithFormat:@"%d%d.mp3", (int)[[NSDate date] timeIntervalSince1970], arc4random() % 100000];
WeakSelf
[IMDownloadManager downLoadFileUrl:message.messageBody.voiceUrlString datePath:[IMBaseAttribute dataAudioPath] fileName:fileName completionHandler:^(BOOL isSuccess) {
if (isSuccess) {
message.messageBody.locationPath = fileName;
[IMSQLiteTool updateMessage:message withKey:@"messageBody"];
message.isPlaying = YES;
[weakSelf reloadDataWithMessage:message isInser:NO];
[[IMAudioTool shareAudioTool] playWithFileName:message.messageBody.locationPath returnBeforeFileName:^(NSString *beforeFileName) {
[weakSelf cancelAudioPlayWithFileName:beforeFileName];
} withFinishBlock:^(BOOL isFinish) {
message.isPlaying = !isFinish;
message.readState = IMMessageReaded;
[weakSelf reloadDataWithMessage:message isInser:NO];
if (next) {
if ([weakSelf getNextIndexPathForUnReadAudioWithCurrentIndexPath:indexPath]) {
[weakSelf playAudioWithIndexPath:[weakSelf getNextIndexPathForUnReadAudioWithCurrentIndexPath:indexPath] autoNext:YES];
}
}
}];
} else {
NSLog(@"播放失敗");
}
}];
}
}
/**
* 獲取下一行未讀的IndexPath
*
* @param currentIndexPath 當(dāng)前播放的IndexPath
*
* @return 下一行未讀的IndexPath
*/
- (NSIndexPath *)getNextIndexPathForUnReadAudioWithCurrentIndexPath:(NSIndexPath *)currentIndexPath{
if (currentIndexPath.row < [self.reloadTable mutableArrayValueForKey:@"messages"].count - 1) {
for (NSInteger i = currentIndexPath.row + 1; i < [self.reloadTable mutableArrayValueForKey:@"messages"].count - 1; i++) {
IMBaseItem *item = [self.reloadTable mutableArrayValueForKey:@"messages"][i];
if (item.messageType == IMMessageAudioType && item.readState == IMMessageUnRead) {
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:i inSection:0];
return indexPath;
} else {
continue;
}
}
}
return nil;
}
各種判斷等等
這里要注意的就是點(diǎn)擊完播放, 并且語(yǔ)音開(kāi)始播放了, 沒(méi)有播放完畢的時(shí)候, 再點(diǎn)擊下一個(gè)語(yǔ)音的播放, 要獲取到上一個(gè)之前播放的語(yǔ)音, 并刷新列表
demo里有很多地方采用的是Block回調(diào), 代理也是完全可以實(shí)現(xiàn)的, 不過(guò)我覺(jué)得麻煩就這么寫(xiě)了,而且讓代碼高聚合
----------------------------- 先到這吧?? ----------------------------------
續(xù)篇:
即時(shí)通訊整個(gè)頁(yè)面的搭建(二)