IOS的UITableView
UITableView
概述
1 UITableView 2 一般用來展示表格數據、可以滾動(繼承自UIScrollView).性能極佳 3 UITableView分兩種樣式: 4 Plain,不分組的樣式 5 Grouped,分組的樣式 6 UITableView默認為Plain樣式,改為Grouped后實現分組,如果再改回Plain 那么在滾動的時候 上一層的頭標簽就會一直作為索引顯示,類似于通訊錄中的A B的顯示方式
使用:
1 如果要使用UITableView 那么需要實現UITableViewDataSource協議后重寫 2
3 //展現數據有幾組,當不實現這個方法時,默認為一組 4 -(NSInteger)numberOfSectionsInTableView:(UITableView *) tableView 5 6 //一組有幾行 7 -(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger) section 8 9 //每行顯示什么內容 10 -(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath; 11 12 //實現右側的索引欄 13 -(NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView 14 (獲取group數組中的每個對象的title值,并返回到一個NSArray中 15 [self.groups valueForKeyPath:@"title"]) 16 17 //通過代理堅挺cell的點擊事件 18 //選中某行 19 -(void)tableView:(UITableView *) tableView didSelectRowAtIndexPath: (NSIndexPath *)indexPath 20 21 //取消選中某行 22 -(void)tableView:(UITableView *)tableView didDeselectRowAtIndexPath:(NSIndexPath *)indexPath; 23
//設置組標題
-(NSString *)tableView :(UITableView *)tableView titleForHeaderInSection:(NSInteger)section;
//設置組描述
-(NSString *)tableView :(UITableView *)tableView titleForFooterInsection:(NSInteger)section;
24 //修改每行的行高 25 1.如果tableView的行高一樣,那么就在控制器的viewDidLoad中統一設置行高tableView.rowHeight(這種方法比較高效) 26 2.通過代理方法實現: 27 -(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath(低效)
//把UITableView中的最后一行的數據滾動到最上面
NSIndexPath *idxPath = [NSIndexPath indexPathForRow:self.goods.count - 1 inSection:0];
[self.tableView scrollToRowAtIndexPath:idxPath atScrollPosition:UITableViewScrollPositionTop animated:YES];
UITableView的常見屬性
1 rowHeight 可以統一設置所有行的高度 2 3 separatorColor 分割線的顏色 4 separatorStyle 分割線的樣式 5 6 tableHeaderView 一般可以放廣告 7 tableFooterView 一般可以放加載更多
Cell的常見屬性
1 imageView 2 textLabel 3 detailTextLabel 4 5 accessoryType 6 accessoryView 7 8 backgroundColor,設置單元格的背景顏色 9 10 backgroundView 可以利用這個屬性來設置單元格的背景圖片,指定一個UIImageView就可以了 11 12 selectedBackgroundView 當某行被選中的時候的背景
單元格Cell的重用
1 //注意:只適用于單元格樣式一致的時候 2 //單元格重用的基本方法 3 //1.聲明一個 靜態的重用ID (只所以聲明靜態是為了節省控制器不斷的釋放,創建成員對象) 4 //2.根據重用ID去緩存池中獲取對應的cell對象 5 //3.如果沒有獲取到,就創建一個,如果獲取到了就直接設置單元格內容 6 //4.返回單元格
注意:當使用自定義的cell的時候 是無法通過 dequeueReusableCellWithIdentifier:ID的方式來指定ID的
所以需要在布局文件中進行設置

代碼示例:
1. .plist的數據結構

模型代碼:
1.1)CZGroup.h
1 #import <Foundation/Foundation.h> 2 3 @interface GZGroup :NSObject 4 5 @property (nonatomic ,copy) NSString *titile; 6 @property (nonatomic, strong) NSArray *cars; 7 8 -(instancetype)initWithDict :(NSDictionary *)dict; 9 +(instancetype)groupWithDict:(NSDictionary *)dict; 10 11 @end
GZGroup.m
#import "CZGroup.h" #import "CZCar.h" @implementation CZGroup - (instancetype)initWithDict:(NSDictionary *)dict { if (self = [super init]) { // self.title = dict[@"title"]; // self.cars = dict[@"cars"]; [self setValuesForKeysWithDictionary:dict]; // 當有模型嵌套的時候需要手動把字典轉成模型 // 創建一個用來保存模型的數組 NSMutableArray *arrayModels = [NSMutableArray array]; // 手動做一下字典轉模型 for (NSDictionary *item_dict in dict[@"cars"]) { CZCar *model = [CZCar carWithDict:item_dict]; [arrayModels addObject:model]; } self.cars = arrayModels; } return self; } + (instancetype)groupWithDict:(NSDictionary *)dict { return [[self alloc] initWithDict:dict]; } @end
控制器代碼,重用cell需要這是一個ID
1 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 2 { 3 // 1. 獲取模型數據 4 // 根據組的索引獲取對應的組的模型 5 CZGroup *group = self.groups[indexPath.section]; 6 // 根據當前行的索引, 獲取對應組的對應行的車 7 CZCar *car = group.cars[indexPath.row]; 8 9 10 11 // 2. 創建單元格 12 // 2.1 聲明一個重用ID 13 static NSString *ID = @"car_cell"; 14 // 2.2 根據重用ID去緩存池中獲取對應的cell對象 15 UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ID]; 16 // 2.3 如果沒有獲取到, 那么就創建一個 17 if (cell == nil) { 18 cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:ID]; 19 } 20 21 // 3. 設置單元格內容 22 cell.imageView.image = [UIImage imageNamed:car.icon]; 23 cell.textLabel.text = car.name; 24 25 // 4. 返回單元格 26 return cell; 27 }
自定義Cell
案例一:團購
Viewontroller.m
1 #import "ViewController.h" 2 #import "CZGoods.h" 3 #import "CZGoodsCell.h" 4 #import "CZFooterView.h" 5 #import "CZHeaderView.h" 6 7 @interface ViewController () <UITableViewDataSource, CZFooterViewDelegate> 8 9 // 用來存儲所有的團購商品的數據 10 @property (nonatomic, strong) NSMutableArray *goods; 11 12 @property (weak, nonatomic) IBOutlet UITableView *tableView; 13 @end 14 15 @implementation ViewController 16 17 18 #pragma mark - 懶加載數據 19 - (NSMutableArray *)goods 20 { 21 if (_goods == nil) { 22 NSString *path = [[NSBundle mainBundle] pathForResource:@"tgs.plist" ofType:nil]; 23 NSArray *arrayDict = [NSArray arrayWithContentsOfFile:path]; 24 NSMutableArray *arrayModels = [NSMutableArray array]; 25 for (NSDictionary *dict in arrayDict) { 26 CZGoods *model = [CZGoods goodsWithDict:dict]; 27 [arrayModels addObject:model]; 28 } 29 _goods = arrayModels; 30 } 31 return _goods; 32 } 33 34 #pragma mark - 數據源方法
//返回有UITableView 有多少個組,默認為1,當組為1時 可以忽略不寫 35 //- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView 36 //{ 37 // return 1; 38 //} 39 40 //返回組內有多少行數據 41 - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section 42 { 43 return self.goods.count; 44 } 45 46 //返回Cell對象 47 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 48 { 49 // 1. 獲取模型數據 50 CZGoods *model = self.goods[indexPath.row]; 51 52 // 2. 創建單元格 53 // 通過xib的方式來創建單元格 54 CZGoodsCell *cell = [CZGoodsCell goodsCellWithTableView:tableView]; 55 56 57 // 3. 把模型數據設置給單元格 58 // 在控制器中直接為cell的每個子控件賦值數據造成的問題: 59 // 1. 控制器強依賴于Cell, 一旦cell內部的子控件發生了變化, 那么控制器中的代碼也得改(這就造成了緊耦合) 60 // 2. cell的封裝不夠完整, 凡是用到這個cell的地方, 每次都要編寫為cell的子控件依次賦值的語句,比如:cell.xxx = model.title; 61 // 3. 解決: 直接把模型傳遞給自定義Cell, 然后在自定義cell內部解析model中的數據賦值給自定義cell內部的子控件。 62 cell.goods = model; 63 64 // 4.返回單元格 65 return cell; 66 } 67 68 69 70 #pragma mark - 隱藏狀態欄 71 - (BOOL)prefersStatusBarHidden 72 { 73 return YES; 74 } 75 80 - (void)viewDidLoad { 81 [super viewDidLoad]; 82 self.tableView.rowHeight = 44; 83 84 // 設置UITableView的footerView 85 // UIButton *btn = [UIButton buttonWithType:UIButtonTypeContactAdd]; 86 // 87 // btn.backgroundColor = [UIColor redColor]; 88 // btn.frame = CGRectMake(20, 50, 30, 100); 89 // // tableView的footerView的特點: 只能修改x和height的值, Y 和 width不能改。 90 // self.tableView.tableFooterView = btn; 91 92 93 94 // 通過Xib設置UITableView的footerView 95 CZFooterView *footerView = [CZFooterView footerView]; 96 // 設置footerView的代理 97 footerView.delegate = self; 98 self.tableView.tableFooterView = footerView; 99 100 101 // 創建Header View 102 CZHeaderView *headerView = [CZHeaderView headerView]; 103 self.tableView.tableHeaderView = headerView; 104 105 106 107 108 109 } 110 111 #pragma mark - CZFooterView的代理方法 112 113 - (void)footerViewUpdateData:(CZFooterView *)footerView 114 { 115 // 3. 增加一條數據 116 117 118 // 3.1 創建一個模型對象 119 CZGoods *model = [[CZGoods alloc] init]; 120 model.title = @"驢肉火燒"; 121 model.price = @"6.0"; 122 model.buyCount = @"1000"; 123 model.icon = @"37e4761e6ecf56a2d78685df7157f097"; 124 125 // 3.2 把模型對象加到控制器的goods集合當中 126 [self.goods addObject:model]; 127 128 // 4. 刷新UITableView 129 [self.tableView reloadData]; 130 131 // // 局部刷新(只適用于UITableView總行數沒有發生變化的情況) 132 // NSIndexPath *idxPath = [NSIndexPath indexPathForRow:self.goods.count - 1 inSection:0]; 133 // [self.tableView reloadRowsAtIndexPaths:@[idxPath] withRowAnimation:UITableViewRowAnimationLeft]; 134 135 136 // 5. 把UITableView中的最后一行的數據滾動到最上面 137 NSIndexPath *idxPath = [NSIndexPath indexPathForRow:self.goods.count - 1 inSection:0]; 138 [self.tableView scrollToRowAtIndexPath:idxPath atScrollPosition:UITableViewScrollPositionTop animated:YES]; 139 } 140 141 - (void)didReceiveMemoryWarning { 142 [super didReceiveMemoryWarning]; 143 // Dispose of any resources that can be recreated. 144 } 145 146 @end
自定義的CZGoodsCell對象
CZGoodsCell.h
//導入 UIKit/UIKit.h 文件 #import <UIKit/UIKit.h> @class CZGoods; //繼承 UITableViewCell @interface CZGoodsCell : UITableViewCell @property (nonatomic, strong) CZGoods *goods; // 封裝一個創建自定義Cell的方法 + (instancetype)goodsCellWithTableView:(UITableView *)tableView; @end
CZGoodsCell.m
1 #import "CZGoodsCell.h" 2 #import "CZGoods.h" 3 4 @interface CZGoodsCell () 5 @property (weak, nonatomic) IBOutlet UIImageView *imgViewIcon; 6 @property (weak, nonatomic) IBOutlet UILabel *lblTitle; 7 @property (weak, nonatomic) IBOutlet UILabel *lblPrice; 8 @property (weak, nonatomic) IBOutlet UILabel *lblBuyCount; 9 10 @end 11 12 13 @implementation CZGoodsCell 14 15 + (instancetype)goodsCellWithTableView:(UITableView *)tableView 16 { 17 static NSString *ID = @"goods_cell"; 18 CZGoodsCell *cell = [tableView dequeueReusableCellWithIdentifier:ID]; 19 if (cell == nil) { 20 cell = [[[NSBundle mainBundle] loadNibNamed:@"CZGoodsCell" owner:nil options:nil] firstObject]; 21 } 22 return cell; 23 } 24 25 26 - (void)setGoods:(CZGoods *)goods 27 { 28 _goods = goods; 29 // 把模型的數據設置給子控件 30 self.imgViewIcon.image = [UIImage imageNamed:goods.icon]; 31 self.lblTitle.text = goods.title; 32 self.lblPrice.text = [NSString stringWithFormat:@"¥ %@", goods.price]; 33 self.lblBuyCount.text = [NSString stringWithFormat:@"%@ 人已購買", goods.buyCount]; 34 } 35 36 - (void)awakeFromNib { 37 // Initialization code 38 } 39 40 - (void)setSelected:(BOOL)selected animated:(BOOL)animated { 41 [super setSelected:selected animated:animated]; 42 43 // Configure the view for the selected state 44 } 45 46 @end
尾部的加載更多按鈕
CZFooterView.h
1 #import <UIKit/UIKit.h> 2 @class CZFooterView; 3
//設置一個代理對象 4 @protocol CZFooterViewDelegate <NSObject, UIScrollViewDelegate>
//提示用戶使用這個代理的時候必須實現下面的代理方法 5 @required 6 - (void)footerViewUpdateData:(CZFooterView *)footerView; 7 @end 8 9 @interface CZFooterView : UIView 10 11 + (instancetype)footerView; 12 @property (nonatomic, weak) id<CZFooterViewDelegate> delegate; 13 @end
CZFooterView.m
1 #import "CZFooterView.h" 2 3 @interface CZFooterView () 4 @property (weak, nonatomic) IBOutlet UIButton *btnLoadMore; 5 @property (weak, nonatomic) IBOutlet UIView *waitingView; 6 - (IBAction)btnLoadMoreClick; 7 @end 8 9 10 @implementation CZFooterView 11 12 + (instancetype)footerView 13 { 14 CZFooterView *footerView = [[[NSBundle mainBundle] loadNibNamed:@"CZFooterView" owner:nil options:nil] lastObject]; 15 return footerView; 16 } 17 18 19 /** 20 * 加載更多按鈕的單擊事件 21 */ 22 - (IBAction)btnLoadMoreClick { 23 // 1. 隱藏"加載更多"按鈕 24 self.btnLoadMore.hidden = YES; 25 26 // 2. 顯示"等待指示器"所在的那個UIView 27 self.waitingView.hidden = NO; 28 29 30 31 //GCD方法,標示延遲一定的時間后執行,由于我們這里是模擬操作,當點擊按鈕后,數據立刻會刷新,所以為了模擬逼真一些,這里就加了一個延遲操作的方法 32 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ 33 34 // 3. 調用代理方法實現下面的功能 35 // 調用footerViewUpdateData方法之前, 為了保證調用不出錯, 所以要先判斷一下代理對象是否真的實現了這個方法, 如果實現了這個方法再調用, 否則不調用. 36 if ([self.delegate respondsToSelector:@selector(footerViewUpdateData:)]) { 37 // 3. 增加一條數據 38 // 3.1 創建一個模型對象 39 // 3.2 把模型對象加到控制器的goods集合當中 40 // 4. 刷新UITableView 41 [self.delegate footerViewUpdateData:self]; 42 } 43 44 45 // 4. 顯示"加載更多"按鈕 46 self.btnLoadMore.hidden = NO; 47 48 // 5. 隱藏"等待指示器"所在的那個UIView 49 self.waitingView.hidden = YES; 50 51 }); 52 53 54 55 56 57 58 } 59 @end
案例二:微博
1):當我們的控制器Controller是 tableView的時候,我們可以直接使用Table View Controller,需要指定dataSource 和 delegate的代理對象
2):由于微博中的內容信息是不一致的,table欄的寬度也是不一致的,所以我們沒有辦法在 viedieLoad中 使用self.tableview.rowHeight 來設置統一的高度,所以我們針對每一個Cell的Frame做了一個封裝,在懶加載數據時,將數據直接給Frame對象,然后Frame對象根據內容計算出高度,后再Cell的時候 直接返回Cell對象
封裝的Frame對象
CZWeiboFrame.h
1 #import <Foundation/Foundation.h> 2 #import <CoreGraphics/CoreGraphics.h> 3 #import <UIKit/UIKit.h> 4 #define nameFont [UIFont systemFontOfSize:12] 5 #define textFont [UIFont systemFontOfSize:14] 6 7 @class CZWeibo; 8 @interface CZWeiboFrame : NSObject 9 10 @property (nonatomic, strong) CZWeibo *weibo; 11 12 // 用來保存頭像的frame 13 @property (nonatomic, assign, readonly) CGRect iconFrame; 14 15 // 昵稱的frame 16 @property (nonatomic, assign, readonly) CGRect nameFrame; 17 18 19 // vip的frame 20 @property (nonatomic, assign, readonly) CGRect vipFrame; 21 22 // 正文的frame 23 @property (nonatomic, assign, readonly) CGRect textFrame; 24 25 //配圖的frame 26 @property (nonatomic, assign, readonly) CGRect picFrame; 27 28 // 行高 29 @property (nonatomic, assign, readonly) CGFloat rowHeight; 30 31 @end
CZWeiboFrame.m
1 #import "CZWeiboFrame.h" 2 #import "CZWeibo.h" 3 4 @implementation CZWeiboFrame 5 6 // 重寫weibo屬性的set方法 7 - (void)setWeibo:(CZWeibo *)weibo 8 { 9 _weibo = weibo; 10 11 // 計算每個控件的frame, 和行高 12 13 // 提取統一的間距 14 CGFloat margin = 10; 15 16 // 1. 頭像 17 CGFloat iconW = 35; 18 CGFloat iconH = 35; 19 CGFloat iconX = margin; 20 CGFloat iconY = margin; 21 _iconFrame = CGRectMake(iconX, iconY, iconW, iconH); 22 23 24 25 // 2. 昵稱 26 // 獲取昵稱字符串 27 NSString *nickName = weibo.name; 28 CGFloat nameX = CGRectGetMaxX(_iconFrame) + margin; 29 30 // 根據Label中文字的內容, 來動態計算Label的高和寬 31 CGSize nameSize = [self sizeWithText:nickName andMaxSize:CGSizeMake(MAXFLOAT, MAXFLOAT) andFont:nameFont]; 32 33 CGFloat nameW = nameSize.width; 34 CGFloat nameH = nameSize.height; 35 CGFloat nameY = iconY + (iconH - nameH) / 2; 36 37 _nameFrame = CGRectMake(nameX, nameY, nameW, nameH); 38 39 40 41 // 3. 會員 42 CGFloat vipW = 10; 43 CGFloat vipH = 10; 44 CGFloat vipX = CGRectGetMaxX(_nameFrame) + margin; 45 CGFloat vipY = nameY; 46 _vipFrame = CGRectMake(vipX, vipY, vipW, vipH); 47 48 49 50 // 4. 正文 51 CGFloat textX = iconX; 52 CGFloat textY = CGRectGetMaxY(_iconFrame) + margin; 53 CGSize textSize = [self sizeWithText:weibo.text andMaxSize:CGSizeMake(300, MAXFLOAT) andFont:textFont]; 54 CGFloat textW = textSize.width; 55 CGFloat textH = textSize.height; 56 _textFrame = CGRectMake(textX, textY, textW, textH); 57 58 59 // 5. 配圖 60 CGFloat picW = 100; 61 CGFloat picH = 100; 62 CGFloat picX = iconX; 63 CGFloat picY = CGRectGetMaxY(_textFrame) + margin; 64 _picFrame = CGRectMake(picX, picY, picW, picH); 65 66 67 //6. 計算每行的高度 68 CGFloat rowHeight = 0; 69 if (self.weibo.picture) { 70 // 如果有配圖, 那么行高就等于配圖的最大的Y值 + margin 71 rowHeight = CGRectGetMaxY(_picFrame) + margin; 72 } else { 73 // 如果沒有配圖, 那么行高就等于正文的最大的Y值 + margin 74 rowHeight = CGRectGetMaxY(_textFrame) + margin; 75 } 76 77 // 注意::: 計算完畢行高以后,不要忘記為屬性賦值。 78 _rowHeight = rowHeight; 79 80 81 } 82 83 // 根據給定的字符串、最大值的size、給定的字體, 來計算文字應該占用的大小 84 - (CGSize)sizeWithText:(NSString *)text andMaxSize:(CGSize)maxSize andFont:(UIFont *)font 85 { 86 NSDictionary *attr = @{NSFontAttributeName : font}; 87 return [text boundingRectWithSize:maxSize options:NSStringDrawingUsesLineFragmentOrigin attributes:attr context:nil].size; 88 } 89 @end
自定義的cell對象
之前封裝的frame主要用來存儲所需要的對象數據,以及計算各個控件和總的tableView Item的frame
而我們在cell中是用來創建我們需要的對象,以及將最終形成的布局文件返回給tableView
CZWeiboCell.h
1 #import <UIKit/UIKit.h> 2 @class CZWeiboFrame; 3 @interface CZWeiboCell : UITableViewCell 4 5 @property (nonatomic, strong) CZWeiboFrame *weiboFrame; 6 7 + (instancetype)weiboCellWithTableView:(UITableView *)tableView; 8 @end
CZWeiboCell.m
1 #import "CZWeiboCell.h" 2 #import "CZWeibo.h" 3 #import "CZWeiboFrame.h" 4 5 6 7 @interface CZWeiboCell () 8 @property (nonatomic, weak) UIImageView *imgViewIcon; 9 @property (nonatomic, weak) UILabel *lblNickName; 10 @property (nonatomic, weak) UIImageView *imgViewVip; 11 @property (nonatomic, weak) UILabel *lblText; 12 @property (nonatomic, weak) UIImageView *imgViewPicture; 13 14 15 @end 16 17 18 @implementation CZWeiboCell 19 20 21 #pragma mark - 重寫單元格的initWithStyle:方法 22 23 + (instancetype)weiboCellWithTableView:(UITableView *)tableView 24 { 25 static NSString *ID = @"weibo_cell"; 26 CZWeiboCell *cell = [tableView dequeueReusableCellWithIdentifier:ID]; 27 if (cell == nil) { 28 cell = [[CZWeiboCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:ID]; 29 } 30 return cell; 31 } 32 33 - (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier 34 { 35 if (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]) { 36 // 創建5個子控件 37 38 // 1. 頭像 39 UIImageView *imgViewIcon = [[UIImageView alloc] init]; 40 [self.contentView addSubview:imgViewIcon]; 41 self.imgViewIcon = imgViewIcon; 42 43 // 2. 昵稱 44 UILabel *lblNickName = [[UILabel alloc] init]; 45 // 設置Label的文字大小 46 lblNickName.font = nameFont; 47 48 [self.contentView addSubview:lblNickName]; 49 self.lblNickName = lblNickName; 50 51 // 3. 會員 52 UIImageView *imgViewVip = [[UIImageView alloc] init]; 53 imgViewVip.image = [UIImage imageNamed:@"vip"]; 54 [self.contentView addSubview:imgViewVip]; 55 self.imgViewVip = imgViewVip; 56 57 // 4. 正文 58 UILabel *lblText = [[UILabel alloc] init]; 59 lblText.font = textFont; 60 // 設置正文的Label可以自動換行 61 lblText.numberOfLines = 0; 62 [self.contentView addSubview:lblText]; 63 self.lblText = lblText; 64 65 // 5. 配圖 66 UIImageView *imgViewPicture = [[UIImageView alloc] init]; 67 [self.contentView addSubview:imgViewPicture]; 68 self.imgViewPicture = imgViewPicture; 69 } 70 return self; 71 } 72 73 74 #pragma mark - 重寫weibo屬性的set方法 75 - (void)setWeiboFrame:(CZWeiboFrame *)weiboFrame 76 { 77 _weiboFrame = weiboFrame; 78 79 // 1. 設置當前單元格中的子控件的數據 80 [self settingData]; 81 82 // 2. 設置當前單元格中的子控件的frame 83 [self settingFrame]; 84 } 85 86 87 // 設置數據的方法 88 - (void)settingData 89 { 90 CZWeibo *model = self.weiboFrame.weibo; 91 // 1. 頭像 92 self.imgViewIcon.image = [UIImage imageNamed:model.icon]; 93 94 // 2. 昵稱 95 self.lblNickName.text = model.name; 96 97 // 3. 會員 98 if (model.isVip) { 99 // 設置顯示會員圖標 100 self.imgViewVip.hidden = NO; 101 // 設置昵稱的顏色是紅色 102 self.lblNickName.textColor = [UIColor redColor]; 103 } else { 104 // 設置隱藏會員圖標 105 self.imgViewVip.hidden = YES; 106 // 設置昵稱的顏色是黑色 107 self.lblNickName.textColor = [UIColor blackColor]; 108 } 109 110 // 4. 正文 111 self.lblText.text = model.text; 112 113 114 // 5. 配圖 115 if (model.picture) { 116 // 有配圖 117 // 如果model.picture的值是nil, 那么下面這句話執行會報異常 118 self.imgViewPicture.image = [UIImage imageNamed:model.picture]; 119 // 顯示圖片框 120 self.imgViewPicture.hidden = NO; 121 } else { 122 // 如果沒有配圖, 隱藏圖片框 123 self.imgViewPicture.hidden = YES; 124 } 125 126 } 127 128 // 設置frame的方法 129 - (void)settingFrame 130 { 131 // 1. 頭像 132 133 self.imgViewIcon.frame = self.weiboFrame.iconFrame; 134 135 // 2. 昵稱 136 self.lblNickName.frame = self.weiboFrame.nameFrame; 137 138 // 3. 會員 139 self.imgViewVip.frame = self.weiboFrame.vipFrame; 140 141 // 4. 正文 142 143 self.lblText.frame = self.weiboFrame.textFrame; 144 145 // 5. 配圖 146 self.imgViewPicture.frame = self.weiboFrame.picFrame; 147 } 148 149 - (void)awakeFromNib { 150 // Initialization code 151 } 152 153 - (void)setSelected:(BOOL)selected animated:(BOOL)animated { 154 [super setSelected:selected animated:animated]; 155 156 // Configure the view for the selected state 157 } 158 159 @end
TableViewController控制器的類
這個類會把我們需要重寫的方法展現出來,我們只需要對我們用到的方法進行重寫就可以了
1 #import "CZTableViewController.h" 2 #import "CZWeibo.h" 3 #import "CZWeiboCell.h" 4 #import "CZWeiboFrame.h" 5 6 @interface CZTableViewController () 7 8 // 現在要求weiboFrames集合中保存的很多個CZWeiboFrame模型,不再是CZWeibo模型了。 9 @property (nonatomic, strong) NSArray *weiboFrames; 10 11 @end 12 13 @implementation CZTableViewController 14 15 #pragma mark - 懶加載數據 16 - (NSArray *)weiboFrames 17 { 18 if (_weiboFrames == nil) { 19 NSString *path = [[NSBundle mainBundle] pathForResource:@"weibos.plist" ofType:nil]; 20 21 NSArray *arrayDict = [NSArray arrayWithContentsOfFile:path]; 22 23 NSMutableArray *arrayModels = [NSMutableArray array]; 24 25 for (NSDictionary *dict in arrayDict) { 26 // 創建一個數據模型 27 CZWeibo *model = [CZWeibo weiboWithDict:dict]; 28 29 // 創建一個frame 模型 30 // 創建了一個空得frame模型 31 CZWeiboFrame *modelFrame = [[CZWeiboFrame alloc] init]; 32 33 // 把數據模型賦值給了modeFrame模型中的weibo屬性 34 modelFrame.weibo = model; 35 36 37 [arrayModels addObject:modelFrame]; 38 } 39 _weiboFrames = arrayModels; 40 } 41 return _weiboFrames; 42 } 43 44 45 46 - (void)viewDidLoad { 47 [super viewDidLoad]; 48 49 // 統一設置行高 50 //self.tableView.rowHeight = 300; 51 52 // NSLog(@"%@", self.view); 53 // NSLog(@"%@", self.tableView); 54 55 // Uncomment the following line to preserve selection between presentations. 56 // self.clearsSelectionOnViewWillAppear = NO; 57 58 // Uncomment the following line to display an Edit button in the navigation bar for this view controller. 59 // self.navigationItem.rightBarButtonItem = self.editButtonItem; 60 } 61 62 - (void)didReceiveMemoryWarning { 63 [super didReceiveMemoryWarning]; 64 // Dispose of any resources that can be recreated. 65 } 66 67 #pragma mark - Table view 數據源方法 68 69 - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { 70 return 1; 71 } 72 73 - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { 74 return self.weiboFrames.count; 75 } 76 77 78 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { 79 80 81 // 1. 獲取模型數據 82 CZWeiboFrame *model = self.weiboFrames[indexPath.row]; 83 84 85 // 2. 創建單元格 86 CZWeiboCell *cell = [CZWeiboCell weiboCellWithTableView:tableView]; 87 88 // 3. 設置單元格數據 89 cell.weiboFrame = model; 90 91 // 4. 返回單元格 92 return cell; 93 } 94 95 96 97 #pragma mark - Table view 代理方法 98 99 // 返回每行的行高的方法,對于這個案例,其中最重要的就是關于如何計算行高 100 - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath 101 { 102 CZWeiboFrame *weiboFrame = self.weiboFrames[indexPath.row]; 103 return weiboFrame.rowHeight; 104 } 105 106 107 - (BOOL)prefersStatusBarHidden 108 { 109 return YES; 110 } 111 112 113 @end
案例三:做一個類似于QQ聊天的tableView界面
這個界面有兩個難點:
1.信息背后的框體 要包裹住消息內容
2.監聽系統的鍵盤彈出事件,將我們的View 整體向上位移
問題1:
1 我們通過 設置 內邊距的形式來進行解決 2 3 btnText.contentEdgeInsets = UIEdgeInsetsMake(15, 20, 15, 20); 4 5 同時對于圖片我們要選擇平鋪的方式進行拉伸 6 7 // 加載圖片 8 UIImage *imageNormal = [UIImage imageNamed:imgNor]; 9 UIImage *imageHighlighted = [UIImage imageNamed:imgHighlighted]; 10 11 // 用平鋪的方式拉伸圖片 12 imageNormal = [imageNormal stretchableImageWithLeftCapWidth:imageNormal.size.width * 0.5 topCapHeight:imageNormal.size.height * 0.5]; 13 imageHighlighted = [imageHighlighted stretchableImageWithLeftCapWidth:imageHighlighted.size.width * 0.5 topCapHeight:imageHighlighted.size.height * 0.5]; 14 15 // 設置背景圖 16 [self.btnText setBackgroundImage:imageNormal forState:UIControlStateNormal]; 17 [self.btnText setBackgroundImage:imageHighlighted forState:UIControlStateHighlighted];
問題二:
1 - (void)viewDidLoad { 2 [super viewDidLoad]; 3 // 取消分割線 4 self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone; 5 6 // 設置UITableView的背景色 7 self.tableView.backgroundColor = [UIColor colorWithRed:236 / 255.0 green:236 / 255.0 blue:236 / 255.0 alpha:1.0]; 8 9 // 設置UITableView的行不允許被選中 10 self.tableView.allowsSelection = NO; 11 12 // 設置文本框最左側有一段間距 13 UIView *leftVw = [[UIView alloc] init]; 14 leftVw.frame = CGRectMake(0, 0, 5, 1); 15 16 // 把leftVw設置給文本框 17 self.txtInput.leftView = leftVw; 18 self.txtInput.leftViewMode = UITextFieldViewModeAlways; 19 20 21 // 監聽鍵盤的彈出事件 22 // 1. 創建一個NSNotificationCenter對象。 23 NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; 24 25 // 2. 監聽鍵盤的彈出通知 26 [center addObserver:self selector:@selector(keyboardWillChangeFrame:) name:UIKeyboardWillChangeFrameNotification object:nil]; 27 28 } 29 30 - (void)keyboardWillChangeFrame:(NSNotification *)noteInfo 31 { 32 // NSLog(@"通知名稱: %@", noteInfo.name); 33 // 34 // NSLog(@"通知的發布者: %@", noteInfo.object); 35 // 36 // NSLog(@"通知的具體內容: %@", noteInfo.userInfo); 37 // 1. 獲取當鍵盤顯示完畢或者隱藏完畢后的Y值 38 CGRect rectEnd = [noteInfo.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue]; 39 CGFloat keyboardY = rectEnd.origin.y; 40 41 // 用鍵盤的Y值減去屏幕的高度計算出平移的值 42 // 1. 如果是鍵盤彈出事件, 那么計算出的值就是負的鍵盤的高度 43 // 2. 如果是鍵盤的隱藏事件, 那么計算出的值就是零, 因為鍵盤在隱藏以后, 鍵盤的Y值就等于屏幕的高度。 44 CGFloat tranformValue = keyboardY - self.view.frame.size.height; 45 46 [UIView animateWithDuration:0.25 animations:^{ 47 // 讓控制器的View執行一次“平移” 48 self.view.transform = CGAffineTransformMakeTranslation(0, tranformValue); 49 }]; 50 51 52 53 // 讓UITableView的最后一行滾動到最上面 54 NSIndexPath *lastRowIdxPath = [NSIndexPath indexPathForRow:self.messageFrames.count - 1 inSection:0]; 55 [self.tableView scrollToRowAtIndexPath:lastRowIdxPath atScrollPosition:UITableViewScrollPositionTop animated:YES]; 56 } 57 58 59 // ***************** 注意: 監聽通知以后一定要在監聽通知的對象的dealloc方法中移除監聽 *************/. 60 61 - (void)dealloc 62 { 63 // 移除通知 64 [[NSNotificationCenter defaultCenter] removeObserver:self]; 65 }
整體代碼
1.做一個NSString的類擴展 用于字體最大的尺寸
NSString+CZNSStringExt.h
1 #import <Foundation/Foundation.h> 2 #import <UIKit/UIKit.h> 3 @interface NSString (CZNSStringExt) 4 5 // 對象方法 6 - (CGSize)sizeOfTextWithMaxSize:(CGSize)maxSize font:(UIFont *)font; 7 8 // 類方法 9 + (CGSize)sizeWithText:(NSString *)text maxSize:(CGSize)maxSize font:(UIFont *)font; 10 @end
NSString+CZNSStringExt.m
1 #import "NSString+CZNSStringExt.h" 2 3 @implementation NSString (CZNSStringExt) 4 5 // 實現對象方法 6 - (CGSize)sizeOfTextWithMaxSize:(CGSize)maxSize font:(UIFont *)font 7 { 8 NSDictionary *attrs = @{NSFontAttributeName : font}; 9 return [self boundingRectWithSize:maxSize options:NSStringDrawingUsesLineFragmentOrigin attributes:attrs context:nil].size; 10 } 11 12 // 類方法 13 + (CGSize)sizeWithText:(NSString *)text maxSize:(CGSize)maxSize font:(UIFont *)font 14 { 15 return [text sizeOfTextWithMaxSize:maxSize font:font]; 16 } 17 18 @end
2.控制器對象
ViewController.m
1 #import "ViewController.h" 2 #import "CZMessage.h" 3 #import "CZMessageFrame.h" 4 #import "CZMessageCell.h" 5 6 @interface ViewController () <UITableViewDataSource, UITableViewDelegate, UITextFieldDelegate> 7 @property (weak, nonatomic) IBOutlet UITableView *tableView; 8 9 // 用來保存所有的消息的frame模型對象 10 @property (nonatomic, strong) NSMutableArray *messageFrames; 11 @property (weak, nonatomic) IBOutlet UITextField *txtInput; 12 13 @end 14 15 @implementation ViewController 16 #pragma mark - /********** 懶加載數據 *********/ 17 - (NSMutableArray *)messageFrames 18 { 19 if (_messageFrames == nil) { 20 NSString *path = [[NSBundle mainBundle] pathForResource:@"messages.plist" ofType:nil]; 21 NSArray *arrayDict = [NSArray arrayWithContentsOfFile:path]; 22 23 NSMutableArray *arrayModels = [NSMutableArray array]; 24 for (NSDictionary *dict in arrayDict) { 25 // 創建一個數據模型 26 CZMessage *model = [CZMessage messageWithDict:dict]; 27 28 // 獲取上一個數據模型 29 CZMessage *lastMessage = (CZMessage *)[[arrayModels lastObject] message]; 30 31 // 判斷當前模型的“消息發送時間”是否和上一個模型的“消息發送時間”一致, 如果一致做個標記 32 if ([model.time isEqualToString:lastMessage.time]) { 33 model.hideTime = YES; 34 } 35 36 // 創建一個frame 模型 37 CZMessageFrame *modelFrame = [[CZMessageFrame alloc] init]; 38 39 modelFrame.message = model; 40 41 42 // 把frame 模型加到arrayModels 43 [arrayModels addObject:modelFrame]; 44 } 45 _messageFrames = arrayModels; 46 } 47 return _messageFrames; 48 } 49 50 51 #pragma mark - /********** 文本框的代理方法 *********/ 52 //- (BOOL)textFieldShouldBeginEditing:(UITextField *)textField 53 //{ 54 // return YES; 55 //} 56 57 // 當鍵盤上的return鍵被單擊的時候觸發 58 - (BOOL)textFieldShouldReturn:(UITextField *)textField 59 { 60 // 1. 獲取用戶輸入的文本 61 NSString *text = textField.text; 62 63 // 2. 發送用戶的消息 64 [self sendMessage:text withType:CZMessageTypeMe]; 65 66 // 3. 發送一個系統消息 67 [self sendMessage:@"不認識!" withType:CZMessageTypeOther]; 68 69 // 清空文本框 70 textField.text = nil; 71 72 return YES; 73 } 74 75 // 發送消息 76 - (void)sendMessage:(NSString *)msg withType:(CZMessageType)type 77 { 78 // 2. 創建一個數據模型和frame 模型 79 CZMessage *model = [[CZMessage alloc] init]; 80 81 // 獲取當前系統時間 82 NSDate *nowDate = [NSDate date]; 83 // 創建一個日期時間格式化器 84 NSDateFormatter *formatter = [[NSDateFormatter alloc] init]; 85 // 設置格式 86 formatter.dateFormat = @"今天 HH:mm"; 87 // 進行日期時間的格式化 88 model.time = [formatter stringFromDate:nowDate]; 89 model.type = type; 90 model.text = msg; 91 92 93 94 // 根據當前消息的時間和上一條消息的時間, 來設置是否需要隱藏時間Label 95 CZMessageFrame *lastMessageFrame = [self.messageFrames lastObject]; 96 NSString *lastTime = lastMessageFrame.message.time; 97 if ([model.time isEqualToString:lastTime]) { 98 model.hideTime = YES; 99 } 100 101 //***** 注意: 要先設置數據模型的hideTime屬性, 然后再設置modelFrame.message = model; 102 // 因為在設置modelFrame.message = model;的時候set方法中, 內部會用到model.hideTime屬性。 103 104 // 創建一個frame 模型 105 CZMessageFrame *modelFrame = [[CZMessageFrame alloc] init]; 106 modelFrame.message = model; 107 108 109 110 // 3. 把frame 模型加到集合中 111 [self.messageFrames addObject:modelFrame]; 112 113 114 115 116 // 4. 刷新UITableView的數據 117 [self.tableView reloadData]; 118 119 // 5. 把最后一行滾動到最上面 120 NSIndexPath *idxPath = [NSIndexPath indexPathForRow:self.messageFrames.count - 1 inSection:0]; 121 [self.tableView scrollToRowAtIndexPath:idxPath atScrollPosition:UITableViewScrollPositionTop animated:YES]; 122 } 123 124 125 126 #pragma mark - /********** UITableView的代理方法 *********/ 127 - (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView 128 { 129 // 把鍵盤叫回去, 思路: 讓控制器所管理的UIView結束編輯 130 [self.view endEditing:YES]; 131 } 132 133 - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event 134 { 135 NSLog(@"★★★★★★★★★"); 136 } 137 138 139 140 #pragma mark - /********** 數據源方法 *********/ 141 - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView 142 { 143 return 1; 144 } 145 146 - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section 147 { 148 return self.messageFrames.count; 149 } 150 151 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 152 { 153 // 1. 獲取模型數據 154 CZMessageFrame *modelFrame = self.messageFrames[indexPath.row]; 155 156 // 2. 創建單元格 157 158 CZMessageCell *cell = [CZMessageCell messageCellWithTableView:tableView]; 159 160 // 3. 把模型設置給單元格對象 161 cell.messageFrame = modelFrame; 162 163 // 4.返回單元格 164 return cell; 165 } 166 167 // 返回每一行的行高 168 - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath 169 { 170 CZMessageFrame *messageFrame = self.messageFrames[indexPath.row]; 171 return messageFrame.rowHeight; 172 } 173 174 175 176 #pragma mark - /********** 其他 *********/ 177 - (void)viewDidLoad { 178 [super viewDidLoad]; 179 // 取消分割線 180 self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone; 181 182 // 設置UITableView的背景色 183 self.tableView.backgroundColor = [UIColor colorWithRed:236 / 255.0 green:236 / 255.0 blue:236 / 255.0 alpha:1.0]; 184 185 // 設置UITableView的行不允許被選中 186 self.tableView.allowsSelection = NO; 187 188 // 設置文本框最左側有一段間距 189 UIView *leftVw = [[UIView alloc] init]; 190 leftVw.frame = CGRectMake(0, 0, 5, 1); 191 192 // 把leftVw設置給文本框 193 self.txtInput.leftView = leftVw; 194 self.txtInput.leftViewMode = UITextFieldViewModeAlways; 195 196 197 // 監聽鍵盤的彈出事件 198 // 1. 創建一個NSNotificationCenter對象。 199 NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; 200 201 // 2. 監聽鍵盤的彈出通知 202 [center addObserver:self selector:@selector(keyboardWillChangeFrame:) name:UIKeyboardWillChangeFrameNotification object:nil]; 203 204 } 205 206 - (void)keyboardWillChangeFrame:(NSNotification *)noteInfo 207 { 208 // NSLog(@"通知名稱: %@", noteInfo.name); 209 // 210 // NSLog(@"通知的發布者: %@", noteInfo.object); 211 // 212 // NSLog(@"通知的具體內容: %@", noteInfo.userInfo); 213 // 1. 獲取當鍵盤顯示完畢或者隱藏完畢后的Y值 214 CGRect rectEnd = [noteInfo.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue]; 215 CGFloat keyboardY = rectEnd.origin.y; 216 217 // 用鍵盤的Y值減去屏幕的高度計算出平移的值 218 // 1. 如果是鍵盤彈出事件, 那么計算出的值就是負的鍵盤的高度 219 // 2. 如果是鍵盤的隱藏事件, 那么計算出的值就是零, 因為鍵盤在隱藏以后, 鍵盤的Y值就等于屏幕的高度。 220 CGFloat tranformValue = keyboardY - self.view.frame.size.height; 221 222 [UIView animateWithDuration:0.25 animations:^{ 223 // 讓控制器的View執行一次“平移” 224 self.view.transform = CGAffineTransformMakeTranslation(0, tranformValue); 225 }]; 226 227 228 229 // 讓UITableView的最后一行滾動到最上面 230 NSIndexPath *lastRowIdxPath = [NSIndexPath indexPathForRow:self.messageFrames.count - 1 inSection:0]; 231 [self.tableView scrollToRowAtIndexPath:lastRowIdxPath atScrollPosition:UITableViewScrollPositionTop animated:YES]; 232 } 233 234 235 // ***************** 注意: 監聽通知以后一定要在監聽通知的對象的dealloc方法中移除監聽 *************/. 236 237 - (void)dealloc 238 { 239 // 移除通知 240 [[NSNotificationCenter defaultCenter] removeObserver:self]; 241 } 242 243 244 - (void)didReceiveMemoryWarning { 245 [super didReceiveMemoryWarning]; 246 // Dispose of any resources that can be recreated. 247 } 248 249 - (BOOL)prefersStatusBarHidden 250 { 251 return YES; 252 } 253 254 @end
3.自定義的cell對象
CZMessageCell.h
1 #import <UIKit/UIKit.h> 2 3 @class CZMessageFrame; 4 @interface CZMessageCell : UITableViewCell 5 6 // 為自定義單元格增加一個frame 模型屬性 7 @property (nonatomic, strong) CZMessageFrame *messageFrame; 8 9 10 // 封裝一個創建自定義Cell的方法 11 + (instancetype)messageCellWithTableView:(UITableView *)tableView; 12 13 @end
CZMessageCell.m
1 #import "CZMessageCell.h" 2 #import "CZMessage.h" 3 #import "CZMessageFrame.h" 4 5 @interface CZMessageCell () 6 7 @property (nonatomic, weak) UILabel *lblTime; 8 @property (nonatomic, weak) UIImageView *imgViewIcon; 9 @property (nonatomic, weak) UIButton *btnText; 10 @end 11 12 13 @implementation CZMessageCell 14 15 #pragma mark - 重寫initWithStyle方法 16 - (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier 17 { 18 if (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]) { 19 // 創建子控件 20 21 // 顯示時間的label 22 UILabel *lblTime = [[UILabel alloc] init]; 23 // 設置文字大小 24 lblTime.font = [UIFont systemFontOfSize:12]; 25 // 設置文字居中 26 lblTime.textAlignment = NSTextAlignmentCenter; 27 [self.contentView addSubview:lblTime]; 28 self.lblTime = lblTime; 29 30 31 // 顯示頭像的UIImageView 32 UIImageView *imgViewIcon = [[UIImageView alloc] init]; 33 [self.contentView addSubview:imgViewIcon]; 34 self.imgViewIcon = imgViewIcon; 35 36 37 // 顯示正文的按鈕 38 UIButton *btnText = [[UIButton alloc] init]; 39 // 設置正文的字體大小 40 btnText.titleLabel.font = textFont; 41 // 修改按鈕的正文文字顏色 42 [btnText setTitleColor:[UIColor redColor] forState:UIControlStateNormal]; 43 // 設置按鈕中的label的文字可以換行 44 btnText.titleLabel.numberOfLines = 0; 45 // 設置按鈕的背景色 46 //btnText.backgroundColor = [UIColor purpleColor]; 47 48 // 設置按鈕中的titleLabel的背景色 49 //btnText.titleLabel.backgroundColor = [UIColor greenColor]; 50 51 // 設置按鈕的內邊距 52 btnText.contentEdgeInsets = UIEdgeInsetsMake(15, 20, 15, 20); 53 54 [self.contentView addSubview:btnText]; 55 self.btnText = btnText; 56 } 57 58 // 設置單元格的背景色為clearColor 59 self.backgroundColor = [UIColor clearColor]; 60 return self; 61 } 62 63 64 #pragma mark - 重寫frame 模型的set方法 65 - (void)setMessageFrame:(CZMessageFrame *)messageFrame 66 { 67 _messageFrame = messageFrame; 68 69 // 獲取數據模型 70 CZMessage *message = messageFrame.message; 71 72 // 分別設置每個子控件的數據 和 frame 73 74 // 設置 "時間Label"的數據 和 frame 75 self.lblTime.text = message.time; 76 self.lblTime.frame = messageFrame.timeFrame; 77 self.lblTime.hidden = message.hideTime; 78 79 80 81 // 設置 頭像 82 // 根據消息類型, 判斷應該使用哪張圖片 83 NSString *iconImg = message.type == CZMessageTypeMe ? @"me" : @"other"; 84 self.imgViewIcon.image = [UIImage imageNamed:iconImg]; 85 self.imgViewIcon.frame = messageFrame.iconFrame; 86 87 88 // 設置消息正文 89 [self.btnText setTitle:message.text forState:UIControlStateNormal]; 90 self.btnText.frame = messageFrame.textFrame; 91 92 93 // 設置正文的背景圖 94 NSString *imgNor, *imgHighlighted; 95 if (message.type == CZMessageTypeMe) { 96 // 自己發的消息 97 imgNor = @"chat_send_nor"; 98 imgHighlighted = @"chat_send_press_pic"; 99 100 // 設置消息的正文文字顏色為 "白色" 101 [self.btnText setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal]; 102 } else { 103 // 對方發的消息 104 imgNor = @"chat_recive_nor"; 105 imgHighlighted = @"chat_recive_press_pic"; 106 107 // 設置消息的正文文字顏色為 "黑色" 108 [self.btnText setTitleColor:[UIColor blackColor] forState:UIControlStateNormal]; 109 } 110 111 // 加載圖片 112 UIImage *imageNormal = [UIImage imageNamed:imgNor]; 113 UIImage *imageHighlighted = [UIImage imageNamed:imgHighlighted]; 114 115 // 用平鋪的方式拉伸圖片 116 imageNormal = [imageNormal stretchableImageWithLeftCapWidth:imageNormal.size.width * 0.5 topCapHeight:imageNormal.size.height * 0.5]; 117 imageHighlighted = [imageHighlighted stretchableImageWithLeftCapWidth:imageHighlighted.size.width * 0.5 topCapHeight:imageHighlighted.size.height * 0.5]; 118 119 // 設置背景圖 120 [self.btnText setBackgroundImage:imageNormal forState:UIControlStateNormal]; 121 [self.btnText setBackgroundImage:imageHighlighted forState:UIControlStateHighlighted]; 122 } 123 124 125 #pragma mark - 創建自定義Cell的方法 126 + (instancetype)messageCellWithTableView:(UITableView *)tableView 127 { 128 static NSString *ID = @"message_cell"; 129 CZMessageCell *cell = [tableView dequeueReusableCellWithIdentifier:ID]; 130 if (cell == nil) { 131 cell = [[CZMessageCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:ID]; 132 } 133 return cell; 134 } 135 136 - (void)awakeFromNib { 137 // Initialization code 138 } 139 140 - (void)setSelected:(BOOL)selected animated:(BOOL)animated { 141 [super setSelected:selected animated:animated]; 142 143 // Configure the view for the selected state 144 } 145 146 @end
4.消息模型
CZMessage.h
1 #import <Foundation/Foundation.h> 2 3 typedef enum { 4 CZMessageTypeMe = 0, // 表示自己 5 CZMessageTypeOther = 1 // 表示對方 6 } CZMessageType; 7 8 9 @interface CZMessage : NSObject 10 11 // 消息正文 12 @property (nonatomic, copy) NSString *text; 13 14 // 消息發送時間 15 @property (nonatomic, copy) NSString *time; 16 17 // 消息的類型(表示是對方發送的消息還是自己發送的消息) 18 @property (nonatomic, assign) CZMessageType type; 19 20 // 用來記錄是否需要顯示"時間Label" 21 @property (nonatomic, assign) BOOL hideTime; 22 23 24 25 - (instancetype)initWithDict:(NSDictionary *)dict; 26 + (instancetype)messageWithDict:(NSDictionary *)dict; 27 28 @end
CZMeesge.m
1 #import "CZMessage.h" 2 3 @implementation CZMessage 4 5 - (instancetype)initWithDict:(NSDictionary *)dict 6 { 7 if (self = [super init]) { 8 [self setValuesForKeysWithDictionary:dict]; 9 } 10 return self; 11 } 12 13 + (instancetype)messageWithDict:(NSDictionary *)dict 14 { 15 return [[self alloc] initWithDict:dict]; 16 } 17 @end
5.展示信息的frame
CZMessageFram.h
1 #import <Foundation/Foundation.h> 2 #import <CoreGraphics/CoreGraphics.h> 3 #define textFont [UIFont systemFontOfSize:13] 4 5 @class CZMessage; 6 @interface CZMessageFrame : NSObject 7 8 // 引用數據模型 9 @property (nonatomic, strong) CZMessage *message; 10 11 // 時間Label的frame 12 @property (nonatomic, assign, readonly) CGRect timeFrame; 13 14 // 頭像的frame 15 @property (nonatomic, assign, readonly) CGRect iconFrame; 16 17 // 正文的frame 18 @property (nonatomic, assign, readonly) CGRect textFrame; 19 20 // 行高 21 @property (nonatomic, assign, readonly) CGFloat rowHeight; 22 23 @end
CZMessageFrame.m
1 #import "CZMessageFrame.h" 2 #import <UIKit/UIKit.h> 3 #import "CZMessage.h" 4 #import "NSString+CZNSStringExt.h" 5 6 @implementation CZMessageFrame 7 8 - (void)setMessage:(CZMessage *)message 9 { 10 _message = message; 11 12 // 計算每個控件的frame 和 行高 13 // 獲取屏幕寬度 14 CGFloat screenW = [UIScreen mainScreen].bounds.size.width; 15 // 設置一個統一的間距 16 CGFloat margin = 5; 17 18 // 計算時間label的frame 19 CGFloat timeX = 0; 20 CGFloat timeY = 0; 21 CGFloat timeW = screenW; 22 CGFloat timeH = 15; 23 if (!message.hideTime) { 24 // 如果需要顯示時間label, 那么再計算時間label的frame 25 _timeFrame = CGRectMake(timeX, timeY, timeW, timeH); 26 } 27 28 29 30 // 計算頭像的frame 31 CGFloat iconW = 30; 32 CGFloat iconH = 30; 33 CGFloat iconY = CGRectGetMaxY(_timeFrame) + margin; 34 CGFloat iconX = message.type == CZMessageTypeOther ? margin : screenW - margin - iconW; 35 _iconFrame = CGRectMake(iconX, iconY, iconW, iconH); 36 37 38 39 // 計算消息正文的frame 40 // 1. 先計算正文的大小 41 CGSize textSize = [message.text sizeOfTextWithMaxSize:CGSizeMake(200, MAXFLOAT) font:textFont]; 42 CGFloat textW = textSize.width + 40; 43 CGFloat textH = textSize.height + 30; 44 // 2. 再計算x,y 45 CGFloat textY = iconY; 46 CGFloat textX = message.type == CZMessageTypeOther ? CGRectGetMaxX(_iconFrame) : (screenW - margin - iconW - textW); 47 _textFrame = CGRectMake(textX, textY, textW, textH); 48 49 50 51 // 計算行高 52 // 獲取 頭像的最大的Y值和正文的最大的Y值, 然后用最大的Y值+ margin 53 CGFloat maxY = MAX(CGRectGetMaxY(_textFrame), CGRectGetMaxY(_iconFrame)); 54 _rowHeight = maxY + margin; 55 56 } 57 @end
方案四,好友列表
幾個要注意的:
1.組上得圖標旋轉后變形
2.cell重用的問題
這里直接繼承的TableViewController控制器 直接上代碼了
CZQQFriendsTableViewController.m
1 #import "CZQQFriendsTableViewController.h" 2 #import "CZGroup.h" 3 #import "CZFriend.h" 4 #import "CZFriendCell.h" 5 #import "CZGroupHeaderView.h" 6 7 @interface CZQQFriendsTableViewController () <CZGroupHeaderViewDelegate> 8 9 // 保存所有的朋友信息(分組信息) 10 @property (nonatomic, strong) NSArray *groups; 11 @end 12 13 @implementation CZQQFriendsTableViewController 14 15 #pragma mark - *********** 懶加載 *********** 16 - (NSArray *)groups 17 { 18 if (_groups == nil) { 19 NSString *path = [[NSBundle mainBundle] pathForResource:@"friends.plist" ofType:nil]; 20 NSArray *arrayDicts = [NSArray arrayWithContentsOfFile:path]; 21 22 NSMutableArray *arrayModels = [NSMutableArray array]; 23 for (NSDictionary *dict in arrayDicts) { 24 CZGroup *model = [CZGroup groupWithDict:dict]; 25 [arrayModels addObject:model]; 26 } 27 _groups = arrayModels; 28 29 } 30 return _groups; 31 } 32 33 34 #pragma mark - *********** CZGroupHeaderViewDelegate的代理方法 *********** 35 - (void)groupHeaderViewDidClickTitleButton:(CZGroupHeaderView *)groupHeaderView 36 { 37 // 刷新table view 38 //[self.tableView reloadData]; 39 40 // 局部刷新(只刷新某個組) 41 // 創建一個用來表示某個組的對象 42 NSIndexSet *idxSet = [NSIndexSet indexSetWithIndex:groupHeaderView.tag]; 43 [self.tableView reloadSections:idxSet withRowAnimation:UITableViewRowAnimationFade]; 44 } 45 46 47 48 49 50 #pragma mark - *********** 實現數據源方法 *********** 51 - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView 52 { 53 return self.groups.count; 54 } 55 56 - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section 57 { 58 // 因為在這個方法中, 要根據當前組的狀態(是否是展開), 來設置不同的返回值 59 // 所以, 需要為CZGroup模型增加一個用來保存"是否展開"狀態的屬性 60 CZGroup *group = self.groups[section]; 61 if (group.isVisible) { 62 return group.friends.count; 63 } else { 64 return 0; 65 } 66 67 } 68 69 // 返回每行的單元格 70 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 71 { 72 // 1. 獲取模型對象(數據) 73 CZGroup *group = self.groups[indexPath.section]; 74 CZFriend *friend = group.friends[indexPath.row]; 75 76 // 2. 創建單元格(視圖) 77 CZFriendCell *cell = [CZFriendCell friendCellWithTableView:tableView]; 78 79 // 3. 設置單元格數據(把模型設置給單元格) 80 cell.friendModel = friend; 81 82 // 4. 返回單元格 83 return cell; 84 } 85 86 87 //// 設置每一組的組標題(下面的這個方法只能設置每一組的組標題字符串, 但是我們要的是每一組中還包含其他子控件) 88 //- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section 89 //{ 90 // CZGroup *group = self.groups[section]; 91 // return group.name; 92 //} 93 94 - (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section 95 { 96 // 不要在這個方法中直接創建一個UIView對象返回, 因為這樣無法實現重用該UIView 97 // 為了能重用每個Header中的UIView, 所以這里要返回一個UITableViewHeaderFooterView 98 // 1. 獲取模型數據 99 CZGroup *group = self.groups[section]; 100 101 102 // 2. 創建UITableViewHeaderFooterView 103 CZGroupHeaderView *headerVw = [CZGroupHeaderView groupHeaderViewWithTableView:tableView]; 104 headerVw.tag = section; 105 106 // 3. 設置數據 107 headerVw.group = group; 108 109 // 設置headerView的代理為當前控制器 110 headerVw.delegate = self; 111 112 113 // 在剛剛創建好的header view中獲取的header view的frame都是0, 因為剛剛創建好的header view我們沒有為其frame賦值, 所以frame都是 0 114 // 但是, 程序運行起來以后, 我們看到的header view是有frame的。原因是: 在當前方法當中, 將header view返回以后, UITableView在執行的時候, 會用到header view, UITableView既然要用Header View, 那么就必須將header view添加到UITableview中, 當把header view添加到UITableView中的時候, UITableView內部會根據一些設置來動態的為header view的frame賦值,也就是說在UITableView即將使用header view的時候, 才會為header view的frame賦值。 115 116 // 4. 返回view 117 return headerVw; 118 119 120 } 121 122 123 124 125 #pragma mark - *********** 隱藏狀態欄 *********** 126 - (BOOL)prefersStatusBarHidden 127 { 128 return YES; 129 } 130 131 132 #pragma mark - *********** 控制器的viewDidLoad方法 *********** 133 - (void)viewDidLoad 134 { 135 [super viewDidLoad]; 136 137 // 統一設置每組的組標題的高度 138 self.tableView.sectionHeaderHeight = 44; 139 } 140 141 @end
cell模型對象
CZFriendCell.h
1 #import <UIKit/UIKit.h> 2 @class CZFriend; 3 @interface CZFriendCell : UITableViewCell 4 5 + (instancetype)friendCellWithTableView:(UITableView *)tableView; 6 7 @property (nonatomic, strong) CZFriend *friendModel; 8 @end
CZFriendCell.m
1 #import "CZFriendCell.h" 2 #import "CZFriend.h" 3 4 @implementation CZFriendCell 5 6 7 + (instancetype)friendCellWithTableView:(UITableView *)tableView 8 { 9 static NSString *ID = @"friend_cell"; 10 CZFriendCell *cell = [tableView dequeueReusableCellWithIdentifier:ID]; 11 if (cell == nil) { 12 cell = [[CZFriendCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:ID]; 13 } 14 return cell; 15 } 16 17 18 - (void)setFriendModel:(CZFriend *)friendModel 19 { 20 _friendModel = friendModel; 21 22 // 把模型中的數據設置給單元格的子控件 23 self.imageView.image = [UIImage imageNamed:friendModel.icon]; 24 self.textLabel.text = friendModel.name; 25 self.detailTextLabel.text = friendModel.intro; 26 27 // 根據當前的好友是不是vip來決定是否要將"昵稱"顯示為紅色 28 self.textLabel.textColor = friendModel.isVip ? [UIColor redColor] : [UIColor blackColor]; 29 } 30 31 32 - (void)awakeFromNib { 33 // Initialization code 34 } 35 36 - (void)setSelected:(BOOL)selected animated:(BOOL)animated { 37 [super setSelected:selected animated:animated]; 38 39 // Configure the view for the selected state 40 } 41 42 @end
CZGroupHeaderView.h
1 #import <UIKit/UIKit.h> 2 @class CZGroupHeaderView; 3 @protocol CZGroupHeaderViewDelegate <NSObject> 4 5 - (void)groupHeaderViewDidClickTitleButton:(CZGroupHeaderView *)groupHeaderView; 6 7 @end 8 9 10 @class CZGroup; 11 @interface CZGroupHeaderView : UITableViewHeaderFooterView 12 13 @property (nonatomic, strong) CZGroup *group; 14 15 + (instancetype)groupHeaderViewWithTableView:(UITableView *)tableView; 16 17 // 增加一個代理屬性 18 @property (nonatomic, weak) id<CZGroupHeaderViewDelegate> delegate; 19 20 @end
CZGroupHeaderView.m
1 #import "CZGroupHeaderView.h" 2 #import "CZGroup.h" 3 4 @interface CZGroupHeaderView () 5 6 @property (nonatomic, weak) UIButton *btnGroupTitle; 7 8 @property (nonatomic, weak) UILabel *lblCount; 9 10 @end 11 12 13 @implementation CZGroupHeaderView 14 15 16 // 封裝一個類方法來創建一個header view 17 + (instancetype)groupHeaderViewWithTableView:(UITableView *)tableView 18 { 19 static NSString *ID = @"group_header_view"; 20 CZGroupHeaderView *headerVw = [tableView dequeueReusableHeaderFooterViewWithIdentifier:ID]; 21 if (headerVw == nil) { 22 headerVw = [[CZGroupHeaderView alloc] initWithReuseIdentifier:ID]; 23 } 24 return headerVw; 25 } 26 27 // 重寫initWithReuseIdentifier方法, 在創建headerView的時候, 同時創建子控件 28 - (instancetype)initWithReuseIdentifier:(NSString *)reuseIdentifier 29 { 30 if (self = [super initWithReuseIdentifier:reuseIdentifier]) { 31 // 創建按鈕 32 UIButton *btnGroupTitle = [[UIButton alloc] init]; 33 // 設置按鈕的圖片(三角圖片) 34 [btnGroupTitle setImage:[UIImage imageNamed:@"buddy_header_arrow"] forState:UIControlStateNormal]; 35 // 設置按鈕的文字顏色 36 [btnGroupTitle setTitleColor:[UIColor blackColor] forState:UIControlStateNormal]; 37 // 設置按鈕默認的背景圖片和高亮時的背景圖片 38 [btnGroupTitle setBackgroundImage:[UIImage imageNamed:@"buddy_header_bg"] forState:UIControlStateNormal]; 39 // 設置按鈕高亮的背景圖片和高亮時的背景圖片 40 [btnGroupTitle setBackgroundImage:[UIImage imageNamed:@"buddy_header_bg_highlighted"] forState:UIControlStateHighlighted]; 41 // 設置按鈕中內容整體左對齊 42 btnGroupTitle.contentHorizontalAlignment = UIControlContentHorizontalAlignmentLeft; 43 // 設置按鈕的內容的內邊距 44 btnGroupTitle.contentEdgeInsets = UIEdgeInsetsMake(0, 10, 0, 0); 45 // 設置按鈕標題距離左邊的邊距 46 btnGroupTitle.titleEdgeInsets = UIEdgeInsetsMake(0, 5, 0, 0); 47 48 // 為按鈕增加一個點擊事件 49 [btnGroupTitle addTarget:self action:@selector(btnGroupTitleClicked) forControlEvents:UIControlEventTouchUpInside]; 50 51 // 設置按鈕中圖片的現實模式 52 btnGroupTitle.imageView.contentMode = UIViewContentModeCenter; 53 // 設置圖片框超出的部分不要截掉 54 btnGroupTitle.imageView.clipsToBounds = NO; 55 56 [self.contentView addSubview:btnGroupTitle]; 57 self.btnGroupTitle = btnGroupTitle; 58 59 // 創建lable 60 UILabel *lblCount = [[UILabel alloc] init]; 61 [self.contentView addSubview:lblCount]; 62 self.lblCount = lblCount; 63 } 64 return self; 65 } 66 67 68 // 組標題按鈕的點擊事件 69 - (void)btnGroupTitleClicked 70 { 71 // 1. 設置組的狀態 72 self.group.visible = !self.group.isVisible; 73 74 // // 2.刷新tableView 75 // 通過代理來實現 76 if ([self.delegate respondsToSelector:@selector(groupHeaderViewDidClickTitleButton:)]) { 77 // 調用代理方法 78 [self.delegate groupHeaderViewDidClickTitleButton:self]; 79 } 80 81 82 } 83 84 // 當一個新的header view 已經加到某個父控件中的時候執行這個方法。 85 - (void)didMoveToSuperview 86 { 87 if (self.group.isVisible) { 88 // 3. 讓按鈕中的圖片實現旋轉 89 self.btnGroupTitle.imageView.transform = CGAffineTransformMakeRotation(M_PI_2); 90 } else { 91 self.btnGroupTitle.imageView.transform = CGAffineTransformMakeRotation(0); 92 } 93 } 94 95 // 重寫group屬性的set方法 96 - (void)setGroup:(CZGroup *)group 97 { 98 _group = group; 99 // 設置數據 100 101 // 設置按鈕上的文字 102 [self.btnGroupTitle setTitle:group.name forState:UIControlStateNormal]; 103 // 設置 lblCount商的文字 104 self.lblCount.text = [NSString stringWithFormat:@"%d / %d", group.online, group.friends.count]; 105 106 // 設置按鈕中的圖片旋轉問題 107 if (self.group.isVisible) { 108 // 3. 讓按鈕中的圖片實現旋轉 109 self.btnGroupTitle.imageView.transform = CGAffineTransformMakeRotation(M_PI_2); 110 } else { 111 self.btnGroupTitle.imageView.transform = CGAffineTransformMakeRotation(0); 112 } 113 114 115 // 設置frame不要寫在這里, 因為在這里獲取的當前控件(self)的寬和高都是0 116 117 118 119 } 120 121 // 當當前控件的frame發生改變的時候會調用這個方法 122 - (void)layoutSubviews 123 { 124 [super layoutSubviews]; 125 126 // 設置按鈕的frame 127 self.btnGroupTitle.frame = self.bounds; 128 //NSLog(@"%@", NSStringFromCGRect(self.btnGroupTitle.frame)); 129 130 // 設置lable的frame 131 CGFloat lblW = 100; 132 CGFloat lblH = self.bounds.size.height; 133 CGFloat lblX = self.bounds.size.width - 10 - lblW; 134 CGFloat lblY = 0; 135 self.lblCount.frame = CGRectMake(lblX, lblY, lblW, lblH); 136 //NSLog(@"%@", NSStringFromCGRect(self.lblCount.frame)); 137 } 138 139 /* 140 // Only override drawRect: if you perform custom drawing. 141 // An empty implementation adversely affects performance during animation. 142 - (void)drawRect:(CGRect)rect { 143 // Drawing code 144 } 145 */ 146 147 @end

浙公網安備 33010602011771號