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
View Code

    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
View Code

    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
View Code

 

  二,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
View Code

    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 }
View Code

    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 }
View Code

    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 }
View Code

    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 }
View Code

    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 }
View Code

    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  }
View Code

    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 }
View Code

    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 }
View Code

    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 Code

 

  三,代碼有點多,不過在關鍵的部分都加有注釋,在圖片顯示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 }
View Code

  

  上面的東西是在本地做的測試,沒有加上XMPP即時通訊協議,以后的博客會通過服務器轉發來進行聊天,並且會繼續對微信進行完善,感興趣的小伙伴繼續關注吧。轉載請注明出處。

  Demo地址:https://github.com/lizelu/WeChat


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM