Tip:通过xib和storyboard不可能将一个控件作为ImageView的子控件,只能通过代码的addSubview方法实现。

设置图片的细节:如果button比图片大(为了方便对齐),将图片设置为image而不是background,图片不会被拉伸到失真。

为了保证在不同系统上显示的效果一样,可以不使用系统默认样式,用自定义的背景等,例如QQ的聊天框,如果要实现,首先将TextField的BorderStyle选为空:

然后设置自己的background即可。

细节:聊天的type(发送的还是接受的)应该用什么数据类型?

为了降低沟通成本,提高可读性和安全性,应该使用枚举。

枚举类型命名规范:类名+属性名(首字母大写)。枚举成员也要类名+属性为前缀。

typedef enum{
MessageTypeMe = 0,
MessageTypeOther
} MessageType ;
@property (nonatomic, assign) MessageType type;

Tip:使用KVC的时候,枚举会自动转整形。

注意弱指针不能指向alloc的对象,否则会被直接销毁,应该先用强指针指着alloc的对象,然后加入到父控件,最后再用弱指针指过去:

@property (nonatomic, weak) UILabel *timeView;
UILabel *timeView = [[UILabel alloc] init];
[self.contentView addSubview:timeView];
self.timeView = timeView;

自定义cell的步骤:

第一步:新建一个继承自UITableViewCell的类

第二步:重写initWithStyle:reuseIdentifier方法

添加所有的子控件,不需要设置数据和frame(声明一个frame属性以便设置),加入到self.contentView中。一定注意弱指针的用法,先用强指针,加入视图后再交给弱指针。

@interface MessageCell : UITableViewCell

@property (nonatomic, strong) MessageFrame *messageFrame;

+ (instancetype)cellWithTableView:(UITableView *)tableView;

@end

@interface MessageCell ()

@property (nonatomic, weak) UILabel *timeView;

@property (nonatomic, weak) UIImageView *iconView;

@property (nonatomic, weak) UIButton *textView;

@end

@implementation MessageCell

- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier{
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
//时间:label
//头像:imgaeView
//正文:button
UILabel *timeView = [[UILabel alloc] init];
[self.contentView addSubview:timeView];
self.timeView = timeView; UIImageView *iconView = [[UIImageView alloc] init];
[self.contentView addSubview:iconView];
self.iconView = iconView; UIButton *textView = [[UIButton alloc] init];
[self.contentView addSubview:textView];
self.textView = textView;
}
return self;
} - (void)awakeFromNib {
// Initialization code
} - (void)setSelected:(BOOL)selected animated:(BOOL)animated {
[super setSelected:selected animated:animated]; // Configure the view for the selected state
} @end

细节:在cell类内写一个类方法用于实现缓存池的性能优化:

+ (instancetype)cellWithTableView:(UITableView *)tableView{

    static NSString *ID = @"message";
MessageCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];
if(cell == nil){
cell = [[MessageCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:ID];
}
return cell; }

这样就大大简化了返回cell的代码:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{

    MessageCell *cell = [MessageCell cellWithTableView:tableView];
return cell; }

第三步:提供数据模型和frame模型(后者存放数据模型和所有子控件的高度、cell的高度)

Tip:要使用CGRect,首先要引入<UIKit/UIKit.h>。

注意frame应该是只读的,只在模型内计算,没有set方法,所以类内使用下划线访问。

对于数据模型message,主要是提供消息的类型、事件、内容:

typedef enum{
MessageTypeMe = 0,
MessageTypeOther
} MessageType ; @interface Message : NSObject @property (nonatomic, copy) NSString *text; @property (nonatomic, copy) NSString *time; @property (nonatomic, assign) MessageType type; - (instancetype)initWithDict:(NSDictionary *)dict;
+ (instancetype)messageWithDict:(NSDictionary *)dict; @end

和以前的模型声明和实现完全一样。

对于messageFrame模型,内部的数据为Cell各部分的尺寸和message模型,最后控制器访问的将是这个模型。

#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h> #define NameFont [UIFont systemFontOfSize:14]
#define TextFont [UIFont systemFontOfSize:15] @class Message; @interface MessageFrame : NSObject
/**
* 头像的Frame
*/
@property (nonatomic, assign, readonly) CGRect iconF;
/**
* 时间的Frame
*/
@property (nonatomic, assign, readonly) CGRect timeF;
/**
* 正文的Frame
*/
@property (nonatomic, assign, readonly) CGRect textF;
/**
* cell的高度
*/
@property (nonatomic, assign, readonly) CGFloat cellHeight;
/**
* 数据模型
*/
@property (nonatomic, strong) Message *message; @end

需要注意的是,Frame属性只在模型内计算,不允许在外部修改,声明为readonly,为了使用CGXxx要引入UIKit框架。

计算Frame的时机应该是在控制器将message信息传入的时候,因此要重写message的set方法来计算尺寸:

计算的过程略为繁琐,总之就是实现类似QQ的聊天起泡效果,这里只是计算了时间、消息框、头像的位置和尺寸,并没有加上起泡。

注意一个细节,使用UIScreen的mainScreen方法的bounds.size得到屏幕尺寸。

- (void)setMessage:(Message *)message{

    _message = message;

    CGFloat screenW = [UIScreen mainScreen].bounds.size.width;
CGFloat padding = 10; CGFloat timeX = 0;
CGFloat timeY = 0;
CGFloat timeW = 320;
CGFloat timeH = 40; _timeF = CGRectMake(timeX, timeY, timeW, timeH);
CGFloat iconX;
CGFloat iconY = CGRectGetMaxY(_timeF);
CGFloat iconW = 40;
CGFloat iconH = 40;
if (message.type == MessageTypeMe) {
iconX = screenW - padding - iconW;
}else{
iconX = padding;
}
_iconF = CGRectMake(iconX, iconY, iconW, iconH); CGSize textMaxSize = CGSizeMake(150, MAXFLOAT);
CGSize textSize = [self sizeWithText:message.text font:TextFont maxSize:textMaxSize];
CGFloat textY = iconY;
CGFloat textX;
if (message.type == MessageTypeMe) {
textX = iconX - padding - textSize.width;
}else{
textX = CGRectGetMaxX(_iconF) + padding;
}
//_textF = CGRectMake(textX, textY, textSize.width, textSize.height);
_textF = (CGRect){{textX,textY},textSize}; CGFloat textMaxY = CGRectGetMaxY(_textF);
CGFloat iconMaxY = CGRectGetMaxY(_iconF);
_cellHeight = MAX(textMaxY, iconMaxY); }

其中在计算文字对应的text尺寸时,使用了如下的方法:

- (CGSize)sizeWithText:(NSString *)text font:(UIFont *)font maxSize:(CGSize)maxSize{
NSDictionary *attrs = @{NSFontAttributeName : font};
return [text boundingRectWithSize:maxSize options:NSStringDrawingUsesLineFragmentOrigin attributes:attrs context:nil].size;
}

最后剩下的最重要的问题,是为cell的子控件设置frame模型的时机,可以注意到,在通过initWithTableView获取到cell之后,应该对cell的messageFrame属性进行设置,所以只要重写cell的messageFrame的set方法即可在这个时候改变控件的属性,然后得到正确的cell。

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

    _messageFrame = messageFrame;

    Message *msg = messageFrame.message;

    self.timeView.text = msg.time;
self.timeView.frame = messageFrame.timeF; NSString *icon = msg.type == MessageTypeMe ? @"me" : @"other";
self.iconView.image = [UIImage imageNamed:icon];
self.iconView.frame = messageFrame.iconF; [self.textView setTitle:msg.text forState:UIControlStateNormal];
self.textView.frame = messageFrame.textF; }

对于要多次计算的数据,放在message的set方法内计算,对于一次性的计算(例如Cell的背景色),放在Cell的init方法中计算:

改良后的Cell初始化方法:注意一个细节,clearColor为透明色(无色)。

- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier{
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
//一次性的修改放到init方法中
//时间:label
//头像:imgaeView
//正文:button
UILabel *timeView = [[UILabel alloc] init];
timeView.textAlignment = NSTextAlignmentCenter;
timeView.textColor = [UIColor grayColor];
[self.contentView addSubview:timeView];
self.timeView = timeView; UIImageView *iconView = [[UIImageView alloc] init];
[self.contentView addSubview:iconView];
self.iconView = iconView; UIButton *textView = [[UIButton alloc] init];
textView.titleLabel.numberOfLines = 0; //自动换行
textView.titleLabel.font = TextFont;
textView.backgroundColor = [UIColor grayColor];
[self.contentView addSubview:textView];
self.textView = textView; //设置cell的背景色
self.backgroundColor = [UIColor clearColor];
}
return self;
}

下面的步骤就和以前一样,控制器新建messageFrames,懒加载数据,然后TableView根据数据源和委托加载数据。

下面总结一下调用过程:

1.控制器加载messageFrames,需要为每一个messageFrame的message属性赋值-

2.由于重写了message的set方法,在set方法内部根据message计算得到文字的宽高,进而得到头像、时间位置尺寸,确定所有的Frame,并且存住message(保存之前先用message的类方法字典转模型)。

3.系统调用获取Cell的方法时,由于调用了被重写的构造方法initWithStyle:style reuseIdentifier: ,在其内部通过性能优化取得Cell,然后创建各个子控件,进行一次性属性的设置,最后返回Cell。

4.Cell内部有messageFrame属性需要设置,在设置该属性时,会调用重写的set方法,在这个方法内部,实现了对各个子控件尺寸根据传入的messageFrame修改的操作。

5.Cell经过以上4步被正确的创建和设置,返回后正确的显示。

调用顺序:Message类(字典转Message模型)->Frame类(设置messageFrame的message时调用set方法)->MessageCell类(初始化Cell)->MessageCell类(设置Cell的messageFrame属性时调用set方法)。

(二十)即时通信的聊天气泡的实现I的更多相关文章

  1. (二十一)即时通信的聊天气泡的实现II

    一些优化: 禁止TableView的点击: self.tableView.allowsSelection = NO; 合并相同的时间: 不需要显示的时间,只要不设置尺寸就行了. 一个if判断的技巧,为 ...

  2. iOS开发之使用XMPPFramework实现即时通信(二)

    上篇的博客iOS开发之使用XMPPFramework实现即时通信(一)只是本篇的引子,本篇博客就给之前的微信加上即时通讯的功能,主要是对XMPPFramework的使用.本篇博客中用到了Spark做测 ...

  3. (转)基于即时通信和LBS技术的位置感知服务(二):XMPP协议总结以及开源解决方案

    在<基于即时通信和LBS技术的位置感知服务(一):提出问题及解决方案>一文中,提到尝试使用XMPP协议来实现即时通信.本文将对XMPP协议框架以及相关的C/S架构进行介绍,协议的底层实现不 ...

  4. 基于Server-Sent Event的简单聊天室 Web 2.0时代,即时通信已经成为必不可少的网站功能,那实现Web即时通信的机制有哪些呢?在这门项目课中我们将一一介绍。最后我们将会实现一个基于Server-Sent Event和Flask简单的在线聊天室。

    基于Server-Sent Event的简单聊天室 Web 2.0时代,即时通信已经成为必不可少的网站功能,那实现Web即时通信的机制有哪些呢?在这门项目课中我们将一一介绍.最后我们将会实现一个基于S ...

  5. 自己动手做聊天机器人 二十九-重磅:近1GB的三千万聊天语料供出

    Reference: http://www.shareditor.com/blogshow/?blogId=112 经过半个月的倾力打造,建设好的聊天语料库包含三千多万条简体中文高质量聊天语料,近1G ...

  6. 上位机面试必备——TCP通信灵魂二十问【下】

    上篇文章跟大家介绍了TCP通信常见的前10个面试题,没看过的小伙伴可以点击下方链接进行查看: 上位机面试必备——TCP通信灵魂二十问[上] 今天就后面的10个面试题接着做下说明:欢迎关注[dotNet ...

  7. QQ 腾讯QQ(简称“QQ”)是腾讯公司开发的一款基于Internet的即时通信(IM)软件

    QQ 编辑 腾讯QQ(简称“QQ”)是腾讯公司开发的一款基于Internet的即时通信(IM)软件.腾讯QQ支持在线聊天.视频通话.点对点断点续传文件.共享文件.网络硬盘.自定义面板.QQ邮箱等多种功 ...

  8. 即时通信(IM)和实时通信(RTC)的区别

    即时通信(IM=nstant messaging)和实时通信(rtc=Real-time communication)都是一套网络通信系统,其本质都是对信息进行转发.其最大的不同点,是对信息传递的时间 ...

  9. 基于XMPP协议的Android即时通信系

    以前做过一个基于XMPP协议的聊天社交软件,总结了一下.发出来. 设计基于开源的XMPP即时通信协议,采用C/S体系结构,通过GPRS无线网络用TCP协议连接到服务器,以架设开源的Openfn'e服务 ...

随机推荐

  1. MySQL LIKE 子句

    MySQL LIKE 子句 我们知道在MySQL中使用 SQL SELECT 命令来读取数据, 同时我们可以在 SELECT 语句中使用 WHERE 子句来获取指定的记录. WHERE 子句中可以使用 ...

  2. JavaScript 调试

    在编写 JavaScript 时,如果没有调试工具将是一件很痛苦的事情. JavaScript 调试 没有调试工具是很难去编写 JavaScript 程序的. 你的代码可能包含语法错误,逻辑错误,如果 ...

  3. ELK 6安装配置 nginx日志收集 kabana汉化

    #ELK 6安装配置 nginx日志收集 kabana汉化 #环境 centos 7.4 ,ELK 6 ,单节点 #服务端 Logstash 收集,过滤 Elasticsearch 存储,索引日志 K ...

  4. Rails rspec测试报patch user_path(user) param not found: user的解决

    其实道理很简单,就是在User控制器的update方法中有一个验证: def user_params params.require(:user).permit(:name,:email,:passwo ...

  5. activiti源码分析

    http://blog.csdn.net/vote/candidate.html?username=qq_30739519 欢迎大家投票吧谢谢

  6. Bootstrap3 栅格系统-实例:从堆叠到水平排列

    使用单一的一组 .col-md-* 栅格类,就可以创建一个基本的栅格系统,在手机和平板设备上一开始是堆叠在一起的(超小屏幕到小屏幕这一范围),在桌面(中等)屏幕设备上变为水平排列.所有"列( ...

  7. 【完整的App项目】颖火虫笔记v2

    好久没写博客了,一方面是因为最近确实很忙,另一方面自己在改进颖火虫笔记这款App,在前面说过该App主要是模仿的印象笔记,(还不知道的,请看:http://blog.csdn.net/htq__/ar ...

  8. 协议系列之UDP协议

    上节说的TCP协议虽然提供了可靠的传输,但是也有一个缺点,发送速度慢.那么有没有一种协议能快速发送的呢?这节要讨论的就是UDP协议,它提供了更加快的发送速度,但也牺牲了可靠性,它是一种无连接的传输协议 ...

  9. antlr v4 使用指南连载2——准备环境

    antlr v4 开发环境         从上一篇文章的例子中可以知道,antlr有一套自己的语法来声明目标语言的语法,因此它本身就需要编译或者使用antlr提供的api来读取这些语法规则,并使之可 ...

  10. 剑指Offer——二叉树

    剑指Offer--二叉树 前言 数据结构通常是编程面试中考察的重点.在参加面试之前,应聘者需要熟练掌握链表.树.栈.队列和哈希表等数据结构,以及它们的操作.本片博文主要讲解二叉树操作的相关知识,主要包 ...