UITableView
一、概述
- 在iOS中,要實現表格數據展示,最常用的做法就是使用UITableView,類似於微信、QQ、新浪微博等軟件基本上隨處都是UITableView。
- UITableView繼承自UIScrollView,因此支持垂直滾動,而且性能極佳。
二、基本介紹
UITableView有兩種風格:UITableViewStylePlain 和 UITableViewStyleGrouped
- UITableViewStylePlain按照普通樣式顯示
- UITableViewStyleGrouped按分組樣式顯示前者
在UITableView中數據只有行的概念,並沒有列的概念,因爲在手機操作系統中顯示多列是不利於操作的。UITableView中每行數據都是一個UITableViewCell,在這個控件中爲了顯示更多的信息,iOS已經在其內部設置好了多個子控件以供開發者使用。如果我們查看UITableViewCell的聲明文件可以發現在內部有一個UIView控件(contentView,作爲其他元素的父控件)、兩個UILable控件(textLabel、detailTextLabel)、一個UIImage控件(imageView),分別用於容器、顯示內容、詳情和圖片。
三、數據源
由於iOS是遵循MVC模式設計的,很多操作都是通過代理和外界溝通的,但對於數據源控件除了代理還有一個數據源屬性,通過它和外界進行數據交互。 對於UITableView設置完dataSource後需要實現UITableViewDataSource協議,在這個協議中定義了多種數據操作方法
- UITableView需要一個數據源(dataSource)來顯示數據
- UITableView會向數據源查詢一共有多少行數據以及每一行顯示什麼數據等
- 凡是遵守UITableViewDataSource協議的OC對象,都可以是UITableView的數據源
1.tableView和數據源
2.tableView展示數據的過程
調用數據源的下面方法得知一共有多少組數據
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView;
調用數據源的下面方法得知每一組有多少行數據
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section;
調用數據源的下面方法得知每一行顯示什麼內容(每當有一個cell進入視野範圍內,就會調用)
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;
3.tableView常用方法
第section組顯示怎樣的頭部標題(概括這一組的作用)
(NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
第section組顯示怎樣的尾部標題(詳細描述這一組是幹什麼用的)
(NSString *)tableView:(UITableView *)tableView titleForFooterInSection:(NSInteger)section
四、代理
UITableView代理方法有很多,例如監聽單元格顯示週期、監聽單元格選擇編輯操作、設置是否高亮顯示單元格、設置行高等。
1.設置行高
設置分組標題內容高度
-(CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
設置每行高度(每行高度可以不一樣)
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
設置尾部說明內容高度
-(CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section
2.監聽點擊
五、cell
1. 簡介
- UITableView的每一行都是一個UITableViewCell,通過dataSource的tableView:cellForRowAtIndexPath:方法來初始化每一行。
- UITableViewCell內部有個默認的子視圖:contentView,contentView是UITableViewCell所顯示內容的父視圖,可顯示一些輔助指示視圖。
- 輔助指示視圖的作用是顯示一個表示動作的圖標,可以通過設置UITableViewCell的accessoryType來顯示。accessoryType有五種枚舉常量:
- UITableViewCellAccessoryNone
- UITableViewCellAccessoryDisclosureIndicator
- UITableViewCellAccessoryDetailDisclosureButton
- UITableViewCellAccessoryCheckmark
- UITableViewCellAccessoryDetailButton
- 還可以通過cell的accessoryView屬性來自定義輔助指示視圖(比如往右邊放一個開關)
cell.accessoryView = [[UISwitch alloc] init];
2. UITableViewCell的背景設置
- 背景設置需要UITableViewCell的兩個屬性:backgroundView 和 selectedBackgroundView(選中時背景的顏色)。
- backgroundView背景顏色的設置方法 (間接設置)
UIView *bgView = [[UIView alloc] init];
bgView.backgroundColor = [UIColor blueColor];
cell.backgroundView = bgView;
- selectedBackgroundView背景顏色的設置方法(間接設置)
UIView *selectedbgView = [[UIView alloc] init];
selectedbgView.backgroundColor = [UIColor orangeColor];
cell.selectedBackgroundView = selectedbgView;
- backgroundView背景圖片的設置方法(背景view不用設置尺寸,backgroundView的優先級大於backgroundcolor)
UIImageView *bgView = [[UIImageView alloc] init];
bgView.image = [UIImage imageNamed:<#(nonnull NSString *)#> ];
cell.backgroundView = bgView;
3. UITableViewCell的contentView
- 下默認有3個子視圖
- 其中2個是UILabel(通過UITableViewCell的textLabel和detailTextLabel屬性訪問)
- 第3個是UIImageView(通過UITableViewCell的imageView屬性訪問)
- UITableViewCell還有一個UITableViewCellStyle屬性,用於決定使用contentView的哪些子視圖,以及這些子視圖在contentView中的位置。
4. UITableViewCell結構
六、UITableView的常見屬性
- separatorColor(分割線的顏色)
self.tableView.separatorColor = [UIColor colorWithRed:0 green:255/255.0 blue:255/255.0 alpha:200/255.0];
- separatorStyle(分割線的格式), 是一個枚舉常量,有三種:
self.tableView.separatorStyle = UITableViewCellSeparatorStyleSingleLineEtched
// UITableViewCellSeparatorStyleNone,
// UITableViewCellSeparatorStyleSingleLine,
// UITableViewCellSeparatorStyleSingleLineEtched
- 顯示錶格的頭部控件(直接顯示錶格的最頂部)(一般放廣告,標誌欄)
self.tableView.tableHeaderView = [[UISwitch alloc]init];
- 顯示錶格的尾部控件(直接顯示錶格的最底部)(一般放 “加載更多”)
self.tableView.tableFooterView = [UIButton buttonWithType:UIButtonTypeContactAdd];
七、UITableView的性能優化*
1. Cell的重用原理
UITableView中的單元格cell是在顯示到用戶可視區域後創建的,那麼如果用戶往下滾動就會繼續創建顯示在屏幕上的單元格,如果用戶向上滾動返回到查看過的內容時同樣會重新創建之前已經創建過的單元格。如此一來即使UITableView的內容不是太多,如果用戶反覆的上下滾動,內存也會瞬間飆升,更何況很多時候UITableView的內容是很多的(例如微博展示列表,基本向下滾動是沒有底限的)。
重用原理:當滾動列表時,部分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對象
2. 代碼實現
// 每當有一個cell進入視野範圍內,就會調用
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// 1.定義一個cell的標識,static修飾局部變量:可以保證局部變量之分配一次存儲空間(只初始化一次)
static NSString *ID = @"mycell";
// 2.通過一個標識去緩存池中尋找循環利用的cell
// dequeue:出列(查找)
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];
// 3.如果緩存池中沒有可循環利用的cell
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:ID];
}
// 4.設置cell的屬性...
return cell;
}
八、UITableView簡單的應用
1. 多組汽車品牌展示(模型中嵌套模型)
重點是掌握模型中嵌套模型的設計理念
結構框架
代碼實現
// ViewController.m
#import "ViewController.h"
#import "CarGroup.h"
#import "Car.h"
@interface ViewController ()<UITableViewDataSource, UITableViewDelegate>
@property (weak, nonatomic) IBOutlet UITableView *tableView;
@property (strong, nonatomic) NSArray *groups;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.tableView.dataSource = self;
}
- (NSArray *)groups
{
if (_groups == nil) {
// 初始化
// 1.獲得plist的全路徑
NSString *path = [[NSBundle mainBundle] pathForResource:@"cars_total.plist" ofType:nil];
// 2.加載數組
NSArray *dictArray = [NSArray arrayWithContentsOfFile:path];
// 3.將dictArray裏面的所有字典轉成模型對象,放到新的數組中
NSMutableArray *groupsArray = [NSMutableArray array];
for (NSDictionary *dict in dictArray) {
// 3.1.創建模型對象
CarGroup *group = [CarGroup groupWithDict:dict];
// 3.2.添加模型對象到數組中
[groupsArray addObject:group];
}
// 4.賦值
_groups = groupsArray;
}
return _groups;
}
#pragma mark - 數據源方法
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView{
return self.groups.count;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
CarGroup *group = self.groups[section];
return group.cars.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
static NSString *ID = @"car";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];
if(cell == nil){
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:ID];
}
CarGroup *group = self.groups[indexPath.section];
Car *car = group.cars[indexPath.row];
cell.imageView.image = [UIImage imageNamed:car.icon];
cell.textLabel.text = car.name;
return cell;
}
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section{
CarGroup *group = self.groups[section];
return group.title;
}
// 返回右邊索引條顯示的字符串數據
- (NSArray<NSString *> *)sectionIndexTitlesForTableView:(UITableView *)tableView{
return [self.groups valueForKeyPath:@"title"];
}
- (BOOL)prefersStatusBarHidden{
return YES;
}
@end
// CarGroup.h
#import <Foundation/Foundation.h>
@interface CarGroup : NSObject
// 這組的標題
@property (copy, nonatomic) NSString *title;
// 存放所有汽車的品牌(裏面裝的都是Car模型)
@property (strong, nonatomic) NSArray *cars;
+ (instancetype)groupWithDict:(NSDictionary *)dict;
- (instancetype)initWithDict:(NSDictionary *)dict;
@end
// CarGroup.m
#import "CarGroup.h"
#import "Car.h"
@implementation CarGroup
+ (instancetype)groupWithDict:(NSDictionary *)dict{
return [[CarGroup alloc] initWithDict:dict];
}
- (instancetype)initWithDict:(NSDictionary *)dict{
if(self = [super init] ){
// 賦值標題
self.title = dict[@"title"];
// 取出這組裏面的字典數組,再轉成模型數組
NSArray *dictArray = dict[@"cars"];
NSMutableArray *carArray = [[NSMutableArray alloc] init];
for(NSDictionary *dict in dictArray){
Car *car = [Car carWithDict:dict];
[carArray addObject:car];
}
self.cars = carArray;
}
return self;
}
@end
// Car.h
#import <Foundation/Foundation.h>
@interface Car : NSObject
// 圖標
@property (copy, nonatomic) NSString *icon;
// 名稱
@property (copy, nonatomic) NSString *name;
+ (instancetype)carWithDict:(NSDictionary *)dict;
- (instancetype)initWithDict:(NSDictionary *)dict;
@end
// Car.m
#import "Car.h"
@implementation Car
+ (instancetype)carWithDict:(NSDictionary *)dict{
return [[Car alloc] initWithDict:dict];
}
- (instancetype)initWithDict:(NSDictionary *)dict{
if (self = [super init]) {
[self setValuesForKeysWithDictionary:dict];
}
return self;
}
@end
對模型中嵌套模型的理解
效果展示
2.數據刷新
數據更新,更新的是模型,MVC模式的設計方式。
結構框架
代碼實現
// ViewController.m
#import "ViewController.h"
#import "Hero.h"
@interface ViewController () <UITableViewDataSource, UITableViewDelegate>
@property (weak, nonatomic) IBOutlet UITableView *tableView;
@property (strong, nonatomic) NSArray *heros;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.tableView.dataSource = self;
self.tableView.delegate = self;
self.tableView.rowHeight = 55.0;
}
- (NSArray *)heros {
if(_heros == nil){
NSString *path = [[NSBundle mainBundle] pathForResource:@"heros.plist" ofType:nil];
NSArray *dictArray = [NSArray arrayWithContentsOfFile:path];
NSMutableArray *heroArray = [[NSMutableArray alloc] init];
for( NSDictionary *dict in dictArray){
Hero *hero = [Hero heroWithDict:dict];
[heroArray addObject:hero];
}
_heros = heroArray;
}
return _heros;
}
#pragma 數據源方法
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
return self.heros.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
static NSString *ID = @"hero";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];
if(cell == nil){
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:ID];
}
Hero *hero = self.heros[indexPath.row];
cell.imageView.image = [UIImage imageNamed:hero.icon];
cell.textLabel.text = hero.name;
cell.detailTextLabel.text = hero.intro;
return cell;
}
#pragma 代理方法
// 選中cell時執行
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
Hero *hero = self.heros[indexPath.row];
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"數據展示" message:nil preferredStyle:UIAlertControllerStyleAlert];
[alertController addTextFieldWithConfigurationHandler:^(UITextField * _Nonnull textField) {
textField.text = hero.name;
}];
UIAlertAction *cancerAction = [UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleCancel handler:nil];
UIAlertAction *okAction = [UIAlertAction actionWithTitle:@"確定" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
// 1.取得文本框最後的文字
NSString *name = alertController.textFields.firstObject.text;
// 2.修改模型數據
hero.name = name;
// 3.告訴tableView重新加載模型數據
// 全部刷新 [self.tableView reloadData];
// reloadData:tableView會向數據源重現請求數據
// 重新調用數據源的tableView:numberOfRowsInSection:獲得行數
// 重新調用數據源的tableView:cellForRowAtIndexPath:得知每一行顯示怎樣的cell
// 局部刷新
// NSIndexPath *path = [NSIndexPath indexPathForRow:<#(NSInteger)#> inSection:<#(NSInteger)#>];
[self.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationBottom];
NSLog(@"%@", name);
}];
[alertController addAction:cancerAction];
[alertController addAction:okAction];
[self presentViewController:alertController animated:YES completion:nil];
}
// 取消選中時執行
- (void)tableView:(UITableView *)tableView didDeselectRowAtIndexPath:(NSIndexPath *)indexPath{
}
- (BOOL)prefersStatusBarHidden{
return YES;
}
@end
// Hero.h
#import <Foundation/Foundation.h>
@interface Hero : NSObject
@property (copy, nonatomic) NSString *icon;
@property (copy, nonatomic) NSString *intro;
@property (copy, nonatomic) NSString *name;
+ (instancetype)heroWithDict:(NSDictionary *)dict;
- (instancetype)initWithDict:(NSDictionary *)dict;
@end
// Hero.m
#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