設置UITableView的dataSource、delegate
UITableView多組數據和單組數據的展示
UITableViewCell的常見屬性
UITableView的性能優化(cell的循環利用)
自定義Cell
UITableView需要一個數據源(dataSource)來顯示數據
UITableView會向數據源查詢一共有多少行數據以及每一行顯示什麼數據等
沒有設置數據源的UITableView只是個空殼
凡是遵守UITableViewDataSource協議的OC對象,都可以是UITableView的數據源
@property (nonatomic, assign) id dataSource;
//調用數據源的下面方法得知一共有多少組數據
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView;
//調用數據源的下面方法得知每一組有多少行數據
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section;
//調用數據源的下面方法得知每一行顯示什麼內容
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;
MVC是一種設計思想,貫穿於整個iOS開發中,需要積累一定的項目經驗,才能深刻體會其中的含義和好處
MVC中的三個角色
//M:Model,模型數據
//V:View,視圖(界面)
//C:Control,控制中心
MVC的幾個明顯的特征和體現:
View上面顯示什麼東西,取決於Model
只要Model數據改了,View的顯示狀態會跟著更改
Control負責初始化Model,並將Model傳遞給View去解析展示
UITableView的每一行都是一個UITableViewCell,通過dataSource的tableView:cellForRowAtIndexPath:方法來初始化每一行
UITableViewCell內部有個默認的子視圖:contentView,contentView是UITableViewCell所顯示內容的父視圖,可顯示一些輔助指示視圖
輔助指示視圖的作用是顯示一個表示動作的圖標,可以通過設置UITableViewCell的accessoryType來顯示,默認是UITableViewCellAccessoryNone(不顯示輔助指示視圖),其他值如下:
UITableViewCellAccessoryDisclosureIndicator
UITableViewCellAccessoryDetailDisclosureButton
UITableViewCellAccessoryCheckmark
還可以通過cell的accessoryView屬性來自定義輔助指示視圖(比如往右邊放一個開關)
contentView下默認有3個子視圖
其中2個是UILabel(通過UITableViewCell的textLabel和detailTextLabel屬性訪問)
第3個是UIImageView(通過UITableViewCell的imageView屬性訪問)
UITableViewCell還有一個UITableViewCellStyle屬性,用於決定使用contentView的哪些子視圖,以及這些子視圖在contentView中的位置
iOS設備的內存有限,如果用UITableView顯示成千上萬條數據,就需要成千上萬個UITableViewCell對象的話,那將會耗盡iOS設備的內存。要解決該問題,需要重用UITableViewCell對象
重用原理:當滾動列表時,部分UITableViewCell會移出窗口,UITableView會將窗口外的UITableViewCell放入一個對象池中,等待重用。當UITableView要求dataSource返回UITableViewCell時,dataSource會先查看這個對象池,如果池中有未使用的UITableViewCell,dataSource會用新的數據配置這個UITableViewCell,然後返回給UITableView,重新顯示到窗口中,從而避免創建新對象
還有一個非常重要的問題:有時候需要自定義UITableViewCell(用一個子類繼承UITableViewCell),而且每一行用的不一定是同一種UITableViewCell,所以一個UITableView可能擁有不同類型的UITableViewCell,對象池中也會有很多不同類型的UITableViewCell,那麼UITableView在重用UITableViewCell時可能會得到錯誤類型的UITableViewCell
解決方案:UITableViewCell有個NSString *reuseIdentifier屬性,可以在初始化UITableViewCell的時候傳入一個特定的字符串標識來設置reuseIdentifier(一般用UITableViewCell的類名)。當UITableView要求dataSource返回UITableViewCell時,先通過一個字符串標識到對象池中查找對應類型的UITableViewCell對象,如果有,就重用,如果沒有,就傳入這個字符串標識來初始化一個UITableViewCell對象
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// 1.定義一個cell的標識
static NSString *ID = @"mjcell";
// 2.從緩存池中取出cell
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];
// 3.如果緩存池中沒有cell
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:ID];
}
// 4.設置cell的屬性...
return cell;
}
對象A內部發生了一些事情,想通知對象B
對象B想監聽對象A內部發生了什麼事情
對象A想在自己的方法內部調用對象B的某個方法,並且對象A不能對對象B有耦合依賴
對象A想傳遞數據給對象B
……
以上情況,結果都一樣:對象B是對象A的代理(delegate)
先搞清楚誰是誰的代理(delegate)
定義代理協議,協議名稱的命名規范:控件類名 + Delegate
定義代理方法
代理方法一般都定義為@optional
代理方法名都以控件名開頭
代理方法至少有1個參數,將控件本身傳遞出去
設置代理(delegate)對象 (比如myView.delegate = xxxx;)
代理對象遵守協議
代理對象實現協議裡面該實現的方法
在恰當的時刻調用代理對象(delegate)的代理方法,通知代理發生了什麼事情
(在調用之前判斷代理是否實現了該代理方法)
1.新建一個繼承自UITableViewCell的類
2.重寫initWithStyle:reuseIdentifier:方法
添加所有需要顯示的子控件(不需要設置子控件的數據和frame, 子控件要添加到contentView中)
進行子控件一次性的屬性設置(有些屬性只需要設置一次, 比如字體\固定的圖片)
3.提供2個模型
數據模型: 存放文字數據\圖片數據
frame模型: 存放數據模型\所有子控件的frame\cell的高度
4.cell擁有一個frame模型(不要直接擁有數據模型)
5.重寫frame模型屬性的setter方法: 在這個方法中設置子控件的顯示數據和frame
6.frame模型數據的初始化已經采取懶加載的方式(每一個cell對應的frame模型數據只加載一次)
數據源plist文件:
#import "ViewController.h"
@interface ViewController ()
@property (weak, nonatomic) IBOutlet UITableView *tableView;
@property (weak, nonatomic) IBOutlet UIToolbar *toolBar;
@property NSArray *provinces;
@property NSArray *cities;
@end
@implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// 載入數據
NSBundle *mainbundle = [NSBundle mainBundle];
//如果類型不給,需要在resource參數中給出後綴名
self.provinces = [NSArray arrayWithContentsOfFile:[mainbundle pathForResource:@"provinces.plist" ofType:nil]];
self.cities =[NSArray arrayWithContentsOfFile:[mainbundle pathForResource:@"cities.plist" ofType:nil]];
//處理toolBar的按鈕事件綁定
NSArray *items=self.toolBar.items;
for (UIBarButtonItem *item in items) {
if (item.title) {
NSLog(@"當前的item: %@",item.title);
[item setAction:@selector(toolBarItemClick:)];
}
}
}
#pragma mark - UITableViewDataSouce 數據源方法
/**
* Section的個數,一共有多少組數據
*/
-(NSInteger) numberOfSectionsInTableView:(UITableView *)tableView
{
return self.provinces.count;
}
/**
* 第section組有多少行
*/
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [self.cities[section] count];
}
/**
* 每一行顯示的內容(cell)
*/
-(UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSLog(@"TableView來獲取內容:section:%d,row:%d",indexPath.section,indexPath.row);
UITableViewCell *cell =[[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nil];
//取cities中的城市數組
NSArray *city = self.cities[indexPath.section];
cell.textLabel.text =city[indexPath.row];
[cell setSelectionStyle:UITableViewCellSelectionStyleNone];
//如果當前的cell是被選中的,則設置其選中的accessoryType
NSArray *selpaths =[tableView indexPathsForSelectedRows];
if ([selpaths containsObject:indexPath]) {
cell.accessoryType = UITableViewCellAccessoryCheckmark;
}
return cell;
}
/**
* 添加索引
*/
- (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView
{
return self.provinces;
}
/**
* 顯示第section組的頭部標題
*/
-(NSString*) tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
{
return self.provinces[section];//每個省的名稱
}
#pragma mark - UITableViewDelegate 代理方法
/**
* 即將被選中時調用
*/
-(NSIndexPath *) tableView:(UITableView *)tableView willSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
NSLog(@"section:%d,row:%d即將被選中",indexPath.section,indexPath.row);
return indexPath;
}
/**
* 當前行已經被選中時調用
*/
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
NSLog(@"section:%d,row:%d已經被選中",indexPath.section,indexPath.row);
//設置其accessoryType 為CheckMark
UITableViewCell *cell=[tableView cellForRowAtIndexPath:indexPath];
[cell setAccessoryType:UITableViewCellAccessoryCheckmark];
}
/**
* 當前行即將被取消選中時調用
*/
-(NSIndexPath *)tableView:(UITableView *)tableView willDeselectRowAtIndexPath:(NSIndexPath *)indexPath
{
NSLog(@"section:%d,row:%d即將被取消被選中",indexPath.section,indexPath.row);
return indexPath;
}
/**
* 當前行被取消選中時調用
*/
-(void)tableView:(UITableView *)tableView didDeselectRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
[cell setAccessoryType:UITableViewCellAccessoryNone];
NSLog(@"section:%d,row:%d已經被取消被選中",indexPath.section,indexPath.row);
}
#pragma mark - 事件方法
/**
* 點擊按鈕
*/
- (void)toolBarItemClick:(UIBarButtonItem *)sender
{
NSLog(@"按鈕: %@被點擊了",sender.title);
if (sender.tag==3) {
NSArray * selIndexLst = [self.tableView indexPathsForSelectedRows];
NSLog(@"selIndex:%@",selIndexLst);
return;
}
}
@end
效果圖:
數據源plist文件:
//模型類
#import
@interface Hero : NSObject
@property(nonatomic,copy) NSString * name;
@property(nonatomic,copy) NSString * icon;
@property(nonatomic,copy) NSString * intro;
+ (instancetype)heroWithDict:(NSDictionary *)dict;
- (instancetype)initWithDict:(NSDictionary *)dict;
@end
#import "Hero.h"
@implementation Hero
+ (instancetype)heroWithDict:(NSDictionary *)dict
{
return [[self alloc]initWithDict:dict];
}
- (instancetype)initWithDict:(NSDictionary *)dict
{
if (self==[super init]) {
[self setValuesForKeysWithDictionary:dict];
}
return self;
}
@end
#import "ViewController.h"
#import "Hero.h"
@interface ViewController ()
@property(nonatomic,strong) NSArray* heros;
@property (weak, nonatomic) IBOutlet UITableView *tableView;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
}
/**
* 隱藏標題欄
*/
- (BOOL)prefersStatusBarHidden
{
return YES;
}
//初始化
- (NSArray *)heros
{
if (_heros==nil) {
//1.獲得plist的全路徑
NSString * path=[[NSBundle mainBundle]pathForResource:@"heros.plist" ofType:nil];
//2.加載數組
NSArray * dictArray=[NSArray arrayWithContentsOfFile:path];
//3.將dictArray裡面的所有字典轉成模型對象,放到新的數組中
NSMutableArray *heroArray=[NSMutableArray array];
for (NSDictionary *dict in dictArray) {
//3.1創建模型對象
Hero *hero=[Hero heroWithDict:dict];
//3.2添加模型對象到數組中
[heroArray addObject:hero];
}
//4.賦值
_heros=heroArray;
}
return _heros;
}
#pragma mark - 數據源方法
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return self.heros.count;
}
/**
* 知識點一: cell的性能優化
* 1.通過一個標識去緩存池中尋找可循環利用的cell
* 2.如果緩存池找不到可循環利用的cell,就會創建一個新的cell,給cell貼個標識
* 3.給cell設置新的數據
*/
/**
* 每當有一個cell進入視野范圍內,就會調用
*/
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
//static修飾局部變量:可以保證局部變量只分配一次存儲空間(只初始化一次)
static NSString * ID=@"hero";
//1.通過一個標識去緩存池中尋找可循環利用的cell,dequeue:出列(查找)
UITableViewCell * cell=[tableView dequeueReusableCellWithIdentifier:ID];
//2.如果沒有可循環利用cell
if (cell==nil) {
cell=[[UITableViewCell alloc]initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:ID];
NSLog(@"------緩存池中找不到,所以創建了cell- %ld",(long)indexPath.row);
}
//3.取出模型
Hero *hero=self.heros[indexPath.row];
//設置cell的數據
cell.textLabel.text=hero.name;
cell.detailTextLabel.text=hero.intro;
cell.imageView.image=[UIImage imageNamed:hero.icon];
//設置cell右邊指示器的類型
cell.accessoryType=UITableViewCellAccessoryDisclosureIndicator;
//設置圖片背景
UIImageView *bgView=[[UIImageView alloc]init];
bgView.image=[UIImage imageNamed:@"buttondelete"];
cell.backgroundView=bgView;
//設置選中背景
UIView *selectedbgView=[[UIView alloc]init];
selectedbgView.backgroundColor=[UIColor greenColor];
cell.selectedBackgroundView=selectedbgView;
return cell;
}
#pragma mark - 代理方法
//控制與狀態欄之間的高度
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
return 60;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
//1.取得被點擊這行對應的模型
Hero *hero=self.heros[indexPath.row];
//彈框
UIAlertView *alert=[[UIAlertView alloc]initWithTitle:@"數據展示" message:nil delegate:self cancelButtonTitle:@"取消" otherButtonTitles:@"確定", nil];
//設置對話框的類型
alert.alertViewStyle=UIAlertViewStylePlainTextInput;
//取得唯一的那個文本框,顯示英雄的名稱
[alert textFieldAtIndex:0].text=hero.name;
[alert show];
//綁定行號到alertView上
alert.tag=indexPath.row;
}
//- (void)tableView:(UITableView *)tableView didDeselectRowAtIndexPath:(NSIndexPath *)indexPath
//{
// // Deselect : 取消選中
// NSLog(@"取消選中了第%d行", indexPath.row);
//}
#pragma mark - alertView的代理方法
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
if (buttonIndex==0) return;
//1.取得文本框最後的文字
NSString *name=[alertView textFieldAtIndex:0].text;
//2.修改模型屬性
int row=alertView.tag;
Hero *hero=self.heros[row];
hero.name=name;
/**
* 知識點二: 數據的刷新
* reloadData:tableView會向數據源重新請求數據,重新調用數據源的相應方法取得數據
* 重新調用數據源的tableView:numberOfRowsInSection:獲得行數
* 重新調用數據源的tableView:cellForRowAtIndexPath:得知每一行顯示怎樣的cell
*/
//3.讓tableView重新加載模型數據
//全部刷新
//[self.tableView reloadData];
//局部刷新
NSIndexPath *path=[NSIndexPath indexPathForItem:row inSection:0];
[self.tableView reloadRowsAtIndexPaths:@[path] withRowAnimation:UITableViewRowAnimationBottom];
}
@end
效果圖: