iOS開發UI篇—UITableView全面解析

概述

在iOS開發中UITableView能夠說是使用最普遍的控件,咱們平時使用的軟件中處處均可以看到它的影子,相似於微信、QQ、新浪微博等軟件基本上隨處都是UITableView。固然它的普遍使用天然離不開它強大的功能,今天這篇文章將針對UITableView重點展開討論。今天的主要內容包括:html

  1. 基本介紹
  2. 數據源
  3. 代理
  4. 性能優化
  5. UITableViewCell
  6. 經常使用操做
  7. UITableViewController
  8. MVC模式

基本介紹

UITableView有兩種風格:UITableViewStylePlain和UITableViewStyleGrouped。這二者操做起來其實並無本質區別,只是後者按分組樣式顯示前者按照普通樣式顯示而已。你們先看一下二者的應用:api

1>分組樣式數組

UITableViewStyleGrouped1      UITableViewStyleGrouped2

2>不分組樣式緩存

UITableViewStylePlain1       UITableViewStylePlain2

你們能夠看到在UITableView中數據只有行的概念,並無列的概念,由於在手機操做系統中顯示多列是不利於操做的。UITableView中每行數據都是一個UITableViewCell,在這個控件中爲了顯示更多的信息,iOS已經在其內部設置好了多個子控件以供開發者使用。若是咱們查看UITableViewCell的聲明文件能夠發如今內部有一個UIView控件(contentView,做爲其餘元素的父控件)、兩個UILable控件(textLabel、detailTextLabel)、一個UIImage控件(imageView),分別用於容器、顯示內容、詳情和圖片。使用效果相似於微信、QQ信息列表:性能優化

UITableViewCell1      UITableViewCell2

固然,這些子控件並不必定要所有使用,具體操做時能夠經過UITableViewCellStyle進行設置,具體每一個枚舉表示的意思已經在代碼中進行了註釋:微信

typedef NS_ENUM(NSInteger, UITableViewCellStyle) {
    UITableViewCellStyleDefault,    // 左側顯示textLabel(不顯示detailTextLabel),imageView可選(顯示在最左邊)
    UITableViewCellStyleValue1,        // 左側顯示textLabel、右側顯示detailTextLabel(默認藍色),imageView可選(顯示在最左邊)
    UITableViewCellStyleValue2,        // 左側依次顯示textLabel(默認藍色)和detailTextLabel,imageView可選(顯示在最左邊)
    UITableViewCellStyleSubtitle    // 左上方顯示textLabel,左下方顯示detailTextLabel(默認灰色),imageView可選(顯示在最左邊)
};

數據源

因爲iOS是遵循MVC模式設計的,不少操做都是經過代理和外界溝通的,但對於數據源控件除了代理還有一個數據源屬性,經過它和外界進行數據交互。 對於UITableView設置完dataSource後須要實現UITableViewDataSource協議,在這個協議中定義了多種 數據操做方法,下面經過建立一個簡單的聯繫人管理進行演示:網絡

首先咱們須要建立一個聯繫人模型KCContactmvc

KCContact.happ

//
//  Contact.h
//  UITableView
//
//  Created by Kenshin Cui on 14-3-1.
//  Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//

#import <Foundation/Foundation.h>

@interface KCContact : NSObject

#pragma mark 姓
@property (nonatomic,copy) NSString *firstName;
#pragma mark 名
@property (nonatomic,copy) NSString *lastName;
#pragma mark 手機號碼
@property (nonatomic,copy) NSString *phoneNumber;

#pragma mark 帶參數的構造函數
-(KCContact *)initWithFirstName:(NSString *)firstName andLastName:(NSString *)lastName andPhoneNumber:(NSString *)phoneNumber;

#pragma mark 取得姓名
-(NSString *)getName;


#pragma mark 帶參數的靜態對象初始化方法
+(KCContact *)initWithFirstName:(NSString *)firstName andLastName:(NSString *)lastName andPhoneNumber:(NSString *)phoneNumber;
@end

KCContact.m函數

//
//  Contact.m
//  UITableView
//
//  Created by Kenshin Cui on 14-3-1.
//  Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//

#import "KCContact.h"

@implementation KCContact

-(KCContact *)initWithFirstName:(NSString *)firstName andLastName:(NSString *)lastName andPhoneNumber:(NSString *)phoneNumber{
    if(self=[super init]){
        self.firstName=firstName;
        self.lastName=lastName;
        self.phoneNumber=phoneNumber;
    }
    return self;
}

-(NSString *)getName{
    return [NSString stringWithFormat:@"%@ %@",_lastName,_firstName];
}

+(KCContact *)initWithFirstName:(NSString *)firstName andLastName:(NSString *)lastName andPhoneNumber:(NSString *)phoneNumber{
    KCContact *contact1=[[KCContact alloc]initWithFirstName:firstName andLastName:lastName andPhoneNumber:phoneNumber];
    return contact1;
}

@end

爲了演示分組顯示咱們不妨將一組數據也抽象成模型KCContactGroup

KCContactGroup.h

//
//  KCContactGroup.h
//  UITableView
//
//  Created by Kenshin Cui on 14-3-1.
//  Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//

#import <Foundation/Foundation.h>
#import "KCContact.h"

@interface KCContactGroup : NSObject

#pragma mark 組名
@property (nonatomic,copy) NSString *name;

#pragma mark 分組描述
@property (nonatomic,copy) NSString *detail;

#pragma mark 聯繫人
@property (nonatomic,strong) NSMutableArray *contacts;

#pragma mark 帶參數個構造函數
-(KCContactGroup *)initWithName:(NSString *)name andDetail:(NSString *)detail andContacts:(NSMutableArray *)contacts;

#pragma mark 靜態初始化方法
+(KCContactGroup *)initWithName:(NSString *)name andDetail:(NSString *)detail andContacts:(NSMutableArray *)contacts;

@end

KCContactGroup.m

//
//  KCContactGroup.m
//  UITableView
//
//  Created by Kenshin Cui on 14-3-1.
//  Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//

#import "KCContactGroup.h"

@implementation KCContactGroup


-(KCContactGroup *)initWithName:(NSString *)name andDetail:(NSString *)detail andContacts:(NSMutableArray *)contacts{
    if (self=[super init]) {
        self.name=name;
        self.detail=detail;
        self.contacts=contacts;
    }
    return self;
}

+(KCContactGroup *)initWithName:(NSString *)name andDetail:(NSString *)detail andContacts:(NSMutableArray *)contacts{
    KCContactGroup *group1=[[KCContactGroup alloc]initWithName:name andDetail:detail andContacts:contacts];
    return group1;
}
@end

而後在viewDidLoad方法中建立一些模擬數據同時實現數據源協議方法:

KCMainViewController.m

//
//  KCMainViewController.m
//  UITableView
//
//  Created by Kenshin Cui on 14-3-1.
//  Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//

#import "KCMainViewController.h"
#import "KCContact.h"
#import "KCContactGroup.h"

@interface KCMainViewController ()<UITableViewDataSource>{
    UITableView *_tableView;
    NSMutableArray *_contacts;//聯繫人模型
}

@end

@implementation KCMainViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    //初始化數據
    [self initData];
    
    //建立一個分組樣式的UITableView
    _tableView=[[UITableView alloc]initWithFrame:self.view.bounds style:UITableViewStyleGrouped];
    
    //設置數據源,注意必須實現對應的UITableViewDataSource協議
    _tableView.dataSource=self;
    
    [self.view addSubview:_tableView];
}

#pragma mark 加載數據
-(void)initData{
    _contacts=[[NSMutableArray alloc]init];
    
    KCContact *contact1=[KCContact initWithFirstName:@"Cui" andLastName:@"Kenshin" andPhoneNumber:@"18500131234"];
    KCContact *contact2=[KCContact initWithFirstName:@"Cui" andLastName:@"Tom" andPhoneNumber:@"18500131237"];
    KCContactGroup *group1=[KCContactGroup initWithName:@"C" andDetail:@"With names beginning with C" andContacts:[NSMutableArray arrayWithObjects:contact1,contact2, nil]];
    [_contacts addObject:group1];
    

    
    KCContact *contact3=[KCContact initWithFirstName:@"Lee" andLastName:@"Terry" andPhoneNumber:@"18500131238"];
    KCContact *contact4=[KCContact initWithFirstName:@"Lee" andLastName:@"Jack" andPhoneNumber:@"18500131239"];
    KCContact *contact5=[KCContact initWithFirstName:@"Lee" andLastName:@"Rose" andPhoneNumber:@"18500131240"];
    KCContactGroup *group2=[KCContactGroup initWithName:@"L" andDetail:@"With names beginning with L" andContacts:[NSMutableArray arrayWithObjects:contact3,contact4,contact5, nil]];
    [_contacts addObject:group2];
    
    
    
    KCContact *contact6=[KCContact initWithFirstName:@"Sun" andLastName:@"Kaoru" andPhoneNumber:@"18500131235"];
    KCContact *contact7=[KCContact initWithFirstName:@"Sun" andLastName:@"Rosa" andPhoneNumber:@"18500131236"];

    KCContactGroup *group3=[KCContactGroup initWithName:@"S" andDetail:@"With names beginning with S" andContacts:[NSMutableArray arrayWithObjects:contact6,contact7, nil]];
    [_contacts addObject:group3];
    
    
    KCContact *contact8=[KCContact initWithFirstName:@"Wang" andLastName:@"Stephone" andPhoneNumber:@"18500131241"];
    KCContact *contact9=[KCContact initWithFirstName:@"Wang" andLastName:@"Lucy" andPhoneNumber:@"18500131242"];
    KCContact *contact10=[KCContact initWithFirstName:@"Wang" andLastName:@"Lily" andPhoneNumber:@"18500131243"];
    KCContact *contact11=[KCContact initWithFirstName:@"Wang" andLastName:@"Emily" andPhoneNumber:@"18500131244"];
    KCContact *contact12=[KCContact initWithFirstName:@"Wang" andLastName:@"Andy" andPhoneNumber:@"18500131245"];
    KCContactGroup *group4=[KCContactGroup initWithName:@"W" andDetail:@"With names beginning with W" andContacts:[NSMutableArray arrayWithObjects:contact8,contact9,contact10,contact11,contact12, nil]];
    [_contacts addObject:group4];
    
    
    KCContact *contact13=[KCContact initWithFirstName:@"Zhang" andLastName:@"Joy" andPhoneNumber:@"18500131246"];
    KCContact *contact14=[KCContact initWithFirstName:@"Zhang" andLastName:@"Vivan" andPhoneNumber:@"18500131247"];
    KCContact *contact15=[KCContact initWithFirstName:@"Zhang" andLastName:@"Joyse" andPhoneNumber:@"18500131248"];
    KCContactGroup *group5=[KCContactGroup initWithName:@"Z" andDetail:@"With names beginning with Z" andContacts:[NSMutableArray arrayWithObjects:contact13,contact14,contact15, nil]];
    [_contacts addObject:group5];

}

#pragma mark - 數據源方法
#pragma mark 返回分組數
-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView{
    NSLog(@"計算分組數");
    return _contacts.count;
}

#pragma mark 返回每組行數
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
    NSLog(@"計算每組(組%i)行數",section);
    KCContactGroup *group1=_contacts[section];
    return group1.contacts.count;
}

#pragma mark返回每行的單元格
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
    //NSIndexPath是一個結構體,記錄了組和行信息
    NSLog(@"生成單元格(組:%i,行%i)",indexPath.section,indexPath.row);
    KCContactGroup *group=_contacts[indexPath.section];
    KCContact *contact=group.contacts[indexPath.row];
    UITableViewCell *cell=[[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nil];
    cell.textLabel.text=[contact getName];
    cell.detailTextLabel.text=contact.phoneNumber;
    return cell;
}

#pragma mark 返回每組頭標題名稱
-(NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section{
    NSLog(@"生成組(組%i)名稱",section);
    KCContactGroup *group=_contacts[section];
    return group.name;
}

#pragma mark 返回每組尾部說明
-(NSString *)tableView:(UITableView *)tableView titleForFooterInSection:(NSInteger)section{
    NSLog(@"生成尾部(組%i)詳情",section);
    KCContactGroup *group=_contacts[section];
    return group.detail;
}
@end

運行能夠看到以下效果:

Contact

你們在使用iPhone通信錄時會發現右側能夠按字母檢索,使用起來很方便,其實這個功能使用UITableView實現很簡單,只要實現數據源協議的一個方法,構建一個分組標題的數組便可實現。數組元素的內容和組標題內容未必徹底一致,UITableView是按照數組元素的索引和每組數據索引順序來定位的而不是按內容查找。

#pragma mark 返回每組標題索引
-(NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView{
    NSLog(@"生成組索引");
    NSMutableArray *indexs=[[NSMutableArray alloc]init];
    for(KCContactGroup *group in _contacts){
        [indexs addObject:group.name];
    }
    return indexs;
}

效果以下:

UITableViewIndex

須要注意的是上面幾個重點方法的執行順序,請看下圖:

image

值得指出的是生成單元格的方法並非一次所有調用,而是隻會生產當前顯示在界面上的單元格,當用戶滾動操做時再顯示其餘單元格。 

注意:隨着咱們的應用愈來愈複雜,可能常常須要調試程序,在iOS中默認狀況下不能定位到錯誤代碼行,咱們能夠經過以下設置讓程序定位到出錯代碼行:Show the Breakpoint  navigator—Add Exception breakpoint。

 

代理

上面咱們已經看到通信錄的簡單實現,可是咱們發現單元格高度、分組標題高度以及尾部說明的高度都須要調整,此時就須要使用代理方法。UITableView代理方法有不少,例如監聽單元格顯示週期、監聽單元格選擇編輯操做、設置是否高亮顯示單元格、設置行高等。

1.設置行高

#pragma mark - 代理方法
#pragma mark 設置分組標題內容高度
-(CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section{
    if(section==0){
        return 50;
    }
    return 40;
}

#pragma mark 設置每行高度(每行高度能夠不同)
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
    return 45;
}

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

2.監聽點擊

在iOS中點擊某聯繫我的就能夠呼叫這個聯繫人,這時就須要監聽點擊操做,這裏就不演示呼叫聯繫人操做了,咱們演示一下修改人員信息的操做。

KCMainViewContrller.m

//
//  KCMainViewController.m
//  UITableView
//
//  Created by Kenshin Cui on 14-3-1.
//  Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//

#import "KCMainViewController.h"
#import "KCContact.h"
#import "KCContactGroup.h"

@interface KCMainViewController ()<UITableViewDataSource,UITableViewDelegate,UIAlertViewDelegate>{
    UITableView *_tableView;
    NSMutableArray *_contacts;//聯繫人模型
    NSIndexPath *_selectedIndexPath;//當前選中的組和行
}

@end

@implementation KCMainViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    //初始化數據
    [self initData];
    
    //建立一個分組樣式的UITableView
    _tableView=[[UITableView alloc]initWithFrame:self.view.bounds style:UITableViewStyleGrouped];
    
    //設置數據源,注意必須實現對應的UITableViewDataSource協議
    _tableView.dataSource=self;
    //設置代理
    _tableView.delegate=self;
    
    [self.view addSubview:_tableView];
}

#pragma mark 加載數據
-(void)initData{
    _contacts=[[NSMutableArray alloc]init];
    
    KCContact *contact1=[KCContact initWithFirstName:@"Cui" andLastName:@"Kenshin" andPhoneNumber:@"18500131234"];
    KCContact *contact2=[KCContact initWithFirstName:@"Cui" andLastName:@"Tom" andPhoneNumber:@"18500131237"];
    KCContactGroup *group1=[KCContactGroup initWithName:@"C" andDetail:@"With names beginning with C" andContacts:[NSMutableArray arrayWithObjects:contact1,contact2, nil]];
    [_contacts addObject:group1];
    

    
    KCContact *contact3=[KCContact initWithFirstName:@"Lee" andLastName:@"Terry" andPhoneNumber:@"18500131238"];
    KCContact *contact4=[KCContact initWithFirstName:@"Lee" andLastName:@"Jack" andPhoneNumber:@"18500131239"];
    KCContact *contact5=[KCContact initWithFirstName:@"Lee" andLastName:@"Rose" andPhoneNumber:@"18500131240"];
    KCContactGroup *group2=[KCContactGroup initWithName:@"L" andDetail:@"With names beginning with L" andContacts:[NSMutableArray arrayWithObjects:contact3,contact4,contact5, nil]];
    [_contacts addObject:group2];
    
    
    
    KCContact *contact6=[KCContact initWithFirstName:@"Sun" andLastName:@"Kaoru" andPhoneNumber:@"18500131235"];
    KCContact *contact7=[KCContact initWithFirstName:@"Sun" andLastName:@"Rosa" andPhoneNumber:@"18500131236"];

    KCContactGroup *group3=[KCContactGroup initWithName:@"S" andDetail:@"With names beginning with S" andContacts:[NSMutableArray arrayWithObjects:contact6,contact7, nil]];
    [_contacts addObject:group3];
    
    
    KCContact *contact8=[KCContact initWithFirstName:@"Wang" andLastName:@"Stephone" andPhoneNumber:@"18500131241"];
    KCContact *contact9=[KCContact initWithFirstName:@"Wang" andLastName:@"Lucy" andPhoneNumber:@"18500131242"];
    KCContact *contact10=[KCContact initWithFirstName:@"Wang" andLastName:@"Lily" andPhoneNumber:@"18500131243"];
    KCContact *contact11=[KCContact initWithFirstName:@"Wang" andLastName:@"Emily" andPhoneNumber:@"18500131244"];
    KCContact *contact12=[KCContact initWithFirstName:@"Wang" andLastName:@"Andy" andPhoneNumber:@"18500131245"];
    KCContactGroup *group4=[KCContactGroup initWithName:@"W" andDetail:@"With names beginning with W" andContacts:[NSMutableArray arrayWithObjects:contact8,contact9,contact10,contact11,contact12, nil]];
    [_contacts addObject:group4];
    
    
    KCContact *contact13=[KCContact initWithFirstName:@"Zhang" andLastName:@"Joy" andPhoneNumber:@"18500131246"];
    KCContact *contact14=[KCContact initWithFirstName:@"Zhang" andLastName:@"Vivan" andPhoneNumber:@"18500131247"];
    KCContact *contact15=[KCContact initWithFirstName:@"Zhang" andLastName:@"Joyse" andPhoneNumber:@"18500131248"];
    KCContactGroup *group5=[KCContactGroup initWithName:@"Z" andDetail:@"With names beginning with Z" andContacts:[NSMutableArray arrayWithObjects:contact13,contact14,contact15, nil]];
    [_contacts addObject:group5];

}

#pragma mark - 數據源方法
#pragma mark 返回分組數
-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView{
    NSLog(@"計算分組數");
    return _contacts.count;
}

#pragma mark 返回每組行數
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
    NSLog(@"計算每組(組%i)行數",section);
    KCContactGroup *group1=_contacts[section];
    return group1.contacts.count;
}

#pragma mark返回每行的單元格
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
    //NSIndexPath是一個對象,記錄了組和行信息
    NSLog(@"生成單元格(組:%i,行%i)",indexPath.section,indexPath.row);
    KCContactGroup *group=_contacts[indexPath.section];
    KCContact *contact=group.contacts[indexPath.row];
    UITableViewCell *cell=[[UITableViewCell alloc]initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:nil];
    cell.textLabel.text=[contact getName];
    cell.detailTextLabel.text=contact.phoneNumber;
    return cell;
}

#pragma mark 返回每組頭標題名稱
-(NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section{
    NSLog(@"生成組(組%i)名稱",section);
    KCContactGroup *group=_contacts[section];
    return group.name;
}

#pragma mark 返回每組尾部說明
-(NSString *)tableView:(UITableView *)tableView titleForFooterInSection:(NSInteger)section{
    NSLog(@"生成尾部(組%i)詳情",section);
    KCContactGroup *group=_contacts[section];
    return group.detail;
}

#pragma mark 返回每組標題索引
-(NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView{
    NSLog(@"生成組索引");
    NSMutableArray *indexs=[[NSMutableArray alloc]init];
    for(KCContactGroup *group in _contacts){
        [indexs addObject:group.name];
    }
    return indexs;
}

#pragma mark - 代理方法
#pragma mark 設置分組標題內容高度
-(CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section{
    if(section==0){
        return 50;
    }
    return 40;
}

#pragma mark 設置每行高度(每行高度能夠不同)
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
    return 45;
}

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

#pragma mark 點擊行
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
    _selectedIndexPath=indexPath;
    KCContactGroup *group=_contacts[indexPath.section];
    KCContact *contact=group.contacts[indexPath.row];
    //建立彈出窗口
    UIAlertView *alert=[[UIAlertView alloc]initWithTitle:@"System Info" message:[contact getName] delegate:self cancelButtonTitle:@"Cancel" otherButtonTitles:@"OK", nil];
    alert.alertViewStyle=UIAlertViewStylePlainTextInput; //設置窗口內容樣式
    UITextField *textField= [alert textFieldAtIndex:0]; //取得文本框
    textField.text=contact.phoneNumber; //設置文本框內容
    [alert show]; //顯示窗口
}

#pragma mark 窗口的代理方法,用戶保存數據
-(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex{
    //當點擊了第二個按鈕(OK)
    if (buttonIndex==1) {
        UITextField *textField= [alertView textFieldAtIndex:0];
        //修改模型數據
        KCContactGroup *group=_contacts[_selectedIndexPath.section];
        KCContact *contact=group.contacts[_selectedIndexPath.row];
        contact.phoneNumber=textField.text;
        //刷新表格
        [_tableView reloadData];
    }
}

#pragma mark 重寫狀態樣式方法
-(UIStatusBarStyle)preferredStatusBarStyle{
    return UIStatusBarStyleLightContent;
}
@end

在上面的代碼中咱們經過修改模型來改變UI顯示,這種方式是經典的MVC應用,在後面的代碼中會常常看到。固然UI的刷新使用了UITableView的reloadData方法,該方法會從新調用數據源方法,包括計算分組、計算每一個分組的行數,生成單元格等刷新整個UITableView。固然這種方式在實際開發中是不可取的,咱們不可能由於修改了一我的的信息就刷新整個UITableViewView,此時咱們須要採用局部刷新。局部刷新使用起來很簡單,只須要調用UITableView的另一個方法:

#pragma mark 窗口的代理方法,用戶保存數據
-(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex{
    //當點擊了第二個按鈕(OK)
    if (buttonIndex==1) {
        UITextField *textField= [alertView textFieldAtIndex:0];
        //修改模型數據
        KCContactGroup *group=_contacts[_selectedIndexPath.section];
        KCContact *contact=group.contacts[_selectedIndexPath.row];
        contact.phoneNumber=textField.text;
        //刷新表格
        NSArray *indexPaths=@[_selectedIndexPath];//須要局部刷新的單元格的組、行
        [_tableView reloadRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationLeft];//後面的參數表明更新時的動畫
    }
}

性能優化

前面已經說過UITableView中的單元格cell是在顯示到用戶可視區域後建立的,那麼若是用戶往下滾動就會繼續建立顯示在屏幕上的單元格,若是用戶向上滾動返回到查看過的內容時一樣會從新建立以前已經建立過的單元格。如此一來即便UITableView的內容不是太多,若是用戶反覆的上下滾動,內存也會瞬間飆升,更況且不少時候UITableView的內容是不少的(例如微博展現列表,基本向下滾動是沒有底限的)。

前面一節中咱們曾經提到過如何優化UIScrollView,當時就是利用有限的UIImageView動態切換其內容來儘量減小資源佔用。一樣的,在UITableView中也能夠採用相似的方式,只是這時咱們不是在滾動到指定位置後更改滾動的位置而是要將當前沒有顯示的Cell從新顯示在將要顯示的Cell的位置而後更新其內容。緣由就是UITableView中的Cell結構佈局多是不一樣的,經過從新定位是不可取的,而是須要重用已經再也不界面顯示的已建立過的Cell。

固然,聽起來這麼作比較複雜,其實實現起來很簡單,由於UITableView已經爲咱們實現了這種機制。在UITableView內部有一個緩存池,初始化時使用initWithStyle:(UITableViewCellStyle) reuseIdentifier:(NSString *)方法指定一個可重用標識,就能夠將這個cell放到緩存池。而後在使用時使用指定的標識去緩存池中取得對應的cell而後修改cell內容便可。

#pragma mark返回每行的單元格
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
    //NSIndexPath是一個對象,記錄了組和行信息
    NSLog(@"生成單元格(組:%i,行%i)",indexPath.section,indexPath.row);
    KCContactGroup *group=_contacts[indexPath.section];
    KCContact *contact=group.contacts[indexPath.row];
    
    //因爲此方法調用十分頻繁,cell的標示聲明成靜態變量有利於性能優化
    static NSString *cellIdentifier=@"UITableViewCellIdentifierKey1";
    //首先根據標識去緩存池取
    UITableViewCell *cell=[tableView dequeueReusableCellWithIdentifier:cellIdentifier];
    //若是緩存池沒有到則從新建立並放到緩存池中
    if(!cell){
        cell=[[UITableViewCell alloc]initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:cellIdentifier];
    }
    
    cell.textLabel.text=[contact getName];
    cell.detailTextLabel.text=contact.phoneNumber;
    NSLog(@"cell:%@",cell);
    return cell;
}

上面的代碼中已經打印了cell的地址,若是你們運行測試上下滾動UITableView會發現滾動時建立的cell地址是初始化時已經建立的。

這裏再次給你們強調兩點:

  1. -(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)方法調用很頻繁,不管是初始化、上下滾動、刷新都會調用此方法,全部在這裏執行的操做必定要注意性能; 
  2. 可重用標識能夠有多個,若是在UITableView中有多類結構不一樣的Cell,能夠經過這個標識進行緩存和從新;

UITableViewCell

1.自帶的UITableViewCell

UITableViewCell是構建一個UITableView的基礎,在UITableViewCell內部有一個UIView控件做爲其餘內容的容器,它上面有一個UIImageView和兩個UILabel,經過UITableViewCellStyle屬性能夠對其樣式進行控制。其結構以下:

UITableViewCellStuct

有時候咱們會發現不少UITableViewCell右側能夠顯示不一樣的圖標,在iOS中稱之爲訪問器,點擊能夠觸發不一樣的事件,例如設置功能:

UITableViewCellAccesoryType

要設置這些圖標只須要設置UITableViewCell的accesoryType屬性,這是一個枚舉類型具體含義以下:

typedef NS_ENUM(NSInteger, UITableViewCellAccessoryType) {
    UITableViewCellAccessoryNone,                   // 不顯示任何圖標
    UITableViewCellAccessoryDisclosureIndicator,    // 跳轉指示圖標
    UITableViewCellAccessoryDetailDisclosureButton, // 內容詳情圖標和跳轉指示圖標
    UITableViewCellAccessoryCheckmark,              // 勾選圖標
    UITableViewCellAccessoryDetailButton NS_ENUM_AVAILABLE_IOS(7_0) // 內容詳情圖標
};

例如在最近通話中咱們一般設置爲詳情圖標,點擊能夠查看聯繫人詳情:

UITableViewCellAccessoryTypeDetaiButtonl

很明顯iOS設置中第一個accessoryType不在枚舉之列,右側的訪問器類型是UISwitch控件,那麼如何顯示自定義的訪問器呢?其實只要設置UITableViewCell的accessoryView便可,它支持任何UIView控件。假設咱們在通信錄每組第一行放一個UISwitch,同時切換時能夠輸出對應信息:

//
//  KCMainViewController.m
//  UITableView
//
//  Created by Kenshin Cui on 14-3-1.
//  Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//

#import "KCMainViewController.h"
#import "KCContact.h"
#import "KCContactGroup.h"

@interface KCMainViewController ()<UITableViewDataSource,UITableViewDelegate,UIAlertViewDelegate>{
    UITableView *_tableView;
    NSMutableArray *_contacts;//聯繫人模型
    NSIndexPath *_selectedIndexPath;//當前選中的組和行
}

@end

@implementation KCMainViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    //初始化數據
    [self initData];
    
    //建立一個分組樣式的UITableView
    _tableView=[[UITableView alloc]initWithFrame:self.view.bounds style:UITableViewStyleGrouped];
    
    //設置數據源,注意必須實現對應的UITableViewDataSource協議
    _tableView.dataSource=self;
    //設置代理
    _tableView.delegate=self;
    
    [self.view addSubview:_tableView];
}

#pragma mark 加載數據
-(void)initData{
    _contacts=[[NSMutableArray alloc]init];
    
    KCContact *contact1=[KCContact initWithFirstName:@"Cui" andLastName:@"Kenshin" andPhoneNumber:@"18500131234"];
    KCContact *contact2=[KCContact initWithFirstName:@"Cui" andLastName:@"Tom" andPhoneNumber:@"18500131237"];
    KCContactGroup *group1=[KCContactGroup initWithName:@"C" andDetail:@"With names beginning with C" andContacts:[NSMutableArray arrayWithObjects:contact1,contact2, nil]];
    [_contacts addObject:group1];
    

    
    KCContact *contact3=[KCContact initWithFirstName:@"Lee" andLastName:@"Terry" andPhoneNumber:@"18500131238"];
    KCContact *contact4=[KCContact initWithFirstName:@"Lee" andLastName:@"Jack" andPhoneNumber:@"18500131239"];
    KCContact *contact5=[KCContact initWithFirstName:@"Lee" andLastName:@"Rose" andPhoneNumber:@"18500131240"];
    KCContactGroup *group2=[KCContactGroup initWithName:@"L" andDetail:@"With names beginning with L" andContacts:[NSMutableArray arrayWithObjects:contact3,contact4,contact5, nil]];
    [_contacts addObject:group2];
    
    
    
    KCContact *contact6=[KCContact initWithFirstName:@"Sun" andLastName:@"Kaoru" andPhoneNumber:@"18500131235"];
    KCContact *contact7=[KCContact initWithFirstName:@"Sun" andLastName:@"Rosa" andPhoneNumber:@"18500131236"];

    KCContactGroup *group3=[KCContactGroup initWithName:@"S" andDetail:@"With names beginning with S" andContacts:[NSMutableArray arrayWithObjects:contact6,contact7, nil]];
    [_contacts addObject:group3];
    
    
    KCContact *contact8=[KCContact initWithFirstName:@"Wang" andLastName:@"Stephone" andPhoneNumber:@"18500131241"];
    KCContact *contact9=[KCContact initWithFirstName:@"Wang" andLastName:@"Lucy" andPhoneNumber:@"18500131242"];
    KCContact *contact10=[KCContact initWithFirstName:@"Wang" andLastName:@"Lily" andPhoneNumber:@"18500131243"];
    KCContact *contact11=[KCContact initWithFirstName:@"Wang" andLastName:@"Emily" andPhoneNumber:@"18500131244"];
    KCContact *contact12=[KCContact initWithFirstName:@"Wang" andLastName:@"Andy" andPhoneNumber:@"18500131245"];
    KCContactGroup *group4=[KCContactGroup initWithName:@"W" andDetail:@"With names beginning with W" andContacts:[NSMutableArray arrayWithObjects:contact8,contact9,contact10,contact11,contact12, nil]];
    [_contacts addObject:group4];
    
    
    KCContact *contact13=[KCContact initWithFirstName:@"Zhang" andLastName:@"Joy" andPhoneNumber:@"18500131246"];
    KCContact *contact14=[KCContact initWithFirstName:@"Zhang" andLastName:@"Vivan" andPhoneNumber:@"18500131247"];
    KCContact *contact15=[KCContact initWithFirstName:@"Zhang" andLastName:@"Joyse" andPhoneNumber:@"18500131248"];
    KCContactGroup *group5=[KCContactGroup initWithName:@"Z" andDetail:@"With names beginning with Z" andContacts:[NSMutableArray arrayWithObjects:contact13,contact14,contact15, nil]];
    [_contacts addObject:group5];

}

#pragma mark - 數據源方法
#pragma mark 返回分組數
-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView{
    NSLog(@"計算分組數");
    return _contacts.count;
}

#pragma mark 返回每組行數
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
    NSLog(@"計算每組(組%i)行數",section);
    KCContactGroup *group1=_contacts[section];
    return group1.contacts.count;
}

#pragma mark返回每行的單元格
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
    //NSIndexPath是一個對象,記錄了組和行信息
    NSLog(@"生成單元格(組:%i,行%i)",indexPath.section,indexPath.row);
    KCContactGroup *group=_contacts[indexPath.section];
    KCContact *contact=group.contacts[indexPath.row];
    
    //因爲此方法調用十分頻繁,cell的標示聲明成靜態變量有利於性能優化
    static NSString *cellIdentifier=@"UITableViewCellIdentifierKey1";
    static NSString *cellIdentifierForFirstRow=@"UITableViewCellIdentifierKeyWithSwitch";
    //首先根據標示去緩存池取
    UITableViewCell *cell;
    if (indexPath.row==0) {
        cell=[tableView dequeueReusableCellWithIdentifier:cellIdentifierForFirstRow];
    }else{
        cell=[tableView dequeueReusableCellWithIdentifier:cellIdentifier];
    }
    //若是緩存池沒有取到則從新建立並放到緩存池中
    if(!cell){
        if (indexPath.row==0) {
            cell=[[UITableViewCell alloc]initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:cellIdentifierForFirstRow];
            UISwitch *sw=[[UISwitch alloc]init];
            [sw addTarget:self action:@selector(switchValueChange:) forControlEvents:UIControlEventValueChanged];
            cell.accessoryView=sw;

        }else{
            cell=[[UITableViewCell alloc]initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:cellIdentifier];
            cell.accessoryType=UITableViewCellAccessoryDetailButton;
        }
    }
    
    if(indexPath.row==0){
        ((UISwitch *)cell.accessoryView).tag=indexPath.section;
    }
    
    cell.textLabel.text=[contact getName];
    cell.detailTextLabel.text=contact.phoneNumber;
    NSLog(@"cell:%@",cell);
    
    return cell;
}

#pragma mark 返回每組頭標題名稱
-(NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section{
    NSLog(@"生成組(組%i)名稱",section);
    KCContactGroup *group=_contacts[section];
    return group.name;
}

#pragma mark 返回每組尾部說明
-(NSString *)tableView:(UITableView *)tableView titleForFooterInSection:(NSInteger)section{
    NSLog(@"生成尾部(組%i)詳情",section);
    KCContactGroup *group=_contacts[section];
    return group.detail;
}

#pragma mark 返回每組標題索引
-(NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView{
    NSLog(@"生成組索引");
    NSMutableArray *indexs=[[NSMutableArray alloc]init];
    for(KCContactGroup *group in _contacts){
        [indexs addObject:group.name];
    }
    return indexs;
}

#pragma mark - 代理方法
#pragma mark 設置分組標題內容高度
-(CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section{
    if(section==0){
        return 50;
    }
    return 40;
}

#pragma mark 設置每行高度(每行高度能夠不同)
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
    return 45;
}

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

#pragma mark 點擊行
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
    _selectedIndexPath=indexPath;
    KCContactGroup *group=_contacts[indexPath.section];
    KCContact *contact=group.contacts[indexPath.row];
    //建立彈出窗口
    UIAlertView *alert=[[UIAlertView alloc]initWithTitle:@"System Info" message:[contact getName] delegate:self cancelButtonTitle:@"Cancel" otherButtonTitles:@"OK", nil];
    alert.alertViewStyle=UIAlertViewStylePlainTextInput; //設置窗口內容樣式
    UITextField *textField= [alert textFieldAtIndex:0]; //取得文本框
    textField.text=contact.phoneNumber; //設置文本框內容
    [alert show]; //顯示窗口
}

#pragma mark 窗口的代理方法,用戶保存數據
-(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex{
    //當點擊了第二個按鈕(OK)
    if (buttonIndex==1) {
        UITextField *textField= [alertView textFieldAtIndex:0];
        //修改模型數據
        KCContactGroup *group=_contacts[_selectedIndexPath.section];
        KCContact *contact=group.contacts[_selectedIndexPath.row];
        contact.phoneNumber=textField.text;
        //刷新表格
        NSArray *indexPaths=@[_selectedIndexPath];//須要局部刷新的單元格的組、行
        [_tableView reloadRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationLeft];//後面的參數代碼更新時的動畫
    }
}


#pragma mark 重寫狀態樣式方法
-(UIStatusBarStyle)preferredStatusBarStyle{
    return UIStatusBarStyleLightContent;
}


#pragma mark 切換開關轉化事件
-(void)switchValueChange:(UISwitch *)sw{
    NSLog(@"section:%i,switch:%i",sw.tag, sw.on);
}
@end

最終運行效果:

Run

注意:

  1. 因爲此時咱們須要兩種UITableViewCell樣式,考慮到性能咱們須要在緩存池緩存兩種Cell。 
  2. UISwitch繼承於UIControl而不是UIView(固然UIControl最終也是繼承於UIView),繼承於UIControl的控件使用addTarget添加對應事件而不是代理,同時有「是否可用」、「是否高亮」、「是否選中」等屬性; 
  3. 上面代碼中若是有些UITableViewCell的UISwitch設置爲on當其餘控件重用時狀態也是on,解決這個問題能夠在模型中設置對應的屬性記錄其狀態,在生成cell時設置當前狀態(爲了儘量簡化上面的代碼這裏就再也不修復這個問題);

2.自定義UITableViewCell

雖然系統自帶的UITableViewCell已經夠強大了,可是不少時候這並不能知足咱們的需求。例如新浪微博的Cell就沒有那麼簡單:

UITableViewCell

沒錯,這個界面佈局也是UITableView實現的,其中的內容就是UITableViewCell,只是這個UITableViewCell是用戶自定義實現的。固然要實現上面的UITableViewCell三言兩語咱們是說不完的,這裏咱們實現一個簡化版本,界面原型以下:

UITableViewCellPrototypeDesign

咱們對具體控件進行拆分:

UITableViewCellPrototypeDesign2

在這個界面中有2個UIImageView控件和4個UILabel,整個界面顯示效果相似於新浪微博的消息內容界面,可是又在新浪微博基礎上進行了精簡以致於利用現有知識可以順利開發出來。

在前面的內容中咱們的數據都是手動構建的,在實際開發中天然不會這麼作,這裏咱們不妨將微博數據存儲到plist文件中而後從plist文件讀取數據構建模型對象(實際開發微博固然須要進行網絡數據請求,這裏只是進行模擬就再也不演示網絡請求的內容)。假設plist文件內容以下:

StatusInfoPlist

接下來就定義一個KCStatusTableViewCell實現UITableViewCell,通常實現自定義UITableViewCell須要分爲兩步:第一初始化控件;第二設置數據,從新設置控件frame。緣由就是自定義Cell通常沒法固定高度,不少時候高度須要隨着內容改變。此外因爲在單元格內部是沒法控制單元格高度的,所以通常會定義一個高度屬性用於在UITableView的代理事件中設置每一個單元格高度。

1.首先看一下微博模型KCStatus,這個模型主要的方法就是根據plist字典內容生成微博對象:

KCStatus.h

//
//  KCStatus.h
//  UITableView
//
//  Created by Kenshin Cui on 14-3-1.
//  Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//

#import <Foundation/Foundation.h>

@interface KCStatus : NSObject

#pragma mark - 屬性
@property (nonatomic,assign) long long Id;//微博id
@property (nonatomic,copy) NSString *profileImageUrl;//頭像
@property (nonatomic,copy) NSString *userName;//發送用戶
@property (nonatomic,copy) NSString *mbtype;//會員類型
@property (nonatomic,copy) NSString *createdAt;//建立時間
@property (nonatomic,copy) NSString *source;//設備來源
@property (nonatomic,copy) NSString *text;//微博內容



#pragma mark - 方法
#pragma mark 根據字典初始化微博對象
-(KCStatus *)initWithDictionary:(NSDictionary *)dic;

#pragma mark 初始化微博對象(靜態方法)
+(KCStatus *)statusWithDictionary:(NSDictionary *)dic;
@end

KCStatus.m

//
//  KCStatus.m
//  UITableView
//
//  Created by Kenshin Cui on 14-3-1.
//  Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//

#import "KCStatus.h"

@implementation KCStatus

#pragma mark 根據字典初始化微博對象
-(KCStatus *)initWithDictionary:(NSDictionary *)dic{
    if(self=[super init]){
        self.Id=[dic[@"Id"] longLongValue];
        self.profileImageUrl=dic[@"profileImageUrl"];
        self.userName=dic[@"userName"];
        self.mbtype=dic[@"mbtype"];
        self.createdAt=dic[@"createdAt"];
        self.source=dic[@"source"];
        self.text=dic[@"text"];
    }
    return self;
}

#pragma mark 初始化微博對象(靜態方法)
+(KCStatus *)statusWithDictionary:(NSDictionary *)dic{
    KCStatus *status=[[KCStatus alloc]initWithDictionary:dic];
    return status;
}

-(NSString *)source{
    return [NSString stringWithFormat:@"來自 %@",_source];
}
@end

2.而後看一下自定義的Cell

KCStatusTableViewCell.h

//
//  KCStatusTableViewCell.h
//  UITableView
//
//  Created by Kenshin Cui on 14-3-1.
//  Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//

#import <UIKit/UIKit.h>
@class KCStatus;

@interface KCStatusTableViewCell : UITableViewCell

#pragma mark 微博對象
@property (nonatomic,strong) KCStatus *status;

#pragma mark 單元格高度
@property (assign,nonatomic) CGFloat height;

@end

KCStatusTableViewCell.m

//
//  KCStatusTableViewCell.m
//  UITableView
//
//  Created by Kenshin Cui on 14-3-1.
//  Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//

#import "KCStatusTableViewCell.h"
#import "KCStatus.h"
#define KCColor(r,g,b) [UIColor colorWithHue:r/255.0 saturation:g/255.0 brightness:b/255.0 alpha:1] //顏色宏定義
#define kStatusTableViewCellControlSpacing 10 //控件間距
#define kStatusTableViewCellBackgroundColor KCColor(251,251,251)
#define kStatusGrayColor KCColor(50,50,50)
#define kStatusLightGrayColor KCColor(120,120,120)

#define kStatusTableViewCellAvatarWidth 40 //頭像寬度
#define kStatusTableViewCellAvatarHeight kStatusTableViewCellAvatarWidth
#define kStatusTableViewCellUserNameFontSize 14
#define kStatusTableViewCellMbTypeWidth 13 //會員圖標寬度
#define kStatusTableViewCellMbTypeHeight kStatusTableViewCellMbTypeWidth
#define kStatusTableViewCellCreateAtFontSize 12
#define kStatusTableViewCellSourceFontSize 12
#define kStatusTableViewCellTextFontSize 14


@interface KCStatusTableViewCell(){
    UIImageView *_avatar;//頭像
    UIImageView *_mbType;//會員類型
    UILabel *_userName;
    UILabel *_createAt;
    UILabel *_source;
    UILabel *_text;
}

@end

@implementation KCStatusTableViewCell

- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
    self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
    if (self) {
        [self initSubView];
    }
    return self;
}

#pragma mark 初始化視圖
-(void)initSubView{
    //頭像控件
    _avatar=[[UIImageView alloc]init];
    [self addSubview:_avatar];
    //用戶名
    _userName=[[UILabel alloc]init];
    _userName.textColor=kStatusGrayColor;
    _userName.font=[UIFont systemFontOfSize:kStatusTableViewCellUserNameFontSize];
    [self addSubview:_userName];
    //會員類型
    _mbType=[[UIImageView alloc]init];
    [self addSubview:_mbType];
    //日期
    _createAt=[[UILabel alloc]init];
    _createAt.textColor=kStatusLightGrayColor;
    _createAt.font=[UIFont systemFontOfSize:kStatusTableViewCellCreateAtFontSize];
    [self addSubview:_createAt];
    //設備
    _source=[[UILabel alloc]init];
    _source.textColor=kStatusLightGrayColor;
    _source.font=[UIFont systemFontOfSize:kStatusTableViewCellSourceFontSize];
    [self addSubview:_source];
    //內容
    _text=[[UILabel alloc]init];
    _text.textColor=kStatusGrayColor;
    _text.font=[UIFont systemFontOfSize:kStatusTableViewCellTextFontSize];
    _text.numberOfLines=0;
//    _text.lineBreakMode=NSLineBreakByWordWrapping;
    [self addSubview:_text];
}

#pragma mark 設置微博
-(void)setStatus:(KCStatus *)status{
    //設置頭像大小和位置
    CGFloat avatarX=10,avatarY=10;
    CGRect avatarRect=CGRectMake(avatarX, avatarY, kStatusTableViewCellAvatarWidth, kStatusTableViewCellAvatarHeight);
    _avatar.image=[UIImage imageNamed:status.profileImageUrl];
    _avatar.frame=avatarRect;
    
    
    //設置會員圖標大小和位置
    CGFloat userNameX= CGRectGetMaxX(_avatar.frame)+kStatusTableViewCellControlSpacing ;
    CGFloat userNameY=avatarY;
    //根據文本內容取得文本佔用空間大小
    CGSize userNameSize=[status.userName sizeWithAttributes:@{NSFontAttributeName: [UIFont systemFontOfSize:kStatusTableViewCellUserNameFontSize]}];
    CGRect userNameRect=CGRectMake(userNameX, userNameY, userNameSize.width,userNameSize.height);
    _userName.text=status.userName;
    _userName.frame=userNameRect;
    
    
    //設置會員圖標大小和位置
    CGFloat mbTypeX=CGRectGetMaxX(_userName.frame)+kStatusTableViewCellControlSpacing;
    CGFloat mbTypeY=avatarY;
    CGRect mbTypeRect=CGRectMake(mbTypeX, mbTypeY, kStatusTableViewCellMbTypeWidth, kStatusTableViewCellMbTypeHeight);
    _mbType.image=[UIImage imageNamed:status.mbtype];
    _mbType.frame=mbTypeRect;
    
    
    //設置發佈日期大小和位置
    CGSize createAtSize=[status.createdAt sizeWithAttributes:@{NSFontAttributeName:[UIFont systemFontOfSize:kStatusTableViewCellCreateAtFontSize]}];
    CGFloat createAtX=userNameX;
    CGFloat createAtY=CGRectGetMaxY(_avatar.frame)-createAtSize.height;
    CGRect createAtRect=CGRectMake(createAtX, createAtY, createAtSize.width, createAtSize.height);
    _createAt.text=status.createdAt;
    _createAt.frame=createAtRect;
    
    
    //設置設備信息大小和位置
    CGSize sourceSize=[status.source sizeWithAttributes:@{NSFontAttributeName:[UIFont systemFontOfSize:kStatusTableViewCellSourceFontSize]}];
    CGFloat sourceX=CGRectGetMaxX(_createAt.frame)+kStatusTableViewCellControlSpacing;
    CGFloat sourceY=createAtY;
    CGRect sourceRect=CGRectMake(sourceX, sourceY, sourceSize.width,sourceSize.height);
    _source.text=status.source;
    _source.frame=sourceRect;
    
    
    //設置微博內容大小和位置
    CGFloat textX=avatarX;
    CGFloat textY=CGRectGetMaxY(_avatar.frame)+kStatusTableViewCellControlSpacing;
    CGFloat textWidth=self.frame.size.width-kStatusTableViewCellControlSpacing*2;
    CGSize textSize=[status.text boundingRectWithSize:CGSizeMake(textWidth, MAXFLOAT) options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName: [UIFont systemFontOfSize:kStatusTableViewCellTextFontSize]} context:nil].size;
    CGRect textRect=CGRectMake(textX, textY, textSize.width, textSize.height);
    _text.text=status.text;
    _text.frame=textRect;
    
    _height=CGRectGetMaxY(_text.frame)+kStatusTableViewCellControlSpacing;
}

#pragma mark 重寫選擇事件,取消選中
-(void)setSelected:(BOOL)selected animated:(BOOL)animated{
    
}
@end

這是咱們自定義Cell這個例子的核心,自定義Cell分爲兩個步驟:首先要進行各類控件的初始化工做,這個過程當中只要將控件放到Cell的View中同時設置控件顯示內容的格式(字體大小、顏色等)便可;而後在數據對象設置方法中進行各個控件的佈局(大小、位置)。在代碼中有幾點須要重點提示你們:

  • 對於單行文本數據的顯示調用- (CGSize)sizeWithAttributes:(NSDictionary *)attrs;方法來獲得文本寬度和高度。 
  • 對於多行文本數據的顯示調用- (CGRect)boundingRectWithSize:(CGSize)size options:(NSStringDrawingOptions)options attributes:(NSDictionary *)attributes context:(NSStringDrawingContext *)context ;方法來獲得文本寬度和高度;同時注意在此以前須要設置文本控件的numberOfLines屬性爲0。 
  • 一般咱們會在自定義Cell中設置一個高度屬性,用於外界方法調用,由於Cell內部設置Cell的高度是沒有用的,UITableViewCell在初始化時會從新設置高度。

3.最後咱們看一下自定義Cell的使用過程:

KCStatusViewController.m

//
//  KCCutomCellViewController.m
//  UITableView
//
//  Created by Kenshin Cui on 14-3-1.
//  Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//

#import "KCStatusCellViewController.h"
#import "KCStatus.h"
#import "KCStatusTableViewCell.h"

@interface KCStatusCellViewController ()<UITableViewDataSource,UITableViewDelegate,UIAlertViewDelegate>{
    UITableView *_tableView;
    NSMutableArray *_status;
    NSMutableArray *_statusCells;//存儲cell,用於計算高度
}
@end

@implementation KCStatusCellViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    
    //初始化數據
    [self initData];
    
    //建立一個分組樣式的UITableView
    _tableView=[[UITableView alloc]initWithFrame:self.view.bounds style:UITableViewStyleGrouped];
    
    //設置數據源,注意必須實現對應的UITableViewDataSource協議
    _tableView.dataSource=self;
    //設置代理
    _tableView.delegate=self;
    
    [self.view addSubview:_tableView];
}

#pragma mark 加載數據
-(void)initData{
    NSString *path=[[NSBundle mainBundle] pathForResource:@"StatusInfo" ofType:@"plist"];
    NSArray *array=[NSArray arrayWithContentsOfFile:path];
    _status=[[NSMutableArray alloc]init];
    _statusCells=[[NSMutableArray alloc]init];
    [array enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
        [_status addObject:[KCStatus statusWithDictionary:obj]];
        KCStatusTableViewCell *cell=[[KCStatusTableViewCell alloc]init];
        [_statusCells addObject:cell];
    }];
}
#pragma mark - 數據源方法
#pragma mark 返回分組數
-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView{
    return 1;
}

#pragma mark 返回每組行數
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{

    return _status.count;
}

#pragma mark返回每行的單元格
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
    static NSString *cellIdentifier=@"UITableViewCellIdentifierKey1";
    KCStatusTableViewCell *cell;
    cell=[tableView dequeueReusableCellWithIdentifier:cellIdentifier];
    if(!cell){
        cell=[[KCStatusTableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier];
    }
    //在此設置微博,以便從新佈局
    KCStatus *status=_status[indexPath.row];
    cell.status=status;
    return cell;
}

#pragma mark - 代理方法
#pragma mark 從新設置單元格高度
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
    //KCStatusTableViewCell *cell=[tableView cellForRowAtIndexPath:indexPath];
    KCStatusTableViewCell *cell= _statusCells[indexPath.row];
    cell.status=_status[indexPath.row];
    return cell.height;
}

#pragma mark 重寫狀態樣式方法
-(UIStatusBarStyle)preferredStatusBarStyle{
    return UIStatusBarStyleLightContent;
}
@end

這個類中須要重點強調一下:Cell的高度須要從新設置(前面說過不管Cell內部設置多高都沒有用,須要從新設置),這裏採用的方法是首先建立對應的Cell,而後在- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath;方法中設置微博數據計算高度通知UITableView。

最後咱們看一下運行的效果:

RunEffect2

經常使用操做

UITableView和UITableViewCell提供了強大的操做功能,這一節中會重點討論刪除、增長、排序等操做。爲了方便演示咱們仍是在以前的通信錄的基礎上演示,在此以前先來給視圖控制器添加一個工具條,在工具條左側放一個刪除按鈕,右側放一個添加按鈕:

#pragma mark 添加工具欄
-(void)addToolbar{
    CGRect frame=self.view.frame;
    _toolbar=[[UIToolbar alloc]initWithFrame:CGRectMake(0, 0, frame.size.width, kContactToolbarHeight)];
    //    _toolbar.backgroundColor=[UIColor colorWithHue:246/255.0 saturation:246/255.0 brightness:246/255.0 alpha:1];
    [self.view addSubview:_toolbar];
    UIBarButtonItem *removeButton=[[UIBarButtonItem alloc]initWithBarButtonSystemItem:UIBarButtonSystemItemTrash target:self action:@selector(remove)];
    UIBarButtonItem *flexibleButton=[[UIBarButtonItem alloc]initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:nil action:nil];
    UIBarButtonItem *addButton=[[UIBarButtonItem alloc]initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:@selector(add)];
    NSArray *buttonArray=[NSArray arrayWithObjects:removeButton,flexibleButton,addButton, nil];
    _toolbar.items=buttonArray;
}

1.刪除

在UITableView中不管是刪除操做仍是添加操做都是經過修改UITableView的編輯狀態來改變的(除非你不用UITableView自帶的刪除功能)。在刪除按鈕中咱們設置UITableView的編輯狀態:

#pragma mark 刪除
-(void)remove{
    //直接經過下面的方法設置編輯狀態沒有動畫
    //_tableView.editing=!_tableView.isEditing;
    
    [_tableView setEditing:!_tableView.isEditing animated:true];
}

點擊刪除按鈕會在Cell的左側顯示刪除按鈕:

UITableViewCellRemoveStatus

此時點擊左側刪除圖標右側出現刪除:

UITableViewCellDeleteStatus2

用過iOS的朋友都知道,通常這種Cell若是向左滑動右側就會出現刪除按鈕直接刪除就能夠了。其實實現這個功能只要實現代理-(void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath;方法,只要實現了此方法向左滑動就會顯示刪除按鈕。只要點擊刪除按鈕這個方法就會調用,可是須要注意的是不管是刪除仍是添加都是執行這個方法,只是第二個參數類型不一樣。下面看一下具體的刪除實現:

#pragma mark 刪除操做
//實現了此方法向左滑動就會顯示刪除按鈕
-(void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath{
    KCContactGroup *group =_contacts[indexPath.section];
    KCContact *contact=group.contacts[indexPath.row];
    if (editingStyle==UITableViewCellEditingStyleDelete) {
        [group.contacts removeObject:contact];
        //考慮到性能這裏不建議使用reloadData
        //[tableView reloadData];
        //使用下面的方法既能夠局部刷新又有動畫效果
        [tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationBottom];
        
        //若是當前組中沒有數據則移除組刷新整個表格
        if (group.contacts.count==0) {
            [_contacts removeObject:group];
            [tableView reloadData];
        }
    }
}

從這段代碼咱們再次看到了MVC的思想,要修改UI先修改數據。並且咱們看到了另外一個刷新表格的方法- (void)deleteRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation;,使用這個方法能夠再刪除以後刷新對應的單元格。效果以下:

UITableViewCellDelete

2.添加

添加和刪除操做都是設置UITableView的編輯狀態,具體是添加仍是刪除須要根據代理方法-(UITableViewCellEditingStyle)tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath;的返回值來肯定。所以這裏咱們定義一個變量來記錄點擊了哪一個按鈕,根據點擊按鈕的不一樣在這個方法中返回不一樣的值。

#pragma mark 取得當前操做狀態,根據不一樣的狀態左側出現不一樣的操做按鈕
-(UITableViewCellEditingStyle)tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath{
    if (_isInsert) {
        return UITableViewCellEditingStyleInsert;
    }
    return UITableViewCellEditingStyleDelete;
}
#pragma mark 編輯操做(刪除或添加) //實現了此方法向左滑動就會顯示刪除(或添加)圖標 -(void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath{ KCContactGroup *group =_contacts[indexPath.section]; KCContact *contact=group.contacts[indexPath.row]; if (editingStyle==UITableViewCellEditingStyleDelete) { [group.contacts removeObject:contact]; //考慮到性能這裏不建議使用reloadData //[tableView reloadData]; //使用下面的方法既能夠局部刷新又有動畫效果 [tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationBottom]; //若是當前組中沒有數據則移除組刷新整個表格 if (group.contacts.count==0) { [_contacts removeObject:group]; [tableView reloadData]; } }else if(editingStyle==UITableViewCellEditingStyleInsert){ KCContact *newContact=[[KCContact alloc]init]; newContact.firstName=@"first"; newContact.lastName=@"last"; newContact.phoneNumber=@"12345678901"; [group.contacts insertObject:newContact atIndex:indexPath.row]; [tableView insertRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationBottom];//注意這裏沒有使用reladData刷新 } }

運行效果:

UITableViewCellInsert

3.排序

只要實現-(void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath *)destinationIndexPath;代理方法當UITableView處於編輯狀態時就能夠排序。

#pragma mark 排序
//只要實現這個方法在編輯狀態右側就有排序圖標
-(void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath *)destinationIndexPath{
    KCContactGroup *sourceGroup =_contacts[sourceIndexPath.section];
    KCContact *sourceContact=sourceGroup.contacts[sourceIndexPath.row];
    KCContactGroup *destinationGroup =_contacts[destinationIndexPath.section];
    
    [sourceGroup.contacts removeObject:sourceContact];
    if(sourceGroup.contacts.count==0){
        [_contacts removeObject:sourceGroup];
        [tableView reloadData];
    }
    
    [destinationGroup.contacts insertObject:sourceContact atIndex:destinationIndexPath.row];
    
}

運行效果:

UITableViewCellMove

最後給你們附上上面幾種操做的完整代碼:

//
//  KCContactViewController.m
//  UITableView
//
//  Created by Kenshin Cui on 14-3-1.
//  Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//

#import "KCContactViewController.h"
#import "KCContact.h"
#import "KCContactGroup.h"
#define kContactToolbarHeight 44

@interface KCContactViewController ()<UITableViewDataSource,UITableViewDelegate,UIAlertViewDelegate>{
    UITableView *_tableView;
    UIToolbar *_toolbar;
    NSMutableArray *_contacts;//聯繫人模型
    NSIndexPath *_selectedIndexPath;//當前選中的組和行
    BOOL _isInsert;//記錄是點擊了插入仍是刪除按鈕
}

@end

@implementation KCContactViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    //初始化數據
    [self initData];
    
    //建立一個分組樣式的UITableView
    _tableView=[[UITableView alloc]initWithFrame:self.view.bounds style:UITableViewStyleGrouped];
    _tableView.contentInset=UIEdgeInsetsMake(kContactToolbarHeight, 0, 0, 0);
    [self.view addSubview:_tableView];
    
    //添加工具欄
    [self addToolbar];
    
    //設置數據源,注意必須實現對應的UITableViewDataSource協議
    _tableView.dataSource=self;
    //設置代理
    _tableView.delegate=self;
    
    
}

#pragma mark 加載數據
-(void)initData{
    _contacts=[[NSMutableArray alloc]init];
    
    KCContact *contact1=[KCContact initWithFirstName:@"Cui" andLastName:@"Kenshin" andPhoneNumber:@"18500131234"];
    KCContact *contact2=[KCContact initWithFirstName:@"Cui" andLastName:@"Tom" andPhoneNumber:@"18500131237"];
    KCContactGroup *group1=[KCContactGroup initWithName:@"C" andDetail:@"With names beginning with C" andContacts:[NSMutableArray arrayWithObjects:contact1,contact2, nil]];
    [_contacts addObject:group1];
    
    
    
    KCContact *contact3=[KCContact initWithFirstName:@"Lee" andLastName:@"Terry" andPhoneNumber:@"18500131238"];
    KCContact *contact4=[KCContact initWithFirstName:@"Lee" andLastName:@"Jack" andPhoneNumber:@"18500131239"];
    KCContact *contact5=[KCContact initWithFirstName:@"Lee" andLastName:@"Rose" andPhoneNumber:@"18500131240"];
    KCContactGroup *group2=[KCContactGroup initWithName:@"L" andDetail:@"With names beginning with L" andContacts:[NSMutableArray arrayWithObjects:contact3,contact4,contact5, nil]];
    [_contacts addObject:group2];
    
    
    
    KCContact *contact6=[KCContact initWithFirstName:@"Sun" andLastName:@"Kaoru" andPhoneNumber:@"18500131235"];
    KCContact *contact7=[KCContact initWithFirstName:@"Sun" andLastName:@"Rosa" andPhoneNumber:@"18500131236"];
    
    KCContactGroup *group3=[KCContactGroup initWithName:@"S" andDetail:@"With names beginning with S" andContacts:[NSMutableArray arrayWithObjects:contact6,contact7, nil]];
    [_contacts addObject:group3];
    
    
    KCContact *contact8=[KCContact initWithFirstName:@"Wang" andLastName:@"Stephone" andPhoneNumber:@"18500131241"];
    KCContact *contact9=[KCContact initWithFirstName:@"Wang" andLastName:@"Lucy" andPhoneNumber:@"18500131242"];
    KCContact *contact10=[KCContact initWithFirstName:@"Wang" andLastName:@"Lily" andPhoneNumber:@"18500131243"];
    KCContact *contact11=[KCContact initWithFirstName:@"Wang" andLastName:@"Emily" andPhoneNumber:@"18500131244"];
    KCContact *contact12=[KCContact initWithFirstName:@"Wang" andLastName:@"Andy" andPhoneNumber:@"18500131245"];
    KCContactGroup *group4=[KCContactGroup initWithName:@"W" andDetail:@"With names beginning with W" andContacts:[NSMutableArray arrayWithObjects:contact8,contact9,contact10,contact11,contact12, nil]];
    [_contacts addObject:group4];
    
    
    KCContact *contact13=[KCContact initWithFirstName:@"Zhang" andLastName:@"Joy" andPhoneNumber:@"18500131246"];
    KCContact *contact14=[KCContact initWithFirstName:@"Zhang" andLastName:@"Vivan" andPhoneNumber:@"18500131247"];
    KCContact *contact15=[KCContact initWithFirstName:@"Zhang" andLastName:@"Joyse" andPhoneNumber:@"18500131248"];
    KCContactGroup *group5=[KCContactGroup initWithName:@"Z" andDetail:@"With names beginning with Z" andContacts:[NSMutableArray arrayWithObjects:contact13,contact14,contact15, nil]];
    [_contacts addObject:group5];
    
}

#pragma mark 添加工具欄
-(void)addToolbar{
    CGRect frame=self.view.frame;
    _toolbar=[[UIToolbar alloc]initWithFrame:CGRectMake(0, 0, frame.size.width, kContactToolbarHeight)];
    //    _toolbar.backgroundColor=[UIColor colorWithHue:246/255.0 saturation:246/255.0 brightness:246/255.0 alpha:1];
    [self.view addSubview:_toolbar];
    UIBarButtonItem *removeButton=[[UIBarButtonItem alloc]initWithBarButtonSystemItem:UIBarButtonSystemItemTrash target:self action:@selector(remove)];
    UIBarButtonItem *flexibleButton=[[UIBarButtonItem alloc]initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:nil action:nil];
    UIBarButtonItem *addButton=[[UIBarButtonItem alloc]initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:@selector(add)];
    NSArray *buttonArray=[NSArray arrayWithObjects:removeButton,flexibleButton,addButton, nil];
    _toolbar.items=buttonArray;
}
#pragma mark 刪除
-(void)remove{
    //直接經過下面的方法設置編輯狀態沒有動畫
    //_tableView.editing=!_tableView.isEditing;
    _isInsert=false;
    [_tableView setEditing:!_tableView.isEditing animated:true];
}
#pragma mark 添加
-(void)add{
    _isInsert=true;
    [_tableView setEditing:!_tableView.isEditing animated:true];
}

#pragma mark - 數據源方法
#pragma mark 返回分組數
-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView{
    return _contacts.count;
}

#pragma mark 返回每組行數
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
    KCContactGroup *group1=_contacts[section];
    return group1.contacts.count;
}

#pragma mark返回每行的單元格
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
    //NSIndexPath是一個對象,記錄了組和行信息
    KCContactGroup *group=_contacts[indexPath.section];
    KCContact *contact=group.contacts[indexPath.row];

    static NSString *cellIdentifier=@"UITableViewCellIdentifierKey1";

    //首先根據標識去緩存池取
    UITableViewCell *cell=[tableView dequeueReusableCellWithIdentifier:cellIdentifier];
    //若是緩存池沒有取到則從新建立並放到緩存池中
    if(!cell){
        cell=[[UITableViewCell alloc]initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:cellIdentifier];
    }
    
    cell.textLabel.text=[contact getName];
    cell.detailTextLabel.text=contact.phoneNumber;
    
    return cell;
}

#pragma mark - 代理方法
#pragma mark 設置分組標題
-(NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section{
    KCContactGroup *group=_contacts[section];
    return group.name;
}

#pragma mark 編輯操做(刪除或添加)
//實現了此方法向左滑動就會顯示刪除(或添加)圖標
-(void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath{
    KCContactGroup *group =_contacts[indexPath.section];
    KCContact *contact=group.contacts[indexPath.row];
    if (editingStyle==UITableViewCellEditingStyleDelete) {
        [group.contacts removeObject:contact];
        //考慮到性能這裏不建議使用reloadData
        //[tableView reloadData];
        //使用下面的方法既能夠局部刷新又有動畫效果
        [tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationBottom];
        
        //若是當前組中沒有數據則移除組刷新整個表格
        if (group.contacts.count==0) {
            [_contacts removeObject:group];
            [tableView reloadData];
        }
    }else if(editingStyle==UITableViewCellEditingStyleInsert){
        KCContact *newContact=[[KCContact alloc]init];
        newContact.firstName=@"first";
        newContact.lastName=@"last";
        newContact.phoneNumber=@"12345678901";
        [group.contacts insertObject:newContact atIndex:indexPath.row];
        [tableView insertRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationBottom];//注意這裏沒有使用reladData刷新
    }
}

#pragma mark 排序
//只要實現這個方法在編輯狀態右側就有排序圖標
-(void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath *)destinationIndexPath{
    KCContactGroup *sourceGroup =_contacts[sourceIndexPath.section];
    KCContact *sourceContact=sourceGroup.contacts[sourceIndexPath.row];
    KCContactGroup *destinationGroup =_contacts[destinationIndexPath.section];
    
    [sourceGroup.contacts removeObject:sourceContact];
    [destinationGroup.contacts insertObject:sourceContact atIndex:destinationIndexPath.row];
    if(sourceGroup.contacts.count==0){
        [_contacts removeObject:sourceGroup];
        [tableView reloadData];
    }
}

#pragma mark 取得當前操做狀態,根據不一樣的狀態左側出現不一樣的操做按鈕
-(UITableViewCellEditingStyle)tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath{
    if (_isInsert) {
        return UITableViewCellEditingStyleInsert;
    }
    return UITableViewCellEditingStyleDelete;
}

#pragma mark 重寫狀態樣式方法
-(UIStatusBarStyle)preferredStatusBarStyle{
    return UIStatusBarStyleLightContent;
}

@end

經過前面的演示這裏簡單總結一些UITableView的刷新方法:

- (void)reloadData;刷新整個表格。

- (void)reloadRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation NS_AVAILABLE_IOS(3_0);刷新指定的分組和行。

- (void)reloadSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation NS_AVAILABLE_IOS(3_0);刷新指定的分組。

- (void)deleteRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation;刪除時刷新指定的行數據。

- (void)insertRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation;添加時刷新指定的行數據。

UITableViewController 

不少時候一個UIViewController中只有一個UITableView,所以蘋果官方爲了方便你們開發直接提供了一個UITableViewController,這個控制器 UITableViewController實現了UITableView數據源和代理協議,內部定義了一個tableView屬性供外部訪問,同時自動鋪滿整個屏幕、自動伸縮以方便咱們的開發。固然UITableViewController也並非簡單的幫咱們定義完UITableView而且設置了數據源、代理而已,它還有其餘強大的功能,例如刷新控件、滾動過程當中固定分組標題等。

有時候一個表格中的數據特別多,檢索起來就顯得麻煩,這個時候能夠實現一個搜索功能幫助用戶查找數據,其實搜索的原理很簡單:修改模型、刷新表格。下面使用UITableViewController簡單演示一下這個功能:

//
//  KCContactTableViewController.m
//  UITableView
//
//  Created by Kenshin Cui on 14-3-1.
//  Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//

#import "KCContactTableViewController.h"
#import "KCContact.h"
#import "KCContactGroup.h"
#define kSearchbarHeight 44

@interface KCContactTableViewController ()<UISearchBarDelegate>{
    UITableView *_tableView;
    UISearchBar *_searchBar;
    //UISearchDisplayController *_searchDisplayController;
    NSMutableArray *_contacts;//聯繫人模型
    NSMutableArray *_searchContacts;//符合條件的搜索聯繫人
    BOOL _isSearching;
}
@end

@implementation KCContactTableViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    //初始化數據
    [self initData];
    
    //添加搜索框
    [self addSearchBar];

}

#pragma mark - 數據源方法

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    if (_isSearching) {
        return 1;
    }
    return _contacts.count;;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    if (_isSearching) {
        return _searchContacts.count;
    }
    KCContactGroup *group1=_contacts[section];
    return group1.contacts.count;
}


- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    KCContact *contact=nil;
    
    if (_isSearching) {
        contact=_searchContacts[indexPath.row];
    }else{
        KCContactGroup *group=_contacts[indexPath.section];
        contact=group.contacts[indexPath.row];
    }
    
    static NSString *cellIdentifier=@"UITableViewCellIdentifierKey1";
    
    //首先根據標識去緩存池取
    UITableViewCell *cell=[tableView dequeueReusableCellWithIdentifier:cellIdentifier];
    //若是緩存池沒有取到則從新建立並放到緩存池中
    if(!cell){
        cell=[[UITableViewCell alloc]initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:cellIdentifier];
    }
    
    cell.textLabel.text=[contact getName];
    cell.detailTextLabel.text=contact.phoneNumber;
    
    return cell;
}

#pragma mark - 代理方法
#pragma mark 設置分組標題
-(NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section{
    KCContactGroup *group=_contacts[section];
    return group.name;
}


#pragma mark - 搜索框代理
#pragma mark  取消搜索
-(void)searchBarCancelButtonClicked:(UISearchBar *)searchBar{
    _isSearching=NO;
    _searchBar.text=@"";
    [self.tableView reloadData];
}

#pragma mark 輸入搜索關鍵字
-(void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText{
    if([_searchBar.text isEqual:@""]){
        _isSearching=NO;
        [self.tableView reloadData];
        return;
    }
    [self searchDataWithKeyWord:_searchBar.text];
}

#pragma mark 點擊虛擬鍵盤上的搜索時
-(void)searchBarSearchButtonClicked:(UISearchBar *)searchBar{
    
    [self searchDataWithKeyWord:_searchBar.text];
    
    [_searchBar resignFirstResponder];//放棄第一響應者對象,關閉虛擬鍵盤
}




#pragma mark 重寫狀態樣式方法
-(UIStatusBarStyle)preferredStatusBarStyle{
    return UIStatusBarStyleLightContent;
}

#pragma mark 加載數據
-(void)initData{
    _contacts=[[NSMutableArray alloc]init];
    
    KCContact *contact1=[KCContact initWithFirstName:@"Cui" andLastName:@"Kenshin" andPhoneNumber:@"18500131234"];
    KCContact *contact2=[KCContact initWithFirstName:@"Cui" andLastName:@"Tom" andPhoneNumber:@"18500131237"];
    KCContactGroup *group1=[KCContactGroup initWithName:@"C" andDetail:@"With names beginning with C" andContacts:[NSMutableArray arrayWithObjects:contact1,contact2, nil]];
    [_contacts addObject:group1];
    
    
    
    KCContact *contact3=[KCContact initWithFirstName:@"Lee" andLastName:@"Terry" andPhoneNumber:@"18500131238"];
    KCContact *contact4=[KCContact initWithFirstName:@"Lee" andLastName:@"Jack" andPhoneNumber:@"18500131239"];
    KCContact *contact5=[KCContact initWithFirstName:@"Lee" andLastName:@"Rose" andPhoneNumber:@"18500131240"];
    KCContactGroup *group2=[KCContactGroup initWithName:@"L" andDetail:@"With names beginning with L" andContacts:[NSMutableArray arrayWithObjects:contact3,contact4,contact5, nil]];
    [_contacts addObject:group2];
    
    
    
    KCContact *contact6=[KCContact initWithFirstName:@"Sun" andLastName:@"Kaoru" andPhoneNumber:@"18500131235"];
    KCContact *contact7=[KCContact initWithFirstName:@"Sun" andLastName:@"Rosa" andPhoneNumber:@"18500131236"];
    
    KCContactGroup *group3=[KCContactGroup initWithName:@"S" andDetail:@"With names beginning with S" andContacts:[NSMutableArray arrayWithObjects:contact6,contact7, nil]];
    [_contacts addObject:group3];
    
    
    KCContact *contact8=[KCContact initWithFirstName:@"Wang" andLastName:@"Stephone" andPhoneNumber:@"18500131241"];
    KCContact *contact9=[KCContact initWithFirstName:@"Wang" andLastName:@"Lucy" andPhoneNumber:@"18500131242"];
    KCContact *contact10=[KCContact initWithFirstName:@"Wang" andLastName:@"Lily" andPhoneNumber:@"18500131243"];
    KCContact *contact11=[KCContact initWithFirstName:@"Wang" andLastName:@"Emily" andPhoneNumber:@"18500131244"];
    KCContact *contact12=[KCContact initWithFirstName:@"Wang" andLastName:@"Andy" andPhoneNumber:@"18500131245"];
    KCContactGroup *group4=[KCContactGroup initWithName:@"W" andDetail:@"With names beginning with W" andContacts:[NSMutableArray arrayWithObjects:contact8,contact9,contact10,contact11,contact12, nil]];
    [_contacts addObject:group4];
    
    
    KCContact *contact13=[KCContact initWithFirstName:@"Zhang" andLastName:@"Joy" andPhoneNumber:@"18500131246"];
    KCContact *contact14=[KCContact initWithFirstName:@"Zhang" andLastName:@"Vivan" andPhoneNumber:@"18500131247"];
    KCContact *contact15=[KCContact initWithFirstName:@"Zhang" andLastName:@"Joyse" andPhoneNumber:@"18500131248"];
    KCContactGroup *group5=[KCContactGroup initWithName:@"Z" andDetail:@"With names beginning with Z" andContacts:[NSMutableArray arrayWithObjects:contact13,contact14,contact15, nil]];
    [_contacts addObject:group5];
    
}

#pragma mark 搜索造成新數據
-(void)searchDataWithKeyWord:(NSString *)keyWord{
    _isSearching=YES;
    _searchContacts=[NSMutableArray array];
    [_contacts enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
        KCContactGroup *group=obj;
        [group.contacts enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
            KCContact *contact=obj;
            if ([contact.firstName.uppercaseString containsString:keyWord.uppercaseString]||[contact.lastName.uppercaseString containsString:keyWord.uppercaseString]||[contact.phoneNumber containsString:keyWord]) {
                [_searchContacts addObject:contact];
            }
        }];
    }];
    
    //刷新表格
    [self.tableView reloadData];
}

#pragma mark 添加搜索欄
-(void)addSearchBar{
    CGRect searchBarRect=CGRectMake(0, 0, self.view.frame.size.width, kSearchbarHeight);
    _searchBar=[[UISearchBar alloc]initWithFrame:searchBarRect];
    _searchBar.placeholder=@"Please input key word...";
    //_searchBar.keyboardType=UIKeyboardTypeAlphabet;//鍵盤類型
    //_searchBar.autocorrectionType=UITextAutocorrectionTypeNo;//自動糾錯類型
    //_searchBar.autocapitalizationType=UITextAutocapitalizationTypeNone;//哪一次shitf被自動按下
    _searchBar.showsCancelButton=YES;//顯示取消按鈕
    //添加搜索框到頁眉位置
    _searchBar.delegate=self;
    self.tableView.tableHeaderView=_searchBar;
}

@end

運行效果:

UITableViewSearch

在上面的搜索中除了使用一個_contacts變量去保存聯繫人數據還專門定義了一個_searchContact變量用於保存搜索的結果。在輸入搜索關鍵字時咱們刷新了表格,此時會調用表格的數據源方法,在這個方法中咱們根據定義的搜索狀態去決定顯示原始數據仍是搜索結果。

咱們發現每次搜索完後都須要手動刷新表格來顯示搜索結果,並且當沒有搜索關鍵字的時候還須要將當前的tableView從新設置爲初始狀態。也就是這個過程當中咱們要用一個tableView顯示兩種狀態的不一樣數據,天然會提升程序邏輯複雜度。爲了簡化這個過程,咱們可使用UISearchDisplayController,UISearchDisplayController內部也有一個UITableView類型的對象searchResultsTableView,若是咱們設置它的數據源代理爲當前控制器,那麼它徹底能夠像UITableView同樣加載數據。同時它自己也有搜索監聽的方法,咱們沒必要在監聽UISearchBar輸入內容,直接使用它的方法便可自動刷新其內部表格。爲了和前面的方法對比在下面的代碼中沒有直接刪除原來的方式而是註釋了對應代碼你們能夠對照學習:

//
//  KCContactTableViewController.m
//  UITableView
//
//  Created by Kenshin Cui on 14-3-1.
//  Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//

#import "KCContactTableViewControllerWithUISearchDisplayController.h"
#import "KCContact.h"
#import "KCContactGroup.h"
#define kSearchbarHeight 44

@interface KCContactTableViewControllerWithUISearchDisplayController ()<UISearchBarDelegate,UISearchDisplayDelegate>{
    UITableView *_tableView;
    UISearchBar *_searchBar;
    UISearchDisplayController *_searchDisplayController;
    NSMutableArray *_contacts;//聯繫人模型
    NSMutableArray *_searchContacts;//符合條件的搜索聯繫人
    //BOOL _isSearching;
}
@end

@implementation KCContactTableViewControllerWithUISearchDisplayController

- (void)viewDidLoad {
    [super viewDidLoad];
    //初始化數據
    [self initData];
    
    //添加搜索框
    [self addSearchBar];

}

#pragma mark - 數據源方法

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
//    if (_isSearching) {
//        return 1;
//    }
    //若是當前是UISearchDisplayController內部的tableView則不分組
    if (tableView==self.searchDisplayController.searchResultsTableView) {
        return 1;
    }
    return _contacts.count;;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
//    if (_isSearching) {
//        return _searchContacts.count;
//    }
    //若是當前是UISearchDisplayController內部的tableView則使用搜索數據
    if (tableView==self.searchDisplayController.searchResultsTableView) {
        return _searchContacts.count;
    }
    KCContactGroup *group1=_contacts[section];
    return group1.contacts.count;
}


- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    KCContact *contact=nil;
    
//    if (_isSearching) {
//        contact=_searchContacts[indexPath.row];
//    }else{
//        KCContactGroup *group=_contacts[indexPath.section];
//        contact=group.contacts[indexPath.row];
//    }
    //若是當前是UISearchDisplayController內部的tableView則使用搜索數據
    if (tableView==self.searchDisplayController.searchResultsTableView) {
        contact=_searchContacts[indexPath.row];
    }else{
        KCContactGroup *group=_contacts[indexPath.section];
        contact=group.contacts[indexPath.row];
    }
    
    static NSString *cellIdentifier=@"UITableViewCellIdentifierKey1";
    
    //首先根據標識去緩存池取
    UITableViewCell *cell=[tableView dequeueReusableCellWithIdentifier:cellIdentifier];
    //若是緩存池沒有取到則從新建立並放到緩存池中
    if(!cell){
        cell=[[UITableViewCell alloc]initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:cellIdentifier];
    }
    
    cell.textLabel.text=[contact getName];
    cell.detailTextLabel.text=contact.phoneNumber;
    
    return cell;
}

#pragma mark - 代理方法
#pragma mark 設置分組標題
-(NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section{
    if (tableView==self.searchDisplayController.searchResultsTableView) {
        return @"搜索結果";
    }
    KCContactGroup *group=_contacts[section];
    return group.name;
}
#pragma mark 選中以前
-(NSIndexPath *)tableView:(UITableView *)tableView willSelectRowAtIndexPath:(NSIndexPath *)indexPath{
    [_searchBar resignFirstResponder];//退出鍵盤
    return indexPath;
}


#pragma mark - 搜索框代理
//#pragma mark  取消搜索
//-(void)searchBarCancelButtonClicked:(UISearchBar *)searchBar{
//    //_isSearching=NO;
//    _searchBar.text=@"";
//    //[self.tableView reloadData];
//    [_searchBar resignFirstResponder];
//}
//
//#pragma mark 輸入搜索關鍵字
//-(void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText{
//    if([_searchBar.text isEqual:@""]){
//        //_isSearching=NO;
//        //[self.tableView reloadData];
//        return;
//    }
//    [self searchDataWithKeyWord:_searchBar.text];
//}

//#pragma mark 點擊虛擬鍵盤上的搜索時
//-(void)searchBarSearchButtonClicked:(UISearchBar *)searchBar{
//    
//    [self searchDataWithKeyWord:_searchBar.text];
//    
//    [_searchBar resignFirstResponder];//放棄第一響應者對象,關閉虛擬鍵盤
//}

#pragma mark - UISearchDisplayController代理方法
-(BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchString:(NSString *)searchString{
    [self searchDataWithKeyWord:searchString];
    return YES;
}


#pragma mark 重寫狀態樣式方法
-(UIStatusBarStyle)preferredStatusBarStyle{
    return UIStatusBarStyleLightContent;
}

#pragma mark 加載數據
-(void)initData{
    _contacts=[[NSMutableArray alloc]init];
    
    KCContact *contact1=[KCContact initWithFirstName:@"Cui" andLastName:@"Kenshin" andPhoneNumber:@"18500131234"];
    KCContact *contact2=[KCContact initWithFirstName:@"Cui" andLastName:@"Tom" andPhoneNumber:@"18500131237"];
    KCContactGroup *group1=[KCContactGroup initWithName:@"C" andDetail:@"With names beginning with C" andContacts:[NSMutableArray arrayWithObjects:contact1,contact2, nil]];
    [_contacts addObject:group1];
    
    
    
    KCContact *contact3=[KCContact initWithFirstName:@"Lee" andLastName:@"Terry" andPhoneNumber:@"18500131238"];
    KCContact *contact4=[KCContact initWithFirstName:@"Lee" andLastName:@"Jack" andPhoneNumber:@"18500131239"];
    KCContact *contact5=[KCContact initWithFirstName:@"Lee" andLastName:@"Rose" andPhoneNumber:@"18500131240"];
    KCContactGroup *group2=[KCContactGroup initWithName:@"L" andDetail:@"With names beginning with L" andContacts:[NSMutableArray arrayWithObjects:contact3,contact4,contact5, nil]];
    [_contacts addObject:group2];
    
    
    
    KCContact *contact6=[KCContact initWithFirstName:@"Sun" andLastName:@"Kaoru" andPhoneNumber:@"18500131235"];
    KCContact *contact7=[KCContact initWithFirstName:@"Sun" andLastName:@"Rosa" andPhoneNumber:@"18500131236"];
    
    KCContactGroup *group3=[KCContactGroup initWithName:@"S" andDetail:@"With names beginning with S" andContacts:[NSMutableArray arrayWithObjects:contact6,contact7, nil]];
    [_contacts addObject:group3];
    
    
    KCContact *contact8=[KCContact initWithFirstName:@"Wang" andLastName:@"Stephone" andPhoneNumber:@"18500131241"];
    KCContact *contact9=[KCContact initWithFirstName:@"Wang" andLastName:@"Lucy" andPhoneNumber:@"18500131242"];
    KCContact *contact10=[KCContact initWithFirstName:@"Wang" andLastName:@"Lily" andPhoneNumber:@"18500131243"];
    KCContact *contact11=[KCContact initWithFirstName:@"Wang" andLastName:@"Emily" andPhoneNumber:@"18500131244"];
    KCContact *contact12=[KCContact initWithFirstName:@"Wang" andLastName:@"Andy" andPhoneNumber:@"18500131245"];
    KCContactGroup *group4=[KCContactGroup initWithName:@"W" andDetail:@"With names beginning with W" andContacts:[NSMutableArray arrayWithObjects:contact8,contact9,contact10,contact11,contact12, nil]];
    [_contacts addObject:group4];
    
    
    KCContact *contact13=[KCContact initWithFirstName:@"Zhang" andLastName:@"Joy" andPhoneNumber:@"18500131246"];
    KCContact *contact14=[KCContact initWithFirstName:@"Zhang" andLastName:@"Vivan" andPhoneNumber:@"18500131247"];
    KCContact *contact15=[KCContact initWithFirstName:@"Zhang" andLastName:@"Joyse" andPhoneNumber:@"18500131248"];
    KCContactGroup *group5=[KCContactGroup initWithName:@"Z" andDetail:@"With names beginning with Z" andContacts:[NSMutableArray arrayWithObjects:contact13,contact14,contact15, nil]];
    [_contacts addObject:group5];
    
}

#pragma mark 搜索造成新數據
-(void)searchDataWithKeyWord:(NSString *)keyWord{
    //_isSearching=YES;
    _searchContacts=[NSMutableArray array];
    [_contacts enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
        KCContactGroup *group=obj;
        [group.contacts enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
            KCContact *contact=obj;
            if ([contact.firstName.uppercaseString containsString:keyWord.uppercaseString]||[contact.lastName.uppercaseString containsString:keyWord.uppercaseString]||[contact.phoneNumber containsString:keyWord]) {
                [_searchContacts addObject:contact];
            }
        }];
    }];
    
    //刷新表格
    //[self.tableView reloadData];
}

#pragma mark 添加搜索欄
-(void)addSearchBar{
    _searchBar=[[UISearchBar alloc]init];
    [_searchBar sizeToFit];//大小自適應容器
    _searchBar.placeholder=@"Please input key word...";
    _searchBar.autocapitalizationType=UITextAutocapitalizationTypeNone;
    _searchBar.showsCancelButton=YES;//顯示取消按鈕
    //添加搜索框到頁眉位置
    _searchBar.delegate=self;
    self.tableView.tableHeaderView=_searchBar;

    _searchDisplayController=[[UISearchDisplayController alloc]initWithSearchBar:_searchBar contentsController:self];
    _searchDisplayController.delegate=self;
    _searchDisplayController.searchResultsDataSource=self;
    _searchDisplayController.searchResultsDelegate=self;
    [_searchDisplayController setActive:NO animated:YES];

}

@end

運行效果:

UITableViewSearch2

注意若是使用Storyboard或xib方式建立上述代碼則無需定義UISearchDisplayController成員變量,由於每一個UIViewController中已經有一個searchDisplayController對象。

MVC模式

經過UITableView的學習相信你們對於iOS的MVC已經有一個大體的瞭解,這裏簡單的分析一下iOS中MVC模式的設計方式。在iOS中多數數據源視圖控件(View)都有一個dataSource屬性用於和控制器(Controller)交互,而數據來源咱們通常會以數據模型(Model)的形式進行定義,View不直接和模型交互,而是經過Controller間接讀取數據。

就拿前面的聯繫人應用舉例,UITableView做爲視圖(View)並不能直接訪問模型Contact,它要顯示聯繫人信息只能經過控制器(Controller)來提供數據源方法。一樣的控制器自己就擁有視圖控件,能夠操做視圖,也就是說視圖和控制器之間能夠互相訪問。而模型既不能訪問視圖也不能訪問控制器。具體依賴關係以下圖:

MVC

 
原文地址:http://www.cnblogs.com/kenshincui/p/3931948.html
相關文章
相關標籤/搜索