iOS應用開發視頻教程筆記(九)Table Views


今天講的是TableViews,它可用於呈現動態數據列表,也可用於靜態數據。

UITableView

tableView是個一維表,這是一個UIScrollView的子類,所以它是一個滾動列表。它可以高度定制化,它從它的兩個不同的delegation中獲取所有的定制化信息,有data source和delegate這兩個不同的properties,data source負責提供表中的數據,delegate負責數據顯示。如果想顯示多維數據,就是有行和列,可以使用sections或者可以把它放進一個navigation controller。

這個是plain風格的tableVeiw的樣子,頂部的F是section header,底部的東西是tab bar controller,和tableVeiw沒關系。

這是group風格,group風格往往是為固定tableVeiw使用。

listView往往用來查詢,查看動態數據。

描述plain風格的tableVeiw的各個部分的術語:

最頂上的東西叫header,那是一個UIView,可以添加到表中,它可以是任何你想要的東西。在底部有個footer,也是個UIView。這些被分組的東西叫section,藍條叫section header,可以用字符串設置或者可以用view,表中的每個row都是一個UIView,叫這個UIView為tableVeiw cell。

使用完全相同的術語描述group風格的tableVeiw:

當有section時,建立tableVeiw並實現data source時得告訴tableVeiw有多少section,然后它會問你每個section里有多少row。

cell有四個基本顯示類型:

Subtitle有粗體的標題,然后下面有灰色的副標題;basic類型就是下面沒有東西;right detail和subtitle一樣,只是東西的排列不同,這是側面和藍色的;left detail也一樣,只是左右換一下。

創建TableView MVC

tableVeiw來自xcode里的一個UITableViewController類,所以ios有controller的類,然后還有view的類,把它們作為一個單元從object library里拖出來。通常不會在一個通用的UITableViewController里用這個東西,通常會子類它,讓子類controller成為delegate里的data source還有實現這些方法。這就是讓tableVeiw做你想做的事情。

如何創建一個新類並使這個TableViewController不是一個通用的UITableViewController?去new file點擊UIViewController,接着得確保你設置你自定義的controller類的父類為tableVeiw,ios中的UITableViewController類做了一些事情來幫助你的tableVeiw掛接到你的子類,然后還要確保在storyboard,你inspect該controller的identity inspect,並設置了正確的類。

在選中cell時,可以控制出現在cell右側的小東西即accessory,accessory為Detail Disclosure Accessory時,把藍色小按鈕連接起來的方式是你的tableVeiw delegate,你得實現這個方法:

- (void)tableView:(UITableView *)tv accessoryTappedForRowAtIndexPath:(NSIndexPath *)ip;

當有人點擊藍色小按鈕,這個方法會被調用。

在做動態時,有個非常重要的區域叫做reuse identifier,你需要在代碼中指定,它才知道要創建的副本的原型是什么。為什么它還需要該字符串?可能有多個場景或tableVeiw,你拖動的UITableViewController實例來自同一個自定義子類,但它們可能有不同的cell原型,因此為cell命名。通常情況下,我們會把reuse identifier用來形容這個cell是什么。

UITableViewDataSource

這一切是如何工作的?如何得到這個UI?數據是如何來回流動的?這些都是通過protocols。tableVeiw有兩種不同的delegate,一個叫delegate,一個叫data source,它們都是protocols。UITableViewController類會自動設置內部tableVeiw的delegate和data source,因此當我們拖出TableViewController,它已經有一個tableVeiw了,子類controller是默認的delegate和data source。這幾乎總是你使用tableVeiw的方式。為什么做這個delegation?因為view不能和它們的controller對話,除了通過不可見通訊,也就是protocol,通過protocol可以來回發消息。所以tableVeiw是這個controller的view,它只能回應target action或delegate的對話,UITableViewController有個property指向這個tableVeiw。

要成為動態的,要實現此data source protocol。那么在這個data source protocol里都有什么方法?有三個要實現的非常重要的方法,一個是表明表里有多少section,二是每個section有多少row,第三個是返回要繪制的每個row的UITableView cell。來看最后一個方法,這是該方法的的樣子:

- (UITableViewCell *)tableView:(UITableView *)sender cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
   // get a cell to use (instance of UITableViewCell)
   UITableViewCell *cell;
   cell = [self.tableView dequeueReusableCellWithIdentifier:@“My Table View Cell”];
   if (!cell) { 
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle
                                      reuseIdentifier:@“My Table View Cell”];
   }
   cell.textLabel.text = [self getMyDataForRow:indexPath.row inSection:indexPath.section];
   return cell;
}

tableVeiw把自己作為第一個參數傳遞,然后第二個參數是一個indexPath。靜態的cell不用實現這個方法。NSIndexPath要做的就是封裝section和row,因此它有兩個屬性,一個叫section,一個叫row,section會告訴你當前是什么section,row會告訴你這個是當前section里的哪個row,因此這個方法只是說,給我一個用來畫這個section里的這個row的UITableView。

這個方法中的代碼通常有兩部分:第一部分,讓自己得到一個cell,然后設置cell里的property,tableVeiw有個神奇的方法叫做dequeueReusableCellWithIdentifier,這是為了效率,tableVeiw就像一個管理這些UITableViewCell的池子,當UITableVeiw離開屏幕,它就把它們放進池子,然后其中一個需要去到屏幕上時,它就進入池中找出一個來,這就是它是如何重用它們。當它們進出屏幕,我們只是一直在重用和復位,有關重用,reuse identifier指定了要用的池子的名字,當我們做了xcode原型cell,這里我們鍵入它的名字,因為當你做一個xcode的原型cell,如果它到達到重用池而池子是空的,比如第一次啟動的時候,它會創建一個,並用原型把它放進去,這就是原型cell的作用,當重用池是空的時候,它會填進去,只要它是空的,就由原型的副本填充。所以這個字符串必須和xcode里的一樣,如果你想要填充原型的話。如果這里返回nil,會發生什么?我們沒有指定與xcode中相同的字符串,因此它不能使用原型副本,所以不能得到任何東西,它返回nil。接着會放些安全代碼在這,alloc/init一個cell。

接下來只要設置property,比如cell有個property叫做text label,這里寫了個方法getMyDataForRow:inSection:可以用來獲取字符串之類的事情。然后返回這個cell。可以有交替的cell機制,但需要兩個不同的池。

在tableVeiw中要有多少section和row,它有兩個簡單的方法,問它的data source這個tableVeiw里有多少section和這個section里有多少row,你只要回答這些問題:

- (NSInteger)numberOfSectionsInTableView:(UITableView *)sender; 
- (NSInteger)tableView:(UITableView *)sender numberOfRowsInSection:(NSInteger)section;

通常是沒有section的,也就是整體就是一個大的section。但是section里的row數量沒有默認值,你必須給出section有多少row。靜態表不必實現任何這些方法。

UITableViewDelegate

UITableView delegate控制如何繪制表,不是表中的數據,而是如何顯示,比如像cell多高之類。常常data source和delegate是同一個對象,是這個UITableViewController,delegate有很多did/will happen方法,最重要的是它會通知你,當有人點擊row的時候。

當有人點擊row,我們可以做兩件事:一是segueing,可以control drag一個row,甚至是prototype row,到其他東西,然后segue。如果從prototype cell處control drag,所有的cell都會做一樣的事,所以我們就必須確保並根據選擇的row准備segue的viewController,該cell被點擊了,並得到一個delegate方法,如果不做segue或自己想做segue,就用手動segue這個方法。每當cell被點擊didSelectRowAtIndexPath都會被調用,它會傳遞indexPath,你基於給予的信息做些什么:

 

- (void)tableView:(UITableView *)sender didSelectRowAtIndexPath:(NSIndexPath *)path {
     // go do something based on information 
     // about my data structure corresponding to indexPath.row in indexPath.section
}

 

Table View Segues

如果表有個原型cell,直接control drag到一些其他的viewController,那么會問想要什么樣的segue。當你prepare for segue,所以你在做segue,prepareForSegue會被發送到你的TableViewController,你要准備segueing的東西:

 

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
     NSIndexPath *indexPath = [self.tableView indexPathForCell:sender]; 
     // prepare segue.destinationController to display based on information 
     // about my data structure corresponding to indexPath.row in indexPath.section
}

 

通常會使用indexPathForCell這個方法,因為prepareForSegue里的sender是這個cell,被點擊的UITableViewCell,所以通常會調用indexPathForCell得到indexPath,基於被點擊的row,去model里查找要傳遞的數據。

如果model改變了呢?可以調用方法reloadData,reloadData重新加載整個表,它會知道表里有多少section及每個section有多少row,它會為所有的section調用此方法,然后它會為每個可見的cell調用cellForRowAtIndexPath:方法,所以reloadData不是輕量級的。

Demo

做一個計算器的例子,主要包括:

1.在NSUserDefaults存儲一個property list;

2.建立一個UITableViewController及其自定義子類;

3.實現data source protocol;

4.創建一個新的delegate,在delegate實現的地方做一件事:如果有一個popover,通過popover放一個viewController,在popover里發生了什么,它需要回過去和controller通信,它不能直接和controller通信,由於popover是view的一部分,view不能直接回應它的controller,它必須使用delegation;

5.在graph view里添加一個按鈕,它要做的是拿起這個graph,把它添加到NSUserDefaults里的一個列表里;

6.在graph view里添加另一個按鈕,它要使用popover segue帶來了一個全新的MVC,這是一個tableVeiw驅動的MVC,就是popover里有一個表,表里是一個其他所有favorite program的列表,當你點擊其中一個,它會更新graph,顯示favorite graph。

CalculatorGraphViewController.m文件的代碼:

#import "CalculatorGraphViewController.h"
#import "CalculatorBrain.h"
#import "CalculatorProgramsTableViewController.h"

@interface CalculatorGraphViewController() <CalculatorProgramsTableViewControllerDelegate>
@property (nonatomic, strong) UIPopoverController *popoverController; // added after lecture to prevent multiple popovers
@end

@implementation CalculatorGraphViewController

@synthesize popoverController;

#define FAVORITES_KEY @"CalculatorGraphViewController.Favorites"

- (IBAction)addToFavorites:(id)sender
{
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    NSMutableArray *favorites = [[defaults objectForKey:FAVORITES_KEY] mutableCopy];
    if (!favorites) favorites = [NSMutableArray array];
    [favorites addObject:self.calculatorProgram];
    [defaults setObject:favorites forKey:FAVORITES_KEY];
    [defaults synchronize];
}

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
    if ([segue.identifier isEqualToString:@"Show Favorite Graphs"]) {
        // this if statement added after lecture to prevent multiple popovers
        // appearing if the user keeps touching the Favorites button over and over
        // simply remove the last one we put up each time we segue to a new one
        if ([segue isKindOfClass:[UIStoryboardPopoverSegue class]]) {
            UIStoryboardPopoverSegue *popoverSegue = (UIStoryboardPopoverSegue *)segue;
            [self.popoverController dismissPopoverAnimated:YES];
            self.popoverController = popoverSegue.popoverController; // might want to be popover's delegate and self.popoverController = nil on dismiss?
        }
        NSArray *programs = [[NSUserDefaults standardUserDefaults] objectForKey:FAVORITES_KEY];
        [segue.destinationViewController setPrograms:programs];
        [segue.destinationViewController setDelegate:self];
    }
}

- (void)calculatorProgramsTableViewController:(CalculatorProgramsTableViewController *)sender
                                 choseProgram:(id)program
{
    self.calculatorProgram = program;
    // if you wanted to close the popover when a graph was selected
    // you could uncomment the following line
    // you'd probably want to set self.popoverController = nil after doing so
    // [self.popoverController dismissPopoverAnimated:YES];
    [self.navigationController popViewControllerAnimated:YES]; // added after lecture to support iPhone
}

// added after lecture to support deletion from the table
// deletes the given program from NSUserDefaults (including duplicates)
// then resets the Model of the sender

- (void)calculatorProgramsTableViewController:(CalculatorProgramsTableViewController *)sender
                               deletedProgram:(id)program
{
    NSString *deletedProgramDescription = [CalculatorBrain descriptionOfProgram:program];
    NSMutableArray *favorites = [NSMutableArray array];
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    for (id program in [defaults objectForKey:FAVORITES_KEY]) {
        if (![[CalculatorBrain descriptionOfProgram:program] isEqualToString:deletedProgramDescription]) {
            [favorites addObject:program];
        }
    }
    [defaults setObject:favorites forKey:FAVORITES_KEY];
    [defaults synchronize];
    sender.programs = favorites;
}

@end

在storyboard中拖出TableViewController,接着創建一個自定義子類,這是一個UITableViewController子類,叫做CalculatorProgramsTableViewController,接下來在storyboard里去到identity inspector為TableViewController指定類為CalculatorProgramsTableViewController。

創建一個按鈕segue到TableViewController,拖出一個barButton到graphView,然后從它control drag到TableViewController,選擇popover segue,設置它的identifier為Show Favorite Graphs。

在自定義的TableViewController里面,設置cell屬性,將style修改為basic,將identifier設為Calculator Program Description。如果不希望TableViewController出現在popover時太大,需要選中這個controller,使popover屬性是200x200。

delegation的5個步驟:

1.創建protocol,是會被用來描述protocol,還有要干嘛;

2.添加property,不管是data source或delegate,通常都在公共接口里;

3.在delegator的實現內部使用這個delegate property,它需要那里的信息或它要和其他對象通信;

4.要在delegate里設置delegate property,是delegate而不是delegator,是接收這些消息的人;

5.它需要設置自己為delegate,它需要實現protocol里它需要的方法。

CalculatorProgramsTableViewController.h文件的代碼:

#import <UIKit/UIKit.h>

@class CalculatorProgramsTableViewController;

@protocol CalculatorProgramsTableViewControllerDelegate <NSObject> // added <NSObject> after lecture so we can do respondsToSelector: on the delegate
@optional
- (void)calculatorProgramsTableViewController:(CalculatorProgramsTableViewController *)sender
                                 choseProgram:(id)program;
- (void)calculatorProgramsTableViewController:(CalculatorProgramsTableViewController *)sender
                                 deletedProgram:(id)program; // added after lecture to support deleting from table
@end

@interface CalculatorProgramsTableViewController : UITableViewController
@property (nonatomic, strong) NSArray *programs; // of CalculatorBrain programs
@property (nonatomic, weak) id <CalculatorProgramsTableViewControllerDelegate> delegate;
@end

CalculatorProgramsTableViewController.m文件的代碼:

#import "CalculatorProgramsTableViewController.h"
#import "CalculatorBrain.h"

@implementation CalculatorProgramsTableViewController

@synthesize programs = _programs;
@synthesize delegate = _delegate;

// added after lecture to be sure table gets reloaded if Model changes
// you should always do this (i.e. reload table when Model changes)
// the Model getting out of synch with the contents of the table is bad

- (void)setPrograms:(NSArray *)programs
{
    _programs = programs;
    [self.tableView reloadData];
}

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
    return YES;
}

#pragma mark - UITableViewDataSource

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    return [self.programs count];
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *CellIdentifier = @"Calculator Program Description";
    
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell == nil) {
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
    }
    
    // Configure the cell...
    id program = [self.programs objectAtIndex:indexPath.row];
    cell.textLabel.text = [@"y = " stringByAppendingString:[CalculatorBrain descriptionOfProgram:program]];
    
    return cell;
}

// this method added after lecture to support deletion
// simply delegates deletion

- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
    if (editingStyle == UITableViewCellEditingStyleDelete) {
        id program = [self.programs objectAtIndex:indexPath.row];
        [self.delegate calculatorProgramsTableViewController:self deletedProgram:program];
    }
}

// added after lecture
// don't allow deletion if the delegate does not support it too!

- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath
{
    return [self.delegate respondsToSelector:@selector(calculatorProgramsTableViewController:deletedProgram:)];
}

#pragma mark - UITableViewDelegate

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    id program = [self.programs objectAtIndex:indexPath.row];
    [self.delegate calculatorProgramsTableViewController:self choseProgram:program];
}

@end

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM