1. 程式人生 > >iOS控制元件——UITableView詳解

iOS控制元件——UITableView詳解

iOS開發中經常會用到UITableView,我們平時使用的軟體中到處都可以看到它,比如微信、QQ、微博等軟體基本上隨處都是UITableView。最主要到還有iOS設定。

一  基本介紹

UITableView有兩種Style:UITableViewStylePlain和UITableViewStyleGrouped。從名字上可以看出:一個是普通樣式的,另一個是分組樣式的。具體上怎樣,可以看一下下面的圖片。

普通樣式(不分組)                                                      分組樣式:

            

UITableView只有行沒有列,每一行都是一個UITableViewCell,如果我們檢視UITableViewCell的宣告檔案可以發現在內部有一個UIView控制元件(contentView,作為其他元素的父控制元件)、兩個UILable控制元件(textLabel   detailTextLabel)、一個UIImage控制元件(imageView),分別用於容器、顯示內容、詳情和圖片。這些控制元件並不一定要全部顯示,可以根據需要設定。

typedef NS_ENUM(NSInteger, UITableViewCellStyle) {
    UITableViewCellStyleDefault,	// Simple cell with text label and optional image view (behavior of UITableViewCell in iPhoneOS 2.x)
    UITableViewCellStyleValue1,		// Left aligned label on left and right aligned label on right with blue text (Used in Settings)
    UITableViewCellStyleValue2,		// Right aligned label on left with blue text and left aligned label on right (Used in Phone/Contacts)
    UITableViewCellStyleSubtitle	// Left aligned label on top and left aligned label on bottom with gray text (Used in iPod).
};
這是UITalbeViewCell的四種style。

二  資料來源

UITableView需要一個數據源(dataSource)來顯示資料,UITableView會向資料來源查詢一共有多少行資料以及每一行顯示什麼資料等。沒有設定資料來源的UITableView只是個空殼。凡是遵守UITableViewDataSource協議的OC物件,都可以是UITableView的資料來源。

首先將UITableView對資料來源和View controller相連。如圖所示:


並且讓這個ViewController這個類實現UITableViewDataSource協議。

#import <UIKit/UIKit.h>

@interface ViewController : UIViewController <UITableViewDataSource,UITableViewDelegate>

@end

先看一下UITableViewDataSource協議:

@protocol UITableViewDataSource<NSObject>

@required  //必須要實現的

第section分割槽一共有多少行
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section;

//建立第section分割槽第row行的UITableViewCell物件(indexPath包含了section和row)
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;

@optional   //可選擇實現的
// 一共有多少個分割槽
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView;    

//第section分割槽的頭部標題
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section;

//第section分割槽的底部標題
- (NSString *)tableView:(UITableView *)tableView titleForFooterInSection:(NSInteger)section;

//某一行是否可以編輯(刪除)
- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath;

//某一行是否可以移動來進行重新排序
- (BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath;

UITableView右邊的索引欄的內容
- (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView;

這些只是UITableView的顯示上面的設定,如果要做一些操作僅這些是不夠的。。。

三  代理(delegate)

通常都要為UITableView設定代理物件(delegate),以便在UITableView觸發一下事件時做出相應的處理,比如選中了某一行。凡是遵守了UITableViewDelegate協議的OC物件,都可以是UITableView的代理物件。一般會讓控制器充當UITableViewdataSourcedelegate。

同樣需要將UITableView的delegate與UIViewController關聯起來。

看一些UITableViewDelegate協議的一些常用方法。

@protocol UITableViewDelegate<NSObject, UIScrollViewDelegate>

@optional
//選中了UITableView的某一行
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath

//某一行的高度
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath

//第section分割槽頭部的高度
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section

//第section分割槽尾部的高度
- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section

//第section分割槽頭部顯示的檢視
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section

//第section分割槽尾部顯示的檢視
- (UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section

//設定每一行的等級縮排(數字越小,等級越高)
- (NSInteger)tableView:(UITableView *)tableView indentationLevelForRowAtIndexPath:(NSIndexPath *)indexPath

四 程式碼示例

先看一下最基本的UITableView。


#pragma mark 這一組裡面有多少行
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
    return 9;
}

#pragma mark 返回第indexPath這行對應的內容
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    /*
      四種style,分別是
             Default : 不顯示detailTextLabel
             Value1 : 在右邊顯示detailTextLabel
             Value2 : 不顯示圖片,顯示detailTextLabel
             Subtitle : 在底部顯示detailTextLabel
     */
    UITableViewCell *cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:nil];
    cell.textLabel.text = [NSString stringWithFormat:@"金克斯-%ld", indexPath.row];
    return cell;
}

通過設定:UITableViewCell的detailTextLabel和imgName的兩個屬性。


#pragma mark 返回第indexPath這行對應的內容
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    UITableViewCell *cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:nil];
    cell.textLabel.text = [NSString stringWithFormat:@"金克斯-%ld", indexPath.row];
    // 設定詳情文字
    cell.detailTextLabel.text = [NSString stringWithFormat:@"英雄-%ld非常好玩!!!!!", indexPath.row];
    
    // 設定圖片
    NSString *imgName = @"jinkesi.png";//[NSString stringWithFormat:@"00%d.png", indexPath.row + 1];
    cell.imageView.image = [UIImage imageNamed:imgName];   
    return cell;
}
上面的圖片中等內容看起來不是太好看。並且程式碼資料也有限制。我們做一些修改。
#import "ViewController.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    _rowLanguage = [[NSMutableArray alloc] initWithObjects:NSLocalizedString(@"str_language_itemTitle_zh", nil)
                       ,NSLocalizedString(@"str_language_itemTitle_en", nil)
                       ,NSLocalizedString(@"str_language_itemTitle_ar", nil)
                       ,NSLocalizedString(@"str_language_itemTitle_de", nil)
                       ,NSLocalizedString(@"str_language_itemTitle_es", nil)
                       ,NSLocalizedString(@"str_language_itemTitle_fa", nil)
                       ,NSLocalizedString(@"str_language_itemTitle_fr", nil)
                       ,NSLocalizedString(@"str_language_itemTitle_it", nil)
                       ,NSLocalizedString(@"str_language_itemTitle_pt", nil)
                       ,NSLocalizedString(@"str_language_itemTitle_ru", nil)
                       ,NSLocalizedString(@"str_language_itemTitle_th", nil)
                       ,nil];
}

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView{
    return 1;
}

#pragma mark 這一組裡面有多少行
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
    return 9;
}

#pragma mark 返回第indexPath這行對應的內容
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    UITableViewCell *cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:nil];
    cell.textLabel.text = [_rowLanguage objectAtIndex:indexPath.row];
                           
    return cell;
}

現在雖然內容改起來水方便了點,但還是有一定的問題。不易於新增照片。

將資料使用模型封裝資料。建立模型類Heros

Heros.hs檔案

#import <Foundation/Foundation.h>

@interface Heros : NSObject

@property (nonatomic, copy) NSString *name; //名字

@property (nonatomic, copy) NSString *icon;//圖片

@property (nonatomic, copy) NSString *desc;//描述

+(Heros *)initWithName:(NSString *)name andicon:(NSString *)icon anddees:(NSString *)desc;

@end
Heros.m檔案
#import "Heros.h"

@implementation Heros
//初始化方法
+(Heros *)initWithName:(NSString *)name andicon:(NSString *)icon anddees:(NSString *)desc{
    Heros *hero = [[Heros alloc]init];
    hero.name = name;
    hero.icon = icon;
    hero.desc = desc;
    return hero;
}
@end
ViewController.m檔案
#import "ViewController.h"
#import "Heros.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];   
    
    Heros *heros1 = [Heros initWithName:@"薇恩" andicon:@"vn.png" anddees:@"讓我們來獵殺那些陷入黑暗中的人吧。"];
    Heros *heros2 = [Heros initWithName:@"男槍" andicon:@"nanqian.png" anddees:@"我與死亡同行。"];
    Heros *heros3 = [Heros initWithName:@"賞金" andicon:@"shangjin.png" anddees:@"好運,不會眷顧傻瓜。"];
    Heros *heros4 = [Heros initWithName:@"奎因" andicon:@"kuiyin.png" anddees:@"正義,展翅翱翔。"];
    Heros *heros5 = [Heros initWithName:@"金克斯" andicon:@"jinkesi.png" anddees:@"規則,就是用來打破的。"];
    Heros *heros6 = [Heros initWithName:@"奧巴馬" andicon:@"luxian.png" anddees:@"人終有一死,可有些人需要一點小小的幫助。"];
    Heros *heros7 = [Heros initWithName:@"希維爾" andicon:@"xiweier.png" anddees:@"你有麻煩了,我有錢賺。"];
    Heros *heros8 = [Heros initWithName:@"伊澤瑞爾" andicon:@"yizeruier.png" anddees:@"是時候表演真正的技術啦。"];
    Heros *heros9 = [Heros initWithName:@"復仇之矛" andicon:@"fuchouzhi.png" anddees:@"我們的誓約,以血為契。"];
    _arrayHeros = [[NSMutableArray alloc]initWithObjects:heros1,heros2,heros3,heros4,heros5,heros6,heros7,heros8,heros9, nil];
}

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView{
    return 1;
}

#pragma mark 這一組裡面有多少行
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
    return _arrayHeros.count;
}

#pragma mark 返回第indexPath這行對應的內容
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{

    UITableViewCell *cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:nil];
    Heros *he = [_arrayHeros objectAtIndex:indexPath.row];
    cell.textLabel.text = he.name;  //設定名字
    cell.detailTextLabel.text = he.desc;  //設定描述
    cell.imageView.image =[UIImage imageNamed:he.icon]; //設定圖片
    return cell;
}

#pragma mark - 代理方法
#pragma mark 返回indexPath這行cell的高度
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    return 45;
}
#pragma mark 選中一行響應事件
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{


}
@end

這樣就可以將資料和UITableView的設定分離開來,以後再加其他英雄也不用動下面的程式碼,只需要改_arrayHeros就可以了。

五 UITableViewCell

UITableView的每一行都是一個UITableViewCell,通過dataSourcetableView:cellForRowAtIndexPath:方法來初始化每一行

UITableViewCellUIView的子類,內部有個預設的子檢視:contentViewcontentViewUITableViewCell所顯示內容的父檢視,並負責顯示一些輔助指示檢視。輔助指示檢視的作用是顯示一個表示動作的圖示,可以通過設定UITableViewCellaccessoryType來顯示,預設是UITableViewCellAccessoryNone。

typedef NS_ENUM(NSInteger, UITableViewCellAccessoryType) {
    UITableViewCellAccessoryNone,                                         // don't show any accessory view
    UITableViewCellAccessoryDisclosureIndicator,                         // regular chevron. doesn't track
    UITableViewCellAccessoryDetailDisclosureButton __TVOS_PROHIBITED,  // info button w/ chevron. tracks
    UITableViewCellAccessoryCheckmark,                                 // checkmark. doesn't track
    UITableViewCellAccessoryDetailButton NS_ENUM_AVAILABLE_IOS(7_0)  __TVOS_PROHIBITED // info button. tracks
};

檢視對應顯示。

#pragma mark 返回第indexPath這行對應的內容
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{

    UITableViewCell *cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:nil];
    Heros *he = [_arrayHeros objectAtIndex:indexPath.row];
    cell.textLabel.text = he.name;  //設定名字
    cell.detailTextLabel.text = he.desc;  //設定描述
    cell.imageView.image =[UIImage imageNamed:he.icon]; //設定圖片
    if(indexPath.row == 0 ||indexPath.row ==1)
        cell.accessoryType = UITableViewCellAccessoryNone;
    else if (indexPath.row == 2 || indexPath.row == 3)
        cell.accessoryType = UITableViewCellAccessoryCheckmark;
    else if (indexPath.row == 4 || indexPath.row == 5)
        cell.accessoryType = UITableViewCellAccessoryDetailButton;
    else if(indexPath.row == 6 || indexPath.row == 7)
        cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
    else
        cell.accessoryType = UITableViewCellAccessoryDetailDisclosureButton;//有倆圖示。
    return cell;
}

設定cell每行的背景顏色,通過cell的backgroundColor設定。
#pragma mark 返回第indexPath這行對應的內容
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{

    UITableViewCell *cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:nil];
    Heros *he = [_arrayHeros objectAtIndex:indexPath.row];
    cell.textLabel.text = he.name;  //設定名字
    cell.detailTextLabel.text = he.desc;  //設定描述
    cell.imageView.image =[UIImage imageNamed:he.icon]; //設定圖片
    if(indexPath.row == 0 ||indexPath.row ==1){
        cell.accessoryType = UITableViewCellAccessoryNone;
        cell.backgroundColor = [UIColor blueColor];
    }
    else if (indexPath.row == 2 || indexPath.row == 3){
        cell.accessoryType = UITableViewCellAccessoryCheckmark;
        cell.backgroundColor = [UIColor yellowColor];
    }
    else if (indexPath.row == 4 || indexPath.row == 5){
        cell.accessoryType = UITableViewCellAccessoryDetailButton;
        cell.backgroundColor = [UIColor redColor];
    }
    else if(indexPath.row == 6 || indexPath.row == 7){
        cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
        cell.backgroundColor = [UIColor purpleColor];
    }
    else{
        cell.accessoryType = UITableViewCellAccessoryDetailDisclosureButton;//有倆圖示。
        cell.backgroundColor = [UIColor brownColor];
    }
    return cell;
}

設定背景

backgroundView      cell.backgroundView

設定被選中時的背景檢視

selectedBackgroundView    cell.selectedBackgroundView

selectionStyle屬性可設定UITableViewCell被選中時的背景顏色:

UITableViewCellSelectionStyleNone  沒有顏色

UITableViewCellSelectionStyleBlue  藍色(預設)

UITableViewCellSelectionStyleGray  灰色

UITableViewCell還有許多設定屬性,需要的可以看一下原始碼。


設定每行的高度

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    return 40+3*index.row;
}

效果圖:


設定內容縮排

- (NSInteger)tableView:(UITableView *)tableView indentationLevelForRowAtIndexPath:(NSIndexPath *)indexPath{
    return indexPath.row;
}

效果:


選中一行響應事件,我們設定讓其彈一個對話方塊,顯示點選行的名字。

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    UIAlertView *alert = [[UIAlertView alloc] initWithTitle:nil
                                                    message:nil
                                                   delegate:self
                                          cancelButtonTitle:@"Cancel"
                                          otherButtonTitles:@"OK",nil];
    alert.title = [[_arrayHeros objectAtIndex:indexPath.row] name];
    [alert show];//顯示對話方塊
}

有時我們會想更改內容,這就需要重新整理我們的UITableView了。
[_tableView reloadData]; // 整體重新整理(每一行都會重新整理)
//只重新整理某行。
[_tableView reloadRowsAtIndexPaths:indexPath withRowAnimation:UITableViewRowAnimationLeft];
我們只需要在相遇重新整理的地方呼叫這個就可以重新載入UITableViewCell了。

六  分組UITableView

   

右側索引條,可以直接調整到對應位置。

程式碼如下:

HeroGroup.h

#import <Foundation/Foundation.h>

@interface HeroGroup : NSObject
//頭部
@property (nonatomic, copy) NSString *header;
//尾部
@property (nonatomic, copy) NSString *footer;
//名字
@property (nonatomic, copy) NSArray *names;
//圖示
@property (nonatomic, copy) NSArray *icons;

+ (HeroGroup *)heroGroupWithHeader:(NSString *)header footer:(NSString *)footer names:(NSArray *)names icons:(NSArray *)icons;

@end
HeroGroup.m檔案
#import "HeroGroup.h"

@implementation HeroGroup


+ (HeroGroup *)heroGroupWithHeader:(NSString *)header footer:(NSString *)footer names:(NSArray *)names icons:(NSArray *)icons{
    HeroGroup *hg = [[HeroGroup alloc] init];
    hg.header = header;
    hg.footer = footer;
    hg.names = names;
    hg.icons = icons;
    return hg;
}
@end

GroupTabViewController.h檔案
#import <UIKit/UIKit.h>

@interface GroupTabViewController : UIViewController <UITableViewDataSource, UITableViewDelegate>{
    UITableView *_tableView;
    NSArray *_heroAry;
}

@end

GroupTabViewController.m檔案
#import "GroupTabViewController.h"
#import "HeroGroup.h"

@interface GroupTabViewController ()

@end

@implementation GroupTabViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    CGRect frame = self.view.bounds;
    frame.origin.y = 20; //設定y座標起始位置,否則會和狀態列重合。
    _tableView = [[UITableView alloc]initWithFrame:frame style:UITableViewStyleGrouped];
    _tableView.dataSource = self;
    _tableView.delegate = self;
    [self.view addSubview:_tableView];
    [self initData];
}

#pragma mark 初始化資料。
- (void)initData {
    // HeroGroup hg = [HeroGroup heroGroupWithHeader:(NSString *)header footer:(NSString *)footer names:(NSArray *)names icons:(NSArray *)icons;
    HeroGroup *top = [HeroGroup heroGroupWithHeader:@"上單" footer:@"上單很猛" names:@[@"劍姬",@"武器",@"人馬"] icons:@[@"jianji.png",@"wuqi.png",@"renma.png"]];
    HeroGroup *jungle = [HeroGroup heroGroupWithHeader:@"打野" footer:@"打野帶動節奏" names:@[@"盲僧",@"蜘蛛",@"挖掘機"] icons:@[@"mangseng.png",@"zhizhu.png",@"wajueji.png"]];
    HeroGroup *mid = [HeroGroup heroGroupWithHeader:@"中單" footer:@"中單爆發" names:@[@"妖姬",@"卡牌",@"發條"] icons:@[@"yaoji.png",@"kapai.png",@"fatiao.png"]];
    HeroGroup *adc = [HeroGroup heroGroupWithHeader:@"ADC" footer:@"ADC持續輸出" names:@[@"薇恩",@"金克絲"] icons:@[@"vn.png",@"jinkesi.png"]];
    HeroGroup *support = [HeroGroup heroGroupWithHeader:@"輔助" footer:@"輔助很肉" names:@[@"牛頭",@"石頭人"] icons:@[@"niutou.png",@"shitouren.png"]];
    _heroAry = @[top,jungle,mid,adc,support];
}

#pragma mark 返回分組數
-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView{
    return _heroAry.count;
}
#pragma mark 返回每組行數
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
    HeroGroup *hg = _heroAry[section];
    return hg.names.count;
}

#pragma mark 返回每行的單元格
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
    HeroGroup *hg = _heroAry[indexPath.section];
    UITableViewCell *cell=[[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nil];
    cell.textLabel.text=[hg.names objectAtIndex:indexPath.row];
    cell.imageView.image =[UIImage imageNamed:hg.icons[indexPath.row]];
    return cell;
}

#pragma mark 返回每組頭標題名稱
-(NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section{
    HeroGroup *hg = _heroAry[section];
    return hg.header;
}

#pragma mark 返回每組尾部說明
-(NSString *)tableView:(UITableView *)tableView titleForFooterInSection:(NSInteger)section{
    HeroGroup *hg = _heroAry[section];
    return hg.footer;
}

#pragma mark 返回每組標題索引
-(NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView{
    NSMutableArray *indexs=[[NSMutableArray alloc]init];
    for(HeroGroup *hg in _heroAry){
        [indexs addObject:hg.header];
    }
    return indexs;
}

#pragma mark - 代理方法
#pragma mark 設定分組頭部內容高度
-(CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section{
    return 40;
}

#pragma mark 設定每行高度(每行高度可以不一樣)
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
    return 45;
}

#pragma mark 設定尾部說明內容高度
-(CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section{
    return 40;
}
@end