源码下载地址:http://download.csdn.net/detail/liu537192/8471945
Demo效果图:
核心代码:
//
// JLViewController.m
// 01-QQ
//
// Created by XinYou on 15-3-3.
// Copyright (c) 2015年 vxinyou. All rights reserved.
//
#import "JLViewController.h"
#import "JLMessage.h"
#import "JLMessageFrame.h"
#import "JLMessageCell.h"
@interface JLViewController ()
@property (weak, nonatomic) IBOutlet UITableView *tableView;
@property (nonatomic, strong) NSMutableArray *messageFrames;
@property (weak, nonatomic) IBOutlet UITextField *inputView;
@end
@implementation JLViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// 1,UITableView的设置
// 设置数据源
self.tableView.dataSource = self;
// 设置代理
self.tableView.delegate = self;
// 设置tableView的背景色
self.tableView.backgroundColor = [UIColor colorWithRed:235/255.0 green:235/255.0 blue:235/255.0 alpha:1];
// 去除cell之间的分割线
self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
// 将cell设置为不可选中(点击)
self.tableView.allowsSelection = NO;
// 2,监听键盘的通知,注册监听,一定要记得反注册
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillChangeFrame:) name:UIKeyboardWillChangeFrameNotification object:nil];
// 3,设置文本框左边显示的view(因为光标太靠左,我们想让光标离UITextField左边有一段距离)
// 在UITextField的左边放一个高度为0,宽度不为0的UIView来占据左边的一段空间
self.inputView.leftView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 0)];
// 设置leftView的显示模式,这里设置为总是显示(如果不设置,默认是不显示的)
self.inputView.leftViewMode = UITextFieldViewModeAlways;
// 4,设置UITextField的代理
self.inputView.delegate = self;
}
/**
* 当键盘的frame改变了(位置和尺寸)的时候调用
*
* @param notification 通知包含的信息
*/
- (void)keyboardWillChangeFrame:(NSNotification *)notification{
self.view.window.backgroundColor = self.tableView.backgroundColor;
NSDictionary *userInfo = notification.userInfo;
// 当键盘弹出的时候,userInfo:
/**
UIKeyboardAnimationCurveUserInfoKey = 7;// 动画执行的节奏
UIKeyboardAnimationDurationUserInfoKey = "0.25"; // 键盘弹出和隐藏动画所需时间
UIKeyboardBoundsUserInfoKey = "NSRect: {{0, 0}, {320, 216}}";
UIKeyboardCenterBeginUserInfoKey = "NSPoint: {160, 588}";
UIKeyboardCenterEndUserInfoKey = "NSPoint: {160, 372}";
UIKeyboardFrameBeginUserInfoKey = "NSRect: {{0, 480}, {320, 216}}";
UIKeyboardFrameChangedByUserInteraction = 0;
UIKeyboardFrameEndUserInfoKey = "NSRect: {{0, 264}, {320, 216}}";
*/
// 当键盘隐藏的时候,userInfo:
/**
UIKeyboardAnimationCurveUserInfoKey = 7;
UIKeyboardAnimationDurationUserInfoKey = "0.25";
UIKeyboardBoundsUserInfoKey = "NSRect: {{0, 0}, {320, 216}}";
UIKeyboardCenterBeginUserInfoKey = "NSPoint: {160, 372}";
UIKeyboardCenterEndUserInfoKey = "NSPoint: {160, 588}";
UIKeyboardFrameBeginUserInfoKey = "NSRect: {{0, 264}, {320, 216}}";
UIKeyboardFrameChangedByUserInteraction = 0;
UIKeyboardFrameEndUserInfoKey = "NSRect: {{0, 480}, {320, 216}}";
*/
// 1,取出键盘动画的时间
CGFloat duration = [userInfo[UIKeyboardAnimationDurationUserInfoKey] doubleValue];
// 2,取得键盘将要移动到的位置的frame
CGRect keyboardFrame = [userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue];
// 3,计算控制器的view需要平移的距离
CGFloat moveY = keyboardFrame.origin.y - self.view.frame.size.height;
// 4,执行动画
[UIView animateWithDuration:duration animations:^{
self.view.transform = CGAffineTransformMakeTranslation(0, moveY);
}];
}
/**
* 在dealloc方法中反注册,移除对通知的监听
*/
- (void)dealloc{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
// 隐藏状态栏
- (BOOL)prefersStatusBarHidden{
return YES;
}
- (NSMutableArray *)messageFrames
{
if (_messageFrames == nil) {
NSArray *dictArray = [NSArray arrayWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"messages.plist" ofType:nil]];
NSMutableArray *tempArray = [NSMutableArray array];
for (NSDictionary *dict in dictArray) {
// 消息模型
JLMessage *message = [JLMessage messageWithDict:dict];
// 取出上一个模型
JLMessageFrame *previousMessageFrame = [tempArray lastObject];
JLMessage *previousMessage = previousMessageFrame.message;
// 判断两个message的发送时间是否一致
message.hideTime = [message.time isEqualToString:previousMessage.time];
// frame模型
JLMessageFrame *messageFrame = [[JLMessageFrame alloc] init];
messageFrame.message = message;
// 添加模型到数组
[tempArray addObject:messageFrame];
}
_messageFrames = tempArray;
}
return _messageFrames;
}
/**
* 发送一条消息
*/
- (void)sendMessage:(NSString *)text type:(JLMessageType)type{
// 1,数据模型
JLMessage *msg = [[JLMessage alloc] init];
// 设置信息类型,是自己发送给别人
msg.type = type;
// 设置信息内容
msg.text = text;
// 设置信息发送时间
NSDate *msgTime = [NSDate date];
// 对当前时间进行格式化
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
formatter.dateFormat = @"HH:mm";
msg.time = [formatter stringFromDate:msgTime];
// 是否需要隐藏时间
JLMessageFrame *previousFrame = [self.messageFrames lastObject];
JLMessage *previousMsg = previousFrame.message;
msg.hideTime = [msg.time isEqualToString:previousMsg.time];
// 2,frame模型
JLMessageFrame *msgFrame = [[JLMessageFrame alloc] init];
msgFrame.message = msg;
[self.messageFrames addObject:msgFrame];
// 3,刷新表格
[self.tableView reloadData];
// 4,自动滚动到表格的最后一行
NSIndexPath *path = [NSIndexPath indexPathForRow:self.messageFrames.count - 1 inSection:0];
[self.tableView scrollToRowAtIndexPath:path atScrollPosition:UITableViewScrollPositionBottom animated:YES];
}
#pragma mark -UITextField的代理方法
/**
* 点击了手机键盘的最右下角的按钮
*/
- (BOOL)textFieldShouldReturn:(UITextField *)textField{
// 1,发送一条消息
[self sendMessage:textField.text type:JLMessageTypeMeToOther];
// 2,UITextField的文字
self.inputView.text = nil;
// 返回YES即可,不要问为什么
return YES;
}
#pragma mark -数据源方法
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView{
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
return self.messageFrames.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
// 创建cell
JLMessageCell *cell = [JLMessageCell cellWithTableView:tableView];
// 给cell传递模型数据
cell.messageFrame = self.messageFrames[indexPath.row];
// 返回cell
return cell;
}
#pragma mark -代理方法
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
JLMessageFrame *messageFrame = self.messageFrames[indexPath.row];
return messageFrame.cellHeight;
}
/**
* 当开始拖拽表格的时候就退出键盘
*/
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView{
// 退出键盘
[self.view endEditing:YES];
}
@end
//
// NSString+Extension.h
// 01-QQ
//
// Created by XinYou on 15-3-3.
// Copyright (c) 2015年 vxinyou. All rights reserved.
//
#import
// 给NSString这个类添加一个分类,让NSString这个类增加一个计算字符串尺寸的方法
@interface NSString (Extension)
/**
* 返回字符串所占用的尺寸
*
* @param font 字符串的字体
* @param maxSize 字符串的最大尺寸
*/
- (CGSize)sizeWithFont:(UIFont*)font maxSize:(CGSize)maxSize;
@end
//
// NSString+Extension.m
// 01-QQ
//
// Created by XinYou on 15-3-3.
// Copyright (c) 2015年 vxinyou. All rights reserved.
//
#import "NSString+Extension.h"
@implementation NSString (Extension)
- (CGSize)sizeWithFont:(UIFont *)font maxSize:(CGSize)maxSize{
NSDictionary *attrs = @{NSFontAttributeName : font};
return [self boundingRectWithSize:maxSize options:NSStringDrawingUsesLineFragmentOrigin attributes:attrs context:nil].size;
}
@end
//
// UIImage+Extension.h
// 01-QQ
//
// Created by XinYou on 15-3-4.
// Copyright (c) 2015年 vxinyou. All rights reserved.
//
#import
// 给UIImage这个类添加分类,让UIImage这个类增加一个对图片进行拉伸的方法
@interface UIImage (Extension)
+ (UIImage *)resizableImage:(NSString *)name;
@end
//
// UIImage+Extension.m
// 01-QQ
//
// Created by XinYou on 15-3-4.
// Copyright (c) 2015年 vxinyou. All rights reserved.
//
#import "UIImage+Extension.h"
@implementation UIImage (Extension)
/**
* 返回一张可以随意拉伸,但四个角不变形的图片
*
* @param name 图片名字
*/
+ (UIImage *)resizableImage:(NSString *)name{
UIImage *resPic = [UIImage imageNamed:name];
CGFloat w = resPic.size.width * 0.5;
CGFloat h = resPic.size.height * 0.5;
UIImage *desPic = [resPic resizableImageWithCapInsets:UIEdgeInsetsMake(h, w, h, w)];
return desPic;
}
@end
//
// JLMessageCell.h
// 01-QQ
//
// Created by XinYou on 15-3-3.
// Copyright (c) 2015年 vxinyou. All rights reserved.
//
#import
@class JLMessageFrame;
@interface JLMessageCell : UITableViewCell
+ (instancetype)cellWithTableView:(UITableView *)tableView;
@property (nonatomic, strong) JLMessageFrame *messageFrame;
@end
//
// JLMessageCell.m
// 01-QQ
//
// Created by XinYou on 15-3-3.
// Copyright (c) 2015年 vxinyou. All rights reserved.
//
#import "JLMessageCell.h"
#import "JLMessage.h"
#import "JLMessageFrame.h"
#import "JLMessageCell.h"
#import "UIImage+Extension.h"
@interface JLMessageCell()
/**
* 信息发送时间
*/
@property (nonatomic, weak) UILabel *timeView;
/**
* 头像
*/
@property (nonatomic, weak) UIImageView *iconView;
/**
* 信息内容
*/
@property (nonatomic, weak) UIButton *textView;
@end
@implementation JLMessageCell
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
// 子控件的创建和初始化
// 1,时间
UILabel *timeView = [[UILabel alloc] init];
// 设置文本居中显示
timeView.textAlignment = NSTextAlignmentCenter;
// 设置文本颜色
timeView.textColor = [UIColor grayColor];
// 设置文本字体
timeView.font = [UIFont systemFontOfSize:13];
[self.contentView addSubview:timeView];
self.timeView = timeView;
// 2,头像
UIImageView *iconView = [[UIImageView alloc] init];
[self.contentView addSubview:iconView];
self.iconView = iconView;
// 3,文本信息
UIButton *textView = [[UIButton alloc] init];
// 设置自动换行
textView.titleLabel.numberOfLines = 0;
// 设置字体
textView.titleLabel.font = JLTextFont;
// 设置文本距离UIButton的边距
textView.contentEdgeInsets = UIEdgeInsetsMake(JLTextPadding, JLTextPadding, JLTextPadding, JLTextPadding);
// 设置文本颜色
[textView setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
[self.contentView addSubview:textView];
self.textView = textView;
// 4,清空cell的背景色,这样才能看见UITableView的背景色
self.backgroundColor = [UIColor clearColor];
}
return self;
}
+ (instancetype)cellWithTableView:(UITableView *)tableView{
static NSString *ID = @"message";
JLMessageCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];
if (cell == nil) {
cell = [[JLMessageCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:ID];
}
return cell;
}
- (void)setMessageFrame:(JLMessageFrame *)messageFrame{
// 固定写法
_messageFrame = messageFrame;
JLMessage *message = messageFrame.message;
// 1,时间
self.timeView.text = message.time;
self.timeView.frame = messageFrame.timeF;
// 2,头像
NSString *icon = (message.type == JLMessageTypeMeToOther ? @"me" : @"other");
self.iconView.image = [UIImage imageNamed:icon];
self.iconView.frame = messageFrame.iconF;
// 3,文本信息
[self.textView setTitle:message.text forState:UIControlStateNormal];
self.textView.frame = messageFrame.textF;
// 4,正文的背景
if (message.type == JLMessageTypeMeToOther) {// 自己发给别人,蓝色背景
[self.textView setBackgroundImage:[UIImage resizableImage:@"chat_send_nor"] forState:UIControlStateNormal];
}else{// 别人发给自己,白色背景
[self.textView setBackgroundImage:[UIImage resizableImage:@"chat_recive_nor"] forState:UIControlStateNormal];
}
}
@end
//
// JLMessage.h
// 01-QQ
//
// Created by XinYou on 15-3-3.
// Copyright (c) 2015年 vxinyou. All rights reserved.
//
#import
//定义一个枚举
typedef enum {
// 枚举中的第一个元素默认=0,第二个元素=1,依次递增,也可以不写
JLMessageTypeMeToOther = 0,//自己发给别人
JLMessageTypeOtherToMe// 别人发给自己
} JLMessageType;
@interface JLMessage : NSObject
/**
* 信息内容
*/
@property (nonatomic, copy) NSString *text;
/**
* 信息发送时间
*/
@property (nonatomic, copy) NSString *time;
/**
* 信息的类型(类型有两种,一种是自己发送给别人的信息,一种是别人发送给自己的信息)
*/
@property (nonatomic, assign) JLMessageType type;
/**
* 是否隐藏时间(如果连续多条信息的时间相同,只能有一条信息显示时间)
*/
@property (nonatomic, assign) BOOL hideTime;
+ (instancetype)messageWithDict:(NSDictionary *)dict;
- (instancetype)initWithDict:(NSDictionary *)dict;
@end
//
// JLMessage.m
// 01-QQ
//
// Created by XinYou on 15-3-3.
// Copyright (c) 2015年 vxinyou. All rights reserved.
//
#import "JLMessage.h"
@implementation JLMessage
- (instancetype)initWithDict:(NSDictionary *)dict{
if (self = [super init]) {
[self setValuesForKeysWithDictionary:dict];
}
return self;
}
+ (instancetype)messageWithDict:(NSDictionary *)dict{
return [[self alloc] initWithDict:dict];
}
@end
//
// JLMessageFrame.h
// 01-QQ
//
// Created by XinYou on 15-3-3.
// Copyright (c) 2015年 vxinyou. All rights reserved.
//
// 正文的字体
#define JLTextFont [UIFont systemFontOfSize:15]
// 正文的内边距
#define JLTextPadding 20
#import
@class JLMessage; @interface JLMessageFrame : 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) JLMessage *message; @end // // JLMessageFrame.m // 01-QQ // // Created by XinYou on 15-3-3. // Copyright (c) 2015年 vxinyou. All rights reserved. // #import "JLMessageFrame.h" #import "JLMessage.h" #import "NSString+Extension.h" @implementation JLMessageFrame - (void)setMessage:(JLMessage *)message{ // 固定写法 _message = message; // 间距 CGFloat padding = 10; // 屏幕的宽度 CGFloat screenW = [UIScreen mainScreen].bounds.size.width; // 1,时间 if (message.hideTime == NO) {// 显示时间 CGFloat timeX = 0; CGFloat timeY = 0; CGFloat timeW = screenW; CGFloat timeH = 40; _timeF = CGRectMake(timeX, timeY, timeW, timeH); } // 2,头像 CGFloat iconY = CGRectGetMaxY(_timeF) + padding; CGFloat iconW = 40; CGFloat iconH = 40; CGFloat iconX; if (message.type == JLMessageTypeOtherToMe) {// 别人发给自己 iconX = padding; } else {// 自己发给别人 iconX = screenW - padding - iconW; } _iconF = CGRectMake(iconX, iconY, iconW, iconH); // 3,正文 CGFloat textY = iconY; // 文本信息的最大尺寸 CGSize textMaxSize = CGSizeMake(200, MAXFLOAT); // 文本信息的真实尺寸 CGSize textRealSize = [message.text sizeWithFont:JLTextFont maxSize:textMaxSize]; // 按钮最终的真实尺寸(按钮的内部有个Label用于显示文本,默认情况,Lable填充满按钮 // 如果想让Lable中的文字跟按钮有一定的边距。。。) CGSize textBtnSize = CGSizeMake(textRealSize.width + JLTextPadding * 2, textRealSize.height + JLTextPadding * 2); CGFloat textX; if (message.type == JLMessageTypeOtherToMe) {// 别人发给自己 textX = CGRectGetMaxX(_iconF) + padding; }else{ textX = iconX - padding - textBtnSize.width; } // _textF = CGRectMake(textX, textY, textBtnSize.width, textBtnSize.height); _textF = (CGRect){{textX, textY}, textBtnSize};// 两种写法是一样的 // 4,cell的高度 CGFloat textMaxY = CGRectGetMaxY(_textF); CGFloat iconMaxY = CGRectGetMaxY(_iconF); _cellHeight = MAX(textMaxY, iconMaxY) + padding; } @end