QQ聊天界面的布局和设计(IOS篇)-第一季
- 我写的源文件整个工程会再第二季中发上来~,存在百度网盘, 感兴趣的童鞋, 可以关注我的博客更新,到时自己去下载~。喵~~~
QQChat Layout - 第一季
一、准备工作
- 1.将假数据messages.plist和icon图片文件导入工程中。

- 2.创建相应的数据模型message, 保持数据模型的属性名和plist中的一样。为message类提供便利构造器。(由于与plist的名字保持一致,所以我们使用KVC技术来初始化数据模型, 其会去找和字典中同名的属性自动赋值)。
#import <UIKit/UIKit.h>
typedef enum {
MessageWhoIsMe,
MessageWHoIsAnother
}MessageWho;
@interface Message : NSObject
@property (nonatomic, strong) NSString * text;
@property (nonatomic, strong) NSString * time;
@property (nonatomic, assign) MessageWho type;
@property (nonatomic, assign) CGFloat height;
+ (instancetype)messageWithDict:(NSDictionary *)dict;
@end
- 3.在SB中的ViewController中,拖入UITableView与UIView控件,并将TableView的cell数目设置为1, UIView用来作QQ的菜单.先给起加上约束, 高44, 距离父控件底部、左边、右边分别为0,0,0.然后设置UITableView的底部距离QQ的菜单栏为0, 左、右、上距离父控件为0, 这样就得到了QQ的聊天界面的大体框架。
二、UI框架搭建

- 观察:如上图,通过观察, 我们可以知道QQ的每一行聊天消息, 其实就是一行的UITableViewCell对象, 也就说聊天框本质上可以看成是一个UITableView。所以我们只要能做出对应的cell对象,那么就能完成这个界面,问题是这个cell是不等高的, 这是这个布局的难点之一。至于怎么解决, 请直接看我项目中的分析。现在我们把重点方法对cell的设计上。很显然,这个cell肯定要由我们来自定义,自定义cell有多种方法, 我们选择storyBoard的方法来自定义。
- 分析:一条消息中最多有时间、消息头像、消息内容这三个元素。所以我们直接给cell中拖入一个用于显示时间的UILabel、一个用于显示内容的UIButton、因为内容有背景,所以用UIButton是比较合适的,还有用于显示头像的UIImageView.然后设置约束。下图为完成了cell的布局和约束设置。

- 细节:
1.创建一个MessageCell让其继承自UITableViewCell,然后将SB中的cell类型改为MessageCell的类型,如下图, (这样做是为了让创建出来的cell就是MessageCell对象)。
2.用拖线的功能快速在MessageCell类中将SB中的cell的各个控件和MessageCell中的属性关联起来。
三、开发
喵喵~~~~~,好鸟.所有准备工作都做好了。接下去要开始编码了, 啥?这就好了。是滴,接下去可要认真听我分析鸟, 交你为什么要这么写代码。大神请绕道~。
第一步:因为我们使用的是假数据,也就是plist文件中的数据进行模拟。所以我们省略掉从网络中获取数据这一步。我们只需要把plist的数据先加载进来就好了。那么怎么加载数据呢,为了提高程序的效率,我们使用懒加载了加载数据, 也就是在要用到数据的地方, 数据才会自动加载并缓存起来。喵~~~,好像很神奇的样子,吓得我都做地上了。
@interface ViewController () <UITableViewDataSource, UITableViewDelegate>
@property (weak, nonatomic) IBOutlet UITableView *tableView;
/**
* 存放所有对话的缓存数组
*/
@property (nonatomic, strong) NSArray * messages;
@end
// 懒加载
- (NSArray *)messages {
if (_messages == nil) {
NSString * path = [[NSBundle mainBundle] pathForResource:@"messages" ofType:@"plist"];
NSArray * dictArray = [NSArray arrayWithContentsOfFile: path];
NSMutableArray * messages = [NSMutableArray array];
for (NSDictionary * dict in dictArray) {
Message * message = [Message messageWithDict: dict];
[messages addObject: message];
}
_messages = messages;
}
return _messages;
}
- 第二步:实现数据源方法,告诉tableView有多少行的数据,有多少行数据就有多少个数据模型, 所以我们返回数据模型的个数, 而不是写死了代码, 这样当聊天数目增加或者减少的时候,我们无需在改变numberOfRowsInSection方法, 只需要增加或删除模型数据就可以。也就是
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return self.messages.count;
}
- 第三步:实现数据源方法的cellForRowAtPath:与代理对象的,heightForRowAtPath方法。我们必须作到的一点就是通过数据模型去驱动视图,通过改变数据来控制视图的变化,而中间的控制器着作为这个变化所对应逻辑传递桥梁。这就是所谓的MVC模型。这样职责分明,逻辑与数据分离开来,代码的耦合程度大大降低。但是有个问题是heightForRowAtPath一般是先于cellForRowAtPath调用, 这时候cell还没创建, 也就是所这时候数据模型还没通过控制器传递给cell对象进行数据的展示,那么这时候就没法获取正确的高度。所以我们得想办法让这两个方法反过来执行。幸运的是Apple为我们提供了一个属性,
estimatedRowHeight,我们只需统一的设置预估属性的大小,或者是为每一行(也就是每个cell)设置预估计的cell高度, 那么就会先调用cellForRowAtPath进行cell对象的创建,并且使用预估计的高度创建初始化的cell,但是当要显示到界面上时候,还会再调用heightForRowAtPath.为了方便, 我直接使用的是提供统一的预估算高度,然后实现这两个方法,如下代码。
- (void)viewDidLoad {
[super viewDidLoad];
self.tableView.estimatedRowHeight = 120;
// Do any additional setup after loading the view, typically from a nib.
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString * ID = @"messageCell";
MessageCellTableViewCell * cell = [self.tableView dequeueReusableCellWithIdentifier:ID];
cell.message = self.messages[indexPath.row];
return cell;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
return ((Message *)self.messages[indexPath.row]).height;
}
- 第四步:驱动开发, 继续完善。我采用的开发方式是类似于测试驱动开发的方式, 先写出想要达到的效果, 然后在围绕的这个效果进行开发。如上代码,
cell.message = self.messages[indexPath.row];。我想实现的是将数据模型直接传递给cell对象,应为我们没必要将view层的东西暴露在controller层中,view层的东西都是和数据相关的, 这样我们就能做到逻辑和数据的分离,好处是,打个比方,如果你项目中有10几个controller也需要用到这个cell,那么他们就要重复的在controller中写10多行的操作数据的操作,这是不明智的,也是不好的封装,所以我们应该将这些代码封装进cell对象所属于的类中,cell的事情,cell自己去处理.这样封装,controller就不必关心cell如何处理这些数据,只需要将数据交给cell处理就行。于是我们现在就可以来到UIMessageCell类中,给其加上Message类的message属性,并且重写setter,再setter中,我们需要做3件事情,第一件事情保留message对,第二件事情是进行控件上数据的展示,第三件事是在数据填充完后计算cell的高度,并将其更行到数据模型上。(计算高度的时候我们需要两次使用layoutIfNeeded来强制布局,具体原因看下面注释)
@implementation MessageCellTableViewCell
// 取得对应头像的图片名
- (NSString *)getPicture:(MessageWho)who {
return who == MessageWhoIsMe? @"me" : @"other";
}
- (void)setMessage:(Message *)message {
// 1.给控件装数据
_message = message;
_icon.image = [UIImage imageNamed:[self getPicture: message.type]];
[_text setTitle:message.text forState:UIControlStateNormal];
_timeLabel.text = message.time;
// 2.装完数据强制布局, 使得设置按钮高度的值准确, 并且更新约束
[_text layoutIfNeeded];
// 要先强制布局, 这时候更新约束才准确
// 更新约束, 使得按钮的高度此时等于文本的高度。
/*
When creating a custom button—that is a button with the type UIButtonTypeCustom—the frame of the button is set to (0, 0, 0, 0) initially. Before adding the button to your interface, you should update the frame to a more appropriate value.
当按钮是custom样式的时候, 其frmae的值都是零。
按钮里面的label默认是不自动换行的。
*/
[_text updateConstraints:^(MASConstraintMaker *make) {
CGFloat textH = CGRectGetHeight(_text.titleLabel.frame);
make.height.equalTo(textH);
}];
// 3.再次强制布局, 使得约束生效, 这样获取到的按钮高度才准确
[_text layoutIfNeeded];
CGFloat textH = CGRectGetMaxY(_text.frame);
CGFloat iconH = CGRectGetMaxY(_icon.frame);
CGFloat cellH = MAX(textH, iconH) + 10;
// 4.更新cell的高度到模型中
_message.height = cellH;
}
- (void)awakeFromNib {
// 设置自动换行
_text.titleLabel.numberOfLines = 0;
}
- (void)setSelected:(BOOL)selected animated:(BOOL)animated {
[super setSelected:selected animated:animated];
// Configure the view for the selected state
}
- 其它的小细节,如果有疑问可以给我留言、大家相互讨论。如有不足还请指教。请继续关注我的博客,会继续完善和介绍如何做成这个QQ聊天界面。后续如果有足够的时间会加上网络功能~~~。
- 第一季的效果如下,敬请期待第二季的完善QQ的对话背景、实现模拟的双人对话效果~。
- 我写的源文件会再第二季中发上来~,存在百度网盘, 感兴趣的童鞋, 可以关注我的博客更新,到时自己去下载~。喵~~~

QQ聊天界面的布局和设计(IOS篇)-第一季的更多相关文章
- QQ聊天界面的布局和设计(IOS篇)-第二季
QQChat Layout - 第二季 本来第二季是快写好了, 也花了点功夫, 结果gitbook出了点问题, 给没掉了.有些细节可能会一带而过, 如有疑问, 相互交流进步~. 在第一季中我们完成了Q ...
- 高仿qq聊天界面
高仿qq聊天界面,给有需要的人,界面效果如下: 真心觉得做界面非常痛苦,给有需要的朋友. chat.xml <?xml version="1.0" encoding=&quo ...
- Objective-c——UI基础开发第八天(QQ聊天界面)
一.知识点: QQ聊天界面 双模型的使用(dataModel和frameModel) UITextField的使用 通知的使用 拉伸图片的两种方法(slicing/image对象的resizeable ...
- 在WEB项目中调用QQ通讯组件打开QQ聊天界面
在很多WEB项目中,需要提供在线服务的功能,加上自己的联系方式,例如:QQ,不用添加QQ好友也可以交谈,那这到底是怎么实现的呢? 对于这个功能,需要提到一个组件,即“QQ通讯组件”.QQ通讯组件是一种 ...
- Android—简单的仿QQ聊天界面
最近仿照QQ聊天做了一个类似界面,先看下界面组成(画面不太美凑合凑合呗,,,,):
- Android 内部启动其他应用,以及打开指定qq聊天界面
在自己应用中打开第三方应用,有好多种方法,这里举例一种: //以打开微信为例,前提需要知道打开应用的包名,一般一个发布版本的应用,包名不会轻易改变的,但是,打开QQ就要注意了,毕竟QQ的发布版本有不下 ...
- Android 根据QQ号跳转到QQ聊天界面
从自己开发的应用中根据QQ号跳转到QQ应用的聊天界面,实现起来很方便: 即: startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(" ...
- 在移动网页网页上点击链接跳转到QQ聊天界面
打开qq聊天窗口的方法 <a href="http://wpa.qq.com/msgrd?v=3&uin=1450612626&site=qq&menu=yes ...
- IOS第九天(3:QQ聊天界面通知的使用)
#import <Foundation/Foundation.h> #import "Person.h" #import "XQCompany.h" ...
随机推荐
- sicily 1007 To and Fro
题意:字符串的操作处理 // Problem#: 8768 // Submission#: 2606406 // The source code is licensed under Creative ...
- 深入理解linux网络技术内幕读书笔记(一)--简介
Table of Contents 1 基本术语 1.1 本书常用的缩写 2 引用计数 2.1 引用计数函数 3 垃圾回收 3.1 异步 3.2 同步 4 函数指针 4.1 缺点 5 goto语句 5 ...
- AngularJs学习笔记4——四大特性之双向数据绑定
双向数据绑定 方向1:模型数据(model)绑定到视图(view) 实现方法:①.{{model变量名}} ②.常用指令(ng-repeat) 方向2:将视图(view)中用户输入的数据绑定到模型数 ...
- JSP简单练习-数组应用实例
<%@ page contentType="text/html; charset=gb2312" %> <html> <body> <% ...
- 查看哪些进程占用了SWAP分区?
在日常管理中,我们经常会遇到swap分区使用比较多,那么导致是那些进程使用的呢,其实我们可以通过/proc/pid/下的smaps来获得.使用下面的命令可以列出所有进程占用的swap分区的大小,分别我 ...
- Tomcat6.0 only supports J2EE 1.2, 1.3, 1.4, and Java EE 5 Web modules
使用tomcat6发布项目的时候,报以上错误,servlet版本太高的原因.tomcat6不支持servlet3.0 解决方法有两个: 1.使用高版本的tomcat,如tomcat7.tomcat8 ...
- window.dialogArguments的使用
<HTML> <HEAD> <TITLE>showModelessDialogEX.htm</TITLE> <SCRIPT> var sUs ...
- struts 标签引用出错
几句句话概括 1.检查 web.xml 出现错误自己改 配置后filter jsp-config 2.检查 tld 目录下的东西 是否缺少 3. 将包复制到web的lib目录下后 之后 b ...
- Oracle11g R2学习系列 之七安全性
其实,对于目前我使用的Oracle的水平来看,还达不到使用安全管理的高度,只是作为一个学习来看一下. 关于Oracle的安全管理,一般使用OEM来操作完成好了,入口是:OEM的“服务器”属性页中,选择 ...
- 织梦dedecms自定义字段在首页列表页文章页的调用
1.首页调用. {dede:arclist addfields='字段英文名' channelid='模型ID' row='条数' type='栏目ID'} [field:字段英文名/ ...