iOS开发之微信聊天页面实现
聊天界面的效果图如下:在下面的聊天界面中中用到了3类cell,一类是显示文字和表情的,一类是显示录音的,一类是显示图片的。当点击图片时会跳转到另一个Controller中来进行图片显示,在图片显示页面中添加了一个捏合的手势。点击播放按钮,会播放录制的音频,cell的大学会根据内容的多少来调整,而cell中textView的高度是通过约束来设置的。 一,定义我们要用的cell,代码如下: 1,显示表情和text的cell,代码如下,需要根据NSMutableAttributedString求出bound,然后改变cell上的ImageView和TextView的宽度的约束值,动态的调整气泡的大小,具体代码如下: #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中,用于放大图片使用。 #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,播放对应的录音,代码如下: #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部分 ChatController.m中的延展和枚举代码如下: //枚举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 实现工具栏中的回调的代码如下,通过Block,工具栏和ViewController交互 //实现工具栏的回调-(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; } }];} 把聊天工具栏中返回的内容显示在tableView中,代码如下: //发送消息-(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]; } 根据ToolView中回调接口,获取工具栏中textView的ContentSize,通过ContentSize来调整ToolView的高度约束,代码如下: //更新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]; }} 从本地获取图片,并显示在相应的Cell上,代码如下: //获取图片后要做的方法-(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:^{}];} 把NSString 转换成NSMutableAttributeString,用于显示表情,代码如下: //显示表情,用属性字符串显示表情-(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 < face.count; 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;} 根据Cell显示内容来调整Cell的高度,代码如下: //调整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; } 根据cell内容和用户类型,来选择Cell,代码如下: //设置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;} 点击发送的图片来放大图片代码如下: //发送图片的放大-(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]; } 根据键盘的高度来调整ToolView的位置,代码如下: //键盘出来的时候调整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中通过捏合手势来调整图片的大小,代码如下: - (IBAction)tapPichGesture:(id)sender { UIPinchGestureRecognizer *gesture = sender; //手势改变时 if (gesture.state == UIGestureRecognizerStateChanged) { //捏合手势中scale属性记录的缩放比例 self.myImageView.transform = CGAffineTransformMakeScale(gesture.scale, gesture.scale); } } 更多iOS开发之微信聊天页面实现 相关文章请关注PHP中文网! |