1、 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
最终效果图:
2、 通知中心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个对象发生了什么事情)
通知和代理的选择:
如果是自定义的操作,一般使用代理。
如果是系统组件事件,就先考虑通知。
3、 实现发布信息和自动回复
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.忘记设置代理对象。