你好,歡迎來到IOS教程網

 Ios教程網 >> IOS編程開發 >> IOS開發綜合 >> iOS開發 - 最常用控件 UITableView詳解

iOS開發 - 最常用控件 UITableView詳解

編輯:IOS開發綜合

UITableView掌握點

設置UITableView的dataSource、delegate
UITableView多組數據和單組數據的展示
UITableViewCell的常見屬性
UITableView的性能優化(cell的循環利用)
自定義Cell

如何展示數據

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

UITableViewUITableViewDataSource

@property (nonatomic, assign) id  dataSource;
//調用數據源的下面方法得知一共有多少組數據
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView;

//調用數據源的下面方法得知每一組有多少行數據
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section;

//調用數據源的下面方法得知每一行顯示什麼內容
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;

MVC設計思想

MVC是一種設計思想,貫穿於整個iOS開發中,需要積累一定的項目經驗,才能深刻體會其中的含義和好處

MVC中的三個角色

//M:Model,模型數據
//V:View,視圖(界面)
//C:Control,控制中心

MVC的幾個明顯的特征和體現:
View上面顯示什麼東西,取決於Model
只要Model數據改了,View的顯示狀態會跟著更改
Control負責初始化Model,並將Model傳遞給View去解析展示

UITableViewCell簡介

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

UITableViewCell內部有個默認的子視圖:contentView,contentView是UITableViewCell所顯示內容的父視圖,可顯示一些輔助指示視圖

輔助指示視圖的作用是顯示一個表示動作的圖標,可以通過設置UITableViewCell的accessoryType來顯示,默認是UITableViewCellAccessoryNone(不顯示輔助指示視圖),其他值如下:

UITableViewCellAccessoryDisclosureIndicator
UITableViewCellAccessoryDetailDisclosureButton
UITableViewCellAccessoryCheckmark

還可以通過cell的accessoryView屬性來自定義輔助指示視圖(比如往右邊放一個開關)

UITableViewCell的contentView

contentView下默認有3個子視圖
其中2個是UILabel(通過UITableViewCell的textLabel和detailTextLabel屬性訪問)
第3個是UIImageView(通過UITableViewCell的imageView屬性訪問)
UITableViewCell還有一個UITableViewCellStyle屬性,用於決定使用contentView的哪些子視圖,以及這些子視圖在contentView中的位置
這裡寫圖片描述

UITableViewCell結構

這裡寫圖片描述

Cell的重用原理

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對象

Cell的重用代碼

- (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;
}

使用xib封裝一個view的步驟

新建一個xib文件描述一個view的內部結構(假設叫做XXXCell.xib) 新建一個自定義的類 (自定義類需要繼承自系統自帶的view, 繼承自哪個類, 取決於xib根對象的Class) 新建類的類名最好跟xib的文件名保持一致(比如類名就叫做XXXTgCell) 將xib中的控件 和 自定義類的.m文件 進行連線 提供一個類方法返回一個創建好的自定義view(屏蔽從xib加載的過程) 提供一個模型屬性讓外界傳遞模型數據 重寫模型屬性的setter方法,在這裡將模型數據展示到對應的子控件上面

Delegate的使用場合

對象A內部發生了一些事情,想通知對象B
對象B想監聽對象A內部發生了什麼事情
對象A想在自己的方法內部調用對象B的某個方法,並且對象A不能對對象B有耦合依賴
對象A想傳遞數據給對象B
……
以上情況,結果都一樣:對象B是對象A的代理(delegate)

先搞清楚誰是誰的代理(delegate)

定義代理協議,協議名稱的命名規范:控件類名 + Delegate

定義代理方法
代理方法一般都定義為@optional
代理方法名都以控件名開頭
代理方法至少有1個參數,將控件本身傳遞出去

設置代理(delegate)對象 (比如myView.delegate = xxxx;)
代理對象遵守協議
代理對象實現協議裡面該實現的方法

在恰當的時刻調用代理對象(delegate)的代理方法,通知代理發生了什麼事情
(在調用之前判斷代理是否實現了該代理方法)

通過代碼自定義cell(cell的高度不一致)

1.新建一個繼承自UITableViewCell的類
2.重寫initWithStyle:reuseIdentifier:方法
添加所有需要顯示的子控件(不需要設置子控件的數據和frame, 子控件要添加到contentView中)
進行子控件一次性的屬性設置(有些屬性只需要設置一次, 比如字體\固定的圖片)
3.提供2個模型
數據模型: 存放文字數據\圖片數據
frame模型: 存放數據模型\所有子控件的frame\cell的高度
4.cell擁有一個frame模型(不要直接擁有數據模型)
5.重寫frame模型屬性的setter方法: 在這個方法中設置子控件的顯示數據和frame
6.frame模型數據的初始化已經采取懶加載的方式(每一個cell對應的frame模型數據只加載一次)

UITableView實例一: 城市列表的展示

數據源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

效果圖:
這裡寫圖片描述

UITableView實例二: 單組數據模型展示

數據源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

效果圖:
這裡寫圖片描述這裡寫圖片描述

  1. 上一頁:
  2. 下一頁:
蘋果刷機越獄教程| IOS教程問題解答| IOS技巧綜合| IOS7技巧| IOS8教程
Copyright © Ios教程網 All Rights Reserved