开发者社区> 青玉伏案> 正文
阿里云
为了无法计算的价值
打开APP
阿里云APP内打开

iOS开发之微信聊天页面实现

简介:
+关注继续查看

  在上篇博客iOS开发之微信聊天工具栏的封装中对微信聊天页面下方的工具栏进行了封装,本篇博客中就使用之前封装的工具栏来进行聊天页面的编写。在聊天页面中主要用到了TableView的知识,还有如何在俩天中显示我们发送的表情,具体请参考之前的博客:IOS开发之显示微博表情,在这儿就不做赘述啦。在聊天页面用到了三对,六种Cell,不过cell的复杂度要比之前的新浪微博(IOS开发之新浪围脖)简单的多。废话少说吧,还是先来几张效果图,在给出实现代码吧。

  聊天界面的效果图如下:在下面的聊天界面中中用到了3类cell,一类是显示文字和表情的,一类是显示录音的,一类是显示图片的。当点击图片时会跳转到另一个Controller中来进行图片显示,在图片显示页面中添加了一个捏合的手势(关于手势,请参考:iOS开发之手势识别)。点击播放按钮,会播放录制的音频,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部分    

    1.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

    2.实现工具栏中的回调的代码如下,通过Block,工具栏和ViewController交互,具体ToolView的Block实现,请参考上一篇博客(iOS开发之微信聊天工具栏的封装),聊天工具栏使用代码如下:
//实现工具栏的回调
-(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中,代码如下:
//发送消息
-(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的高度约束,代码如下:
//更新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上,代码如下:
//获取图片后要做的方法
-(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,用于显示表情,代码如下:
//显示表情,用属性字符串显示表情
-(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;
}

    7.根据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;
 }

    8.根据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;
}

    9.点击发送的图片来放大图片代码如下:
//发送图片的放大
-(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的位置,代码如下:
//键盘出来的时候调整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);
    }
    
}

  上面的东西是在本地做的测试,没有加上XMPP即时通讯协议,以后的博客会通过服务器转发来进行聊天,并且会继续对微信进行完善,感兴趣的小伙伴继续关注吧。转载请注明出处。

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
带你读《Python Django Web典型模块 开发实战》之二:用Django REST framework实现
本书内容涵盖了收费API业务模型的开发、网站防爬虫策略、网站违禁词自查系统的搭建、会员系统的搭建、前后端分离项目的上线部署等大大小小十余个项目模块分析,可以基本解决Django学习者从理论到实践过渡过程中经常会遇到的大部分问题。
1471 0
微信公众号开发--.Net Core实现微信消息加解密
1:准备工作 进入微信公众号后台设置微信服务器配置参数(注意:Token和EncodingAESKey必须和微信服务器验证参数保持一致,不然验证不会通过)。 2:基本配置 设置为安全模式 3、代码实现(主要分为验证接口和消息处理接口): /// /// 验证接口 /// ...
1526 0
用rem实现响应式页面开发
rem是什么?     rem(font size of the root element)是指相对于根元素的字体大小的单位。简单的说它就是一个相对单位。看到rem大家一定会想起em单位,em(font size of the element)是指相对于父元素的字体大小的单位。
3673 0
IOS开发---菜鸟学习之路--(十八)-利用代理实现向上一级页面传递数据
其实我一开始是想实现微信的修改个人信息那样的效果 就是点击昵称,然后跳转到另外一个页面输入信息 但是细想发现微信的话应该是修改完一个信息后就保存了 而我做的项目可能需要输入多个数据之后再点击提交的。
694 0
iOS开发那些事-故事板实现标签导航
<p><strong>故事板实现标签导航</strong></p> <p>用故事板技术实现标签导航模式。</p> <p><span id="more-244"></span></p> <p>使用“Tabbed Application”模版,创建一个名为 “TabNavigationStoryborad”的工程。勾选“Use Storyborads”、“Use Automatic Referen
1168 0
iOS开发那些事-nib实现标签导航
<p><strong>nib实现标签导航</strong></p> <p>在目前的Xcode(4.5)版本创建标签导航的应用可以使用工程模板 Tabbed Application。我们可以选择故事板或nib技术实现。故事板创建固然简单,但这种实现方式屏蔽了标签导航很多技术细节,为了更有益于我们学习, 在实现环节我们分别采用了nib技术和storyboard技术。首先我们来看看用nib技术
1027 0
linux下面的j2sdk的安装和配置过程
linux下面的j2sdk的安装和配置过程!   1.
848 0
+关注
青玉伏案
在技术的世界中,一直在路上~
135
文章
0
问答
文章排行榜
最热
最新
相关电子书
更多
低代码开发师(初级)实战教程
立即下载
阿里巴巴DevOps 最佳实践手册
立即下载
冬季实战营第三期:MySQL数据库进阶实战
立即下载