(二十)即时通信的聊天气泡的实现I
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的更多相关文章
- (二十一)即时通信的聊天气泡的实现II
一些优化: 禁止TableView的点击: self.tableView.allowsSelection = NO; 合并相同的时间: 不需要显示的时间,只要不设置尺寸就行了. 一个if判断的技巧,为 ...
- iOS开发之使用XMPPFramework实现即时通信(二)
上篇的博客iOS开发之使用XMPPFramework实现即时通信(一)只是本篇的引子,本篇博客就给之前的微信加上即时通讯的功能,主要是对XMPPFramework的使用.本篇博客中用到了Spark做测 ...
- (转)基于即时通信和LBS技术的位置感知服务(二):XMPP协议总结以及开源解决方案
在<基于即时通信和LBS技术的位置感知服务(一):提出问题及解决方案>一文中,提到尝试使用XMPP协议来实现即时通信.本文将对XMPP协议框架以及相关的C/S架构进行介绍,协议的底层实现不 ...
- 基于Server-Sent Event的简单聊天室 Web 2.0时代,即时通信已经成为必不可少的网站功能,那实现Web即时通信的机制有哪些呢?在这门项目课中我们将一一介绍。最后我们将会实现一个基于Server-Sent Event和Flask简单的在线聊天室。
基于Server-Sent Event的简单聊天室 Web 2.0时代,即时通信已经成为必不可少的网站功能,那实现Web即时通信的机制有哪些呢?在这门项目课中我们将一一介绍.最后我们将会实现一个基于S ...
- 自己动手做聊天机器人 二十九-重磅:近1GB的三千万聊天语料供出
Reference: http://www.shareditor.com/blogshow/?blogId=112 经过半个月的倾力打造,建设好的聊天语料库包含三千多万条简体中文高质量聊天语料,近1G ...
- 上位机面试必备——TCP通信灵魂二十问【下】
上篇文章跟大家介绍了TCP通信常见的前10个面试题,没看过的小伙伴可以点击下方链接进行查看: 上位机面试必备——TCP通信灵魂二十问[上] 今天就后面的10个面试题接着做下说明:欢迎关注[dotNet ...
- QQ 腾讯QQ(简称“QQ”)是腾讯公司开发的一款基于Internet的即时通信(IM)软件
QQ 编辑 腾讯QQ(简称“QQ”)是腾讯公司开发的一款基于Internet的即时通信(IM)软件.腾讯QQ支持在线聊天.视频通话.点对点断点续传文件.共享文件.网络硬盘.自定义面板.QQ邮箱等多种功 ...
- 即时通信(IM)和实时通信(RTC)的区别
即时通信(IM=nstant messaging)和实时通信(rtc=Real-time communication)都是一套网络通信系统,其本质都是对信息进行转发.其最大的不同点,是对信息传递的时间 ...
- 基于XMPP协议的Android即时通信系
以前做过一个基于XMPP协议的聊天社交软件,总结了一下.发出来. 设计基于开源的XMPP即时通信协议,采用C/S体系结构,通过GPRS无线网络用TCP协议连接到服务器,以架设开源的Openfn'e服务 ...
随机推荐
- CRM客户关系管理系统(八)
第八章.只读字段处理和filter_horizontal的实现 8.1.只读字段的处理 (1)kingadmin/admin_base.py # kingadmin/admin_base.py cl ...
- Nginx之(三)Nginx配置
一个简单的配置文件如下: #定义Nginx运行的用户及用户组 user userName userGroupName; #工作进程数目,根据硬件调整,通常等于CPU数量或者2倍于CPU worker_ ...
- 《读书报告 -- Elasticsearch入门 》--简单使用(2)
<读书报告 – Elasticsearch入门 > ' 第四章 分布式文件存储 这章的主要内容是理解数据如何在分布式系统中存储. 4.1 路由文档到分片 创建一个新文档时,它是如何确定应该 ...
- GDAL C#读取shp中文属性值乱码问题
GDAL的C#版本读取shp中,如果属性值中含有中文,读出来有可能是乱码的问题,根据SWIG生成的C#代码调试发现问题所在,在Ogr.cs文件中有这么一个函数,代码如下: internal stati ...
- Android艺术开发探索第三章————View的事件体系(下)
Android艺术开发探索第三章----View的事件体系(下) 在这里就能学习到很多,主要还是对View的事件分发做一个体系的了解 一.View的事件分发 上篇大致的说了一下View的基础知识和滑动 ...
- Android Lollipop 5.0 经典新特性回顾
*Tamic 专注移动开发! 更多文章请关注 http://blog.csdn.net/sk719887916 虽然Android已到了7.0 ,但是我们还是不能忘怀视觉革命性改变的5.0,今天回顾下 ...
- SpringMVC+BUI实现文件上传(附详解,源码下载)
中午有限时间写这博文,前言就不必多说了,直奔主题吧. BUI是一个前端框架,关于BUI的介绍请看博主的文章那些年用过的一些前端框架. 下面我们开始实例的讲解! 一.效果演示: 上传成功后,会发现本地相 ...
- 使用maven执行单元测试总结
maven本身没有单元测试框架,但是maven的default生命周期的test阶段绑定了maven-surefire-plugin插件,该插件可以调用Junit3.Junit4.TestNG等Jav ...
- OpenCV, MatBGR2ARGB, ARGB2MatBGR
代码片段~ unsigned int* abMatBGR2ARGB(Mat imag) { int nCols; int nRows; unsigned int *pbuff = NULL; if(i ...
- java之异常处理
异常Exception我们分为 |--RuntimeException运行期异常,我们需要修正代码 |--非RuntimeException 编译期异常,必须处理的,否则程序编译不通过 异常有两种处理 ...