圖片和音頻文件發送的基本思路就是:
先將圖片轉化成二進制文件,然后將二進制文件進行base64編碼,編碼后成字符串。在即將發送的message內添加一個子節點,節點的stringValue(節點的值)設置這個編碼后的字符串。然后消息發出后取出消息文件的時候,通過messageType 先判斷是不是圖片信息,如果是圖片信息先通過自己之前設置的節點名稱,把這個子節點的stringValue取出來,應該是一個base64之后的字符串,
往期回顧:
xmpp整理筆記:聊天信息的發送與顯示 http://www.cnblogs.com/dsxniubility/p/4307073.html
xmpp整理筆記:環境的快速配置(附安裝包) http://www.cnblogs.com/dsxniubility/p/4304570.html
xmpp整理筆記:xmppFramework框架的導入和介紹 http://www.cnblogs.com/dsxniubility/p/4307057.html
xmpp整理筆記:用戶網絡連接及好友管理 http://www.cnblogs.com/dsxniubility/p/4307066.html
一。圖片發送
如果你不是在董鉑然博客園看到本文,請點擊查看原文
圖片是通過界面的加號點擊彈出相冊界面,然后點擊相冊中的某張圖片,相冊退下,圖片發出
- (IBAction)setPhoto { UIImagePickerController *picker = [[UIImagePickerController alloc]init]; picker.delegate = self; [self presentViewController:picker animated:YES completion:nil]; }
這是加號點擊方法,之后設置UIImagePickerController的代理,然后再遵守對應的協議
這里需要注意的是,遵守了UIImagePickerControllerDelegate的 同時還必須要遵守 UINavigationControllerDelegate。協議
下面就是彈出相冊點擊了一張圖片后觸發的代理方法,都是常用方法在此也不過多解釋。
#pragma mark - ******************** imgPickerController代理方法 - (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info { UIImage *image = info[UIImagePickerControllerOriginalImage]; NSData *data = UIImagePNGRepresentation(image); [self sendMessageWithData:data bodyName:@"image"]; [self dismissViewControllerAnimated:YES completion:nil]; }
其中的sendMessageWithData: bodyName: 是自定義的方法
此方法的功能就是傳入一個data二進制文件 和 文件的類型,就把這個文件發出去。
之所有在后面有bodyName,讓用戶傳入一個類型名,是為了區分發送圖片和發送音頻
方法內代碼如下:
/** 發送二進制文件 */ - (void)sendMessageWithData:(NSData *)data bodyName:(NSString *)name { XMPPMessage *message = [XMPPMessage messageWithType:@"chat" to:self.chatJID]; [message addBody:name]; // 轉換成base64的編碼 NSString *base64str = [data base64EncodedStringWithOptions:0]; // 設置節點內容 XMPPElement *attachment = [XMPPElement elementWithName:@"attachment" stringValue:base64str]; // 包含子節點 [message addChild:attachment]; // 發送消息 [[SXXMPPTools sharedXMPPTools].xmppStream sendElement:message]; }
這個方法內流程就是一開始說得,先編碼再發送。這個自定義的方法同樣適用於發送音頻信息。
二。圖片的顯示
這個是在tableView數據源方法中,取出信息即將賦值之前多了一層判斷,如果是圖片信息,采用下面的方法賦值。
關於基本發送流程哪里忘了可以查看普通文本信息的發送方法:http://www.cnblogs.com/dsxniubility/p/4307073.html
if ([message.body isEqualToString:@"image"]) { XMPPMessage *msg = message.message; for (XMPPElement *node in msg.children) { // 取出消息的解碼 NSString *base64str = node.stringValue; NSData *data = [[NSData alloc]initWithBase64EncodedString:base64str options:0]; UIImage *image = [[UIImage alloc]initWithData:data]; // 把圖片在label中顯示 NSTextAttachment *attach = [[NSTextAttachment alloc]init]; attach.image = [image scaleImageWithWidth:200]; NSAttributedString *attachStr = [NSAttributedString attributedStringWithAttachment:attach]; // 用了這個label的屬性賦值方法,就可以忽略那個普通的賦值方法 cell.messageLabel.attributedText = attachStr; [self.view endEditing:YES]; } }
這其中用到了一個 scaleImageWithWidth:方法,這個方法是傳入一個允許的最大寬度width,然后這個方法內部先判斷,如片大小是否超過最大值,如果沒有超過最大值就是圖片有多大發多大,如果圖片的尺寸超過了最大寬度,就把圖片的整體尺寸都等比例縮小到正好等於最大寬度的尺寸。這其中要用到Quartz2D的上下文的知識。
這個方法可以寫成UIimage的分類,代碼如下
/** 把圖片縮小到指定的寬度范圍內為止 */ - (UIImage *)scaleImageWithWidth:(CGFloat)width{ if (self.size.width <width || width <= 0) { return self; } CGFloat scale = self.size.width/width; CGFloat height = self.size.height/scale; CGRect rect = CGRectMake(0, 0, width, height); // 開始上下文 目標大小是 這么大 UIGraphicsBeginImageContext(rect.size); // 在指定區域內繪制圖像 [self drawInRect:rect]; // 從上下文中獲得繪制結果 UIImage *resultImage = UIGraphicsGetImageFromCurrentImageContext(); // 關閉上下文返回結果 UIGraphicsEndImageContext(); return resultImage; }
三。音頻的發送
音頻的發送,與之前圖片的發送,有一定的相似,也有一些不同。音頻發送的核心思想,是按下按鈕開始錄音,松開手結束錄音並且保存錄音。因此需要處理按鈕的按下和抬手兩個監聽方法。但是其中有一個蘋果的bug: 自定義的按鈕無法同時處理TouchUpInSide 和 TouchDown。 就是按下按鈕不松手是一個打印,手一松開一個打印。這是不行的,都是手一松兩個同時打印。(除非按鈕特別大,一般小按鈕無法同時監聽這兩個點擊事件)。但是蘋果自帶的系統按鈕卻可以,不管多小,比如buttonWithTypeAdd(小加號按鈕)都可以,因此設置點擊聲音按鈕之后下面出現一個inputView,上面是可以同時處理這兩個時間的按鈕。通過這個按鈕來控制開始錄音和結束錄音。保存之后,也是轉化成data二進制文件,然后再通過base64編碼。然后加入子節點,和圖片類似發過去。接收的時候,也是取出節點內的stringValue解碼。但是顯示在tableview的cell中的是聲音的時間,點擊這個cell觸發聲音播放時間。從而播放音頻。播放時cell內部的某些樣式變化也是可以控制的。
先把界面中的聲音按鈕的點擊事件連線。
- (IBAction)setRecord { // 切換焦點,彈出錄音按鈕 [self.recordText becomeFirstResponder]; }
其實就是自己隨便寫了個textField 點擊時就讓他獲取焦點,然后下面彈出一個輸入框上面有按鈕
這個textField的懶加載如下
- (UITextField *)recordText { if (_recordText == nil) { _recordText = [[UITextField alloc] init]; UIButton *btn = [UIButton buttonWithType:UIButtonTypeContactAdd]; _recordText.inputView = btn; [btn addTarget:self action:@selector(startRecord) forControlEvents:UIControlEventTouchDown]; [btn addTarget:self action:@selector(stopRecord) forControlEvents:UIControlEventTouchUpInside]; [self.inputMessageView addSubview:_recordText]; } return _recordText; }
對於音頻文件的一系列處理操作,最好抽出一個工具類寫好,然后在需要的時候直接調用,並且以后其他項目也可以拖過去直接使用。
首先需要用到的屬性如下。
@interface SXRecordTools ()<AVAudioPlayerDelegate> /** 錄音器 */ @property(nonatomic,strong) AVAudioRecorder *recorder; /** 錄音地址 */ @property(nonatomic,strong) NSURL *recordURL; /** 播放器 */ @property(nonatomic,strong) AVAudioPlayer *player; /** 播放完成時回調 */ @property(nonatomic,copy) void (^palyCompletion)(); @end
至於其中的開始錄音和結束錄音方法如下
/** 開始錄音 */ - (void)startRecord{ [self.recorder record]; } /** 停止錄音 */ - (void)stopRecordSuccess:(void (^)(NSURL *url,NSTimeInterval time))success andFailed:(void (^)())failed { // 只有在這里才能取到currentTime NSTimeInterval time = self.recorder.currentTime; [self.recorder stop]; if (time < 1.5) { if (failed) { failed(); } }else{ if (success) { success(self.recordURL,time); } } }
開始錄音和結束錄音,框架中都自己有方法。主要是判斷了一下,音頻的時長,小於1.5秒會回調錄音失敗的代碼塊。
這里需要注意的是, recorder.currentTime 當前錄音的時長,只有在這個方法中才能取到,出了方法就取不到值了。
然后在控制器中,那個小加號按鈕的按下和抬起的監聽方法中調用工具類中的方法
#pragma mark - ******************** 錄音方法 - (void)startRecord { NSLog(@"開始錄音"); [[SXRecordTools sharedRecorder] startRecord]; } - (void)stopRecord { NSLog(@"停止錄音"); [[SXRecordTools sharedRecorder] stopRecordSuccess:^(NSURL *url, NSTimeInterval time) { // 發送聲音數據 NSData *data = [NSData dataWithContentsOfURL:url]; [self sendMessageWithData:data bodyName:[NSString stringWithFormat:@"audio:%.1f秒", time]]; } andFailed:^{ [[[UIAlertView alloc] initWithTitle:@"提示" message:@"時間太短" delegate:nil cancelButtonTitle:@"確定" otherButtonTitles:nil, nil] show]; }]; }
可以清楚的看到,發送聲音調sendMessageWithData:時把聲音的時長當做參數bodyName 傳入。 然后就會將這個字符串存到message的子節點內發出。
四。音頻文件的顯示
也是和圖片一樣,對於本行取出的信息先判斷是不是音頻信息,如果是,遍歷節點,取出字符串,並且截取了一下,截取掉“audio:”,讓tableView的cell中只顯示 時長,
else if ([message.body hasPrefix:@"audio"]){ XMPPMessage *msg = message.message; for (XMPPElement *node in msg.children) { NSString *base64str = node.stringValue; NSData *data = [[NSData alloc]initWithBase64EncodedString:base64str options:0]; NSString *newstr = [message.body substringFromIndex:6]; cell.messageLabel.text = newstr; cell.audioData = data; } }
這個audioData是個專門用來存放聲音文件的信息。但是表格是可以重用的,為了讓一個剛剛重用的cell里面的音頻文件別形成沖突,疊加。建議在剛取出cell時就加上一行
cell.audioData = nil;
五。關於聲音文件的播放
雖然,框架自己就有聲音文件的播放方法,但是還需要做很多附加操作,建議先在工具類中寫一個方法,就是播放data文件,並且設置完成后的回調代碼。即playData:completion: 。在播放的方法中先判斷聲音是否正在播放,如果正在播放則不做任何操作。然后在方法中設置player的代理,這樣可以通過代理方法來監聽聲音文件何時播放完,觸發代理方法。因此這個傳入的completion代碼塊必須要先用成員變量記錄下,然后在聲音文件播放完的代理方法中再執行此代碼塊
- (void)playData:(NSData *)data completion:(void(^)())completion { // 判斷是否正在播放 if (self.player.isPlaying) { [self.player stop]; } // 記錄塊代碼 self.palyCompletion = completion; // 監聽播放器播放狀態 self.player = [[AVAudioPlayer alloc]initWithData:data error:NULL]; self.player.delegate = self; [self.player play]; }
代理方法在聲音文件播放完的代理方法中再執行保存的代碼塊
#pragma mark - ******************** 完成播放時的代理方法 - (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag { if (self.palyCompletion) { self.palyCompletion(); } }
工具類中的方法寫完了之后,可以去外面調用了。給自己這個自定義的SXChatCell添加一個點擊方法。默認情況下按鈕是默認顏色的,點擊時顏色變成紅色,然后播放完成時的回調代碼再把顏色恢復成默認顏色。
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { // 如果有音頻數據,直接播放音頻 if (self.audioData != nil) { // 播放音頻 self.messageLabel.textColor = [UIColor redColor]; // 如果單例的塊代碼中包含self,一定使用weakSelf __weak SXChatCell *weakSelf = self; [[SXRecordTools sharedRecorder] playData:self.audioData completion:^{ weakSelf.messageLabel.textColor = [UIColor blackColor]; }]; } }
如圖紅色的那個cell是正在播放
如果你不是在董鉑然博客園看到本文,請點擊查看原文
到此之后,就完成了完整的圖片及音頻文件的發送。