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

2023-05-16

文章目录

    • 难点
    • 思路
    • 需要用到的方法的大致解析(只是简单的介绍,如果想要仔细理解推荐再去看看别的博客)
    • GitHub地址
    • 代码
    • 效果图

难点

  • 因为聊天长度不一样,需要设置自适应高度
  • 发送信息后,需要使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:
在这里插入图片描述

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

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

随机推荐

  • 【sv与c】sv与c交互

    网上此类文章很多 xff0c 这里暂时不放具体实现和测试结果 xff0c 后续持续更新 下面引用一些帖子 xff0c 帖子中涉及到具体做法 vcs联合编译v sv c 43 43 代码 sxlwzl的专栏 CSDN博客 1 xff0c 假设
  • stm32f103c8t6最小系统

    提示 xff1a 文章写完后 xff0c 目录可以自动生成 xff0c 如何生成可参考右边的帮助文档 文章目录 前言stm32f103c8t6构成二 xff1a 电源电路稳压模块注意 复位电路NRST 时钟电路程序下载电路JTAGSWD 启
  • udp中的connect()&bind()

    connect amp bind 的作用 udp udp connect span class hljs preprocessor include lt sys types h gt span span class hljs preproc
  • Linux Hook技术实践

    LInux Hook技术实践 什么是hook 简单的说就是别人本来是执行libA so里面的函数的 xff0c 结果现在被偷偷换成了执行你的libB so里面的代码 xff0c 是一种替换 为什么hook 恶意代码注入调用常用库函数时打lo
  • Tensorflow Cnn mnist 的一些细节

    Tensorflow cnn MNIST 笔记 写这个完全是记录看官网example时不懂 xff0c 但后来弄懂的一些细节 当然这个可以算是对官方文档的补充 xff0c 也许每个人遇到的不懂都不一样 xff0c 但希望对大家有帮助 先上代
  • effective cpp 读书笔记2

    当你用到多态时 xff0c 务必把析构函数做成虚函数 以前知道子类的析构函数会自动调用父类的析构函数 xff0c 然而今天却发现不总是这样 当你用基类的指针指向派生类时 xff0c 然后delete这个指针时 xff0c 有可能就不会调用派
  • malloc与缺页的一些的时间测量

    malloc与缺页的一些的时间测量 时间函数 span class hljs keyword struct span timespec time t tv sec span class hljs keyword long span span
  • N皇后问题的并行解法

    N皇后问题的并行解法 N皇后问题 其实就是一个n n的棋盘上摆n个皇后 xff0c 任意两个皇后不能同行 xff0c 同列 xff0c 同对角线 xff0c 求出所有的摆法 这个问题可以看做是求n的组合数 比如第一列上的皇后在第x1行 xf
  • apache服务器使用时网页乱码问题

    查看原文 xff1a http www hellonet8 com 440 html 在apache的配置文件httpd conf中 xff0c 或许我们会用到 AddDefaultCharset UTF 8 来设置所有主机或者某虚拟主机的
  • 将群晖NAS变为本地盘

    本文介绍一个工具 xff0c 可以在 Windows 系统下将群晖NAS的目录变为本地盘 xff0c 好处是在外部访问的时候 xff0c 能够大大改善体验 可以用本地的应用程序直接打开 xff0c 速度依赖网络带宽 xff0c 正常情况下
  • Deepin | 修改网卡名 | 配置IP地址 | DHCP

    文章目录 修改网卡名解决方案 xff1a 配置IP地址解决方案 xff1a DHCP自动获取 修改网卡名 解决方案 xff1a 禁用该可预测命名规则 span class token function sudo span vim etc d
  • windows商店直接安装ubuntu子系统

    文章目录 安装报错WslRegisterDistribution failed with error 0x8007019eWSL安装Linux报错WslRegisterDistribution failed with error 0x803
  • 基于VTK的任意平面切割

    这位 小兵 太传奇了 xff0c 先收藏 xff0c 再学习 xff0c http www cnblogs com dawnWind archive 2013 02 17 3D 06 html 先贴码 以后再 切割介绍 对于一个模型的切割需
  • 百度OCR接口使用

    最近在研究ocr识别 也对比了一些的方法 现在来介绍一下 调用百度提供的ocr接口 小量调用的话 是不收费的 1 首先 你要有一个百度账号 如果已经有的话 登录进去会进入到这样一个界面 点击 34 创建应用 34 创建成功后 返回应用列表
  • Python 元组(tuple)剖析详解

    目录 概念元组的常见操作 概念 元组 span class token punctuation span 是一个有序 span class token punctuation span 可重复的集合 特点 span class token
  • Dom型XSS跨站脚本攻击和防御

    在前面的文章中 xff0c 我们讲了持久型XSS和反射型XSS 我个人觉得这些命名真的很贴切 xff0c 反应了概念的本质 无论是持久型XSS还是反射型XSS 恶意的js脚本内容都需要由服务端返回给用户 xff0c 今天我们要说的Dom型X
  • 编译错误导致浪费10多分钟, 编译错误的提示:xxx does not name a type xxx

    最近 xff0c 我在google protobuf 协议文件xxx pb增加了结构体 类 请求字段 xff0c 生成xxx h和xxx cpp文件 xff0c 然后放到对应目录进行编译 xff0c 奇葩的是 xff0c 编译出错 xff0
  • 树莓派上安装php

    简单东西 xff1a sudo apt get install php 然后 xff1a pi 64 raspberrypi taoge cat test php lt php aa 61 60 echo 39 hello 39 39 xx
  • 结构体和数组

    结构体中可以有数组类型的成员 xff0c 数组的元素也可以是结构体 数组和结构体的初始化是一样的 xff0c 都是把各个元素放在一个大括号里 xff0c 各个成员用逗号分隔 结构体数组使用示例 include lt stdio h gt i
  • iOS聊天室 简单的对话聊天界面(cell自适应高度)

    文章目录 难点思路需要用到的方法的大致解析 xff08 只是简单的介绍 xff0c 如果想要仔细理解推荐再去看看别的博客 xff09 GitHub地址代码效果图 难点 因为聊天长度不一样 xff0c 需要设置自适应高度发送信息后 xff0c