最终效果图:
自定义cell的封装
BeyondCell
//
// BeyondCell.h
// 29_仿微信聊天
//
// Created by beyond on 14-9-4.
// Copyright (c) 2014年 com.beyond. All rights reserved.
//
#import <UIKit/UIKit.h>
@class BeyondCellFrame;
@interface BeyondCell : UITableViewCell
// 一行自定义的cell,初始化的时候,全部生成各个控件并添加到contentView,然后通过cellWithCellFrame方法,将参数CellFrame(内含Msg对象)的所有成员frame和数据 设置到cell中的各个控件上面去
// 返回xib界面上写的重用cellID
+ (NSString *)cellID;
// 通过一个WeiboFrames模型对象(它本身就含有一个Weibo数据 模型),返回一个填充好数据的cell对象
- (BeyondCell *)cellWithCellFrame:(BeyondCellFrame *)cellFrame;
@end
//
// BeyondCell.m
// 29_仿微信聊天
//
// Created by beyond on 14-9-4.
// Copyright (c) 2014年 com.beyond. All rights reserved.
//
#import "BeyondCell.h"
#import "BeyondCellFrame.h"
#import "Msg.h"
// 类扩展,又叫匿名分类
@interface BeyondCell()
{
// 1,头像
UIImageView *_headImg;
// 2,正文内容
UILabel *_content;
// 3,大图片
UIImageView *_bgImg;
}
@end
@implementation BeyondCell
// 返回xib界面上写的重用cellID
+ (NSString *)cellID
{
return @"BeyondCell";
}
// 当池中没有Cell的时候,创建出一个纯洁的Cell,一次性alloc 出所有的各个子控件 ,并加到contentView
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
//选中cell后背景无颜色
self.selectionStyle = UITableViewCellSelectionStyleNone;
self.backgroundColor = [UIColor clearColor];
// 不管三七二十一,先把所有的控件实例化,并添加到contentView里面
// 1,头像
_headImg = [[UIImageView alloc]init];
_headImg.layer.cornerRadius = 10;
_headImg.layer.masksToBounds = YES;
[self.contentView addSubview:_headImg];
// 2,大图片
_bgImg = [[UIImageView alloc]init];
[self.contentView addSubview:_bgImg];
// 3,正文内容,添加大背景图片里面
_content = [[UILabel alloc]init];
_content.backgroundColor = [UIColor clearColor];
// 正文内容用的字体,宏定义在.h
_content.font = kContentFnt;
_content.numberOfLines = 0;
_content.lineBreakMode = NSLineBreakByWordWrapping;
[_bgImg addSubview:_content];
}
return self;
}
// 通过一个Frames模型对象(它本身就含有一个数据 模型),返回一个填充好数据的cell对象,将参数Frames(内含对象)的所有成员frames和数据 设置到cell中的各个控件上面去
- (BeyondCell *)cellWithCellFrame:(BeyondCellFrame *)cellFrame
{
Msg *msg = cellFrame.msg;
// 将模型对象中的所有属性值,全部赋值到cell对象中的成员控件上显示
// 1,头像
if ([msg.name isEqualToString:@"nana"]) {
_headImg.image = [UIImage imageNamed:@"icon01.jpg"];
} else {
_headImg.image = [UIImage imageNamed:@"icon02.jpg"];
}
// 5,正文内容
_content.text = msg.content;
// 6,大图片
if ([msg.name isEqualToString:@"nana"]) {
_bgImg.image = [UIImage imageStretchedWithName:@"chatfrom_bg_normal.png" xPos:0.5 yPos:0.6];
} else {
_bgImg.image = [UIImage
imageStretchedWithName:@"chatto_bg_normal.png" xPos:0.5 yPos:0.6];
}
// 1,头像的frame
_headImg.frame = cellFrame.headImgFrame;
// 2,正文的frame
_content.frame = cellFrame.contentFrame;
// 3,bigImg的frame
_bgImg.frame = cellFrame.contentBgImgFrame;
return self;
}
@end
封装的数据源Model
//
// Msg.h
// 29_仿微信聊天
//
// Created by beyond on 14-9-4.
// Copyright (c) 2014年 com.beyond. All rights reserved.
// 模型,成员:icon,正文text
#import <Foundation/Foundation.h>
// 内容 用的字体
#define kContentFnt [UIFont fontWithName:@"HelveticaNeue" size:18.0f]
@interface Msg : NSObject
// 头像图片名
@property (nonatomic,copy) NSString *headImg;
// 消息内容
@property (nonatomic,copy) NSString *content;
@property (nonatomic,copy) NSString *name;
@property (nonatomic,strong) NSString *recordFileFath;
// 类方法,字典 转 对象 类似javaBean一次性填充
+ (Msg *)msgWithDict:(NSDictionary *)dict;
// 对象方法,设置对象的属性后,返回对象
- (Msg *)initWithDict:(NSDictionary *)dict;
+ (Msg*)msgWithName:(NSString *)name content:(NSString *)content recordFilePath:(NSString *)path;
@end
//
// Msg.m
// 29_仿微信聊天
//
// Created by beyond on 14-9-4.
// Copyright (c) 2014年 com.beyond. All rights reserved.
//
#import "Msg.h"
@implementation Msg
// 类方法,字典 转 对象 类似javaBean一次性填充
+ (Msg *)msgWithDict:(NSDictionary *)dict
{
return [[self alloc]initWithDict:dict];
}
// 对象方法,设置对象的属性后,返回对象
- (Msg *)initWithDict:(NSDictionary *)dict
{
// 必须先调用父类NSObject的init方法
if (self = [super init]) {
// 设置对象自己的属性
// 通过遍历 将 字典 赋值为对象各个属性
for (NSString *key in dict) {
[self setValue:dict[key] forKeyPath:key];
}
// 一次性 将 字典 赋值为对象各个属性
// [self setValuesForKeysWithDictionary:dict];
}
// 返回填充好的对象
return self;
}
+ (Msg*)msgWithName:(NSString *)name content:(NSString *)content recordFilePath:(NSString *)path
{
Msg *msg = [[self alloc]init];
msg.name = name;
msg.content = content;
msg.recordFileFath = path;
return msg;
}
@end
重点:根据数据源计算frame
//
// BeyondCellFrame.h
// 29_仿微信聊天
//
// Created by beyond on 14-9-4.
// Copyright (c) 2014年 com.beyond. All rights reserved.
//
#import <Foundation/Foundation.h>
@class Msg;
// 控件与控件之间的外边距
#define kMargin 7
// 头像的高宽
#define kHeadImgHW 85
@interface BeyondCellFrame : NSObject
// 最大的Y值,就是行高
@property (nonatomic,assign,readonly) CGFloat maxY;
// 重要,拥有一个成员:对象,目的是在控制器中,传递对象进来之后,可以通过此模型对象的数据,计算出所有的frames
@property (nonatomic,strong) Msg *msg;
// 头像 的frame
@property (nonatomic,assign,readonly) CGRect headImgFrame;
// 聊天正文的背景图片 的frame
@property (nonatomic,assign,readonly) CGRect contentBgImgFrame;
// 正文内容 的frame
@property (nonatomic,assign,readonly) CGRect contentFrame;
@end
//
// BeyondCellFrame.m
// 29_仿微信聊天
//
// Created by beyond on 14-9-4.
// Copyright (c) 2014年 com.beyond. All rights reserved.
//
#import "BeyondCellFrame.h"
#import "Msg.h"
@implementation BeyondCellFrame
// CellFrame类 唯一的一个方法:设置Msg的时候,可以通过其数据,计算出各个frame,以及最大的Y,也就是行高
- (void)setMsg:(Msg *)msg
{
_msg = msg;
// 具体的计算各个frame的代码,放在这儿~~~
if ([msg.name isEqualToString:@"nana"]) {
[self standLeft:msg];
} else {
[self standRight:msg];
}
}
// 我说的放在右边
- (void)standRight:(Msg *)msg
{
// 1,头像的frame
// 头像的x
CGFloat headImgX = 320 - kHeadImgHW - kMargin;
// 头像的y
CGFloat headImgY = 0;
// 头像的H
CGFloat headImgH = kHeadImgHW;
// 头像的W
CGFloat headImgW = kHeadImgHW;
_headImgFrame = CGRectMake(headImgX, headImgY, headImgH, headImgW);
//===============****************=======================
// 2,bg的frame
// 宽度W
CGFloat bgW = 320 - kHeadImgHW - kMargin;
// x
CGFloat bgX = 320 - bgW - kHeadImgHW - kMargin;
// y
CGFloat bgY = 0;
// CGFloat winWidth = [[UIApplication sharedApplication] statusBarFrame].size.width;
// 高度先假设 H
CGFloat bgH = 300;
_contentBgImgFrame = CGRectMake(bgX, bgY, bgW,bgH);
//===============****************=======================
// 3,正文的frame 正文添加到图片里面,以图片的左上角为 0 0
// x
CGFloat contentX = kMargin*1.5;
// y
CGFloat contentY = kMargin;
// 宽度W 先假设大于一行
CGFloat contentW = bgW - contentX - kMargin ;
CGFloat contentH = 0;
// 判断 内容够不够一行...
// 根据字体得到NSString的尺寸
CGSize oneLineSize = [msg.content sizeWithAttributes:[NSDictionary dictionaryWithObjectsAndKeys:kContentFnt,NSFontAttributeName, nil]];
CGFloat oneLineW = oneLineSize.width;
if (oneLineW < contentW) {
// 如果不够一行
CGFloat oneLineH = oneLineSize.height;
contentX = kMargin * 1.2;
_contentFrame = CGRectMake(contentX, contentY, oneLineW,oneLineH);
contentH = oneLineH;
contentW = oneLineW;
// 5,重新调整 contentBgImg的frame的高度
// 以下三步为OC标准代码,因为OC中不允许直接修该对象中结构体属性的成员的值,要通过中间的临时结构体变量
CGRect frame = _contentBgImgFrame;
frame.size.width = contentW + kMargin *3.5;
frame.size.height = contentH + kMargin * 3 ;
frame.origin.x = 320 - contentW - headImgW - kMargin*4;
_contentBgImgFrame = frame;
} else {
// 如果超过一行,按下面算法计算 高度
// 根据内容动态设置 高度
CGRect tmpRect = [msg.content boundingRectWithSize:CGSizeMake(contentW, MAXFLOAT) options:NSStringDrawingUsesLineFragmentOrigin attributes:[NSDictionary dictionaryWithObjectsAndKeys:kContentFnt,NSFontAttributeName, nil] context:nil];
// 高度H
contentH = tmpRect.size.height;
_contentFrame = CGRectMake(contentX, contentY, contentW,contentH);
// 5,重新调整 contentBgImg的frame的高度
// 以下三步为OC标准代码,因为OC中不允许直接修该对象中结构体属性的成员的值,要通过中间的临时结构体变量
CGRect frame = _contentBgImgFrame;
frame.size.height = contentH + kMargin * 3 ;
_contentBgImgFrame = frame;
}
// 8,这个时候就可以计算最大Y 即行高了
if (headImgH > _contentBgImgFrame.size.height) {
_maxY = CGRectGetMaxY(_headImgFrame) + kMargin;
} else {
_maxY = CGRectGetMaxY(_contentBgImgFrame) + kMargin;
}
}
- (void)standLeft:(Msg *)msg
{
// 1,头像的frame
// 头像的x
CGFloat headImgX = kMargin;
// 头像的y
CGFloat headImgY = kMargin;
// 头像的H
CGFloat headImgH = kHeadImgHW;
// 头像的W
CGFloat headImgW = kHeadImgHW;
_headImgFrame = CGRectMake(headImgX, headImgY, headImgH, headImgW);
//===============****************=======================
// 4,bg的frame
// x
CGFloat bgX = _headImgFrame.size.width + kMargin;
// y
CGFloat bgY = _headImgFrame.origin.y;
// CGFloat winWidth = [[UIApplication sharedApplication] statusBarFrame].size.width;
// 宽度W
CGFloat bgW = 320 - bgX - kMargin;
// 高度H
CGFloat bgH = 300;
_contentBgImgFrame = CGRectMake(bgX, bgY, bgW,bgH);
// 4,正文的frame 正文添加到图片里面,以图片的左上角为 0 0
// x
CGFloat contentX = kMargin*3;
// y
CGFloat contentY = kMargin;
// CGFloat winWidth = [[UIApplication sharedApplication] statusBarFrame].size.width;
// 宽度W 先假设大于一行
CGFloat contentW = bgW - contentX - kMargin ;
CGFloat contentH = 0;
// 判断 内容够不够一行...
// 根据字体得到NSString的尺寸
CGSize oneLineSize = [msg.content sizeWithAttributes:[NSDictionary dictionaryWithObjectsAndKeys:kContentFnt,NSFontAttributeName, nil]];
CGFloat oneLineW = oneLineSize.width;
if (oneLineW < contentW) {
// 如果不够一行
CGFloat oneLineH = oneLineSize.height;
contentX = kMargin * 2;
_contentFrame = CGRectMake(contentX, contentY, oneLineW,oneLineH);
contentH = oneLineH;
contentW = oneLineW;
// 5,重新调整 contentBgImg的frame的高度
// 以下三步为OC标准代码,因为OC中不允许直接修该对象中结构体属性的成员的值,要通过中间的临时结构体变量
CGRect frame = _contentBgImgFrame;
frame.size.width = contentW + kMargin *3.5;
frame.size.height = contentH + kMargin * 3 ;
_contentBgImgFrame = frame;
} else {
// 如果超过一行,按下面算法计算 高度
// 根据内容动态设置 高度
CGRect tmpRect = [msg.content boundingRectWithSize:CGSizeMake(contentW, MAXFLOAT) options:NSStringDrawingUsesLineFragmentOrigin attributes:[NSDictionary dictionaryWithObjectsAndKeys:kContentFnt,NSFontAttributeName, nil] context:nil];
// 高度H
contentH = tmpRect.size.height;
_contentFrame = CGRectMake(contentX, contentY, contentW,contentH);
// 5,重新调整 contentBgImg的frame的高度
// 以下三步为OC标准代码,因为OC中不允许直接修该对象中结构体属性的成员的值,要通过中间的临时结构体变量
CGRect frame = _contentBgImgFrame;
frame.size.height = contentH + kMargin * 3 ;
_contentBgImgFrame = frame;
}
// 8,这个时候就可以计算最大Y 即行高了
if (headImgH > _contentBgImgFrame.size.height) {
_maxY = CGRectGetMaxY(_headImgFrame) + kMargin;
} else {
_maxY = CGRectGetMaxY(_contentBgImgFrame) + kMargin;
}
}
@end
控制器
//
// BeyondViewController.m
// 29_仿微信聊天
//
// Created by beyond on 14-9-2.
// Copyright (c) 2014年 com.beyond. All rights reserved.
//
#import "BeyondViewController.h"
#import "Msg.h"
#import "BeyondCellFrame.h"
#import "BeyondCell.h"
@interface BeyondViewController ()
{
// 从plist文件中加载的所有weiboFrames(因为它已经含有一个weibo成员),返回所有的对象组成的数组
NSMutableArray *_msgFrames;
}
@end
@implementation BeyondViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// 初始化 对象数组
_msgFrames = [NSMutableArray array];
}
#pragma mark - UITextField代理,发送请求
- (BOOL)textFieldShouldReturn:(UITextField *)textField
{
if (textField.text.length == 0){
return NO;
}
BeyondCellFrame *frame = [[BeyondCellFrame alloc]init];
// ***********设置的WeiboFrames的成员weibo的同时,进行了复杂的计算,并填充了WeiboFrames各个frame成员
frame.msg = [Msg msgWithName:@"jackey" content:textField.text recordFilePath:@"NoRecord"];
// 添加到对象数组
[_msgFrames addObject:frame];
[self.tableView reloadData];
return YES;
}
#pragma mark - UITableView代理方法
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
//去除cell间隔线
tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
// 返回对象数组的长度
return _msgFrames.count;
}
// 生成自定义的cell,并传递cellFrame给它,设置好后,返回cell
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// 1,从池中取
BeyondCell *cell = [tableView dequeueReusableCellWithIdentifier:[BeyondCell cellID]];
// 2,取不到的时候,创建一个纯洁的WeiboCell
if (cell == nil) {
cell = [[BeyondCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:[BeyondCell cellID]];
}
// 3,设置独一无二的数据
BeyondCellFrame *frame = [_msgFrames objectAtIndex:indexPath.row];
cell = [cell cellWithCellFrame:frame];
return cell;
}
// cellFrame对象数组有每一行的行高,其内部已经计算好了
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
// cellFrame的成员有:Msg数据模型对象,以及根据数据模型计算出来的所有的frames,以及最大的Y即对应数据模型的行高
BeyondCellFrame *frame = [_msgFrames objectAtIndex:indexPath.row];
return frame.maxY;
}
// 恢复view全屏,并且让键盘退出
- (void)exitKeyboard
{
[UIView animateWithDuration:0.2 animations:^{
self.view.frame = CGRectMake(0, 0, self.view.frame.size.width, [[UIScreen mainScreen]bounds].size.height);
}completion:^(BOOL finished){}];
[_chatInput resignFirstResponder];
}
// 滚至表格最后一行
- (void)scrollToLastCell;
{
if (_msgFrames.count >1) {
[self.chatTableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:_msgFrames.count-1 inSection:0] atScrollPosition:UITableViewScrollPositionBottom animated:YES];
}
}