UITableView深刻解析 (一) 待續中...

# 一.目錄ios

  • 此爲第一部分:緩存

  • ### 1. - UITableView的基本使用方式性能優化

  • ### 2. - UITableView的兩種顯示方式app

  • ### 3. - UITableView之因此能滾動的緣由ide

  • ### 4. - 3個數據源方法工具

  • ### 5. - 設置行高性能

  • ### 6. - UITableView的常見屬性優化

  • ### 7. - UITableViewCell的常見屬性ui

  • ### 8. - UITableViewCell的重用this

  • ### 9. - 監聽UITableView行的選中事件

# 二. 解析

### 1. - UITableView的基本使用方式
**1.1UITableView:總體上來講,就是一個表格控件,在ios的實際開發過程當中, UITableView的使用至關普遍,是很是重要的一個控件,因此必定要引發重視,特別是對於新手來講;**
**1.2. UITableView就是表格控件**
- UITableView由行和列來組成
- UITableView中每行只有1列
- 每行中保存的都是一個UITableViewCell對象
**1.3. UITableView通常用來展現表格數據、能夠滾動(繼承自UIScrollView)、性能極佳**
若是沒有UITableView, 實現相似的功能只能本身經過循環建立控件, 性能差 

### 2. - UITableView的兩種顯示方式 

1> Plain, 簡明樣式(不分組的樣式)
2> Grouped, 分組的樣式

- 不管分組樣式仍是不分組樣式, 其實都能顯示分組數據、顯示組標題、組描述。

設置方法: Main.storyboard—>選擇控件UITableView —>屬性 —>Style—> Plain / Grouped 


 ### 3. - UITableView之因此能滾動的緣由

從繼承關係上來講,UITableView繼承自UIScrollView,這是它之因此能滾動的根本緣由, 以下圖:

### 4. -UITableView的 3個數據源方法

  說到UITableView,首先咱們須要知道的就是,UITableView有兩個必需要實現的數據源方法, 和多個可選實現的數據源方法

  咱們先來看UITableView的數據源方法底層

-UITableViewDataSource 的底層實現:

// this protocol represents the data model object. as such, it supplies no information about appearance (including the cells)
@protocol UITableViewDataSource<NSObject>
@required
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section;
// Row display. Implementers should *always* try to reuse cells by setting each cell's reuseIdentifier and querying for available reusable cells with dequeueReusableCellWithIdentifier:
// Cell gets various attributes set automatically based on table (separators) and data source (accessory views, editing controls)
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;
@optional
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView;              // Default is 1 if not implemented

從UITableViewDataSource數據源方法的底層,咱們能夠看出,有兩個數據源方法,是咱們必需要實現的,那就是:返回行,和返回cell的方法:以下

// 返回對應組有多少行
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section;
// 根據索引,返回每組每行的單元格cell到底長什麼樣
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;

而對於,返回組的數量的數據源方法來講,是可選實現的方法, 默認不實現的狀況下,表示組數爲1;若是有多個組,就必需要有這個方法:

// 返回有多少組
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView;

注意:在實現數據源方法以前,先要找數據源方法的數據源代理對象,通常咱們都找當前控制器:

 - (void)viewDidLoad {
     [super viewDidLoad];
     //設置控制器爲數據源對象
     self.tableView.dataSource = self;
 }

而後讓當前控制器遵照數據源協議:

@interface ViewController ()<UITableViewDataSource>


補充: UITableView組標題和組描述的數據源方法:

 - (nullable NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section;    // fixed font style. use custom view (UILabel) if you want something different      //組標題
 - (nullable NSString *)tableView:(UITableView *)tableView titleForFooterInSection:(NSInteger)section;   //組描述


### 5. - 設置行高

對於設置行高,通常狀況咱們有兩種,一種是統一設置行高,一種是預設行高,

統一設置行高:   主要用於cell行高都是同樣的狀況下

- (void)viewDidLoad {
    [super viewDidLoad];
    //設置控制器爲數據源對象
    self.tableView.dataSource = self;
    //設置統一的行高
    self.tableView.rowHeight = 70;
}

預設行高:   主要用於動態計算cell行高的狀況下:

- (void)viewDidLoad {
    [super viewDidLoad];
    //設置控制器爲數據源對象
    self.tableView.dataSource = self;
  
    //預設行高
    self.tableView.estimatedRowHeight = 300;
}

設置預估行高的意義?

設置預估行高,可讓程序加載效率高不少,一開始的時候只顯示首屏的幾行,而不是顯示全部的行數,只有須要的時候再加載調用; 預估行高越接近實際行高,越好,這樣性能越高!

 

### 6. - UITableView的常見屬性 

基本屬性解讀:  

右側屬性欄中屬性分爲三大類屬性:一、Table View 二、Scroll View 三、 View

下面只看Table View中的屬性:

Content:  Dynamic Prototypes 動態單元格

       Static Cells 靜態單元格

Prototype: Cells cell的行數

Style 樣式 : Plain 簡單樣式(未分組)

      Grouped 分組樣式

separator : 分隔線屬性

selection : 單前選中行

background : 背景色

上面是在Main.storyboard中的展現,而衆多的屬性,都是有源代碼的,這些也能夠在UITableView的頭文件中查看,

下面是我摘錄的部分UITableView的底層源代碼: (部分)  

NS_CLASS_AVAILABLE_IOS(2_0) @interface UITableView : UIScrollView <NSCoding>
- (instancetype)initWithFrame:(CGRect)frame style:(UITableViewStyle)style NS_DESIGNATED_INITIALIZER; // must specify style at creation. -initWithFrame: calls this with UITableViewStylePlain
- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder NS_DESIGNATED_INITIALIZER;
@property (nonatomic, readonly) UITableViewStyle style;
@property (nonatomic, weak, nullable) id <UITableViewDataSource> dataSource;
@property (nonatomic, weak, nullable) id <UITableViewDelegate> delegate;
@property (nonatomic) CGFloat rowHeight;             // will return the default value if unset
@property (nonatomic) CGFloat sectionHeaderHeight;   // will return the default value if unset
@property (nonatomic) CGFloat sectionFooterHeight;   // will return the default value if unset
@property (nonatomic) CGFloat estimatedRowHeight NS_AVAILABLE_IOS(7_0); // default is 0, which means there is no estimate
@property (nonatomic) CGFloat estimatedSectionHeaderHeight NS_AVAILABLE_IOS(7_0); // default is 0, which means there is no estimate
@property (nonatomic) CGFloat estimatedSectionFooterHeight NS_AVAILABLE_IOS(7_0); // default is 0, which means there is no estimate
@property (nonatomic) UIEdgeInsets separatorInset NS_AVAILABLE_IOS(7_0) UI_APPEARANCE_SELECTOR; // allows customization of the frame of cell separators
@property (nonatomic, strong, nullable) UIView *backgroundView NS_AVAILABLE_IOS(3_2); // the background view will be automatically resized to track the size of the table view.  this will be placed as a subview of the table view behind all cells and headers/footers.  default may be non-nil for some devices.

源文件前半部分代碼如上,咱們能夠看到,越靠前的屬性越爲重要:

第6行 UITableViewStyle 枚舉類型的 style 屬性,是用於設置UITableView的style樣式,源文件中枚舉類型以下:

 typedef NS_ENUM(NSInteger, UITableViewStyle) {
     UITableViewStylePlain,                         // regular table view
     UITableViewStyleGrouped     // preferences style table view
 };

經過查看底層,咱們能夠看出,越是靠前的屬性,就越是重要, 

能夠看出tableView的樣式和工具欄上的同樣是兩種,Plain和Grouped。

第7行是提供了一個設置數據源方法的屬性   上面已經有介紹了

第8行提供了一個tableView代理方法的屬性  

第9行爲row的高度,也就是行高   

第10行爲頭View高度 ---組標題

第11行爲尾部View高度   ---組描述

>>>UITableView的部分常見屬性解析:

* rowHeight, 能夠統一設置全部行的高度  在viewdidload 裏面

若是每行的行高是一致的,就不要使用代理方法,直接設置UITableView的rowHeight屬性便可,這樣高效!

  見上面第6;

 

* separatorColor, 分隔線的顏色

* separatorStyle, 分割線的樣式

  //設置每行之間分割線的顏色和樣式
     self.tableView.separatorColor = [UIColor redColor];
     self.tableView.separatorStyle = UITableViewCellSeparatorStyleSingleLine;

* tableHeaderView, 通常能夠放廣告   是一個UIView類型  在最上面 也就是頭部

* tableFooterView, 通常能夠放加載更多  在最下面,也就是尾部

//設置tableview的header
    UIView *hearderVw = [[UIView alloc]init];
    hearderVw.backgroundColor = [UIColor redColor];
    hearderVw.alpha = 0.5; 
    //當一個控件要做爲tableview的header view來顯示的時候,此時設置的x,y,w都無效,只有高h有效
    hearderVw.frame = CGRectMake(0, 0, 0, 150);
    self.tableView.tableHeaderView = hearderVw;
    //設置tableview的footer view
    UIView *footerVw = [[UIView alloc]init];
    footerVw.backgroundColor = [UIColor blueColor];
    //當一個控件要做爲tableview的footer view來顯示的時候,此時設置的y,w都無效,只有高h和x有效
    footerVw.frame = CGRectMake(10, 0, 0, 200);
    self.tableView.tableFooterView = footerVw;


 ### 7. - UITableViewCell的常見屬性

  對於UITableViewCell來講,它的屬性很是多,我這裏列舉幾個最經常使用的: 

源代碼底層以下:

NS_CLASS_AVAILABLE_IOS(2_0) @interface UITableViewCell : UIView <NSCoding, UIGestureRecognizerDelegate>
// Designated initializer.  If the cell can be reused, you must pass in a reuse identifier.  You should use the same reuse identifier for all cells of the same form.  
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(nullable NSString *)reuseIdentifier NS_AVAILABLE_IOS(3_0) NS_DESIGNATED_INITIALIZER;
- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder NS_DESIGNATED_INITIALIZER;
// Content.  These properties provide direct access to the internal label and image views used by the table view cell.  These should be used instead of the content properties below.
@property (nonatomic, readonly, strong, nullable) UIImageView *imageView NS_AVAILABLE_IOS(3_0);   // default is nil.  image view will be created if necessary.
@property (nonatomic, readonly, strong, nullable) UILabel *textLabel NS_AVAILABLE_IOS(3_0);   // default is nil.  label will be created if necessary.
@property (nonatomic, readonly, strong, nullable) UILabel *detailTextLabel NS_AVAILABLE_IOS(3_0); // default is nil.  label will be created if necessary (and the current style supports a detail label).
// If you want to customize cells by simply adding additional views, you should add them to the content view so they will be positioned appropriately as the cell transitions into and out of editing mode.
@property (nonatomic, readonly, strong) UIView *contentView;
// Default is nil for cells in UITableViewStylePlain, and non-nil for UITableViewStyleGrouped. The 'backgroundView' will be added as a subview behind all other views.
@property (nonatomic, strong, nullable) UIView *backgroundView;


  • UITableViewCell的常見屬性: 

    * imageView   圖片框  如頭像                都是放在Cell contentView 裏面的

    * textLabel   上面的label 如英雄名字        都是放在Cell contentView 裏面的

    * detailTextLabel   下面的label  如英雄介紹

    * accessoryType     右側指示器  這是一個枚舉enmu集合.裏面有好幾種樣式,如小箭頭

    * accessoryView   這是一個view類型,能夠給任何的空間,如開關

    * backgroundColor , 設置單元格的背景顏色

    * backgroundView, 能夠利用這個屬性來設置單元格的背景圖片, 指定一個UIImageView就能夠了。

    * selectedBackgroundView , 當某行被選中的時候的背景。選中以後顯示的效果,


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

  • UITableViewCell內部有個默認的子視圖:contentView,contentView是UITableViewCell所顯示內容的父視圖,可顯示一些輔助指示視圖  見第14行代碼

  • 輔助指示視圖的做用是顯示一個表示動做的圖標,能夠經過設置UITableViewCell的accessoryType來顯示,默認是UITableViewCellAccessoryNone(不顯示輔助指示視圖),其餘值以下:

        

  • contentView下默認有3個子視圖  

  • 其中2個是UILabel(經過UITableViewCell的textLabel和detailTextLabel屬性訪問) 見第10 ,11行代碼

  • 第3個是UIImageView(經過UITableViewCell的imageView屬性訪問)

  • UITableViewCell還有一個UITableViewCellStyle屬性,用於決定使用contentView的哪些子視圖,以及這些子視圖在contentView中的位置

            

 

  • ### 8. - UITableViewCell的重用

  1. iOS設備的內存有限,若是用UITableView顯示成千上萬條數據,就須要成千上萬個UITableViewCell對象的話,那將會耗盡iOS設備的內存。要解決該問題,須要重用UITableViewCell對象

  2. 重用原理:當滾動列表時,部分UITableViewCell會移出窗口,UITableView會將窗口外的UITableViewCell放入一個對象池中,等待重用。當UITableView要求dataSource返回UITableViewCell時,dataSource會先查看這個對象池,若是池中有未使用的UITableViewCell,dataSource會用新的數據配置這個UITableViewCell,而後返回給UITableView,從新顯示到窗口中,從而避免建立新對象

      * 性能優化步驟:

      1> 經過一個標識符ID去緩存池中查找是否有對應的cell

      2> 若是有則取出來使用, 若是沒有, 則建立一個。
      3> 設置cell數據
      4> 代碼實現重用cell功能。

      5> 優化cell_id變量。(注意標識命名要規範)

 

  下面我總結一下用代碼建立cell的三種方式,重用ID的方式,是後面第2,3兩種方法,推薦使用第3個方法!

@implementation ViewController
static NSString *ID = @"cell_id」;
//這裏要申明一下ID,也就是緩存池建立的cell的標記符 在ViewController裏面申明這個ID是什麼!
//添加static 關鍵字, 這樣建立的ID會轉移保存到靜態區,這樣每次建立的ID和 常量」cell_id」 都不會被銷燬,  下一次建立的時候 直接調用,不會重複建立了;
    //2.建立一個單元格  有三種方式:以下  推薦使用第三種;
    // 2.1> 先從緩存池中嘗試去取一個單元格
    // 用來標識緩存池中的cell的id (可重用id)
    // static NSString *ID = @"cell_id";
    // 根據可重用ID, 在緩存池中查找對應的cell
    //方式1 :原始方式  方式1是每移除一個,就建立一個cell,會不停的建立,性能低,總有內存爆滿的時候,會崩潰
    UITableViewCell *cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:nil];
    //方式2: 如恰好一個 頁面有8個cell,一啓動會建立8個,當滾動一點點的時候,第一個沒有進緩存池,而第9個已經出來了,這個時候又多了一個cell,這種方式至少會建立9個,若是是ABAB型的,還會有第10個,也有點不妥,
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];
    if (cell == nil) {
        cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:ID];
    }
    //方式3:這種方式是最好的,如,也是ABAB類型的,到緩存池裏面找和建立cell是一步搞定,底層是直接就寫好了的, 但注意,這裏須要在啓動程序的時候註冊一個cell,也就是在viewDidLoad裏面,   推薦用這種!!
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ID forIndexPath:indexPath];
      //此時,當一個cell從屏幕滾出去之後,不會銷燬,而是放到了"緩存池"中;
#pragma mark ----viewDidLoad 設置數據源對象
- (void)viewDidLoad {
    [super viewDidLoad];
    //設置控制器爲數據源對象
    self.tableView.dataSource = self;
    //設置統一的行高
    self.tableView.rowHeight = 70;
    //註冊一個cell
    [self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:ID];
}
——>
這個是爲方法3註冊的一個cell,便於方式3調用; 要用這種方法,必需要先註冊一個cell 在viewDidload 裏面註冊,註冊就是告訴系統,咱們用的ID究竟是什麼類型的cell,



  • ### 9. - 監聽UITableView行的選中事件


  •  

     在UITableView中,咱們時常須要監聽cell的事件,來實現一些咱們自定義的屬性和方法,那麼怎樣監聽每一個cell的點擊事件 呢?

     這裏我列舉一些咱們經常使用的監聽方法: 

    1>* 經過代理來監聽,   可使用於cell的任何一種監聽場景

    很重要!!!   我這裏主要列舉兩個很是經常使用的方法,更多的方法,你們能夠在UITableViewDelegate方法中查看

    ** 選中某行: - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath

    ** 取消選中某行: - (void)tableView:(UITableView *)tableView didDeselectRowAtIndexPath:(NSIndexPath *)indexPath

     ——> 注意,這個方法名只有細微的差異,千萬不要錯了  

     代碼底層:

// Called after the user changes the selection.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath;
- (void)tableView:(UITableView *)tableView didDeselectRowAtIndexPath:(NSIndexPath *)indexPath NS_AVAILABLE_IOS(3_0);

2> 彈出UIAlertView、UIAlertController 來監聽,

    這個方法主要適用場景是:用於在點擊某一行cell,須要跳轉,或者增,刪,改,的時候
* 修改彈出對話框的樣式
alertView.alertViewStyle = UIAlertViewStylePlainTextInput;

* 根據索引獲取指定的某個文本框
[alertView textFieldAtIndex:0];

e.g:
[alertView textFieldAtIndex:0].text = hero.name;

 

* 經過UIAlertView的代理來監聽對話框中的按鈕的點擊事件

* 實現UIAlertView的 - (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex代理方法

UIAlertViewDelegate 代理方法底層:  

@protocol UIAlertViewDelegate <NSObject>@optional

// Called when a button is clicked. The view will be automatically dismissed after this call returns
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex NS_DEPRECATED_IOS(2_0, 9_0);
====>> 代碼實現:
 DXTableViewCell.h
#import <UIKit/UIKit.h>
@interface HMTableViewCell : UITableViewCell
@end
//  DXTableViewCell.m
#import "HMTableViewCell.h"
@implementation HMTableViewCell
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
    return [super initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:reuseIdentifier];
}
@end
這裏重定義的類是爲了,來刷新數據的時候,讓cell始終處於一種狀態:UITableViewCellStyleSubtitle,這樣定義以後,物理註冊的時候cell是什麼狀態,最後都會返回這個狀態;
didSelectRowAtIndexPath   選中某行作什麼事情,  很重要!!! 裏面能夠寫任何操做方式
--------------------------------------------------
@interface ViewController () <UITableViewDataSource, UITableViewDelegate, UIAlertViewDelegate>    //同時讓控制器要繼承UIAlertViewDelegate這個協議
// 記錄當前選中行    這裏須要先定義一個屬性,讓下面的代理方法的索引選中的行,賦值給它,便於局部刷新的時候能夠調用
@property (nonatomic, strong) NSIndexPath *idxPath;
--------------------------------------------------
#pragma  mark - table view的代理方法
// 當用戶選中某個的時候會觸發這個事件(會執行這個方法)
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    self.idxPath = indexPath;   //把當前選中的行賦值給這個屬性
    // 馬上取消某行的選中效果(取消選中)
    [tableView deselectRowAtIndexPath:indexPath animated:YES];
    // 0. 獲取當前選中行的模型數據
    HMHero *hero = self.heros[indexPath.row];
    // 1. 彈出一個UIAlertView
    UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"編輯" message:nil delegate:self cancelButtonTitle:@"取消" otherButtonTitles:@"肯定", nil];
    // 1.1修改alert view的樣式, 讓alertView中有一個文本框
    alertView.alertViewStyle = UIAlertViewStylePlainTextInput;
    // 1.2獲取alert view中的文本框
    UITextField *txtField = [alertView textFieldAtIndex:0];
    // 1.3把英雄的名字設置到文本框中
    txtField.text = hero.name;
    //2.顯示出來
    [alertView show];
}
相關文章
相關標籤/搜索