iOS聊天室 简单的对话聊天界面(cell自适应高度)

难点

  • 因为聊天长度不一样,需要设置自适应高度
  • 发送信息后,需要使tableView添加一条cell,并更新
  • cell的所有子视图需要清除,否则会有bug(在最后会附上不清除子视图的效果)
  • 键盘弹出界面上移,点击空白处键盘回收,界面下移

思路

  • 聊天界面的对话其实就是一个tableView,创建一个可变数组记录每句话的高度,根据话语的高度设置单元格高度
  • 按发送键时插入一条新cell在最底端,获取该条对话的高度,存入数组,并让界面随着消息上移
  • 对话的label和聊天气泡的imageView随着对话的长度改变位置(这里没有什么一定的距离,自己观察在适合的顺眼的地方就成)

需要用到的方法的大致解析(只是简单的介绍,如果想要仔细理解推荐再去看看别的博客)

有的内容是我从别的博客中看到的,如有侵权请私聊我
有些我认为不太容易理解的方法在代码中也会有注释

  • boundingRectWithSize: options: attributes: context:
    用于计算自适应高度
    P1:文本显示的最大宽度和最大高度
    P2:计算的类型 NSStringDrawingUsesLineFragmentOrigin 绘制文本时使用,一般使用这项
    P3:文本属性
    P4:包括一些信息,例如如何调整字间距以及缩放。该参数一般可为 nil

  • NSDictionary *attri = @{NSFontAttributeName:[UIFont systemFontOfSize:18]};
    设置字典数组字体大小为18
    另外, 方法 NSForegroundColorAttributeName 为设置字典数组字体颜色
    NSBackgroundColorAttributeName: 设置背景颜色

  • insertRowsAtIndexPaths: withRowAnimation:
    在索引路径处插入行
    P1: 想要该行数之后
    P2: 指定插入单元格时要执行的动画类型
    此处我们使用UITableViewRowAnimationBottom动画类型,从底部滑入或滑出
    另外:
    UITableViewRowAnimationFade, 淡入或淡出UITableViewRowAnimationRight, 从右侧滑入或滑出 UITableViewRowAnimationLeft,从左侧滑入或滑出UITableViewRowAnimationTop, 从顶部滑入或滑出UITableViewRowAnimationBottom, 从底部滑入或滑出UITableViewRowAnimationNone, 使用默认动画,上部或下部cell,上下移动覆盖掉要删除的cell
    可参考简书 : UITableViewRowAnimation 描述及Demo

  • NSNotificationCenter
    观察者,在该代码中我们用于监测键盘的弹出及回收
    具体了解可看苹果公司: NSNotificationCenter

  • scrollToRowAtIndexPath: atScrollPosition: animated:
    滚动视图至指定位置,该代码中我们用它来滚动视图(随着消息上移)
    P1: 索引行
    P2:标识row滚动结束时表视图中的相对位置
    P3: 是否产生动画效果(即缓冲)
    另外:
    UITableViewScrollPositionNone 表格视图以最小的移动滚动感兴趣的行,使其完全可见。如果该行已完全可见,则不会进行滚动
    UITableViewScrollPositionTop 表视图将感兴趣的行滚动到可见表视图的顶部。
    UITableViewScrollPositionMiddle 表视图将感兴趣的行滚动到可见表视图的中间。
    UITableViewScrollPositionBottom 表视图将感兴趣的行滚动到可见表视图的底部。

  • animateWithDuration: animations:
    P1:动画持续时间
    P2:方法, 这里让视图恢复原来的位置就好

前面说了这么多,下来放代码

GitHub地址

chat

代码

此处我采用导航栏跳转的方式跳转至该界面,在第一个界面中设置点击跳转事件就好

ViewController.m 里:

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    
    ChatViewController *chat = [[ChatViewController alloc] init];
    
    _nav = [[UINavigationController alloc] initWithRootViewController:chat];
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
   
    [self presentViewController:_nav animated:NO completion:nil];
}

下来在 ChatViewController.h 中声明需要的属性及协议:

@interface ChatViewController : UIViewController
<
UITableViewDelegate,
UITableViewDataSource,
UITextFieldDelegate
>

@property UITextField *textField;
@property UITableView *tableView;
@property NSMutableArray *messageArr;
@property (nonatomic) NSNumber *rowHeight;
@property NSMutableArray *rowHeightArr;

在 ChatViewController.m 里写具体操作:

首先,为了方便起见,宏定义屏幕的宽高

#define W ([UIScreen mainScreen].bounds.size.width)
#define H ([UIScreen mainScreen].bounds.size.height)

在 - (void)viewDidLoad 里初始化

self.view.backgroundColor = [UIColor whiteColor];
    self.navigationController.navigationBar.barTintColor = [UIColor colorWithRed:0.21 green:0.56 blue:0.8 alpha:1.0];
    self.navigationItem.title = @"chat";
    [self.navigationController.navigationBar setTitleTextAttributes:@{NSForegroundColorAttributeName:[UIColor whiteColor], NSForegroundColorAttributeName:[UIFont systemFontOfSize:18]}];
    
    //导航栏左侧按钮
    UIButton *backbutton = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, 40, 40)];
    [backbutton setImage:[UIImage imageNamed:@"back.png"] forState:UIControlStateNormal];
    [backbutton addTarget:self action:@selector(back) forControlEvents:UIControlEventAllTouchEvents];
    UIBarButtonItem *backItem = [[UIBarButtonItem alloc] initWithCustomView:backbutton];
    self.navigationItem.leftBarButtonItem = backItem;
    
    //设置输入框
    _textField = [[UITextField alloc] initWithFrame:CGRectMake(W * 0.07, H * 0.94, W * 0.75, H * 0.06)];
    _textField.borderStyle = UITextBorderStyleRoundedRect;
    _textField.layer.borderColor = [UIColor blackColor].CGColor;
    _textField.delegate = self;
    
    //设置发送按钮
    UIButton *sendButton = [UIButton buttonWithType:UIButtonTypeCustom];
    sendButton.backgroundColor = [UIColor colorWithRed:0.27 green:0.55 blue:0.8 alpha:1.0];
    [sendButton setTitle:@"发送" forState:UIControlStateNormal];
    [sendButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
    [sendButton addTarget:self action:@selector(send) forControlEvents:UIControlEventTouchDown];
    sendButton.frame = CGRectMake(W * 0.83, H * 0.94, W * 0.15, H * 0.06);
    sendButton.layer.borderWidth = 1;
    sendButton.layer.cornerRadius = 10;
    
   /* UIView *view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, W, H - 108)];
    view.backgroundColor = [UIColor blackColor];
    view.tag = 101;*/
    [self.view addSubview:_textField];
    [self.view addSubview:sendButton];
    
    _tableView = [[UITableView alloc] initWithFrame:CGRectMake(0, 0, W, H - 88) style:UITableViewStylePlain];
    _tableView.delegate = self;
    _tableView.dataSource = self;
    //设置分割线(设置为无样式)
    _tableView.separatorStyle = UITableViewCellAccessoryNone;
    _tableView.showsVerticalScrollIndicator = NO;
    [self.view addSubview:_tableView];
//    [self.view addSubview:view];
//    [self.view bringSubviewToFront:view];
    
    //设置聊天信息数值
    _messageArr = [NSMutableArray arrayWithObjects:@"选择总是会有代价的,承受它就好了", @"当你见到我时,我已是更好的自己", @"自律并不是一个什么远在天边的大词儿,它是你每一天每一分钟,能在那些不想做的一瞬间,说服自己咬着牙继续坚持下去", @"永远有期待", nil];
    _rowHeightArr = [[NSMutableArray alloc] init];
    for (NSString *str in _messageArr) {
        //因为boundingRectWithSize: options: attributes: context: 函数中参数三需要使用字典数组
        //P1:文本显示的最大宽度和最大高度
        //P2:计算的类型 NSStringDrawingUsesLineFragmentOrigin 绘制文本时使用,一般使用这项
        //P3:文本属性
        //P4:包括一些信息,例如如何调整字间距以及缩放。该参数一般可为 nil
        NSDictionary *attri = @{NSFontAttributeName:[UIFont systemFontOfSize:18]};
        CGSize size = [str boundingRectWithSize:CGSizeMake(W * 0.6, H * 0.41) options:NSStringDrawingUsesLineFragmentOrigin attributes:attri context:nil].size;
        //聊天框高度,+ W * 0.15为了保持会话之间的距离
        int height = size.height + W * 0.15;
        _rowHeight = [NSNumber numberWithInt:height];
        //存储在数组里,设置行高时使用
        [_rowHeightArr addObject:_rowHeight];
    }
    //监视键盘回收
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillAppear:) name:UIKeyboardWillShowNotification object:nil];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillDisAppear:) name:UIKeyboardWillHideNotification object:nil];

创建button发送事件

- (void)send{
    [_messageArr addObject:_textField.text];
    NSDictionary *attri = @{NSFontAttributeName:[UIFont systemFontOfSize:18]};
    //自适应高度,并计算
    CGSize size = [_textField.text boundingRectWithSize:CGSizeMake(W * 0.6, H * 0.58) options:NSStringDrawingUsesLineFragmentOrigin attributes:attri context:nil].size;
    int height = size.height + W * 0.15;
    _rowHeight = [NSNumber numberWithInt:height];
    [_rowHeightArr addObject:_rowHeight];
    //_messageArr.count - 1 : 显示的最后一行
    NSIndexPath *indexPath = [NSIndexPath indexPathForRow:(_messageArr.count - 1) inSection:0];
    //加入一个cell
    [self.tableView insertRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationBottom];
    //更新tableView
    [_tableView reloadData];
    //滚动界面(随着消息发送上移)
    [self.tableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionBottom animated:YES];
    //清空textField
    _textField.text = @"";
    
}

!!!!!!核心: cell的使用:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
    
    static NSString *cellId = @"cell";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellId];
    if(!cell){
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:cellId];
    } else {
        //tableView的复用,如果不删除,会出现bug
        //删除cell所有的子视图
        while ([cell.contentView.subviews lastObject] != nil) {
            [(UIView *)[cell.contentView.subviews lastObject] removeFromSuperview];
        }
    }
    //分割线风格(无显示)
    cell.selectionStyle = UITableViewCellSelectionStyleNone;
    //一人一句话
    if(indexPath.row % 2 != 0){
        //设置头像
        UIImageView *imageView = [[UIImageView alloc]initWithImage:[UIImage imageNamed: @"image1.jpg"]];
        imageView.frame = CGRectMake(W * 0.01, W * 0.05, W * 0.1, W * 0.1);
        [cell.contentView addSubview:imageView];
        
        //设置对话框
        UILabel *label = [[UILabel alloc] init];
        label.numberOfLines = 0;
        label.text = _messageArr[indexPath.row];
        label.font = [UIFont systemFontOfSize:18];
        NSDictionary *attri = @{NSFontAttributeName:label.font};
        //自适应高度
        CGSize size = [label.text boundingRectWithSize:CGSizeMake(W * 0.6, H * 0.58) options:NSStringDrawingUsesLineFragmentOrigin attributes:attri context:nil].size;
        label.frame = CGRectMake(W * 0.13, W * 0.07, size.width, size.height + W * 0.05);
        
        //设置聊天气泡
        UIImageView *imageViewBubble = [[UIImageView alloc] init];
        imageViewBubble.backgroundColor = [UIColor colorWithRed:0.94 green:0.94 blue:0.94 alpha:1.0];
        imageViewBubble.frame = CGRectMake(W * 0.12, W * 0.07, size.width + W * 0.05, size.height + W * 0.03);
        
        [cell.contentView addSubview:imageViewBubble];
        [cell.contentView addSubview:label];
        
    } else {
        
        UIImageView *imageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"image5.jpg"]];
        imageView.frame = CGRectMake(W * 0.89, W * 0.01, W * 0.1, W * 0.1);
        [cell.contentView addSubview:imageView];
        
        UILabel *label = [[UILabel alloc] init];
        label.numberOfLines = 0;
        label.text = _messageArr[indexPath.row];
        label.font = [UIFont systemFontOfSize:18];
        NSDictionary *attri = @{NSFontAttributeName:label.font};
        CGSize size = [label.text boundingRectWithSize:CGSizeMake(W * 0.6, H * 0.58) options:NSStringDrawingUsesLineFragmentOrigin attributes:attri context:nil].size;
        label.frame = CGRectMake(W * 0.86 - size.width, W * 0.05, size.width, size.height);
        
        UIImageView *imageViewBubble = [[UIImageView alloc] init];
        imageViewBubble.backgroundColor = [UIColor colorWithRed:0.94 green:0.94 blue:0.94 alpha:1.0];
        imageViewBubble.frame = CGRectMake(W * 0.82 - size.width, W * 0.03, size.width + W * 0.05, size.height + W * 0.03);
        
        [cell.contentView addSubview:imageViewBubble];
        [cell.contentView addSubview:label];
        
    }
    
    return cell;
    
}

//设置单元格高度
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
    //将NSNumber型的height转换为CGFloat型
    CGFloat height = [_rowHeightArr[indexPath.row] floatValue];
    return height;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return _messageArr.count;
}

下来回收键盘:

//显示简单的键盘回收
- (void)keyboardWillDisAppear:(NSNotification *)notification{
    //第一个参数是动画持续时间
    //第二个参数是方法,这里让视图恢复原来的位置就好
    [UIView animateWithDuration:1 animations:^{self.view.transform = CGAffineTransformMakeTranslation(0, 0);}];
    
}

- (void)keyboardWillAppear:(NSNotification *)notification{
    //计算键盘高度
    CGRect keyboardFrame = [notification.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue];
    CGFloat keyboardY = keyboardFrame.origin.y;
    //视图整体上升
    [UIView animateWithDuration:1.0 animations:^{self.view.transform = CGAffineTransformMakeTranslation(0, keyboardY - self.view.frame.size.height);}];
}

//点击空白处回收键盘
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    [_textField endEditing:YES];
}

//点击return回收键盘
- (BOOL)textFieldShouldReturn:(UITextField *)textField {
    [textField endEditing:YES];
    return YES;
}

最后加上返回按钮事件:

- (void)back{
    [self dismissViewControllerAnimated:NO completion:nil];
}

效果图

这是我在初始化时加入的话:
在这里插入图片描述
这是我发送几句话后的效果:
在这里插入图片描述
若不清除cell的子视图,则会产生这样的bug:
在这里插入图片描述

  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值