在上篇部落格(iOS開發之微信聊天工具欄的封裝)中對微信聊天頁面下方的工具欄進行了封裝,本篇部落格中就使用之前封裝的工具欄來進行聊天頁面的編寫。在聊天頁面中主要用到了TableView的知識,還有如何在倆天中顯示我們傳送的表情,具體請參考之前的部落格:IOS開發之顯示微博表情,在這兒就不做贅述啦。在聊天頁面用到了三對,六種Cell,不過cell的複雜度要比之前的新浪微博(IOS開發之新浪圍脖)簡單的多。廢話少說吧,還是先來幾張效果圖,在給出實現程式碼吧。
聊天介面的效果圖如下:在下面的聊天介面中中用到了3類cell,一類是顯示文字和表情的,一類是顯示錄音的,一類是顯示圖片的。當點選圖片時會跳轉到另一個Controller中來進行圖片顯示,在圖片顯示頁面中新增了一個捏合的手勢(關於手勢,請參考:iOS開發之手勢識別)。點選播放按鈕,會播放錄製的音訊,cell的大學會根據內容的多少來調整,而cell中textView的高度是通過約束來設定的。
一,定義我們要用的cell,程式碼如下:
1,顯示錶情和text的cell,程式碼如下,需要根據NSMutableAttributedString求出bound,然後改變cell上的ImageView和TextView的寬度的約束值,動態的調整氣泡的大小,具體程式碼如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
#import "TextCell.h" @interface TextCell() @property (strong, nonatomic) IBOutlet UIImageView *headImageView; @property (strong, nonatomic) IBOutlet UIImageView *chatBgImageView; @property (strong, nonatomic) IBOutlet UITextView *chatTextView; @property (strong, nonatomic) IBOutlet NSLayoutConstraint *chatBgImageWidthConstraint; @property (strong, nonatomic) IBOutlet NSLayoutConstraint *chatTextWidthConstaint; @property (strong, nonatomic) NSMutableAttributedString *attrString; @end @implementation TextCell -(void)setCellValue:(NSMutableAttributedString *)str { //移除約束 [self removeConstraint:_chatBgImageWidthConstraint]; [self removeConstraint:_chatTextWidthConstaint]; self.attrString = str; NSLog(@"%@",self.attrString); //由text計算出text的寬高 CGRect bound = [self.attrString boundingRectWithSize:CGSizeMake(150, 1000) options:NSStringDrawingUsesLineFragmentOrigin context:nil]; //根據text的寬高來重新設定新的約束 //背景的寬 NSString *widthImageString; NSArray *tempArray; widthImageString = [NSString stringWithFormat:@"H:[_chatBgImageView(%f)]", bound.size.width+45]; tempArray = [NSLayoutConstraint constraintsWithVisualFormat:widthImageString options:0 metrics:0 views:NSDictionaryOfVariableBindings(_chatBgImageView)]; _chatBgImageWidthConstraint = tempArray[0]; [self addConstraint:self.chatBgImageWidthConstraint]; widthImageString = [NSString stringWithFormat:@"H:[_chatTextView(%f)]", bound.size.width+20]; tempArray = [NSLayoutConstraint constraintsWithVisualFormat:widthImageString options:0 metrics:0 views:NSDictionaryOfVariableBindings(_chatTextView)]; _chatBgImageWidthConstraint = tempArray[0]; [self addConstraint:self.chatBgImageWidthConstraint]; //設定圖片 UIImage *image = [UIImage imageNamed:@"chatfrom_bg_normal.png"]; image = [image resizableImageWithCapInsets:(UIEdgeInsetsMake(image.size.height * 0.6, image.size.width * 0.4, image.size.height * 0.3, image.size.width * 0.4))]; //image = [image stretchableImageWithLeftCapWidth:image.size.width * 0.5 topCapHeight:image.size.height * 0.5]; [self.chatBgImageView setImage:image]; self.chatTextView.attributedText = str; } @end |
2.顯示圖片的cell,通過block回撥把圖片傳到Controller中,用於放大圖片使用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
#import "MyImageCell.h" @interface MyImageCell() @property (strong, nonatomic) IBOutlet UIImageView *bgImageView; @property (strong, nonatomic) IBOutlet UIButton *imageButton; @property (strong, nonatomic) ButtonImageBlock imageBlock; @property (strong, nonatomic) UIImage *buttonImage; @end @implementation MyImageCell -(void)setCellValue:(UIImage *)sendImage { self.buttonImage = sendImage; UIImage *image = [UIImage imageNamed:@"chatto_bg_normal.png"]; image = [image resizableImageWithCapInsets:(UIEdgeInsetsMake(image.size.height * 0.6, image.size.width * 0.4, image.size.height * 0.3, image.size.width * 0.4))]; [self.bgImageView setImage:image]; [self.imageButton setImage:sendImage forState:UIControlStateNormal]; } -(void)setButtonImageBlock:(ButtonImageBlock)block { self.imageBlock = block; } - (IBAction)tapImageButton:(id)sender { self.imageBlock(self.buttonImage); } @end |
3.顯示錄音的cell,點選cell上的button,播放對應的錄音,程式碼如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
#import "VoiceCellTableViewCell.h" @interface VoiceCellTableViewCell() @property (strong, nonatomic) NSURL *playURL; @property (strong, nonatomic) AVAudioPlayer *audioPlayer; @end @implementation VoiceCellTableViewCell -(void)setCellValue:(NSDictionary *)dic { _playURL = dic[@"body"][@"content"]; } - (IBAction)tapVoiceButton:(id)sender { NSError *error = nil; AVAudioPlayer *player = [[AVAudioPlayer alloc]initWithContentsOfURL:_playURL error:&error]; if (error) { NSLog(@"播放錯誤:%@",[error description]); } self.audioPlayer = player; [self.audioPlayer play]; } @end |
二,cell搞定後要實現我們的ChatController部分
1.ChatController.m中的延展和列舉程式碼如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
//列舉Cell型別 typedef enum : NSUInteger { SendText, SendVoice, SendImage } MySendContentType; //列舉使用者型別 typedef enum : NSUInteger { MySelf, MyFriend } UserType; @interface ChatViewController () //工具欄 @property (nonatomic,strong) ToolView *toolView; //音量圖片 @property (strong, nonatomic) UIImageView *volumeImageView; //工具欄的高約束,用於當輸入文字過多時改變工具欄的約束 @property (strong, nonatomic) NSLayoutConstraint *tooViewConstraintHeight; //存放所有的cell中的內容 @property (strong, nonatomic) NSMutableArray *dataSource; //storyBoard上的控制元件 @property (strong, nonatomic) IBOutlet UITableView *myTableView; //使用者型別 @property (assign, nonatomic) UserType userType; //從相簿獲取圖片 @property (strong, nonatomic) UIImagePickerController *imagePiceker; @end |
2.實現工具欄中的回撥的程式碼如下,通過Block,工具欄和ViewController互動,具體ToolView的Block實現,請參考上一篇部落格(iOS開發之微信聊天工具欄的封裝),聊天工具欄使用程式碼如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
//實現工具欄的回撥 -(void)setToolViewBlock { __weak __block ChatViewController *copy_self = self; //通過block回撥接收到toolView中的text [self.toolView setMyTextBlock:^(NSString *myText) { NSLog(@"%@",myText); [copy_self sendMessage:SendText Content:myText]; }]; //回撥輸入框的contentSize,改變工具欄的高度 [self.toolView setContentSizeBlock:^(CGSize contentSize) { [copy_self updateHeight:contentSize]; }]; //獲取錄音聲量,用於聲音音量的提示 [self.toolView setAudioVolumeBlock:^(CGFloat volume) { copy_self.volumeImageView.hidden = NO; int index = (int)(volume*100)%6+1; [copy_self.volumeImageView setImage:[UIImage imageNamed:[NSString stringWithFormat:@"record_animate_%02d.png",index]]]; }]; //獲取錄音地址(用於錄音播放方法) [self.toolView setAudioURLBlock:^(NSURL *audioURL) { copy_self.volumeImageView.hidden = YES; [copy_self sendMessage:SendVoice Content:audioURL]; }]; //錄音取消(錄音取消後,把音量圖片進行隱藏) [self.toolView setCancelRecordBlock:^(int flag) { if (flag == 1) { copy_self.volumeImageView.hidden = YES; } }]; //擴充套件功能回撥 [self.toolView setExtendFunctionBlock:^(int buttonTag) { switch (buttonTag) { case 1: //從相簿獲取 [copy_self presentViewController:copy_self.imagePiceker animated:YES completion:^{ }]; break; case 2: //拍照 break; default: break; } }]; } |
3.把聊天工具欄中返回的內容顯示在tableView中,程式碼如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
//傳送訊息 -(void)sendMessage:(MySendContentType) sendType Content:(id)content { //把收到的url封裝成字典 UserType userType = self.userType; NSMutableDictionary *tempDic = [[NSMutableDictionary alloc] initWithCapacity:2]; [tempDic setValue:@(userType) forKey:@"userType"]; NSDictionary *bodyDic = @{@"type":@(sendType), @"content":content}; [tempDic setValue:bodyDic forKey:@"body"]; [self.dataSource addObject:tempDic]; //過載tableView [self.myTableView reloadData]; NSIndexPath *indexPath = [NSIndexPath indexPathForRow:self.dataSource.count-1 inSection:0]; [self.myTableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionBottom animated:YES]; } |
4.根據ToolView中回撥介面,獲取工具欄中textView的ContentSize,通過ContentSize來調整ToolView的高度約束,程式碼如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
//更新toolView的高度約束 -(void)updateHeight:(CGSize)contentSize { float height = contentSize.height + 18; if (height 80) { [self.view removeConstraint:self.tooViewConstraintHeight]; NSString *string = [NSString stringWithFormat:@"V:[_toolView(%f)]", height]; NSArray * tooViewConstraintV = [NSLayoutConstraint constraintsWithVisualFormat:string options:0 metrics:0 views:NSDictionaryOfVariableBindings(_toolView)]; self.tooViewConstraintHeight = tooViewConstraintV[0]; [self.view addConstraint:self.tooViewConstraintHeight]; } } |
5.從本地獲取圖片,並顯示在相應的Cell上,程式碼如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
//獲取圖片後要做的方法 -(void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info { UIImage *pickerImage = info[UIImagePickerControllerEditedImage]; //傳送圖片 [self sendMessage:SendImage Content:pickerImage]; [self dismissViewControllerAnimated:YES completion:^{}]; } -(void)imagePickerControllerDidCancel:(UIImagePickerController *)picker { //在ImagePickerView中點選取消時回到原來的介面 [self dismissViewControllerAnimated:YES completion:^{}]; } |
6.把NSString 轉換成NSMutableAttributeString,用於顯示錶情,程式碼如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 |
//顯示錶情,用屬性字串顯示錶情 -(NSMutableAttributedString *)showFace:(NSString *)str { //載入plist檔案中的資料 NSBundle *bundle = [NSBundle mainBundle]; //尋找資源的路徑 NSString *path = [bundle pathForResource:@"emoticons" ofType:@"plist"]; //獲取plist中的資料 NSArray *face = [[NSArray alloc] initWithContentsOfFile:path]; //建立一個可變的屬性字串 NSMutableAttributedString *attributeString = [[NSMutableAttributedString alloc] initWithString:str]; UIFont *baseFont = [UIFont systemFontOfSize:17]; [attributeString addAttribute:NSFontAttributeName value:baseFont range:NSMakeRange(0, str.length)]; //正則匹配要替換的文字的範圍 //正規表示式 NSString * pattern = @"\[[a-zA-Z0-9\u4e00-\u9fa5]+\]"; NSError *error = nil; NSRegularExpression * re = [NSRegularExpression regularExpressionWithPattern:pattern options:NSRegularExpressionCaseInsensitive error:&error]; if (!re) { NSLog(@"%@", [error localizedDescription]); } //通過正規表示式來匹配字串 NSArray *resultArray = [re matchesInString:str options:0 range:NSMakeRange(0, str.length)]; //用來存放字典,字典中儲存的是圖片和圖片對應的位置 NSMutableArray *imageArray = [NSMutableArray arrayWithCapacity:resultArray.count]; //根據匹配範圍來用圖片進行相應的替換 for(NSTextCheckingResult *match in resultArray) { //獲取陣列元素中得到range NSRange range = [match range]; //獲取原字串中對應的值 NSString *subStr = [str substringWithRange:range]; for (int i = 0; i ) { if ([face[i][@"chs"] isEqualToString:subStr]) { //face[i][@"gif"]就是我們要載入的圖片 //新建文字附件來存放我們的圖片 NSTextAttachment *textAttachment = [[NSTextAttachment alloc] init]; //給附件新增圖片 textAttachment.image = [UIImage imageNamed:face[i][@"png"]]; //把附件轉換成可變字串,用於替換掉源字串中的表情文字 NSAttributedString *imageStr = [NSAttributedString attributedStringWithAttachment:textAttachment]; //把圖片和圖片對應的位置存入字典中 NSMutableDictionary *imageDic = [NSMutableDictionary dictionaryWithCapacity:2]; [imageDic setObject:imageStr forKey:@"image"]; [imageDic setObject:[NSValue valueWithRange:range] forKey:@"range"]; //把字典存入陣列中 [imageArray addObject:imageDic]; } } } //從後往前替換 for (int i = imageArray.count -1; i >= 0; i--) { NSRange range; [imageArray[i][@"range"] getValue:&range]; //進行替換 [attributeString replaceCharactersInRange:range withAttributedString:imageArray[i][@"image"]]; } return attributeString; } |
7.根據Cell顯示內容來調整Cell的高度,程式碼如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
//調整cell的高度 -(float)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { //根據文字計算cell的高度 if ([self.dataSource[indexPath.row][@"body"][@"type"] isEqualToNumber:@(SendText)]) { NSMutableAttributedString *contentText = [self showFace:self.dataSource[indexPath.row][@"body"][@"content"]]; CGRect textBound = [contentText boundingRectWithSize:CGSizeMake(150, 1000) options:NSStringDrawingUsesLineFragmentOrigin context:nil]; float height = textBound.size.height + 40; return height; } if ([self.dataSource[indexPath.row][@"body"][@"type"] isEqualToNumber:@(SendVoice)]) { return 73; } if ([self.dataSource[indexPath.row][@"body"][@"type"] isEqualToNumber:@(SendImage)]) { return 125; } return 100; } |
8.根據cell內容和使用者型別,來選擇Cell,程式碼如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 |
//設定cell - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { //根據型別選cell MySendContentType contentType = [self.dataSource[indexPath.row][@"body"][@"type"] integerValue]; if ([self.dataSource[indexPath.row][@"userType"] isEqual: @(MyFriend)]) { switch (contentType) { case SendText: { TextCell *cell = [tableView dequeueReusableCellWithIdentifier:@"textCell" forIndexPath:indexPath]; NSMutableAttributedString *contentText = [self showFace:self.dataSource[indexPath.row][@"body"][@"content"]]; [cell setCellValue:contentText]; return cell; } break; case SendImage: { heImageCell *cell = [tableView dequeueReusableCellWithIdentifier:@"heImageCell" forIndexPath:indexPath]; [cell setCellValue:self.dataSource[indexPath.row][@"body"][@"content"]]; __weak __block ChatViewController *copy_self = self; //傳出cell中的圖片 [cell setButtonImageBlock:^(UIImage *image) { [copy_self displaySendImage:image]; }]; return cell; } break; case SendVoice: { VoiceCellTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"heVoiceCell" forIndexPath:indexPath]; [cell setCellValue:self.dataSource[indexPath.row]]; return cell; } break; default: break; } } if ([self.dataSource[indexPath.row][@"userType"] isEqual: @(MySelf)]) { switch (contentType) { case SendText: { TextCell *cell = [tableView dequeueReusableCellWithIdentifier:@"myselfTextCell" forIndexPath:indexPath]; NSMutableAttributedString *contentText = [self showFace:self.dataSource[indexPath.row][@"body"][@"content"]]; [cell setCellValue:contentText]; return cell; } break; case SendImage: { MyImageCell *cell = [tableView dequeueReusableCellWithIdentifier:@"myImageCell" forIndexPath:indexPath]; [cell setCellValue:self.dataSource[indexPath.row][@"body"][@"content"]]; __weak __block ChatViewController *copy_self = self; //傳出cell中的圖片 [cell setButtonImageBlock:^(UIImage *image) { [copy_self displaySendImage:image]; }]; return cell; } break; case SendVoice: { VoiceCellTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"myVoiceCell" forIndexPath:indexPath]; [cell setCellValue:self.dataSource[indexPath.row]]; return cell; } break; default: break; } } UITableViewCell *cell; return cell; } |
9.點選傳送的圖片來放大圖片程式碼如下:
1 2 3 4 5 6 7 8 9 10 11 12 |
//傳送圖片的放大 -(void) displaySendImage : (UIImage *)image { //把照片傳到放大的controller中 UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"Main" bundle:[NSBundle mainBundle]]; ImageViewController *imageController = [storyboard instantiateViewControllerWithIdentifier:@"imageController"]; [imageController setValue:image forKeyPath:@"image"]; [self.navigationController pushViewController:imageController animated:YES]; } |
10.根據鍵盤的高度來調整ToolView的位置,程式碼如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
//鍵盤出來的時候調整tooView的位置 -(void) keyChange:(NSNotification *) notify { NSDictionary *dic = notify.userInfo; CGRect endKey = [dic[@"UIKeyboardFrameEndUserInfoKey"] CGRectValue]; //座標系的轉換 CGRect endKeySwap = [self.view convertRect:endKey fromView:self.view.window]; //運動時間 [UIView animateWithDuration:[dic[UIKeyboardAnimationDurationUserInfoKey] floatValue] animations:^{ [UIView setAnimationCurve:[dic[UIKeyboardAnimationCurveUserInfoKey] doubleValue]]; CGRect frame = self.view.frame; frame.size.height = endKeySwap.origin.y; self.view.frame = frame; [self.view layoutIfNeeded]; }]; } |
三,程式碼有點多,不過在關鍵的部分都加有註釋,在圖片顯示View中通過捏合手勢來調整圖片的大小,程式碼如下:
1 2 3 4 5 6 7 8 9 10 11 12 |
- (IBAction)tapPichGesture:(id)sender { UIPinchGestureRecognizer *gesture = sender; //手勢改變時 if (gesture.state == UIGestureRecognizerStateChanged) { //捏合手勢中scale屬性記錄的縮放比例 self.myImageView.transform = CGAffineTransformMakeScale(gesture.scale, gesture.scale); } } |
上面的東西是在本地做的測試,沒有加上XMPP即時通訊協議,以後的部落格會通過伺服器轉發來進行聊天,並且會繼續對微信進行完善,感興趣的小夥伴繼續關注吧。轉載請註明出處。
Demo地址:https://github.com/lizelu/WeChat
打賞支援我寫出更多好文章,謝謝!
打賞作者
打賞支援我寫出更多好文章,謝謝!