学习目标
1.【理解】QQ界面搭建
2.【理解】通知中心NSNotificationCenter
3.【理解】实现发布信息和自动回复
一、QQ界面搭建
手机QQ聊天软件应该大多数人都接触过,就不细说了,需求是自定义cell搭建手机QQ聊天界面。
分析:顶部、底部是单独UIView控件,中间是tableview。所以可以一眼看出顶部、底部不是在tableview中的,因为他们不会随着tableview而滑动。效果图如下:点击这里查看动态图
还是和上一篇文章的微博案例一样,先封装数据模型和cell的frame模型。导入素材和plist文件,创建模型类。素材下载地址
创建模型类注意:
type建议定义为枚举类型,虽然也不是必须要这样,但是这样看起来必须形象些吧。还有需要定义一个时间是否隐藏的属性,当多条消息时间相同的时候,只显示第一条消息的时间,而隐藏之后消息的时间。我们每天都在用QQ,应该都很熟悉这个了,如下图所示:
JFMessage.h
#import <Foundation/Foundation.h> //定义一个消息类型枚举,0表示我发的消息,1表示别人发的消息 typedef enum { JFMessageSelf = 0, JFMessageOther } JFMessageType; @interface JFMessage : NSObject @property (copy, nonatomic) NSString *text; @property (copy, nonatomic) NSString *time; @property (assign, nonatomic) JFMessageType type; //是否隐藏时间显示 @property (assign, nonatomic, getter=isHideTime) BOOL hideTime; //快速创建模型的方法 - (instancetype)initWithDictionary:(NSDictionary *)dict; + (instancetype)messageWithDictionary:(NSDictionary *)dict; //返回一个模型数组 + (NSMutableArray *)messages; @end
JFMessage.m
#import "JFMessage.h" @implementation JFMessage //快速创建模型的方法 - (instancetype)initWithDictionary:(NSDictionary *)dict { if (self = [super init]) { [self setValuesForKeysWithDictionary:dict]; } return self; } + (instancetype)messageWithDictionary:(NSDictionary *)dict { return [[self alloc] initWithDictionary:dict]; } //返回一个模型数组 + (NSMutableArray *)messages { NSArray *array = [NSArray arrayWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"messages.plist" ofType:nil]]; NSMutableArray *arrayM = [NSMutableArray array]; for (NSDictionary *dict in array) { JFMessage *message = [JFMessage messageWithDictionary:dict]; //第一次笔记可变数组最后一个元素是nil,所以和当前模型的时间不同。 //第二次可变数组最后一个元素是上一次存入模型,和当前模型进行比较。 //如果相同则当前模型的时间隐藏,不同则显示 if ([message.time isEqualToString:[[arrayM lastObject] time]]) { //如果当前时间和上一条消息时间相同,则隐藏当前消息的时间显示 message.hideTime = YES; } //将封装的模型存入数组 [arrayM addObject:message]; } return arrayM; } @end
封装好数据模型后再继续封装frame模型,和微博案例类似,frame模型类中有模型对象属性、cell子控件的frame和cell的高度。因为封装frame的最终目的就是为了在创建cell之前计算出cell的高度,而计算cell的高度又必须根据cell子控件的frame来计算。最后提供一个返回frame模型数组的类方法,就可以将懒加载中的部分代码封装到模型类中,减少控制器中的代码量。
JFMessageFrame.h
#import <Foundation/Foundation.h> #import <UIKit/UIKit.h> @class JFMessage; //定义时间字体、消息字体的宏,写到这里是为了让其他类引入该头文件也能使用此宏 #define timeFont [UIFont systemFontOfSize:13] #define textFont [UIFont systemFontOfSize:13] @interface JFMessageFrame : NSObject //模型对象属性 @property (strong, nonatomic) JFMessage *message; //cell高度 @property (assign, nonatomic) CGFloat cellHeight; //时间、头像、消息的frame @property (assign, nonatomic) CGRect timeFrame; @property (assign, nonatomic) CGRect iconFrame; @property (assign, nonatomic) CGRect textFrame; //返回一个frame模型数组 + (NSMutableArray *)messageFrames; @end
JFMessageFrame.m
#import "JFMessageFrame.h" #import "JFMessage.h" #import "NSString+JFFontSize.h" #define margin 10 //每个子控件之间的间距 @implementation JFMessageFrame //返回一个frame模型数组 + (NSMutableArray *)messageFrames { //模型数组 NSMutableArray *arrayModel = [JFMessage messages]; //frame模型数组 NSMutableArray *arrayFrameModel = [NSMutableArray array]; for (JFMessage *message in arrayModel) { //创建frame模型 JFMessageFrame *messageFrame = [[JFMessageFrame alloc] init]; //为frame模型赋值,这个步骤会调用我们重写后的set方法,计算各种frame和cell高度 messageFrame.message = message; //将带数据的frame模型存入数组 [arrayFrameModel addObject:messageFrame]; } return arrayFrameModel; } //重写set方法为frame模型赋值 - (void)setMessage:(JFMessage *)message { _message = message; //取出屏幕宽度 CGFloat screenW = [UIScreen mainScreen].bounds.size.width; //计算frame //时间 CGFloat timeViewW = screenW; CGFloat timeViewH = 20; //时间高度先定死 CGFloat timeViewX = 0; //x、y坐标也设为原点,创建View时让文字居中显示 CGFloat timeViewY = 0; _timeFrame = CGRectMake(timeViewX, timeViewY, timeViewW, timeViewH); //头像 CGFloat iconViewW = 30; //头像尺寸先定死 CGFloat iconViewH = 30; CGFloat iconViewX = 0; //需要判断消息类型确定x CGFloat iconViewY = CGRectGetMaxY(_timeFrame) + margin; //根据消息类型计算iconViewX的值 if (self.message.type == JFMessageSelf) { //自己发的信息 iconViewX = screenW - (iconViewW + margin); } else { //别人发的信息 iconViewX = margin; } _iconFrame = CGRectMake(iconViewX, iconViewY, iconViewW, iconViewH); //文本 //调用方法计算文本的宽、高 CGSize textViewSize = [self.message.text getFontSizeWithMaxSize:CGSizeMake(250, MAXFLOAT) andFont:textFont]; CGFloat textViewW = textViewSize.width + 40; //这里增加的宽、高是为了让消息框中文字显示在背景图中。 CGFloat textViewH = textViewSize.height + 30; //消息框frame增大、并添加内间距(contentEdgeInsets属性),就能让文字向中间挤,从而达到文字在背景图中居中的效果。 CGFloat textViewX = 0; //要根据消息类型计算 CGFloat textViewY = 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添加的分类。分类的代码如下:
- (CGSize)getFontSizeWithMaxSize:(CGSize)maxSize andFont:(UIFont *)font;
NSString+JFFontSize.h
#import <Foundation/Foundation.h> #import <UIKit/UIKit.h> @interface NSString (JFFontSize) //根据传入最大Size和字体,返回一个Size - (CGSize)getFontSizeWithMaxSize:(CGSize)maxSize andFont:(UIFont *)font; @end
NSString+JFFontSize.m
#import "NSString+JFFontSize.h" @implementation NSString (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
#import <UIKit/UIKit.h> @class JFMessageFrame; @interface JFMessageCell : UITableViewCell //数据模型属性,用于为cell子控件赋值数据 @property (strong, nonatomic) JFMessageFrame *messageFrame; //创建cell + (instancetype)messageCellWithTableView:(UITableView *)tableView; @end
JFMessageCell.m
#import "JFMessageCell.h" #import "JFMessage.h" #import "JFMessageFrame.h" #import "UIImage+JFImageSize.h" //添加扩展,封装类内部属性,禁止外界访问 @interface JFMessageCell () @property (weak, nonatomic) UILabel *timeView; @property (weak, nonatomic) UIButton *iconButtonView; @property (weak, nonatomic) UIButton *textButtonView; @end @implementation JFMessageCell //创建cell + (instancetype)messageCellWithTableView:(UITableView *)tableView { static NSString *ID = @"qq"; //从缓存中创建cell JFMessageCell *cell = [tableView dequeueReusableCellWithIdentifier:ID]; //缓存中没有就新创建 if (cell == nil) { cell = [[JFMessageCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:ID]; } return cell; } //重写initWithStyle:reuseIdentifier:方法创建cell的子控件 - (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier { //重写父类构造方法需要先调用父类的构造方法,让父类先初始化完毕子类再初始化 if (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]) { //创建cell的所有子控件 //----------时间不能点击,使用UILabel---------- UILabel *timeView = [[UILabel alloc] init]; //将创建好的控件添加到cell中 [self addSubview:timeView]; //因为这里创建的控件是局部的,不能被类中其他方法访问到,所以定义控件属性指向这个控件 self.timeView = timeView; //----------头像可点击,使用按钮----------- UIButton *iconButtonView = [[UIButton alloc] init]; [self addSubview:iconButtonView]; self.iconButtonView = iconButtonView; //----------消息可点击,使用按钮---------- UIButton *textButtonView = [[UIButton alloc] init]; [self addSubview:textButtonView]; self.textButtonView = textButtonView; //将cell的背景颜色设为透明 self.backgroundColor = [UIColor clearColor]; } return self; } //重写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:[UIColor whiteColor] forState:UIControlStateNormal]; [self.textButtonView setBackgroundImage:selfbgNor forState:UIControlStateNormal]; [self.textButtonView setBackgroundImage:selfbgLigh forState:UIControlStateHighlighted]; } else { //改变按钮文字颜色 [self.textButtonView setTitleColor:[UIColor blackColor] forState:UIControlStateNormal]; [self.textButtonView setBackgroundImage:otherbgNor forState:UIControlStateNormal]; [self.textButtonView setBackgroundImage:otherbgLigh forState:UIControlStateHighlighted]; } } @end
上面用到的拉升图片的方法,是一个自定义的UIImage分类的方法。分类的代码如下:
UIImage+JFImageSize.h
#import <UIKit/UIKit.h> @interface UIImage (JFImageSize) //传入一张图片名称返回一张拉升后的图片对象 + (UIImage *)getReSizeingImageWithName:(NSString *)name; @end
UIImage+JFImageSize.m
#import "UIImage+JFImageSize.h" @implementation UIImage (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
#import "ViewController.h" #import "JFMessage.h" #import "JFMessageCell.h" #import "JFMessageFrame.h" @interface ViewController () <UITableViewDataSource,UITableViewDelegate> @property (weak, nonatomic) IBOutlet UITableView *tableView; @property (weak, nonatomic) IBOutlet UITextField *textFieldView; //frame模型数组 @property (strong, nonatomic) NSMutableArray *messageFrames; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; //指定数据源对象、代理对象 self.tableView.dataSource = self; self.tableView.delegate = self; //设置tableview背景图 UIImageView *bg = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"1"]]; self.tableView.backgroundView = bg; //取消tableView分割线 self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone; //取消垂直滚动条 self.tableView.showsVerticalScrollIndicator = NO; //禁止tableView被点击 self.tableView.allowsSelection = NO; } - (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; } //懒加载数据 - (NSMutableArray *)messageFrames { if (_messageFrames == nil) { _messageFrames = [JFMessageFrame messageFrames]; } return _messageFrames; } //一共有多少行 - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return self.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; return cell; } //返回每个cell的高度,这个方法的执行优先权比创建cell的优先权更高 - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { //获取frame模型数据 JFMessageFrame *messageFrame = self.messageFrames[indexPath.row]; //返回cell高度 return messageFrame.cellHeight; } @end
最终效果图:
二、通知中心NSNotificationCenter
通知中心是程序内部提供的消息广播的一种机制。实际上就是一个消息传递者,把接收到的消息,根据内部的一个消息转发表,来将消息转发给需要的对象。通知中心是基于观察者模式的,它允许注册、删除观察者。
一个NSNotificationCenter可以有许多的通知消息NSNotification,对于每一个NSNotification可以有很多的观察者Observer来接收通知。
创建通知中心
一个应用程序内部只有一个通知中心实例对象(单例对象):
NSNotificationCenter *center=[NSNotificationCenter defaultCenter];
发布通知
/* 参数说明 postNotificationName: 通知的名称 object: 通知发布者 userInfo: 通知的额外信息 */ [center postNotificationName:<#(NSString *)#> object:<#(id)#> userInfo:<#(NSDictionary *)#>] [center postNotificationName:<#(NSString *)#> object:<#(id)#>]
注册通知监听器
/* 参数说明: addObserver:监听器:即谁要接收这个通知,这里传入需要接收通知的对象 selector:接收者接到通知后调用这个回调函数进行处理,这埋在注意要将当前通知对象做为参数传入 name:所接收的通知的名称,只接收这个名称所对应的通知,如果为nil,则接收所有通知 object:通知发布者,只接收这个名称的对象所发布的通知,如果为nil,则接收任何对象所发布的通知 */ [center addObserver:<#(id)#> selector:<#(SEL)#> name:<#(NSString *)#> object:<#(id)#>]
取消注册通知监听器
//取消指定对象的所有注册监听 [[NSNotificationCenter defaultCenter] removeObserver:<#(id)#>]; //取消指定对象的所有注册监听 [[NSNotificationCenter defaultCenter] removeObserver:<#(id)#> name:<#(NSString *)#> object:<#(id)#>];
键盘通知
键盘状态发生改变的时候,系统会发出一些特定的通知
UIKeyboardWillShowNotification // 键盘即将显示 UIKeyboardDidShowNotification // 键盘显示完毕 UIKeyboardWillHideNotification // 键盘即将隐藏 UIKeyboardDidHideNotification // 键盘隐藏完毕 UIKeyboardWillChangeFrameNotification // 键盘的位置尺寸即将发生改变 UIKeyboardDidChangeFrameNotification // 键盘的位置尺寸改变完毕
注册键盘通知监听器
我们经常需要在键盘弹出或者隐藏的时候做一些特定的操作,因此需要监听键盘的状态。让控件器监听系统自动发出的键盘的frame将要发生改变事件,调用控制器的方法进行处理。代码如下:
//创建通知中心 NSNotificationCenter *center=[NSNotificationCenter defaultCenter]; //注册键盘通知 [center addObserver:self selector:@selector(keyBoardWillChangeFrame:) name:UIKeyboardWillChangeFrameNotification object:nil];
键盘通知的额外信息
系统发出键盘通知时,会附带一下跟键盘有关的额外信息(字典),字典常见的key:
UIKeyboardFrameBeginUserInfoKey // 键盘刚开始的frame UIKeyboardFrameEndUserInfoKey // 键盘最终的frame(动画执行完毕后) UIKeyboardAnimationDurationUserInfoKey // 键盘动画的时间 UIKeyboardAnimationCurveUserInfoKey // 键盘动画的执行节奏(快慢)
实现键盘监听事件处理
获取键盘的开始和最终的frame,计算出偏移值,让tableView以动画的形式也进行相应的偏移。
//处理监听键盘事件 - (void) keyBoardWillChangeFrame:(NSNotification *)note { //获取额外信息 NSDictionary *info = note.userInfo; //获取动画时间,将来以相同的时间以动画的形式移动view CGFloat duration = [info[UIKeyboardAnimationDurationUserInfoKey] floatValue]; //获取键盘完全出现后的frame CGRect endFrame = [info[@"UIKeyboardFrameEndUserInfoKey"] CGRectValue]; //计算Y轴偏移值 CGFloat offsetY = 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
// // 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" @interface ViewController () <UITableViewDataSource,UITableViewDelegate> @property (weak, nonatomic) IBOutlet UITableView *tableView; @property (weak, nonatomic) IBOutlet UITextField *textFieldView; //frame模型数组 @property (strong, nonatomic) NSMutableArray *messageFrames; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; //指定数据源对象、代理对象 self.tableView.dataSource = self; self.tableView.delegate = self; //设置tableview背景图 UIImageView *bg = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"1"]]; self.tableView.backgroundView = bg; //取消tableView分割线 self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone; //取消垂直滚动条 self.tableView.showsVerticalScrollIndicator = NO; //禁止tableView被点击 self.tableView.allowsSelection = NO; //创建通知中心 NSNotificationCenter *center=[NSNotificationCenter defaultCenter]; //注册键盘通知 [center addObserver:self selector:@selector(keyBoardWillChangeFrame:) name:UIKeyboardWillChangeFrameNotification object:nil]; } //处理监听键盘事件 - (void) keyBoardWillChangeFrame:(NSNotification *)note { //获取额外信息 NSDictionary *info = note.userInfo; //获取动画时间,将来以相同的时间以动画的形式移动view CGFloat duration = [info[UIKeyboardAnimationDurationUserInfoKey] floatValue]; //获取键盘完全出现后的frame CGRect endFrame = [info[UIKeyboardFrameEndUserInfoKey] CGRectValue]; //计算Y轴偏移值 CGFloat offsetY = 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 { [[NSNotificationCenter defaultCenter] removeObserver:self]; } //拖拽tableview的时候执行这个方法,收起键盘 - (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView { //收起键盘 [self.view endEditing:YES]; } - (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; } //懒加载数据 - (NSMutableArray *)messageFrames { if (_messageFrames == nil) { _messageFrames = [JFMessageFrame messageFrames]; } return _messageFrames; } //一共有多少行 - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return self.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; return cell; } //返回每个cell的高度,这个方法的执行优先权比创建cell的优先权更高 - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { //获取frame模型数据 JFMessageFrame *messageFrame = self.messageFrames[indexPath.row]; //返回cell高度 return messageFrame.cellHeight; } @end
实现发布信息和自动回复功能,监听Send键的点击。当点击键盘上的Send键后创建模型,添加到frame模型数组中,并重新加载tableview中的数据。
ViewController.m
#import "ViewController.h" #import "JFMessage.h" #import "JFMessageCell.h" #import "JFMessageFrame.h" @interface ViewController () <UITableViewDataSource,UITableViewDelegate,UITextFieldDelegate> @property (weak, nonatomic) IBOutlet UITableView *tableView; @property (weak, nonatomic) IBOutlet UITextField *textFieldView; //frame模型数组 @property (strong, nonatomic) NSMutableArray *messageFrames; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; //指定数据源对象、代理对象 self.tableView.dataSource = self; self.tableView.delegate = self; self.textFieldView.delegate = self; //设置tableview背景图 UIImageView *bg = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"1"]]; self.tableView.backgroundView = bg; //取消tableView分割线 self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone; //取消垂直滚动条 self.tableView.showsVerticalScrollIndicator = NO; //禁止tableView被点击 self.tableView.allowsSelection = NO; //创建通知中心 NSNotificationCenter *center=[NSNotificationCenter defaultCenter]; //注册键盘通知 [center addObserver:self selector:@selector(keyBoardWillChangeFrame:) name:UIKeyboardWillChangeFrameNotification object:nil]; } //处理监听键盘事件 - (void) keyBoardWillChangeFrame:(NSNotification *)note { //获取额外信息 NSDictionary *info = note.userInfo; //获取动画时间,将来以相同的时间以动画的形式移动view CGFloat duration = [info[UIKeyboardAnimationDurationUserInfoKey] floatValue]; //获取键盘完全出现后的frame CGRect endFrame = [info[UIKeyboardFrameEndUserInfoKey] CGRectValue]; //计算Y轴偏移值 CGFloat offsetY = 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 { [[NSNotificationCenter defaultCenter] 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; return YES; } // 发送消息 - (void)sendMessage:(NSString *)msg withType:(JFMessageType)type { //获取当前系统时间 NSDate *nowDate = [NSDate date]; //创建一个日期时间格式化器 NSDateFormatter *formatter = [[NSDateFormatter alloc] init]; //设置格式 formatter.dateFormat = @"今天 HH:mm"; JFMessageFrame *modelFrame = [[JFMessageFrame alloc] init]; JFMessage *message = [[JFMessage alloc] 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 { [super didReceiveMemoryWarning]; } //懒加载数据 - (NSMutableArray *)messageFrames { if (_messageFrames == nil) { _messageFrames = [JFMessageFrame messageFrames]; } return _messageFrames; } //一共有多少行 - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return self.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; return cell; } //返回每个cell的高度,这个方法的执行优先权比创建cell的优先权更高 - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { //获取frame模型数据 JFMessageFrame *messageFrame = self.messageFrames[indexPath.row]; //返回cell高度 return messageFrame.cellHeight; } @end
案例易错总结:
1.注册的监听一定要在对象销毁的时候取消,不然系统还会一直向当前注册监听的对象发送通知,但是对象已经不存在,就会造成野指针的错误。
2.这里不能直接使用UITableViewController,因为当前界面还有其他控件。
3.忘记设置代理对象。