最终效果图:
自定义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]; } }