iOS开发之微信聊天页面实现
在上篇博客(iOS开发之微信聊天工具栏的封装)中对微信聊天页面下方的工具栏进行了封装,本篇博客中就使用之前封装的工具栏来进行聊天页面的编写。在聊天页面中主要用到了TableView的知识,还有如何在俩天中显示我们发送的表情,具体请参考之前的博客:IOS开发之显示微博表情,在这儿就不做赘述啦。在聊天页面用到了三对,六种Cell,不过cell的复杂度要比之前的新浪微博(IOS开发之新浪围脖)简单的多。废话少说吧,还是先来几张效果图,在给出实现代码吧。
聊天界面的效果图如下:在下面的聊天界面中中用到了3类cell,一类是显示文字和表情的,一类是显示录音的,一类是显示图片的。当点击图片时会跳转到另一个Controller中来进行图片显示,在图片显示页面中添加了一个捏合的手势(关于手势,请参考:iOS开发之手势识别)。点击播放按钮,会播放录制的音频,cell的大学会根据内容的多少来调整,而cell中textView的高度是通过约束来设置的。
一,定义我们要用的cell,代码如下:
1,显示表情和text的cell,代码如下,需要根据NSMutableAttributedString求出bound,然后改变cell上的ImageView和TextView的宽度的约束值,动态的调整气泡的大小,具体代码如下:
1 #import "TextCell.h" 2 3 @interface TextCell() 4 5 @property (strong, nonatomic) IBOutlet UIImageView *headImageView; 6 @property (strong, nonatomic) IBOutlet UIImageView *chatBgImageView; 7 @property (strong, nonatomic) IBOutlet UITextView *chatTextView; 8 @property (strong, nonatomic) IBOutlet NSLayoutConstraint *chatBgImageWidthConstraint; 9 @property (strong, nonatomic) IBOutlet NSLayoutConstraint *chatTextWidthConstaint; 10 @property (strong, nonatomic) NSMutableAttributedString *attrString; 11 12 @end 13 14 @implementation TextCell 15 16 -(void)setCellValue:(NSMutableAttributedString *)str 17 { 18 //移除约束 19 [self removeConstraint:_chatBgImageWidthConstraint]; 20 [self removeConstraint:_chatTextWidthConstaint]; 21 22 self.attrString = str; 23 NSLog(@"%@",self.attrString); 24 25 //由text计算出text的宽高 26 CGRect bound = [self.attrString boundingRectWithSize:CGSizeMake(150, 1000) options:NSStringDrawingUsesLineFragmentOrigin context:nil]; 27 28 //根据text的宽高来重新设置新的约束 29 //背景的宽 30 NSString *widthImageString; 31 NSArray *tempArray; 32 33 widthImageString = [NSString stringWithFormat:@"H:[_chatBgImageView(%f)]", bound.size.width+45]; 34 tempArray = [NSLayoutConstraint constraintsWithVisualFormat:widthImageString options:0 metrics:0 views:NSDictionaryOfVariableBindings(_chatBgImageView)]; 35 _chatBgImageWidthConstraint = tempArray[0]; 36 [self addConstraint:self.chatBgImageWidthConstraint]; 37 38 widthImageString = [NSString stringWithFormat:@"H:[_chatTextView(%f)]", bound.size.width+20]; 39 tempArray = [NSLayoutConstraint constraintsWithVisualFormat:widthImageString options:0 metrics:0 views:NSDictionaryOfVariableBindings(_chatTextView)]; 40 _chatBgImageWidthConstraint = tempArray[0]; 41 [self addConstraint:self.chatBgImageWidthConstraint]; 42 43 //设置图片 44 UIImage *image = [UIImage imageNamed:@"chatfrom_bg_normal.png"]; 45 image = [image resizableImageWithCapInsets:(UIEdgeInsetsMake(image.size.height * 0.6, image.size.width * 0.4, image.size.height * 0.3, image.size.width * 0.4))]; 46 47 //image = [image stretchableImageWithLeftCapWidth:image.size.width * 0.5 topCapHeight:image.size.height * 0.5]; 48 49 50 51 [self.chatBgImageView setImage:image]; 52 53 self.chatTextView.attributedText = str; 54 55 56 } 57 58 @end
2.显示图片的cell,通过block回调把图片传到Controller中,用于放大图片使用。
1 #import "MyImageCell.h" 2 3 @interface MyImageCell() 4 @property (strong, nonatomic) IBOutlet UIImageView *bgImageView; 5 @property (strong, nonatomic) IBOutlet UIButton *imageButton; 6 @property (strong, nonatomic) ButtonImageBlock imageBlock; 7 @property (strong, nonatomic) UIImage *buttonImage; 8 9 @end 10 11 @implementation MyImageCell 12 13 -(void)setCellValue:(UIImage *)sendImage 14 { 15 self.buttonImage = sendImage; 16 UIImage *image = [UIImage imageNamed:@"chatto_bg_normal.png"]; 17 image = [image resizableImageWithCapInsets:(UIEdgeInsetsMake(image.size.height * 0.6, image.size.width * 0.4, image.size.height * 0.3, image.size.width * 0.4))]; 18 [self.bgImageView setImage:image]; 19 [self.imageButton setImage:sendImage forState:UIControlStateNormal]; 20 21 } 22 23 -(void)setButtonImageBlock:(ButtonImageBlock)block 24 { 25 self.imageBlock = block; 26 } 27 28 - (IBAction)tapImageButton:(id)sender { 29 self.imageBlock(self.buttonImage); 30 } 31 32 @end
3.显示录音的cell,点击cell上的button,播放对应的录音,代码如下:
1 #import "VoiceCellTableViewCell.h" 2 3 @interface VoiceCellTableViewCell() 4 5 @property (strong, nonatomic) NSURL *playURL; 6 @property (strong, nonatomic) AVAudioPlayer *audioPlayer; 7 8 @end 9 10 @implementation VoiceCellTableViewCell 11 12 -(void)setCellValue:(NSDictionary *)dic 13 { 14 _playURL = dic[@"body"][@"content"]; 15 } 16 17 - (IBAction)tapVoiceButton:(id)sender { 18 19 20 NSError *error = nil; 21 AVAudioPlayer *player = [[AVAudioPlayer alloc]initWithContentsOfURL:_playURL error:&error]; 22 if (error) { 23 NSLog(@"播放错误:%@",[error description]); 24 } 25 self.audioPlayer = player; 26 [self.audioPlayer play]; 27 } 28 @end
二,cell搞定后要实现我们的ChatController部分
1.ChatController.m中的延展和枚举代码如下:
1 //枚举Cell类型 2 typedef enum : NSUInteger { 3 SendText, 4 SendVoice, 5 SendImage 6 } MySendContentType; 7 8 9 //枚举用户类型 10 typedef enum : NSUInteger { 11 MySelf, 12 MyFriend 13 } UserType; 14 15 @interface ChatViewController () 16 17 //工具栏 18 @property (nonatomic,strong) ToolView *toolView; 19 20 //音量图片 21 @property (strong, nonatomic) UIImageView *volumeImageView; 22 23 //工具栏的高约束,用于当输入文字过多时改变工具栏的约束 24 @property (strong, nonatomic) NSLayoutConstraint *tooViewConstraintHeight; 25 26 //存放所有的cell中的内容 27 @property (strong, nonatomic) NSMutableArray *dataSource; 28 29 //storyBoard上的控件 30 @property (strong, nonatomic) IBOutlet UITableView *myTableView; 31 32 //用户类型 33 @property (assign, nonatomic) UserType userType; 34 35 //从相册获取图片 36 @property (strong, nonatomic) UIImagePickerController *imagePiceker; 37 38 @end
2.实现工具栏中的回调的代码如下,通过Block,工具栏和ViewController交互,具体ToolView的Block实现,请参考上一篇博客(iOS开发之微信聊天工具栏的封装),聊天工具栏使用代码如下:
1 //实现工具栏的回调 2 -(void)setToolViewBlock 3 { 4 __weak __block ChatViewController *copy_self = self; 5 //通过block回调接收到toolView中的text 6 [self.toolView setMyTextBlock:^(NSString *myText) { 7 NSLog(@"%@",myText); 8 9 [copy_self sendMessage:SendText Content:myText]; 10 }]; 11 12 13 //回调输入框的contentSize,改变工具栏的高度 14 [self.toolView setContentSizeBlock:^(CGSize contentSize) { 15 [copy_self updateHeight:contentSize]; 16 }]; 17 18 19 //获取录音声量,用于声音音量的提示 20 [self.toolView setAudioVolumeBlock:^(CGFloat volume) { 21 22 copy_self.volumeImageView.hidden = NO; 23 int index = (int)(volume*100)%6+1; 24 [copy_self.volumeImageView setImage:[UIImage imageNamed:[NSString stringWithFormat:@"record_animate_%02d.png",index]]]; 25 }]; 26 27 //获取录音地址(用于录音播放方法) 28 [self.toolView setAudioURLBlock:^(NSURL *audioURL) { 29 copy_self.volumeImageView.hidden = YES; 30 31 [copy_self sendMessage:SendVoice Content:audioURL]; 32 }]; 33 34 //录音取消(录音取消后,把音量图片进行隐藏) 35 [self.toolView setCancelRecordBlock:^(int flag) { 36 if (flag == 1) { 37 copy_self.volumeImageView.hidden = YES; 38 } 39 }]; 40 41 42 //扩展功能回调 43 [self.toolView setExtendFunctionBlock:^(int buttonTag) { 44 switch (buttonTag) { 45 case 1: 46 //从相册获取 47 [copy_self presentViewController:copy_self.imagePiceker animated:YES completion:^{ 48 49 }]; 50 break; 51 case 2: 52 //拍照 53 break; 54 55 default: 56 break; 57 } 58 }]; 59 }
3.把聊天工具栏中返回的内容显示在tableView中,代码如下:
1 //发送消息 2 -(void)sendMessage:(MySendContentType) sendType Content:(id)content 3 { 4 5 //把收到的url封装成字典 6 UserType userType = self.userType; 7 8 NSMutableDictionary *tempDic = [[NSMutableDictionary alloc] initWithCapacity:2]; 9 [tempDic setValue:@(userType) forKey:@"userType"]; 10 11 NSDictionary *bodyDic = @{@"type":@(sendType), 12 @"content":content}; 13 [tempDic setValue:bodyDic forKey:@"body"]; 14 [self.dataSource addObject:tempDic]; 15 16 //重载tableView 17 [self.myTableView reloadData]; 18 19 NSIndexPath *indexPath = [NSIndexPath indexPathForRow:self.dataSource.count-1 inSection:0]; 20 21 [self.myTableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionBottom animated:YES]; 22 23 24 }
4.根据ToolView中回调接口,获取工具栏中textView的ContentSize,通过ContentSize来调整ToolView的高度约束,代码如下:
1 //更新toolView的高度约束 2 -(void)updateHeight:(CGSize)contentSize 3 { 4 float height = contentSize.height + 18; 5 if (height <= 80) { 6 [self.view removeConstraint:self.tooViewConstraintHeight]; 7 8 NSString *string = [NSString stringWithFormat:@"V:[_toolView(%f)]", height]; 9 10 NSArray * tooViewConstraintV = [NSLayoutConstraint constraintsWithVisualFormat:string options:0 metrics:0 views:NSDictionaryOfVariableBindings(_toolView)]; 11 self.tooViewConstraintHeight = tooViewConstraintV[0]; 12 [self.view addConstraint:self.tooViewConstraintHeight]; 13 } 14 }
5.从本地获取图片,并显示在相应的Cell上,代码如下:
1 //获取图片后要做的方法 2 -(void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info 3 { 4 UIImage *pickerImage = info[UIImagePickerControllerEditedImage]; 5 6 //发送图片 7 [self sendMessage:SendImage Content:pickerImage]; 8 9 [self dismissViewControllerAnimated:YES completion:^{}]; 10 11 } 12 13 -(void)imagePickerControllerDidCancel:(UIImagePickerController *)picker 14 { 15 //在ImagePickerView中点击取消时回到原来的界面 16 [self dismissViewControllerAnimated:YES completion:^{}]; 17 }
6.把NSString 转换成NSMutableAttributeString,用于显示表情,代码如下:
1 //显示表情,用属性字符串显示表情 2 -(NSMutableAttributedString *)showFace:(NSString *)str 3 { 4 //加载plist文件中的数据 5 NSBundle *bundle = [NSBundle mainBundle]; 6 //寻找资源的路径 7 NSString *path = [bundle pathForResource:@"emoticons" ofType:@"plist"]; 8 //获取plist中的数据 9 NSArray *face = [[NSArray alloc] initWithContentsOfFile:path]; 10 11 //创建一个可变的属性字符串 12 13 NSMutableAttributedString *attributeString = [[NSMutableAttributedString alloc] initWithString:str]; 14 15 UIFont *baseFont = [UIFont systemFontOfSize:17]; 16 [attributeString addAttribute:NSFontAttributeName value:baseFont 17 range:NSMakeRange(0, str.length)]; 18 19 //正则匹配要替换的文字的范围 20 //正则表达式 21 NSString * pattern = @"\\[[a-zA-Z0-9\\u4e00-\\u9fa5]+\\]"; 22 NSError *error = nil; 23 NSRegularExpression * re = [NSRegularExpression regularExpressionWithPattern:pattern options:NSRegularExpressionCaseInsensitive error:&error]; 24 25 if (!re) { 26 NSLog(@"%@", [error localizedDescription]); 27 } 28 29 //通过正则表达式来匹配字符串 30 NSArray *resultArray = [re matchesInString:str options:0 range:NSMakeRange(0, str.length)]; 31 32 33 //用来存放字典,字典中存储的是图片和图片对应的位置 34 NSMutableArray *imageArray = [NSMutableArray arrayWithCapacity:resultArray.count]; 35 36 //根据匹配范围来用图片进行相应的替换 37 for(NSTextCheckingResult *match in resultArray) { 38 //获取数组元素中得到range 39 NSRange range = [match range]; 40 41 //获取原字符串中对应的值 42 NSString *subStr = [str substringWithRange:range]; 43 44 for (int i = 0; i < face.count; i ++) 45 { 46 if ([face[i][@"chs"] isEqualToString:subStr]) 47 { 48 49 //face[i][@"gif"]就是我们要加载的图片 50 //新建文字附件来存放我们的图片 51 NSTextAttachment *textAttachment = [[NSTextAttachment alloc] init]; 52 53 //给附件添加图片 54 textAttachment.image = [UIImage imageNamed:face[i][@"png"]]; 55 56 //把附件转换成可变字符串,用于替换掉源字符串中的表情文字 57 NSAttributedString *imageStr = [NSAttributedString attributedStringWithAttachment:textAttachment]; 58 59 //把图片和图片对应的位置存入字典中 60 NSMutableDictionary *imageDic = [NSMutableDictionary dictionaryWithCapacity:2]; 61 [imageDic setObject:imageStr forKey:@"image"]; 62 [imageDic setObject:[NSValue valueWithRange:range] forKey:@"range"]; 63 64 //把字典存入数组中 65 [imageArray addObject:imageDic]; 66 67 } 68 } 69 } 70 71 //从后往前替换 72 for (int i = imageArray.count -1; i >= 0; i--) 73 { 74 NSRange range; 75 [imageArray[i][@"range"] getValue:&range]; 76 //进行替换 77 [attributeString replaceCharactersInRange:range withAttributedString:imageArray[i][@"image"]]; 78 79 } 80 81 return attributeString; 82 }
7.根据Cell显示内容来调整Cell的高度,代码如下:
1 //调整cell的高度 2 -(float)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath 3 { 4 5 //根据文字计算cell的高度 6 if ([self.dataSource[indexPath.row][@"body"][@"type"] isEqualToNumber:@(SendText)]) { 7 NSMutableAttributedString *contentText = [self showFace:self.dataSource[indexPath.row][@"body"][@"content"]]; 8 9 CGRect textBound = [contentText boundingRectWithSize:CGSizeMake(150, 1000) options:NSStringDrawingUsesLineFragmentOrigin context:nil]; 10 11 float height = textBound.size.height + 40; 12 return height; 13 } 14 if ([self.dataSource[indexPath.row][@"body"][@"type"] isEqualToNumber:@(SendVoice)]) 15 { 16 return 73; 17 } 18 19 if ([self.dataSource[indexPath.row][@"body"][@"type"] isEqualToNumber:@(SendImage)]) 20 { 21 return 125; 22 } 23 24 return 100; 25 }
8.根据cell内容和用户类型,来选择Cell,代码如下:
1 //设置cell 2 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 3 { 4 //根据类型选cell 5 MySendContentType contentType = [self.dataSource[indexPath.row][@"body"][@"type"] integerValue]; 6 7 8 if ([self.dataSource[indexPath.row][@"userType"] isEqual: @(MyFriend)]) { 9 switch (contentType) { 10 case SendText: 11 { 12 TextCell *cell = [tableView dequeueReusableCellWithIdentifier:@"textCell" forIndexPath:indexPath]; 13 NSMutableAttributedString *contentText = [self showFace:self.dataSource[indexPath.row][@"body"][@"content"]]; 14 [cell setCellValue:contentText]; 15 return cell; 16 } 17 break; 18 19 case SendImage: 20 { 21 heImageCell *cell = [tableView dequeueReusableCellWithIdentifier:@"heImageCell" forIndexPath:indexPath]; 22 [cell setCellValue:self.dataSource[indexPath.row][@"body"][@"content"]]; 23 24 25 __weak __block ChatViewController *copy_self = self; 26 27 //传出cell中的图片 28 [cell setButtonImageBlock:^(UIImage *image) { 29 [copy_self displaySendImage:image]; 30 }]; 31 return cell; 32 } 33 break; 34 35 case SendVoice: 36 { 37 VoiceCellTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"heVoiceCell" forIndexPath:indexPath]; 38 [cell setCellValue:self.dataSource[indexPath.row]]; 39 return cell; 40 } 41 42 break; 43 44 default: 45 break; 46 } 47 48 } 49 50 51 if ([self.dataSource[indexPath.row][@"userType"] isEqual: @(MySelf)]) { 52 53 switch (contentType) { 54 case SendText: 55 { 56 TextCell *cell = [tableView dequeueReusableCellWithIdentifier:@"myselfTextCell" forIndexPath:indexPath]; 57 NSMutableAttributedString *contentText = [self showFace:self.dataSource[indexPath.row][@"body"][@"content"]]; 58 [cell setCellValue:contentText]; 59 return cell; 60 } 61 break; 62 63 case SendImage: 64 { 65 MyImageCell *cell = [tableView dequeueReusableCellWithIdentifier:@"myImageCell" forIndexPath:indexPath]; 66 [cell setCellValue:self.dataSource[indexPath.row][@"body"][@"content"]]; 67 68 __weak __block ChatViewController *copy_self = self; 69 70 //传出cell中的图片 71 [cell setButtonImageBlock:^(UIImage *image) { 72 [copy_self displaySendImage:image]; 73 }]; 74 75 76 return cell; 77 } 78 break; 79 80 case SendVoice: 81 { 82 VoiceCellTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"myVoiceCell" forIndexPath:indexPath]; 83 [cell setCellValue:self.dataSource[indexPath.row]]; 84 return cell; 85 } 86 87 break; 88 89 default: 90 break; 91 } 92 } 93 UITableViewCell *cell; 94 return cell; 95 }
9.点击发送的图片来放大图片代码如下:
1 //发送图片的放大 2 -(void) displaySendImage : (UIImage *)image 3 { 4 //把照片传到放大的controller中 5 UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"Main" bundle:[NSBundle mainBundle]]; 6 7 ImageViewController *imageController = [storyboard instantiateViewControllerWithIdentifier:@"imageController"]; 8 [imageController setValue:image forKeyPath:@"image"]; 9 10 [self.navigationController pushViewController:imageController animated:YES]; 11 12 13 }
10.根据键盘的高度来调整ToolView的位置,代码如下:
1 //键盘出来的时候调整tooView的位置 2 -(void) keyChange:(NSNotification *) notify 3 { 4 NSDictionary *dic = notify.userInfo; 5 6 7 CGRect endKey = [dic[@"UIKeyboardFrameEndUserInfoKey"] CGRectValue]; 8 //坐标系的转换 9 CGRect endKeySwap = [self.view convertRect:endKey fromView:self.view.window]; 10 //运动时间 11 [UIView animateWithDuration:[dic[UIKeyboardAnimationDurationUserInfoKey] floatValue] animations:^{ 12 13 [UIView setAnimationCurve:[dic[UIKeyboardAnimationCurveUserInfoKey] doubleValue]]; 14 CGRect frame = self.view.frame; 15 16 frame.size.height = endKeySwap.origin.y; 17 18 self.view.frame = frame; 19 [self.view layoutIfNeeded]; 20 }]; 21 }
三,代码有点多,不过在关键的部分都加有注释,在图片显示View中通过捏合手势来调整图片的大小,代码如下:
1 - (IBAction)tapPichGesture:(id)sender { 2 UIPinchGestureRecognizer *gesture = sender; 3 4 //手势改变时 5 if (gesture.state == UIGestureRecognizerStateChanged) 6 { 7 8 //捏合手势中scale属性记录的缩放比例 9 self.myImageView.transform = CGAffineTransformMakeScale(gesture.scale, gesture.scale); 10 } 11 12 }
上面的东西是在本地做的测试,没有加上XMPP即时通讯协议,以后的博客会通过服务器转发来进行聊天,并且会继续对微信进行完善,感兴趣的小伙伴继续关注吧。转载请注明出处。
Demo地址:https://github.com/lizelu/WeChat
作者:青玉伏案
出处:http://www.cnblogs.com/ludashi/
本文版权归作者和共博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
如果文中有什么错误,欢迎指出。以免更多的人被误导。
收简历:坐标美团(北京总部),长期招聘FE/iOS/Android靠谱工程师,入职后,可内部联系楼主,有小礼品赠送,有意者可邮箱投递简历:zeluli@foxmail.com