最終效果圖:
<喎?/kf/ware/vc/" target="_blank" class="keylink">vcD4KPHA+19S2qNLlY2VsbLXEt+LXsDwvcD4KPHA+PC9wPgo8cCBjbGFzcz0="p1">BeyondCell
// // BeyondCell.h // 29_仿微信聊天 // // Created by beyond on 14-9-4. // Copyright (c) 2014年 com.beyond. All rights reserved. // #import@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// 內容 用的字體 #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@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]; } }