简单的聊天界面

在近期的一个项目中,用到了客服功能,然而聊天的实现并不是用的即时通讯的SDK,而是用的一个网页版的某通,这就需要自己写一个聊天的界面,下面总结一下这个聊天界面的搭建过程。

一、基础UI

谈到聊天界面,我们最熟悉的就是QQ、微信等的聊天界面,看到这两种的聊天界面的形式,都是将消息逐条展示出来,我们不难想到,聊天界面的主体要用UItableView实现,而每条消息就是一个cell,至于cell中的内容,无非就是三项:时间、对话人的头像、消息的内容。既然大概结构已经清楚,那么下面我们俩看看具体的实现。

1、数据的处理

在自定义cell之前,我们首先来看看消息的数据结构,将数据转换成Model,以便对数据进行更方便的处理。

Snip20170323_3.png

数据看起来非常简单,其意义分别为:text-消息内容;time-消息的发送时间;type-消息的类型(0:自己发送的消息,1:对方发送的消息)。

那么我们在定制Model的时候是不是就只需要这几种属性呢?答案是NO。因为我们知道,聊天的内容不是只有文本内容,有时候还会有图片,表情,语音,设置还会有文件,那么我们就还需要一个区分消息种类的属性messageType。此外我们知道,QQ聊天界面中,并不是所有的消息发送时间都会显示的,只有当新消息与上一条消息的发送时间不同时才会显示时间,那么我们就还需要一个区别时间是否展示的属性hiddenTime。

下面是消息模型XTMessage的属性:

@property (nonatomic, copy) NSString *time;//对话类型:0:自己的消息;1:对方的消息
@property (nonatomic, strong) NSNumber *type;//消息类型:0:文字消息;1:图片;2:文件
@property(nonatomic,assign)int messageType;
@property(nonatomic,copy)NSString *message;//文本消息
@property(nonatomic,strong)UIImage *messageImage;//图片消息
@property(nonatomic,strong)NSData *messageData;//文件消息
@property (nonatomic, assign, getter = isHiddenTime) BOOL hiddenTime;//是否隐藏时间

下面是字典转模型的具体实现方法:

- (instancetype)initWithDictionary:(NSDictionary *)dict
{
    self = [super init];
    if (self) {
        _type = dict[@"type"];
        _time = dict[@"time"];
        if (_messageType == 0) {//文本消息
            _message = dict[@"text"];
        } else if (_messageType == 1) {//图片消息
            _messageImage = [UIImage imageNamed:dict[@"text"]];
        } else {//文件或语音消息
            _messageData = dict[@"text"];
        }
    }
    return self;
}

2、FrameModel的处理

上面我们将原始数据处理完了,接下来我们就要为自定义cell做准备了。对于cell中的控件无非就是要显示头像的一个UIimageView,一个显示时间的label,然后就是展示消息内容的,而展示内容我们这里选择的是UIbutton,因为UIbutton既可以展示文本,又能展示图片,能最大限度满足我们的需求。这里我们要注意了,首先对于消息发送者的不同,头像的位置是不同的;消息内容的不同,展示内容控件的size也是动态变化的。下面我们说说对此的处理。

首先我们声明一下XTMessageFrame的公开属性:

@property (nonatomic, assign, readonly) CGRect titleLabelFrame;//时间标题的高度
@property (nonatomic, assign, readonly) CGRect contentBtnFrame;
@property (nonatomic, assign, readonly) CGRect iconImageViewFrame;
@property (nonatomic, assign) CGFloat cellHeight;//行高
@property(nonatomic,strong)XTMessage *messageModel;

下面我们来实现XTMessageFrame的私有方法。首先我们先定义一个- (CGSize)sizeWithText:(NSString *)text;根据文本长度自适应大小,代码如下:

- (CGSize)sizeWithText:(NSString *)text {
    return [text boundingRectWithSize:CGSizeMake(SCREEN_WIDTH - W(150), MAXFLOAT)options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName : FONTONE} context:nil].size;
}

下面我们来实现MessageModel的set方法:
首先判断时间label的高度:

//设置时间标题label的高度 如果时间隐藏 高度为0 即不显示
CGFloat titleLabelHeight = messageModel.hiddenTime ? 0 : H(30);

时间label的frame:

_titleLabelFrame = CGRectMake(0, 0, SCREEN_WIDTH, titleLabelHeight);

下面我们定义两个宏:

#define kContentBtnWidth (contentBtnSize.width + W(40))//内容的宽度
#define kContentBtnHeight (contentBtnSize.height + H(35))//内容的高度

下面根据消息的类型,得出内容按钮的size:

CGSize contentBtnSize;

if (_messageModel.messageType == 0) {

    //文字大小
    contentBtnSize = [self sizeWithText:_messageModel.message];

} else if (_messageModel.messageType == 1) {

    CGSize size = _messageModel.messageImage.size;
    contentBtnSize = CGSizeMake(W(150), size.height/size.width * W(150));

} else {

    contentBtnSize = CGSizeMake(W(150), H(150));

}

接下来根据发送消息的对象,设置头像和内容button的frame:

//头像大小 50 * 50
if ([_messageModel.type isEqualToNumber:@0]) {//如果是自己发送的消息

    _iconImageViewFrame = CGRectMake(SCREEN_WIDTH - iconWH - margin, CGRectGetMaxY(_titleLabelFrame), iconWH, iconWH);//头像在左侧
    _contentBtnFrame = CGRectMake(SCREEN_WIDTH - kContentBtnWidth - iconWH - margin - margin, CGRectGetMaxY(_titleLabelFrame), kContentBtnWidth, kContentBtnHeight);//设置内容button

}else {//如果是对方发送的消息

    _iconImageViewFrame = CGRectMake(margin, CGRectGetMaxY(_titleLabelFrame), iconWH, iconWH);//头像在右侧
    _contentBtnFrame = CGRectMake(margin + iconWH + margin,CGRectGetMaxY(_titleLabelFrame),kContentBtnWidth,kContentBtnHeight);

}

最后根据cell中控件的frame设置cell的行高:

_cellHeight = MAX(CGRectGetMaxY(_contentBtnFrame), CGRectGetMaxY(_titleLabelFrame)) + margin;

3、自定义cell

首先重写initWithStyle方法,在cell中初始化timeTitleLab(时间label)、headImageView(头像imageView)、contentBtn(内容按钮),并进行一些初始设置。

实现setMessageFrame方法:

- (void)setMessageFrame:(XTMessageFrame *)messageFrame
{

    _messageFrame = messageFrame;
    [self setSubViewData];
    [self setSubViewFrame];

}

setSubViewFrame方法,就是给cell中控件的frame赋值,这个方法相信就不用多说了,我们来说说setSubViewData方法,在这个方法中,我们要做的就是根据消息发送人,以及消息类型,分别给控件的内容赋值,比如当消息是自己发送的时候,代码如下:

self.headImageView.image = [UIImage imageNamed:@"me"];

UIImage *btnBackImage = [UIImage imageNamed:@"chat_send_nor"];

[self.contentBtn setBackgroundImage:[btnBackImage stretchableImageWithLeftCapWidth:btnBackImage.size.width / 2 topCapHeight:btnBackImage.size.height / 2] forState:UIControlStateNormal];

UIImage *btnBackImageHelight = [UIImage imageNamed:@"chat_send_press_pic"];

[self.contentBtn setBackgroundImage:[btnBackImageHelight stretchableImageWithLeftCapWidth:btnBackImageHelight.size.width / 2 topCapHeight:btnBackImageHelight.size.height / 2] forState:UIControlStateHighlighted];

if (self.messageFrame.messageModel.messageType == 0) {//如果是文本信息

    [self.contentBtn setTitle:self.messageFrame.messageModel.message forState:UIControlStateNormal];

} else if (self.messageFrame.messageModel.messageType == 1) {

    [self.contentBtn setImage:self.messageFrame.messageModel.messageImage forState:UIControlStateNormal];
    [self.contentBtn setImageEdgeInsets:UIEdgeInsetsMake(H(20), W(20), H(20), W(20))];

} else {

    [self.contentBtn setImage:[UIImage imageNamed:@"wenj"] forState:UIControlStateNormal];

}

这里要强调一点,对于聊天内容button的backgroundImage,需要根据内容的大小进行大小的拉伸,这里我们用到的是- (UIImage *)stretchableImageWithLeftCapWidth:(NSInteger)leftCapWidth topCapHeight:(NSInteger)topCapHeight方法,这个函数是UIImage的一个实例函数,它的功能是创建一个内容可拉伸,而边角不拉伸的图片,需要两个参数,第一个是左边不拉伸区域的宽度,第二个参数是上面不拉伸的高度。我自己的理解(认识)是在左边、上面找一个基点,对基点范围以外的内容拉伸。

具体的代码已上传GitHub:XTChatViewController

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,163评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,301评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,089评论 0 352
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,093评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,110评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,079评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,005评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,840评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,278评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,497评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,667评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,394评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,980评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,628评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,796评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,649评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,548评论 2 352

推荐阅读更多精彩内容

  • 2017.02.22 可以练习,每当这个时候,脑袋就犯困,我这脑袋真是神奇呀,一说让你做事情,你就犯困,你可不要太...
    Carden阅读 1,342评论 0 1
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 172,060评论 25 707
  • 今天午饭后,散步,看到小镇上一家人的门口挂着藍染(あいぞめ)的布上写的【I hope peace】这也许只是单单的...
    WoodSage阅读 658评论 0 0
  • 我们都是幸运儿 上帝,关了一扇门,总会打开一扇窗的。 人生难免有挫折,但也有快乐的存在。在忙忙碌碌的一天中,我们经...
    玖珞神阅读 156评论 0 1
  • 感赏自己开启新的生活模式,以后早睡早起,晚上10:30之前休息,早上5点起床,学习锻炼。希望自己可以坚持下去,并有...
    完美蜕变阅读 207评论 0 1