在上篇博客(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