学习目标
1.【理解】QQ界面搭建
2.【理解】通知中心NSNotificationCenter
3.【理解】实现发布信息和自动回复
一、QQ界面搭建
手机QQ聊天软件应该大多数人都接触过,就不细说了,需求是自定义cell搭建手机QQ聊天界面。
分析:顶部、底部是单独UIView控件,中间是tableview。所以可以一眼看出顶部、底部不是在tableview中的,因为他们不会随着tableview而滑动。效果图如下:点击这里查看动态图
还是和上一篇文章的微博案例一样,先封装数据模型和cell的frame模型。导入素材和plist文件,创建模型类。素材下载地址
创建模型类注意:
type建议定义为枚举类型,虽然也不是必须要这样,但是这样看起来必须形象些吧。还有需要定义一个时间是否隐藏的属性,当多条消息时间相同的时候,只显示第一条消息的时间,而隐藏之后消息的时间。我们每天都在用QQ,应该都很熟悉这个了,如下图所示:
JFMessage.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
#import <Foundation/Foundation.h>
//定义一个消息类型枚举,0表示我发的消息,1表示别人发的消息
typedefenum{
JFMessageSelf=0,
JFMessageOther
}JFMessageType;
@interface JFMessage : NSObject
@property(copy,nonatomic)NSString*text;
@property(copy,nonatomic)NSString*time;
@property(assign,nonatomic)JFMessageTypetype;
//是否隐藏时间显示
@property(assign,nonatomic,getter=isHideTime)BOOLhideTime;
//快速创建模型的方法
-(instancetype)initWithDictionary:(NSDictionary*)dict;
+(instancetype)messageWithDictionary:(NSDictionary*)dict;
//返回一个模型数组
+(NSMutableArray*)messages;
@end
|
JFMessage.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
|
#import "JFMessage.h"
@implementationJFMessage
//快速创建模型的方法
-(instancetype)initWithDictionary:(NSDictionary*)dict{
if(self=[superinit]){
[self setValuesForKeysWithDictionary:dict];
}
returnself;
}
+(instancetype)messageWithDictionary:(NSDictionary*)dict{
return[[selfalloc] initWithDictionary:dict];
}
//返回一个模型数组
+(NSMutableArray*)messages{
NSArray*array=[NSArray arrayWithContentsOfFile:[[NSBundlemainBundle] pathForResource:@"messages.plist" ofType:nil]];
NSMutableArray*arrayM=[NSMutableArrayarray];
for(NSDictionary*dictinarray){
JFMessage*message=[JFMessage messageWithDictionary:dict];
//第一次笔记可变数组最后一个元素是nil,所以和当前模型的时间不同。
//第二次可变数组最后一个元素是上一次存入模型,和当前模型进行比较。
//如果相同则当前模型的时间隐藏,不同则显示
if([message.time isEqualToString:[[arrayM lastObject] time]]){
//如果当前时间和上一条消息时间相同,则隐藏当前消息的时间显示
message.hideTime=YES;
}
//将封装的模型存入数组
[arrayM addObject:message];
}
returnarrayM;
}
@end
|
封装好数据模型后再继续封装frame模型,和微博案例类似,frame模型类中有模型对象属性、cell子控件的frame和cell的高度。因为封装frame的最终目的就是为了在创建cell之前计算出cell的高度,而计算cell的高度又必须根据cell子控件的frame来计算。最后提供一个返回frame模型数组的类方法,就可以将懒加载中的部分代码封装到模型类中,减少控制器中的代码量。
JFMessageFrame.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
@classJFMessage;
//定义时间字体、消息字体的宏,写到这里是为了让其他类引入该头文件也能使用此宏
#define timeFont [UIFont systemFontOfSize:13]
#define textFont [UIFont systemFontOfSize:13]
@interface JFMessageFrame : NSObject
//模型对象属性
@property(strong,nonatomic)JFMessage*message;
//cell高度
@property(assign,nonatomic)CGFloatcellHeight;
//时间、头像、消息的frame
@property(assign,nonatomic)CGRecttimeFrame;
@property(assign,nonatomic)CGRecticonFrame;
@property(assign,nonatomic)CGRecttextFrame;
//返回一个frame模型数组
+(NSMutableArray*)messageFrames;
@end
|
JFMessageFrame.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
|
#import "JFMessageFrame.h"
#import "JFMessage.h"
#import "NSString+JFFontSize.h"
#define margin 10 //每个子控件之间的间距
@implementationJFMessageFrame
//返回一个frame模型数组
+(NSMutableArray*)messageFrames{
//模型数组
NSMutableArray*arrayModel=[JFMessagemessages];
//frame模型数组
NSMutableArray*arrayFrameModel=[NSMutableArrayarray];
for(JFMessage*messageinarrayModel){
//创建frame模型
JFMessageFrame*messageFrame=[[JFMessageFramealloc] init];
//为frame模型赋值,这个步骤会调用我们重写后的set方法,计算各种frame和cell高度
messageFrame.message=message;
//将带数据的frame模型存入数组
[arrayFrameModel addObject:messageFrame];
}
returnarrayFrameModel;
}
//重写set方法为frame模型赋值
-(void)setMessage:(JFMessage*)message{
_message=message;
//取出屏幕宽度
CGFloatscreenW=[UIScreenmainScreen].bounds.size.width;
//计算frame
//时间
CGFloattimeViewW=screenW;
CGFloattimeViewH=20; //时间高度先定死
CGFloattimeViewX=0; //x、y坐标也设为原点,创建View时让文字居中显示
CGFloattimeViewY=0;
_timeFrame=CGRectMake(timeViewX,timeViewY,timeViewW,timeViewH);
//头像
CGFloaticonViewW=30; //头像尺寸先定死
CGFloaticonViewH=30;
CGFloaticonViewX=0; //需要判断消息类型确定x
CGFloaticonViewY=CGRectGetMaxY(_timeFrame)+margin;
//根据消息类型计算iconViewX的值
if(self.message.type==JFMessageSelf){
//自己发的信息
iconViewX=screenW-(iconViewW+margin);
}else{
//别人发的信息
iconViewX=margin;
}
_iconFrame=CGRectMake(iconViewX,iconViewY,iconViewW,iconViewH);
//文本
//调用方法计算文本的宽、高
CGSizetextViewSize=[self.message.text getFontSizeWithMaxSize:CGSizeMake(250,MAXFLOAT) andFont:textFont];
CGFloattextViewW=textViewSize.width+40; //这里增加的宽、高是为了让消息框中文字显示在背景图中。
CGFloattextViewH=textViewSize.height+30;//消息框frame增大、并添加内间距(contentEdgeInsets属性),就能让文字向中间挤,从而达到文字在背景图中居中的效果。
CGFloattextViewX=0; //要根据消息类型计算
CGFloattextViewY=CGRectGetMaxY(_iconFrame)-margin;//比头像的底部高一个margin
//根据消息类型计算textViewX的值
if(self.message.type==JFMessageSelf){
//自己发的信息
textViewX=screenW-(textViewW+iconViewW+2*margin);
}else{
//别人发的信息
textViewX=CGRectGetMaxX(_iconFrame)+margin;
}
_textFrame=CGRectMake(textViewX,textViewY,textViewW,textViewH);
//计算cell高度
_cellHeight=CGRectGetMaxY(_textFrame)+margin;
}
@end
|
这里用到了一个方法,不是NSString自带的方法,而是我们给NSString添加的分类。分类的代码如下:
1
|
-(CGSize)getFontSizeWithMaxSize:(CGSize)maxSize andFont:(UIFont*)font;
|
NSString+JFFontSize.h
1
2
3
4
5
6
7
8
|
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
@interfaceNSString(JFFontSize)
//根据传入最大Size和字体,返回一个Size
-(CGSize)getFontSizeWithMaxSize:(CGSize)maxSize andFont:(UIFont*)font;
@end
|
NSString+JFFontSize.m
1
2
3
4
5
6
7
8
9
|
#import "NSString+JFFontSize.h"
@implementationNSString(JFFontSize)
//根据传入最大Size和字体,返回一个Size
-(CGSize)getFontSizeWithMaxSize:(CGSize)maxSize andFont:(UIFont*)font{
return[self boundingRectWithSize:maxSize options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName: font} context:nil].size;
}
@end
|
封装好数据模型和frame模型后,就可以开始封装cell。方法和微博案例也是类似,只不过需要根据消息发送时间判断时间是否显示。并且要用到一个新的方法来拉升背景图片,从而达到背景图放大而不失真的效果。
JFMessageCell.h
1
2
3
4
5
6
7
8
9
10
11
12
|
#import <UIKit/UIKit.h>
@classJFMessageFrame;
@interface JFMessageCell : UITableViewCell
//数据模型属性,用于为cell子控件赋值数据
@property(strong,nonatomic)JFMessageFrame*messageFrame;
//创建cell
+(instancetype)messageCellWithTableView:(UITableView*)tableView;
@end
|
JFMessageCell.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
|
#import "JFMessageCell.h"
#import "JFMessage.h"
#import "JFMessageFrame.h"
#import "UIImage+JFImageSize.h"
//添加扩展,封装类内部属性,禁止外界访问
@interfaceJFMessageCell()
@property(weak,nonatomic)UILabel*timeView;
@property(weak,nonatomic)UIButton*iconButtonView;
@property(weak,nonatomic)UIButton*textButtonView;
@end
@implementationJFMessageCell
//创建cell
+(instancetype)messageCellWithTableView:(UITableView*)tableView{
staticNSString*ID=@"qq";
//从缓存中创建cell
JFMessageCell*cell=[tableView dequeueReusableCellWithIdentifier:ID];
//缓存中没有就新创建
if(cell==nil){
cell=[[JFMessageCellalloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:ID];
}
returncell;
}
//重写initWithStyle:reuseIdentifier:方法创建cell的子控件
-(instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString*)reuseIdentifier{
//重写父类构造方法需要先调用父类的构造方法,让父类先初始化完毕子类再初始化
if(self=[super initWithStyle:style reuseIdentifier:reuseIdentifier]){
//创建cell的所有子控件
//----------时间不能点击,使用UILabel----------
UILabel*timeView=[[UILabelalloc] init];
//将创建好的控件添加到cell中
[self addSubview:timeView];
//因为这里创建的控件是局部的,不能被类中其他方法访问到,所以定义控件属性指向这个控件
self.timeView=timeView;
//----------头像可点击,使用按钮-----------
UIButton*iconButtonView=[[UIButtonalloc] init];
[self addSubview:iconButtonView];
self.iconButtonView=iconButtonView;
//----------消息可点击,使用按钮----------
UIButton*textButtonView=[[UIButtonalloc] init];
[self addSubview:textButtonView];
self.textButtonView=textButtonView;
//将cell的背景颜色设为透明
self.backgroundColor=[UIColorclearColor];
}
returnself;
}
//重写set方法为cell的子控件赋值
-(void)setMessageFrame:(JFMessageFrame*)messageFrame{
_messageFrame=messageFrame;
//----------时间----------
//设置时间文字文字字体
self.timeView.font=timeFont;
//让时间居中显示
self.timeView.textAlignment=NSTextAlignmentCenter;
//时间隐藏属性非YES则显示时间,否则不显示
if(!self.messageFrame.message.hideTime){
//为时间控件赋值数据
self.timeView.text=self.messageFrame.message.time;
//为时间控件设置frame
self.timeView.frame=self.messageFrame.timeFrame;
}
//----------头像----------
//判断消息发送对象,再赋值头像数据
if(self.messageFrame.message.type==JFMessageSelf){
//为头像控件赋值数据,头像直接写死
[self.iconButtonView setBackgroundImage:[UIImage imageNamed:@"me"] forState:UIControlStateNormal];
}else{
//为头像控件赋值数据,头像直接写死
[self.iconButtonView setBackgroundImage:[UIImage imageNamed:@"other"] forState:UIControlStateNormal];
}
//为头像设置frame
self.iconButtonView.frame=self.messageFrame.iconFrame;
//----------消息----------
//设置消息文字字体
self.textButtonView.titleLabel.font=textFont;
//按钮文字自动换行
self.textButtonView.titleLabel.numberOfLines=0;
//为消息控件赋值数据
[self.textButtonView setTitle:self.messageFrame.message.text forState:UIControlStateNormal];
//设置消息控件的frame
self.textButtonView.frame=self.messageFrame.textFrame;
//设置文字的内边距
self.textButtonView.contentEdgeInsets=UIEdgeInsetsMake(20,20,20,20);
//调用分类方法,拉升背景图片
UIImage*selfbgNor=[UIImage getReSizeingImageWithName:@"chat_send_nor"];
UIImage*selfbgLigh=[UIImage getReSizeingImageWithName:@"chat_send_press_pic"];
UIImage*otherbgNor=[UIImage getReSizeingImageWithName:@"chat_recive_nor"];
UIImage*otherbgLigh=[UIImage getReSizeingImageWithName:@"chat_recive_press_pic"];
//判断消息发送对象,设置背景图片
if(self.messageFrame.message.type==JFMessageSelf){
//改变按钮文字颜色
[self.textButtonView setTitleColor:[UIColorwhiteColor] forState:UIControlStateNormal];
[self.textButtonView setBackgroundImage:selfbgNor forState:UIControlStateNormal];
[self.textButtonView setBackgroundImage:selfbgLigh forState:UIControlStateHighlighted];
}else{
//改变按钮文字颜色
[self.textButtonView setTitleColor:[UIColorblackColor] forState:UIControlStateNormal];
[self.textButtonView setBackgroundImage:otherbgNor forState:UIControlStateNormal];
[self.textButtonView setBackgroundImage:otherbgLigh forState:UIControlStateHighlighted];
}
}
@end
|
上面用到的拉升图片的方法,是一个自定义的UIImage分类的方法。分类的代码如下:
UIImage+JFImageSize.h
1
2
3
4
5
6
7
|
#import <UIKit/UIKit.h>
@interfaceUIImage(JFImageSize)
//传入一张图片名称返回一张拉升后的图片对象
+(UIImage*)getReSizeingImageWithName:(NSString*)name;
@end
|
UIImage+JFImageSize.m
1
2
3
4
5
6
7
8
9
|
#import "UIImage+JFImageSize.h"
@implementationUIImage(JFImageSize)
//传入一张图片名称返回一张拉升后的图片对象
+(UIImage*)getReSizeingImageWithName:(NSString*)name{
return[[UIImage imageNamed:name] resizableImageWithCapInsets:UIEdgeInsetsMake(30,20,18,30) resizingMode:UIImageResizingModeStretch];
}
@end
|
封装好cell后,就可以在控制器调用并展示数据了。首先在Main.storyboard中拖拽好基本界面,并对tableview和textField进行属性连线。
在控制器中遵守数据源协议、代理协议,并指定当前控制器为tableview的数据源对象、代理对象。并实现对应的方法。需要注意的是这里设置背景图的前提是cell的背景色是clearColor,否则会被cell自带的背景色给覆盖住。
ViewController.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
|
#import "ViewController.h"
#import "JFMessage.h"
#import "JFMessageCell.h"
#import "JFMessageFrame.h"
@interfaceViewController()<UITableViewDataSource,UITableViewDelegate>
@property(weak,nonatomic)IBOutletUITableView*tableView;
@property(weak,nonatomic)IBOutletUITextField*textFieldView;
//frame模型数组
@property(strong,nonatomic)NSMutableArray*messageFrames;
@end
@implementationViewController
-(void)viewDidLoad{
[superviewDidLoad];
//指定数据源对象、代理对象
self.tableView.dataSource=self;
self.tableView.delegate=self;
//设置tableview背景图
UIImageView*bg=[[UIImageViewalloc] initWithImage:[UIImage imageNamed:@"1"]];
self.tableView.backgroundView=bg;
//取消tableView分割线
self.tableView.separatorStyle=UITableViewCellSeparatorStyleNone;
//取消垂直滚动条
self.tableView.showsVerticalScrollIndicator=NO;
//禁止tableView被点击
self.tableView.allowsSelection=NO;
}
-(void)didReceiveMemoryWarning{
[superdidReceiveMemoryWarning];
}
//懒加载数据
-(NSMutableArray*)messageFrames{
if(_messageFrames==nil){
_messageFrames=[JFMessageFramemessageFrames];
}
return_messageFrames;
}
//一共有多少行
-(NSInteger)tableView:(UITableView*)tableView numberOfRowsInSection:(NSInteger)section{
returnself.messageFrames.count;
}
//创建每行的cell
-(UITableViewCell*)tableView:(UITableView*)tableView cellForRowAtIndexPath:(NSIndexPath*)indexPath{
//获取frame模型数据
JFMessageFrame*messageFrame=self.messageFrames[indexPath.row];
//创建cell
JFMessageCell*cell=[JFMessageCell messageCellWithTableView:tableView];
//为cell赋值数据
cell.messageFrame=messageFrame;
returncell;
}
//返回每个cell的高度,这个方法的执行优先权比创建cell的优先权更高
-(CGFloat)tableView:(UITableView*)tableView heightForRowAtIndexPath:(NSIndexPath*)indexPath{
//获取frame模型数据
JFMessageFrame*messageFrame=self.messageFrames[indexPath.row];
//返回cell高度
returnmessageFrame.cellHeight;
}
@end
|
最终效果图:
二、通知中心NSNotificationCenter
通知中心是程序内部提供的消息广播的一种机制。实际上就是一个消息传递者,把接收到的消息,根据内部的一个消息转发表,来将消息转发给需要的对象。通知中心是基于观察者模式的,它允许注册、删除观察者。
一个NSNotificationCenter可以有许多的通知消息NSNotification,对于每一个NSNotification可以有很多的观察者Observer来接收通知。
创建通知中心
一个应用程序内部只有一个通知中心实例对象(单例对象):
1
|
NSNotificationCenter*center=[NSNotificationCenterdefaultCenter];
|
发布通知
1
2
3
4
5
6
7
8
9
10
|
/*
参数说明
postNotificationName: 通知的名称
object: 通知发布者
userInfo: 通知的额外信息
*/
[centerpostNotificationName:<#(NSString *)#> object:<#(id)#> userInfo:<#(NSDictionary *)#>]
[centerpostNotificationName:<#(NSString *)#> object:<#(id)#>]
|
注册通知监听器
1
2
3
4
5
6
7
8
9
|
/*
参数说明:
addObserver:监听器:即谁要接收这个通知,这里传入需要接收通知的对象
selector:接收者接到通知后调用这个回调函数进行处理,这埋在注意要将当前通知对象做为参数传入
name:所接收的通知的名称,只接收这个名称所对应的通知,如果为nil,则接收所有通知
object:通知发布者,只接收这个名称的对象所发布的通知,如果为nil,则接收任何对象所发布的通知
*/
[centeraddObserver:<#(id)#> selector:<#(SEL)#> name:<#(NSString *)#> object:<#(id)#>]
|
取消注册通知监听器
1
2
3
4
5
|
//取消指定对象的所有注册监听
[[NSNotificationCenterdefaultCenter] removeObserver:<#(id)#>];
//取消指定对象的所有注册监听
[[NSNotificationCenterdefaultCenter] removeObserver:<#(id)#> name:<#(NSString *)#> object:<#(id)#>];
|
键盘通知
键盘状态发生改变的时候,系统会发出一些特定的通知
1
2
3
4
5
6
|
UIKeyboardWillShowNotification// 键盘即将显示
UIKeyboardDidShowNotification// 键盘显示完毕
UIKeyboardWillHideNotification// 键盘即将隐藏
UIKeyboardDidHideNotification// 键盘隐藏完毕
UIKeyboardWillChangeFrameNotification// 键盘的位置尺寸即将发生改变
UIKeyboardDidChangeFrameNotification// 键盘的位置尺寸改变完毕
|
注册键盘通知监听器
我们经常需要在键盘弹出或者隐藏的时候做一些特定的操作,因此需要监听键盘的状态。让控件器监听系统自动发出的键盘的frame将要发生改变事件,调用控制器的方法进行处理。代码如下:
1
2
3
4
5
|
//创建通知中心
NSNotificationCenter*center=[NSNotificationCenterdefaultCenter];
//注册键盘通知
[center addObserver:self selector:@selector(keyBoardWillChangeFrame:) name:UIKeyboardWillChangeFrameNotification object:nil];
|
键盘通知的额外信息
系统发出键盘通知时,会附带一下跟键盘有关的额外信息(字典),字典常见的key:
1
2
3
4
|
UIKeyboardFrameBeginUserInfoKey// 键盘刚开始的frame
UIKeyboardFrameEndUserInfoKey// 键盘最终的frame(动画执行完毕后)
UIKeyboardAnimationDurationUserInfoKey// 键盘动画的时间
UIKeyboardAnimationCurveUserInfoKey// 键盘动画的执行节奏(快慢)
|
实现键盘监听事件处理
获取键盘的开始和最终的frame,计算出偏移值,让tableView以动画的形式也进行相应的偏移。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
//处理监听键盘事件
-(void) keyBoardWillChangeFrame:(NSNotification*)note{
//获取额外信息
NSDictionary*info=note.userInfo;
//获取动画时间,将来以相同的时间以动画的形式移动view
CGFloatduration=[info[UIKeyboardAnimationDurationUserInfoKey] floatValue];
//获取键盘完全出现后的frame
CGRectendFrame=[info[@"UIKeyboardFrameEndUserInfoKey"] CGRectValue];
//计算Y轴偏移值
CGFloatoffsetY=endFrame.origin.y-self.view.frame.size.height;
//动画移动整个view
[UIView animateWithDuration:duration animations:^{
//进行y方向的偏移
self.view.transform=CGAffineTransformMakeTranslation(0,offsetY);
}];
}
|
通知和代理的区别:
代理 :一对一关系 (1个对象只能告诉另1个对象发生了什么事情)
通知:多对多关系(1个对象能告诉N个对象发生了什么事情, 1个对象能得知N个对象发生了什么事情)
通知和代理的选择:
如果是自定义的操作,一般使用代理。
如果是系统组件事件,就先考虑通知。
三、实现发布信息和自动回复
QQ界面已经搭建完毕,接下来实现键盘的弹出和收起,发布消息和自动回复功能。先设置UITextField的Return Key选项为Send,并勾选Auto-enable Return Key,让发送键在未输入内容的情况下禁止点击。
然后创建通知中心单例对象,注册通知监听事件并监听键盘的状态。在键盘弹出后,屏幕内容也跟着向上偏移,在拖动tableview的时候,收起键盘。
ViewController.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
|
//
// ViewController.m
// QQ案例练习
//
// Created by itcast on 15/8/17.
// Copyright © 2015年 itcast. All rights reserved.
//
#import "ViewController.h"
#import "JFMessage.h"
#import "JFMessageCell.h"
#import "JFMessageFrame.h"
@interfaceViewController()<UITableViewDataSource,UITableViewDelegate>
@property(weak,nonatomic)IBOutletUITableView*tableView;
@property(weak,nonatomic)IBOutletUITextField*textFieldView;
//frame模型数组
@property(strong,nonatomic)NSMutableArray*messageFrames;
@end
@implementationViewController
-(void)viewDidLoad{
[superviewDidLoad];
//指定数据源对象、代理对象
self.tableView.dataSource=self;
self.tableView.delegate=self;
//设置tableview背景图
UIImageView*bg=[[UIImageViewalloc] initWithImage:[UIImage imageNamed:@"1"]];
self.tableView.backgroundView=bg;
//取消tableView分割线
self.tableView.separatorStyle=UITableViewCellSeparatorStyleNone;
//取消垂直滚动条
self.tableView.showsVerticalScrollIndicator=NO;
//禁止tableView被点击
self.tableView.allowsSelection=NO;
//创建通知中心
NSNotificationCenter*center=[NSNotificationCenterdefaultCenter];
//注册键盘通知
[center addObserver:self selector:@selector(keyBoardWillChangeFrame:) name:UIKeyboardWillChangeFrameNotification object:nil];
}
//处理监听键盘事件
-(void) keyBoardWillChangeFrame:(NSNotification*)note{
//获取额外信息
NSDictionary*info=note.userInfo;
//获取动画时间,将来以相同的时间以动画的形式移动view
CGFloatduration=[info[UIKeyboardAnimationDurationUserInfoKey] floatValue];
//获取键盘完全出现后的frame
CGRectendFrame=[info[UIKeyboardFrameEndUserInfoKey] CGRectValue];
//计算Y轴偏移值
CGFloatoffsetY=endFrame.origin.y-self.view.frame.size.height;
//动画移动整个view
[UIView animateWithDuration:duration animations:^{
//进行y方向的偏移
self.view.transform=CGAffineTransformMakeTranslation(0,offsetY);
}];
//让tableview滑到底部
NSIndexPath*path=[NSIndexPath indexPathForRow:self.messageFrames.count-1 inSection:0];
[self.tableView scrollToRowAtIndexPath:path atScrollPosition:UITableViewScrollPositionBottom animated:YES];
}
// 移除通知
-(void)dealloc{
[[NSNotificationCenterdefaultCenter] removeObserver:self];
}
//拖拽tableview的时候执行这个方法,收起键盘
-(void)scrollViewWillBeginDragging:(UIScrollView*)scrollView{
//收起键盘
[self.view endEditing:YES];
}
-(void)didReceiveMemoryWarning{
[superdidReceiveMemoryWarning];
}
//懒加载数据
-(NSMutableArray*)messageFrames{
if(_messageFrames==nil){
_messageFrames=[JFMessageFramemessageFrames];
}
return_messageFrames;
}
//一共有多少行
-(NSInteger)tableView:(UITableView*)tableView numberOfRowsInSection:(NSInteger)section{
returnself.messageFrames.count;
}
//创建每行的cell
-(UITableViewCell*)tableView:(UITableView*)tableView cellForRowAtIndexPath:(NSIndexPath*)indexPath{
//获取frame模型数据
JFMessageFrame*messageFrame=self.messageFrames[indexPath.row];
//创建cell
JFMessageCell*cell=[JFMessageCell messageCellWithTableView:tableView];
//为cell赋值数据
cell.messageFrame=messageFrame;
returncell;
}
//返回每个cell的高度,这个方法的执行优先权比创建cell的优先权更高
-(CGFloat)tableView:(UITableView*)tableView heightForRowAtIndexPath:(NSIndexPath*)indexPath{
//获取frame模型数据
JFMessageFrame*messageFrame=self.messageFrames[indexPath.row];
//返回cell高度
returnmessageFrame.cellHeight;
}
@end
|
实现发布信息和自动回复功能,监听Send键的点击。当点击键盘上的Send键后创建模型,添加到frame模型数组中,并重新加载tableview中的数据。
ViewController.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
|
#import "ViewController.h"
#import "JFMessage.h"
#import "JFMessageCell.h"
#import "JFMessageFrame.h"
@interfaceViewController()<UITableViewDataSource,UITableViewDelegate,UITextFieldDelegate>
@property(weak,nonatomic)IBOutletUITableView*tableView;
@property(weak,nonatomic)IBOutletUITextField*textFieldView;
//frame模型数组
@property(strong,nonatomic)NSMutableArray*messageFrames;
@end
@implementationViewController
-(void)viewDidLoad{
[superviewDidLoad];
//指定数据源对象、代理对象
self.tableView.dataSource=self;
self.tableView.delegate=self;
self.textFieldView.delegate=self;
//设置tableview背景图
UIImageView*bg=[[UIImageViewalloc] initWithImage:[UIImage imageNamed:@"1"]];
self.tableView.backgroundView=bg;
//取消tableView分割线
self.tableView.separatorStyle=UITableViewCellSeparatorStyleNone;
//取消垂直滚动条
self.tableView.showsVerticalScrollIndicator=NO;
//禁止tableView被点击
self.tableView.allowsSelection=NO;
//创建通知中心
NSNotificationCenter*center=[NSNotificationCenterdefaultCenter];
//注册键盘通知
[center addObserver:self selector:@selector(keyBoardWillChangeFrame:) name:UIKeyboardWillChangeFrameNotification object:nil];
}
//处理监听键盘事件
-(void) keyBoardWillChangeFrame:(NSNotification*)note{
//获取额外信息
NSDictionary*info=note.userInfo;
//获取动画时间,将来以相同的时间以动画的形式移动view
CGFloatduration=[info[UIKeyboardAnimationDurationUserInfoKey] floatValue];
//获取键盘完全出现后的frame
CGRectendFrame=[info[UIKeyboardFrameEndUserInfoKey] CGRectValue];
//计算Y轴偏移值
CGFloatoffsetY=endFrame.origin.y-self.view.frame.size.height;
//动画移动整个view
[UIView animateWithDuration:duration animations:^{
//进行y方向的偏移
self.view.transform=CGAffineTransformMakeTranslation(0,offsetY);
}];
//让tableview滑到底部
NSIndexPath*path=[NSIndexPath indexPathForRow:self.messageFrames.count-1 inSection:0];
[self.tableView scrollToRowAtIndexPath:path atScrollPosition:UITableViewScrollPositionBottom animated:YES];
}
// 移除通知
-(void)dealloc{
[[NSNotificationCenterdefaultCenter] removeObserver:self];
}
//拖拽tableview的时候执行这个方法,收起键盘
-(void)scrollViewWillBeginDragging:(UIScrollView*)scrollView{
//收起键盘
[self.view endEditing:YES];
}
// 当键盘上的return键被单击的时候触发
-(BOOL)textFieldShouldReturn:(UITextField*)textField{
//获取用户输入的文本
NSString*text=textField.text;
//发送用户的消息
[self sendMessage:text withType:JFMessageSelf];
//发送一个系统消息
[self sendMessage:@"不认识!" withType:JFMessageOther];
//清空文本框
textField.text=nil;
returnYES;
}
// 发送消息
-(void)sendMessage:(NSString*)msg withType:(JFMessageType)type{
//获取当前系统时间
NSDate*nowDate=[NSDatedate];
//创建一个日期时间格式化器
NSDateFormatter*formatter=[[NSDateFormatteralloc] init];
//设置格式
formatter.dateFormat=@"今天 HH:mm";
JFMessageFrame*modelFrame=[[JFMessageFramealloc] init];
JFMessage*message=[[JFMessagealloc] init];
//设置模型的属性
message.time=[formatter stringFromDate:nowDate];
message.type=type;
message.text=msg;
//为frame模型赋值
modelFrame.message=message;
//根据当前消息的时间和上一条消息的时间, 来设置是否需要隐藏时间Label
JFMessageFrame*lastMessageFrame=[self.messageFrames lastObject];
NSString*lastTime=lastMessageFrame.message.time;
//判断时间是否需要隐藏
if([message.time isEqualToString:lastTime]){
modelFrame.message.hideTime=YES;
}
//添加frame模型到模型数组
[self.messageFrames addObject:modelFrame];
//刷新tableview中所有数据
[self.tableView reloadData];
//把最后一行滚动到最上面
NSIndexPath*idxPath=[NSIndexPath indexPathForRow:self.messageFrames.count-1 inSection:0];
[self.tableView scrollToRowAtIndexPath:idxPath atScrollPosition:UITableViewScrollPositionTop animated:YES];
}
-(void)didReceiveMemoryWarning{
[superdidReceiveMemoryWarning];
}
//懒加载数据
-(NSMutableArray*)messageFrames{
if(_messageFrames==nil){
_messageFrames=[JFMessageFramemessageFrames];
}
return_messageFrames;
}
//一共有多少行
-(NSInteger)tableView:(UITableView*)tableView numberOfRowsInSection:(NSInteger)section{
returnself.messageFrames.count;
}
//创建每行的cell
-(UITableViewCell*)tableView:(UITableView*)tableView cellForRowAtIndexPath:(NSIndexPath*)indexPath{
//获取frame模型数据
JFMessageFrame*messageFrame=self.messageFrames[indexPath.row];
//创建cell
JFMessageCell*cell=[JFMessageCell messageCellWithTableView:tableView];
//为cell赋值数据
cell.messageFrame=messageFrame;
returncell;
}
//返回每个cell的高度,这个方法的执行优先权比创建cell的优先权更高
-(CGFloat)tableView:(UITableView*)tableView heightForRowAtIndexPath:(NSIndexPath*)indexPath{
//获取frame模型数据
JFMessageFrame*messageFrame=self.messageFrames[indexPath.row];
//返回cell高度
returnmessageFrame.cellHeight;
}
@end
|
案例易错总结:
1.注册的监听一定要在对象销毁的时候取消,不然系统还会一直向当前注册监听的对象发送通知,但是对象已经不存在,就会造成野指针的错误。
2.这里不能直接使用UITableViewController,因为当前界面还有其他控件。
3.忘记设置代理对象。