IOS开发入门(12)-表视图I:基础知识

IOS开发入门(12)-表视图I:基础知识

  在前面几部分中,主屏幕只能展示一个汽车对象的信息。而在实际iOS中,一次显示多条数据并实现滚动查看是十分常见的,例如通讯录、音乐以及其他多个以表格样式展示数据的应用程序。

  表视图是开发者的工具包中至关重要的控件之一,当设计和创建那些运行在屏幕空间不大的iPhone/iPad设备上的应用陈旭时,该视图的作用尤为重要。与UINavigationController结合使用时,表视图控件能建议地展示有着层次结构的数据,轻松实现导航。表视图允许用户便捷地访问对象概要列表。本节将介绍有关表视图的一些基本知识。

表视图介绍

3类与2协议

  • UITableViewController是主视图控制器,拥有很少几个有关视图的属性,并且只有一个方法。大多数工作由两个控制器所遵从的协议来实现。通过实现这些协议的方法,可以显示表视图的单元格,在某些情况下,实现第用户交互进行响应。
  • UITableViewDataSouce提供的是从对象获取的数据详情,这些数据会在与表中的每个表格段外和表格行对应的每一个单元格中进行展示。此外提供了多个可选的方法,用于实现单元格的添加、删除和排序。
  • UITableViewDelegate支持表视图的多个行为。利用UITableViewDelegate,可以实现任何操作:从配置某个单元格的高度或表头视图,以及支持编辑和突出强调功能。
  • UITableView实现所有的功能,能够配置并显示表格视图的各个部分。它拥有一些额外的方法和属性,能够实现添加、移动和删除单元格;更新数据,手动实现视图内容的滚动;访问视图的其他部分;配置索引等。当然,其中的某些功能,最好由视图数据源(UITableDataSouce)和委托(UITableViewDelegate)来实现。
  • UITableViewCell表示在表视图中显示的单行。通过“标题和副标题,添加图片”的不同配置,能形成4种不同的、系统自带的单元格样式。此外,也可以自定义单元格的样式。

表视图的常见行为

  • 设置——对表视图的表头视图、表尾视图、表格段、每个表格段的表头和/或表尾、每组中单元格的个数进行设置。
  • 显示——显示可见的表格
  • 滚动——计算出大暖阁何时进入屏幕并负责加载它们。表视图管理单元格的创建,以及单元格的重用,它会创建缓存池,存储已创建的单元格,并负责权衡如何为赋予了新的数据之后、即将滚入屏幕的单元格的配置样式。
  • 选中——更新单元格的选中状态或者负责更新表视图配置的单元格。根据选中状态来调用相应的委托方法。

阶段I:学习一下表视图的基本用法

创建空白文件

  首先我们先创建一个Empty Application的模板(当然xcode6以后就没有这个了),但是没关系,我们正常创建一个 Single View Application模板,然后将 ViewController.m、ViewController.h和Main.storyboard删除。
(方法查自于http://www.cocoachina.com/bbs/read.php?tid=231701)

  这样就创建了一个Empty Application了。

  接下来在xcode中选择file|new,添加一个iphone故事板文件,选中user interface类别中的storyboard,添加到文件中,将.plist将Main storyboard file base name 那项删掉中的Main改成刚才添加的那个故事面版文件。

  我们得到了一个什么都没有的故事面板文件,这时候我们将一个表视图控制器(Table View Controller)拖入到故事面板文件中,然后点击刚才放入的故事面板,将 Is Initial View Controller 选中

IOS开发入门(12)-表视图I:基础知识_第1张图片

  好了,创建完了

  现在,最简单的诗经就是创建一些预设的单元格了

  • 选择主故事版(VIew Controller)并选择Attributes检查器
  • 在Attributes检查器的顶端的Table View部分,将Content下拉菜单选为Static Cells(静态单元格),可以看到,故事面板表视图图像由一个变成了三个。如下图(嘿嘿嘿,升级版动态图)

IOS开发入门(12)-表视图I:基础知识_第2张图片

  运行后,虽然看起来没变,但是点击上面三行表格时,表格会变成灰色表示选中,而之前是不行的

创建单元格

  有时候我们想要创建预先定义的单元格。但更常见的情况是,我们想要显示数量不定、数量可变的数据项列表,因此我们就需要提前知道其内容。上面我们创建的表视图是使用静态单元格。在表格中,它们的表现类似于静态视图。但有时候,我们不知道需要显示多少单元格,以及如何来显示它们。此时,我们就需要使用动态原型。

  当然,使用什么类型的单元格取决于我们要做什么,像setting显然我们是知道需要多少单元格的就使用静态单元格,而类似于备忘录的,则是使用动态原型。

下面我们将进行动态原型的演练

  • 将表格类型改回原来的动态原型,将多出来的两个单元格直接删掉就行了;
  • 选中Table View Cell,将Identifiler设置为MyCell

      如果没有设置为MyCell(其他名字也行,总之要有名字),会有如下警告

IOS开发入门(12)-表视图I:基础知识_第3张图片

  这是在告诉我们:“原型单元格(prototype cell)没有设置重用标识符。重用标识符(reuse identifier)能让表视图控制器判定创建和重用哪一种类型的单元格原型

  现在我们需要编写代码告诉表视图需要创建多少单元格,并需要给这些单元格填充数据,因此,我们需要一个UITableViewDataSource。

  创建步骤:(跟以前创建的差不多,不过有点变化)

  • 创建新文件,选择Cocoa Touch类别,选择Objective-C类
  • IOS开发入门(12)-表视图I:基础知识_第4张图片
  • 返回故事版,选中表视图控制器(再次说一下就是Table View Controller),并且使用Identity检查器将该类设置为MyTableViewController

      在MyTableViewController.m中会有警告,就是文件中有#warning,这个特殊的标记是要告诉xcode有些事需要处理。警告是要告诉我们还需要写一写代码。

分行和组

  每个警告都告诉我们一些知识,有关表视图如何组织数据的方式。表视图的数据组(表格段)可以为0个或多个,每个表格段可以拥有0行或者多行的数据。个数从0开始计,看上去很奇怪,但是在表视图中,全控的数据集或仅仅是空的表格段都是有可能的

  下面我们通过代码告诉表视图数据段有多少,以及每段数据含有多少行(其实从方法名称上就能大致知道怎么用了)

  • 将MyTableViewController.m中的两个方法替换为如下代码
//这个方法是告诉表视图一共有多少数据段(该方法是可选的,如果没有提供该方法,则默认1)
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
//warning Incomplete implementation, return the number of sections
    return 1;
}
//这个方法说明指定的数据段含有多少个数据项,这里我们弄5个
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
//warning Incomplete implementation, return the number of rows
    return 5;
}
  • 在刚替换的两个方法下面的tableView:cellForRowAtIndexPath:方法中,将CellIndentifier的值由@"Cell"改为@"MyCell。这告诉表视图创建的新的单元格时应用哪个原型。

      再次运行,这时候会有5个可以单击的单元格

生成单元格和索引路径(Index Path)

  首先,我们需要知道表视图中的Index Path是什么。表视图数据显示为一个或多个组。每个行组被称为section(表格段)。表中的任何行可以根据它的组和行的数字,进行识别、予以区分。这个组合被称为index Path(索引路径),(我觉得可以简单的认为是一个动态的二维数组)

  在表视图中,所以路径使用的是NSIndexPath类中的一个更通用、专门针对表视图的版本来表示索引路径。它由两个整数组成:第一个是表格段编号,第二个是行号。因为索引路径是对象,所以可以发送消息或使用点表示法来访问其部件。表视图使用一组特殊访问器,section和row让代码更容易读且更可维护

  如果在表中有个NSIndexPath名为cellIndexPath,那么可以使用cellIndexPath.section来获取它的表格段标号,用cellIndexPath.row来访问他的行。

代码如下:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"MyCell" forIndexPath:indexPath];

    NSString *myLabel = [NSString stringWithFormat:@"@Section: %ld Row: %ld",(long)indexPath.section,(long)indexPath.row];
    cell.textLabel.text = myLabel;
    return cell;
}

运行结果:

IOS开发入门(12)-表视图I:基础知识_第5张图片

添加表格段

  现在我们要像表格中添加表格段。要实现这一点,仅仅需要将numberOfSectionsInTableView:的返回值从1改为2,运行这个程序,则会有两个含有5分元素的表格段,但是它们没有表格段的表头视图。

添加表格段的表头视图,只需要在numberOfSectionsInTableView:方法的下方插入以下代码

- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
    return [NSString stringWithFormat:@"Section: %ld",(long)section];
}

效果如下图

IOS开发入门(12)-表视图I:基础知识_第6张图片

  可以试着添加更多的表格段或再每个表格段中添加更多的单元格。即使添加的数量非常大,表视图还是能工作。部分原因是,表视图仅仅会保留那些处于可见的单元格,以及一些额外的单元格行,从而实现快速的滚动。如果创建一个含有数千数据的表格,内存中一次只保留几十个数据。表视图创建他所需要的单元格,并回收已经不在屏幕上的单元格。这正是重用标识符的用处。

阶段II:替换Add/View场景

PS可以到ArnoldYUV ,我的git上下载代码

替换表视图控制器

  首先,将Add/View场景替换为一个基于表视图控制器的场景:

  • 打开故事面板,拖入一个表视图控制器,并将其放到已有的Edit/View场景的上方;
  • 在我们的项目中添加两个新的Objective-C类:CarTableViewController继承自UITableViewController,CarTableViewCell继承自UITableViewCell,这里就不贴图了;
  • 在故事面板中,将第一步添加的表视图控制器的类设置为CarTableViewController,并且将表视图的原型单元格的类设置为CarTableViewCell。保持原型单元格被选中,选择属性并将重用标识符设置为CarCell;

IOS开发入门(12)-表视图I:基础知识_第7张图片

IOS开发入门(12)-表视图I:基础知识_第8张图片

  • 将Car对象的一个属性添加到汽车单元格中。这个属性用于单元格中要显示的数据,并且最终决定查看和编辑哪辆汽车。打开CarTableViewCell.h并添加如下代码:
#import 
@class Car;
@interface CarTableViewCell : UITableViewCell

@property (strong, nonatomic) Car *myCar;

@end
  • 将Car.h导入CarTableViewCell.m文件中
  • 打开CarTableViewController.m并添加如下代码,除了导入汽车模型类和汽车表视图单元格外,还需声明一个汽车数据数组,正如我们在ViewController.m中所实现的操作:
#import "CarTableViewController.h"
#import "Car.h"
#import "CarTableVIewCell.h"

@interface CarTableViewController ()

@end

@implementation CarTableViewController{
    NSMutableArray *arrayOfCars;//这是用来存车的数组
}
  • 将VIewController.m中的newCar:方法复制到CarTableController.m文件的底部,在@end的上面。然后删除updateLabel:withBaseString:andCount:调用
  • 在CarTableViewController.m中,修改viewDidLoad
- (void)viewDidLoad {
    [super viewDidLoad];

    arrayOfCars = [NSMutableArray new];

    [self newCar:nil];
}
  • 将表格段编号修改为1,将总的行数修改为arrayOfCars数组的大小:
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    return 1;
}
//根据有多少量车就设置多少单元格
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return [arrayOfCars count];
}
  • tableView:cellForRowAtIndexPath:中,确保静态的CellIdentifier正确设置。代码如下
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    static NSString *CellIdentifier = @"CarCell";    
    //.. .. ..我忘了之前啥样子的.. .. .. 关键是要加上面这一句
    //暂时这些 还有
}
  • 切换到故事版,并改变导航控制器的指向

IOS开发入门(12)-表视图I:基础知识_第9张图片

  运行程序后。表视图控制器现在是Add/View场景。在继续进行之前,将导航栏的标题设置为CarValet,也可以从ViewController.m中复制本地化代码

添加汽车查看单元格

  我们现在已经有了一辆汽车的数据了,以及一个现实这辆汽车的单元格。但是我们没有拥有视图元素来显示该数据。下一步就是修改原型单元格,从而显示汽车数据。步骤如下:

  • 向Car对象中添加类为NSDate(很容易错写成NSData要注意)的属性dateCreated。在基类的初始化方法中将其设置为[NSDate date]
//在Car.h中
@property NSDate* dateCreated;//新加

//在Car.m中
- (id)init {
    self = [super init];
    if (self != nil) {
        _year = kModelTYear;
        _fuleAmount = 0.0f;
        _dateCreated = [NSDate date];//新加
    }
    return self;
}

- (id)initWithMake:(NSString *)make
            model:(NSString *)model
             year:(int)year
       fuelAmount:(float)fuelAmount {

    self = [super init];
    if (self !=nil) {
        _make = [make copy];
        _model = [model copy];
        _year = year;
        _fuleAmount = fuelAmount;
        _dateCreated = [NSDate date];//新加
    }
    return self;
}
  • 在故事板上,打开汽车表视图的单元格的Attributes检查器,并且将其设置为RightDetail样式(其实上面已经有图这么弄了),这是表格的样式会改变,会在坐标以黑色显示标题,并且在右边以浅灰色显示详情。将详情的字号设置为12。
    IOS开发入门(12)-表视图I:基础知识_第10张图片

  • 向CarTableViewCell中添加一个公有方法,代码如下,注意要在.h文件中声明

//.h文件 增加
- (void)configureCell;

//.m文件
- (void)configureCell {
    NSString *make = (self.myCar.make == nil) ? @"Unknown" : self.myCar.make;//1 汽车的make或model可能为nil,所以将其设置为默认值
    NSString *model = (self.myCar.model == nil) ? @"Unknown" : self.myCar.model;
    self.textLabel.text = [NSString stringWithFormat:@"%d %@ %@",self.myCar.year,make,model];//2 将主编前设置为汽车的year、make和model

    NSString *dateStr = [NSDateFormatter localizedStringFromDate:self.myCar.dateCreated
                                                       dateStyle:NSDateFormatterShortStyle
                                                       timeStyle:NSDateFormatterShortStyle];//3 获得本地化版本的创建日期,日期样式应尽可能简短
    NSLog(@"%@", self.myCar.dateCreated);
    self.detailTextLabel.text = dateStr;//4 将段版本的创建日期设置到详细信息文本去中
}
  • 在CarTableViewController.m的tableView:cellForRowAtIndexPath:方法中添加代码,为单元格设置数据。然后让单元格更新自己
//上面说少一部分的那段代码完整的在这里
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    static NSString *CellIdentifier = @"CarCell";

    CarTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
    cell.myCar = arrayOfCars[indexPath.row];
    [cell configureCell];

    return cell;
}
//注意必须修改我们所创建的单元格的类,因为只有这样操作,它才能知道myCar属性和configureCell方法

添加汽车

  我们已经有了一个添加汽车的方法(就是表示图文件那个newcar的方法)。我们只需要通过一个按钮就可以实现对他的调用了。

  • 打开故事板,将一个bar button元素拖入到Add/View场景的导航栏(建议放右边)
  • 选中刚才放进去的按钮,如下操作,就能变成“➕”了
    IOS开发入门(12)-表视图I:基础知识_第11张图片

  • 由动态图就是方便,注意是右击,这样就为按钮添加方法了
    IOS开发入门(12)-表视图I:基础知识_第12张图片

  • 在newCar:方法的末尾添加如下代码让表视图重载自己(不然你点击后会发现程序貌似没有变,其实已经变了,只是没有刷新)

[self.tableView reloadData];

  如果能让新车带动画的进入表格顶部,应该会更好一点。对newCar方法进行修改,代码如下:

- (IBAction)newCar:(id)sender {
    Car *newCar = [Car new];
    [arrayOfCars insertObject:newCar atIndex:0];//1 将汽车插入到数组的前边
    NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:0];//2 创建一个NSindexPath对象来指定新单元格的位置——他的section和row

    [self.tableView insertRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];//3 让表视图在新的索引路径上插入一个对象。这回事表视图调用数据源来查找第0表格段第0行的数据——也就是数组的第一个元素。因为数组已经被更新,所以新的单元格会被返回
    //[self.tableView reloadData];
}

运行如图:PS看前面增加的效果就行了……后面的是后续的内容

删除汽车

  能够创建汽车,当然也就可以删除汽车了。其实,在我们添加CarTableViewController类的时候,一个简单的删除方法所需要的大部分代码已经创建好了。我们只需要将他的注释删除掉就可以了

在CarTableViewController.m修改代码如下:

- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath {
    // Return NO if you do not want the specified item to be editable.
    return YES;//这里如果返回的是no那么就是不能编辑的包括删除,假设我们写的是return (indexPath.row%2!=0);那么0,2,4这些行数就不能进行操作了,下面给出一个不能删除的模拟运行图
}

- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
    if (editingStyle == UITableViewCellEditingStyleDelete) {
        // Delete the row from the data source
        [arrayOfCars removeObjectAtIndex:indexPath.row];

        [tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationFade];
    } //else if (editingStyle == UITableViewCellEditingStyleInsert) {
        // Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view
    //}
}

这是之前截的图不要在意内容,在意要展示的效果就行啦

IOS开发入门(12)-表视图I:基础知识_第13张图片

  扫动是很好的,但是如果是新手的话,可能不知道扫动这个手势。所以我们最好是提供一个用户界面元素用于编辑会好一些。为此,通过以下步骤添加一个编辑按钮:

  • 打开故事面板。病将两个bar button item拖动视图元素列表中First Responder下面的表视图控制器浏览器中
    IOS开发入门(12)-表视图I:基础知识_第14张图片

  • 为刚才添加的两个工具栏按钮一个命名为Edit,另一个命名为Done

  • 打开Assistant编译器,确保打开的是CarTableViewController.h,分别为每个按钮添加一个属性,editButton指向Edit,doneButton指向Done
  • 为两个按钮添加一个action,命名为editTableView,两个按钮都指向这个action
  • 将下面的代码添加到viewDidLoad方法的末尾:
self.navigationItem.leftBarButtonItem = self.editButton;//为导航栏添加左按钮
  • 使用下面代码填充editTableView:方法
- (IBAction)editTableView:(id)sender {
    BOOL startEdit = (sender == self.editButton); //1 如果发送这条消息的是编辑按钮,那么开始编辑

    UIBarButtonItem *nextButton = (startEdit) ? self.doneButton :self.editButton; //2 下一个要显示的按钮是当前没有显示的那个,就是交替出现done和edit

    [self.navigationItem setLeftBarButtonItem:nextButton animated:YES];//3为新的导航栏按钮添加动画
    [self.tableView setEditing:startEdit animated:YES];//4让表视图动画过度到编辑或非编辑状态
}

运行如图:

IOS开发入门(12)-表视图I:基础知识_第15张图片

阶段III

  在本节中,我们将添加一些场景用来查看和编辑汽车。

添加查看汽车场景

  在故事版中完成创建汽车详情屏幕的大多数可视化工作:

  • 拖入一个表视图控制器,就把他放到新汽车场景的左侧吧
  • 选择表视图并将内容设置为static cells,将样式设置为Grouped。当修改样式时,会看到表格段数目的设置。将表格设置为三个表格段
    IOS开发入门(12)-表视图I:基础知识_第16张图片

      基于每个组要显示的数据数目来修改其中的单元格数目:

  • 选中第一个分组,然后Attributes检查器会发生变化,允许修改行数、表头和表尾。将行数保持为3,将表头设置为Make、Model&year。这些表头文字要添加到故事板的分组中
    IOS开发入门(12)-表视图I:基础知识_第17张图片

  • 将带有一个单元格的第二个分组的表头设置为Fuel

  • 将第三个分组的表头设置为Date PARKED

  • 对每个单元格,将类型设置为Basic,将内容设置到它所显示的数据元素的名称。例如,第一个分组的三个单元格分别为Make,Model,Year
    IOS开发入门(12)-表视图I:基础知识_第18张图片
    IOS开发入门(12)-表视图I:基础知识_第19张图片

  • 最后的结果图如下

IOS开发入门(12)-表视图I:基础知识_第20张图片

  现在看看这个视图:

  • 从原型汽车单元格拖拽一个Push Selection Segue拖到查看汽车表视图控制器。确保不要选中附属segue,因为它们由单元格附属控件触发,而不是选择整个单元格时触发。此外,确保是从单元格拖拽而不是其中的其他内容
  • 选中刚才出啊年segue,将标识符设置为ViewSegue
  • 将汽车详情场景的导航标题栏设置为View Car

结果图如下:

IOS开发入门(12)-表视图I:基础知识_第21张图片

运行结果如下:

用数据填充查看汽车场景

  要为查看汽车场景填入数据,需要为这个场景的视图控制器制定一个自定义类,步骤如下:

  • 添加继承UITableViewController类的ViewCarTableViewController
  • 添加下面代码到ViewCarTableViewController.h中:
@class Car;

@property Car *myCar
  • 打开故事版,选中静态表视图控制器也就是我们刚才创建的显示汽车详情的表视图控制器,并将其类设置为ViewCarTableViewController
  • 打开Assistant编辑器,并确保他所显示的是ViewCarTableViewController.h。把每个标签拖拽到文件中,命名为makeLabel、modelLabel、yearLabel、fuelLabel、dateLabel和timeLabel。确保这些属性的类型为UITable。这时候ViewCarTableViewController.h代码应该如下:
#import 

@class Car;
@interface ViewCarTableViewController : UITableViewController

@property (weak, nonatomic) IBOutlet UILabel *makeLabel;
@property (weak, nonatomic) IBOutlet UILabel *modelLabel;
@property (weak, nonatomic) IBOutlet UILabel *yearLabel;
@property (weak, nonatomic) IBOutlet UILabel *fuelLabel;
@property (weak, nonatomic) IBOutlet UILabel *dateLabel;
@property (weak, nonatomic) IBOutlet UILabel *timeLabel;
@property Car *myCar;

@end
  • 在编辑器中打开ViewCarTableViewController.m并导入Car.h。因为这个表格显示静态单元格,不需要任何数据源协议方法,因此将他们删除。也可以在viewDidLoad方法中设置一些动态内容。代码如下:
- (void)viewDidLoad {
    [super viewDidLoad];
    dataUpdated = NO;
    self.myCar = [self.delegate carToView];
    self.makeLabel.text = (self.myCar.make == nil) ? @"Unknown" : self.myCar.make;
    self.modelLabel.text = (self.myCar.model == nil) ? @"Unknown" : self.myCar.model;
    self.yearLabel.text = [NSString stringWithFormat:@"%d",self.myCar.year];
    self.fuelLabel.text = [NSString stringWithFormat:@"%0.2f",self.myCar.fuelAmount];
    self.dateLabel.text = [NSDateFormatter localizedStringFromDate:self.myCar.dateCreated
                                                         dateStyle:NSDateFormatterMediumStyle
                                                         timeStyle:NSDateFormatterNoStyle];
    self.timeLabel.text = [NSDateFormatter localizedStringFromDate:self.myCar.dateCreated
                                                         dateStyle:NSDateFormatterNoStyle
                                                         timeStyle:NSDateFormatterMediumStyle];

    // Uncomment the following line to preserve selection between presentations.
    // self.clearsSelectionOnViewWillAppear = NO;

    // Uncomment the following line to display an Edit button in the navigation bar for this view controller.
    // self.navigationItem.rightBarButtonItem = self.editButtonItem;
}
  • 将ViewCarTableViewController.h导入CarTableViewController.m中,并在viewDidLoad方法下添加如下方法:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
    if([segue.identifier isEqualToString:@"ViewSegue"]) {
        ViewCarTableViewController *nextController;
        nextController = segue.destinationViewController;
//        NSInteger index = [self.tableView indexPathForSelectedRow].row;
//        nextController.myCar = arrayOfCars[index];
        nextController.delegate = self;
    }
}

  为ViewController类创建类似的prepareForSeguesender:方法。在此处,为传进来的ViewCarTableViewController设置Car对象

  运行程序,穿件一些汽车,并单机其中的一些。新的查看汽车场景会打开,我们能够多动这个表格,并且旋转也能正确处理。实际上,我们创建的新场景都能处理旋转,改变他们的单元格尺寸,并根据需要来支持滚动

  选中一辆不同的汽车时,这个程序显示含有正确数据的新增加汽车视图屏幕、当然这些详情屏幕的去呗只有Date Packed文本区。现在该添加make、model和year的编辑控件了

  对了,两个表视图控制器的底部可能会有一个白条(忘了前面有没有取消掉了)。这是之前章节创建的工具栏。可以自己尝试的去删除他。

编辑数据

  汽车的大部分数据都可编辑。我们需要提供场景来编辑汽车的不同属性。首先是make和model(这俩一样)。所有元素都基于文本,因此他们都可以使用同一类场景,步骤如下:

  • 将一个UIViewController拖动到查看汽车场景的右侧
  • 在接近顶部的地方添加一个UINavigationBar,并且将首尾约束都设置为0,顶部约束标准距离
  • 往导航栏的每一段拖入一个工具栏按钮,将左边的一个设置为Cancel按钮,将右边的一个设置为Done按钮
  • 拖入一个标签,将其约束设置为距离导航元素的底部20点,首尾两边为0
  • 拖入一个文本框,并将其约束设置为距离标签下面为系统距离,然后与首尾两边为0
  • 保持文本框被选中,使用Attributes检查器,将清除按钮设置为Appear While Editing。
    IOS开发入门(12)-表视图I:基础知识_第22张图片

添加MakeModelEditViewController类

  新的控制器用于编辑汽车的品牌或型号。这意味着,在视图显示之前,标题、标签和文本框数据需要被设置好。有两种方法实现:查看汽车场景要么可在prepareForSeguesender:里面设置这些属性,要么可以使用协议。使用协议是更好的方法,因为具有更高的灵活性和可维护性,另外还意味着可以在其他项目中重用这些元素

  到此为止,当改变当前屏幕时,可以沿着视图层次向上或乡下导航,或者切换到一个新的标签页或视图集合。在一个层次结构中,我们从所有汽车的概览,到达一辆汽车的详情。随着层次深入,用户可以看到视图从右侧滑入并且当向上返回时,从左侧消失

  有事我们想要显示某些视图层次结构之外的东西。编辑汽车的特定属性是个示例。编辑并不是修改某件东西的能力;我们还提供取消修改的机会,即使用户已经输入了新的值

  这样的操作是不同的,这是向用户表明他或她并不只向前进一步操作的好方式。使用一个模态屏幕实现该要求,这能迫使用户做出选择。用户做出修改后既可以接受也可以取消。用户现在不能浏览其他地方的应用程序,直到做出选择之后。模态屏幕,设置切换的视觉效果也与通常不同。模态屏幕中默认切换效果是从底部和下面滑入,结束时消失

  在iOS中,可以将任何场景(或视图控制器)显示为模式。如何实现转场有不同选项。正如下面我们将看到的,可以使用模态segue或对当前导航控制器的不同调用

  而当用户已经完成编辑时,触摸Done或Cancel的一个,回到原来的显示模态屏幕的场景。当使用segue显示模态屏幕时,返回叫回退(unwinding)

  步骤如下,创建交换数据的协议:

  • 创建名为MakeModelEditViewController的一个视图控制器子类,并将其设置为刚创建的场景的类
  • 确保新的make/model的编辑场景在故事版中被选中,并且.h文件在辅助视图中被打开。将标签拖出属性命名为editLabel,将文本框拖出属性命名为editField
  • 将导航栏拖出属性,命名为myNavigationItem
  • 将Cancel和Done按钮拖拽,创建action 名为:editCancel和editDone
  • 添加名为MakeModelEditProtocol的协议并将文件的内容设置为下面的代码。该协议中的每个方法被用于创建make/model编辑场景的不同部分,除了最后一个,它用来将编辑后的值发送回委托对象:
#import 

@protocol MakeModelEditProtocol <NSObject>

- (NSString*)titleText;

- (NSString*)editLabelText;

- (NSString*)editFieldText;

- (NSString*)editFieldPlaceholderText;

- (void)editDone:(NSString*)textFieldValue;
@end
  • 将这个协议导入到MakeModelEditViewController.h中,并添加如属性:
#import "MakeModelEditProtocol.h"
@property (weak, nonatomic) id  delegate;
  • 使用协议方法初始化标题、标签和文本编辑框。打开MakeViewCarTableViewController.m并用下面的代码替换viewDidLoad,将用户界面中的每个元素设置为委托返回的内容:
- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    self.myNavigationItem.title = [self.delegate titleText];

    self.editLabel.text = [self.delegate editLabelText];
    self.editField.text = [self.delegate editFieldText];
    self.editField.placeholder = [self.delegate editFieldPlaceholderText];
    [self.editField becomeFirstResponder];
}
  • Make/Model控制器所需要的其他方法只有cancelTouched:和editTouched:。这些方法被切换回那个打开编辑器的场景。此外,如果用户触摸Done,当前的文本框值会被发送给委托。我们需要关闭一个为模态切花而打开的控制器,并且同时回退segue。可以使用UIViewController的方法dismissViewControllerAnimated:completion:来实现这一点。用下面的代码替换两个IBAction:
- (IBAction)editCancel:(id)sender {
    [self dismissViewControllerAnimated:YES completion:nil];
}

- (IBAction)editDone:(id)sender {
    [self.delegate editDone:self.editField.text];

    [self dismissViewControllerAnimated:YES completion:nil];
}
  • 将协议导入ViewCarTableViewController.h并且赶工在@interface声明行之后添加下面代码:
<MakeModelEditProtocol>

上面代码表示ViewCarTableViewController同意遵照该协议

  • 将这些协议添加到ViewCarTableViewController.m的底部,刚好在@end的上面
- (NSString*)titleText {
}

- (NSString*)editLabelText {
}

- (NSString*)editFieldText {
}

- (NSString*)editFieldPlaceholderText {
}

让查看汽车控制器成为MakeModelEditProtocol委托

make/model编辑器已经能够编辑一些内容并让委托得知结果。下一步就是准备委托,在当前这种情况下,也就是VIewCarTableViewController。这个不寻常的部分用于跟踪当前正在被编辑的是哪个数据字段

make/model编辑器根本不在乎编辑的是什么。他可以编辑任何文本元素。所有上下文都是由delegate设置的,这意味着查看汽车控制器需要跟踪哪个输入框正被编辑

通过下面步骤完成添加汽车品牌和型号的编辑工能:

  • 修改ViewCarTableViewController.m的顶部,并通过添加如下代码来添加一个状态白能量和一些状态值常量:
#define kCurrentEditMake 0
#define kCurrentEditModel 1
@interface ViewCarTableViewController () {
    NSInteger currentEdutType;
}
  • 在故事板中,添加从品牌单元格到make/model编辑场景的模态选择segue(不是辅助动作)。选中这个色鬼并且将标识符设置为MakeEditSegue。当设置标识符时,确保风格为Model。使用型号单元格重复这个动作,并且将标识符设置为ModelEditSegue
    IOS开发入门(12)-表视图I:基础知识_第23张图片

  • 使用prepareForSeguesender:创建Make/Model编辑控制器。使用该方法设置正在编辑的是哪个数据字段。记得导入编辑器的头文件,在当前这种情况下,是MakeModelEditViewController.h。在ViewCarTableViewController.m中,代码如下:

#import "MakeModelEditViewController.h"


- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
    if ([segue.identifier isEqualToString:@"MakeEditSegue"]) {
        MakeModelEditViewController *nextController;
        nextController = segue.destinationViewController;
        nextController.delegate = self;
        currentEdutType = kCurrentEditMake;
    } else if ([segue.identifier isEqualToString:@"ModelEditSegue"]) {
        MakeModelEditViewController *nextController;
        nextController = segue.destinationViewController;
        nextController.delegate = self;
        currentEdutType = kCurrentEditModel;
    }
}
  • 为make/model编辑场景添加协议方法的内容。每个方法需要确定正在被编辑的是哪个字段,并返回正确的字符串;他们看上去基本相同。有很多方法可以实现这种行为。在当前这种情况下,使用switch语句来提供添加更多字段的灵活性,代码如下:
- (NSString*)titleText {
    NSString *titleString = @"";

    switch (currentEdutType) {
        case kCurrentEditMake:
            titleString = @"Make";
            break;
        case kCurrentEditModel:
            titleString = @"Model";
            break;
        default:
            break;
    }
    return titleString;
}

- (NSString*)editLabelText {
    NSString *titleString = @"";

    switch (currentEdutType) {
        case kCurrentEditMake:
            titleString = @"Enter the Make:";
            break;
        case kCurrentEditModel:
            titleString = @"Enter the Model:";
            break;
        default:
            break;
    }
    return titleString;
}

- (NSString*)editFieldText {
    NSString *titleString = @"";

    switch (currentEdutType) {
        case kCurrentEditMake:
            titleString = self.myCar.make;
            break;
        case kCurrentEditModel:
            titleString = self.myCar.model;
            break;
        default:
            break;
    }
    return titleString;
}

- (NSString*)editFieldPlaceholderText {
    NSString *titleString = @"";

    switch (currentEdutType) {
        case kCurrentEditMake:
            titleString = @"Car Make";
            break;
        case kCurrentEditModel:
            titleString = @"Car Model";
            break;
        default:
            break;
    }
    return titleString;
}
  • 使用从editDone:协议方法获得的值来更新文本框,注意,用户可能点击Cancel从而不更新值,点击Done的时候也可能不会更新(一进去就点Done),代码如下:
- (void)editDone:(NSString*)textFieldValue {
    if(textFieldValue != nil && [textFieldValue length] > 0) { //1 仅当有新的文本时才更新。如果文本框一开始根据editFieldText设置为nil,而且现在仍然是nil,那么不做任何改变。它也可以是一个空字符串
        switch (currentEdutType) {
            case kCurrentEditMake:
                if (self.myCar.make == nil || !([self.myCar.make isEqualToString:textFieldValue])) { //2 文本框中有一些文字,如果当前没有值或者新值与旧值不同,则更新
                    self.myCar.make = textFieldValue; //3 如果有更新,则修改Car对象

                    self.makeLabel.text = textFieldValue; // 4 在汽车视图中修改标签

                    dataUpdated = YES;
                }
                break;

            case kCurrentEditModel:
                if (self.myCar.model == nil || !([self.myCar.model isEqualToString:textFieldValue])) {
                    self.myCar.model = textFieldValue;

                    self.modelLabel.text = textFieldValue;

                    dataUpdated = YES;
                }
            default:
                break;
        }
    }

}

运行结果如下:

  我们发现在根目录中并没有更新,这就是下面要解决的问题

添加ViewCarProtocol协议

  不存在从查看汽车控制器连接回汽车表格控制器的东西。唯一的通信是在CarTableViewController为进入的汽车视图设置myCar时,发生在prepareForSegue:sender:方法中。我们需要一个协议来让两个视图控制器能够通信

  这个协议需要两条消息:一条用于设置要查看哪辆汽车,还有一条用于在数据发生变化时通知委托对象。最后一部分意味着查看汽车控制器必须跟踪是否有变化。

  • 在ViewCarTableViewController.m中,添加另一个状态变量,类型为BOOL,位置在currentEditTyppe的下面。将他命名为dataUpdated。在viewDidload中,将dataUpdated初始化为NO,刚好在调用super代码的下面
  • 当在editDone:方法中更新self.myCar的任何时候将dataUpdated设置为YES
  • 刚好在ViewCarTableViewController的下面,添加新的协议,名为ViewCarProtocol,并添加如下代码:
#import 

@class Car;

@protocol ViewCarProtocol <NSObject>

- (Car*)carToView;

- (void)carViewDone: (BOOL)dataChanged;

@end
  • 将ViewCarProtocol.h导入到ViewCarTableViewController.h中,并添加一行代码来声明一个遵守这个协议的委托
@property (weak, nonatomic) id  delegate;
  • ViewCarTableViewController的viewDidLoad:方法中,通过在初始化dataUpdated:方法的代码的下面添加以下代码来设置myCar属性的值:
self.myCar = [self.delegate carToView];
  • CarTableViewController设置为委托,方法为向.h文件中导入ViewCarProtocol.h,并刚好在@interface那一行的下面添加以下代码:
<ViewCarProtocol>
  • 在CarTableViewController.m中,在newCar方法的下面添加协议方法。将carViewDone:保持空白。在prepareForSegue:sender:中不再设置myCar属性的值。代码如下,carToView:
- (Car *)carToView {
    NSInteger index = [self.tableView indexPathForSelectedRow].row;
    return arrayOfCars[index];
}
  • 通过将prepareForSegue:sender:的内容修改为如下代码,将汽车表格设置为查看汽车的委托:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
    if([segue.identifier isEqualToString:@"ViewSegue"]) {
        ViewCarTableViewController *nextController;
        nextController = segue.destinationViewController;
//        NSInteger index = [self.tableView indexPathForSelectedRow].row;
//        nextController.myCar = arrayOfCars[index];
        nextController.delegate = self;
    }
}

捕捉返回主要汽车表格的切换行为

  下图显示了当前的转移行为以及他们如何发生。这里有三个segue:从汽车列表到查看汽车的push类型,还有从查看汽车场景到make/model编辑场景的两个model类型。最后还有才能够make/model编辑场景回退到查看汽车场景,方法是使用dismissControllerAnimated:completion:

Created with Raphaël 2.1.0 汽车列表 汽车列表 查看汽车场景 查看汽车场景 编辑品牌型号 编辑品牌型号 push segue modal segue 查看消息 隐示的unwind segue

  从查看汽车场景到汽车表格场景的转换时个隐式的unwind segue,源自一个并未控制的用户界面元素。用户单机导航栏中的Back按钮,从而segue展开,但并未调用故事版和segue。

  然而,存在另一个机制,即Back按钮由导航控制器进行管理。每当视图控制器被推入(pushed)或从堆栈中弹出(popped off)时,导航控制器就会查找可选的委托,并能在这次转场的前后都发送消息

  为了捕捉从汽车查看场景返回到汽车表格场景的转换,需要将正确的控制器设置为导航控制器的委托,然后实现合适的方法。当前协议位于查看汽车表视图的中间,因此这就是需要监听场景切换的地方:

  • 将查看汽车表视图控制器设置为导航控制器的委托,方法是在ViewCarTableViewController.h文件中修改如下代码:
<MakeModelEditProtocol, UINavigationControllerDelegate>
  • 将查看汽车场景的列表控制器设置为导航控制器的委托。在ViewCarTableViewController.m中添加viewWillAppear:方法,放在viewDidLoad:方法的下面:
- (void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];

    self.navigationController.delegate = self;
}
  • 将下列方法添加到ViewCarTableViewController.m的顶部,以向委托对象发送一条carViewDone:协议消息,只要数据被更新并且下一视图控制器是委托。此外一处该对象的委托身份,方式是设置导航控制器的委托属性为nil:
- (void)navigationController:(UINavigationController *)navigationController didShowViewController:(UIViewController *)viewController animated:(BOOL)animated {
    if (viewController == (UIViewController*)self.delegate) {
        if (dataUpdated) {
            [self.delegate carViewDone:dataUpdated];
        }

        navigationController.delegate = nil;
    }
}
  • 最后,实现CarTableViewController.m中的carViewDone:方法:
- (void)carViewDone:(BOOL)dataChanged {
    if (dataChanged) {
        [self.tableView reloadData];
    }
}

运行结果如下:

一些更新策略

  当使用carViewDone:方法中的reloadTable是我,我们告诉表视图,每个单元格中的内容是无效的,所有的内容都是过期的。这会导致表视图至少会对所有的(通常会更多)展示给用户的单元格进行更新。在应用程序中,这非常快,因为数据简单。但是当数据复杂又多的时候,这可能就变得很慢了

  所以,我们应该只告诉表格更新那些发生改变的单元格

  reloadRowsAtIndexPath:withRowAnimation:方法更新特定集合中的单元格。要使用这个方法,必须知道被查看单元格的索引路径——也即是被选中的单元格。所有我们能够获得的被选中的单元格的索引路径,可以通过使用indexPathForSelectedRow方法得到。步骤如下:

  • 在arrayOfCars下方添加一个状态常量:
@implementation CarTableViewController{
    NSMutableArray *arrayOfCars;
    NSIndexPath *currentViewCarPath;//添加的
}
  • 通过改变carToView:以设置值
- (Car *)carToView {
//    NSInteger index = [self.tableView indexPathForSelectedRow].row;
    currentViewCarPath = [self.tableView indexPathForSelectedRow];

//    return arrayOfCars[index];
    return arrayOfCars[currentViewCarPath.row];
}
  • 最后,更改carViewDone,只更新那些被查看且变化了的单元格
- (void)carViewDone:(BOOL)dataChanged {
//    if (dataChanged) {
//        [self.tableView reloadData];
//    }
    if (dataChanged) {
        [self.tableView reloadRowsAtIndexPaths:@[currentViewCarPath] withRowAnimation:YES];
    }
    currentViewCarPath = nil;
}

编辑年份

  型号和品牌都基于文本。可以使用文本标示年份并且展示数字文本的键盘。但这并不能确保用户的输入在特定范围年数内的4位年份数。相反,可以创建一个基于选择器的编辑视图,特定于罗马时间

  UIPickerView控件可以让我们展示更长的、由单个选项甚至多个分栏组成的列表。闹铃就是一个例子

  选择器看似是老虎机,由回转轴和回转轴上的多个位置组成。可指定回转轴上的数字和回转轴上每个数字的位置。可以通过实现UIPickerViewDataSource协议里的方法来设置回转轴

  也需要设置每个位置的内容、这一点如同字符串一样简单,也可同特定宽度和高度的自定义视图一样复杂。可以在UIPickerDelegateProtocol协议的方法中进行设置。委托也会在行被选中时通知我们,这点对于动态的更新选项或者其他部分的用户界面都是有用的

设置年份编辑器

  不像老虎机,我们只需要回转轴。需要将选择器视图放置到视图控制器中作为年份编辑器,步骤如下:

  • 拖入一个视图控制器到故事板中
  • 将导航栏添加到顶部,并且添加Cancel和Done导航栏按钮
  • 放置一个选择器视图,将其约束设置为前后距离0,底部标准距离
    IOS开发入门(12)-表视图I:基础知识_第24张图片

  • 添加继承自UIViewController的类YearEditViewController,并使其成为该类的新场景
    IOS开发入门(12)-表视图I:基础知识_第25张图片

  • 为Cancel和Done分别添加action,名称分别为editCancel和editDone

  • 为选择器视图添加一个属性命名为editPicker

所以这时候YearEditViewController.h应该有如下代码:

- (IBAction)editCancel:(id)sender;
- (IBAction)editDone:(id)sender;
@property (weak, nonatomic) IBOutlet UIPickerView *editPicker;

实现品牌年份选择器

  一辆汽车的品牌年份是个有范围的数字:不会大于当前年份,也不会小于第一辆生产的汽车

在修改年份编辑器之前,更新Car对象,使用新的最早的品牌年份:

  • 在Car.h文件中,添加代码#define kModelTYear 1908
  • 修改init方法,以使用kModelTYear代替1900
  • 将Car.h文件导入到YearEditViewController.m文件中

选择器数学

步骤如下:

  • 通过在YearEditViewController.h文件中的@interface的下方,添加下列代码以遵从选择器的两个方法
<UIPickerViewDelegate,UIPickerViewDataSource>
  • 添加代码,用于获得年份
```
- (NSInteger)getYearFromDate:(NSData*)theDate {
    NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];//1 年份要求将日期分解为组件,这需要有日历。该方法只针对罗马日了,虽然可以使用当前系统日历来对返回的值进行本地化

    NSDateComponents *components;

    components = [gregorian components:NSYearCalendarUnit fromDate:theDate];//2 从theDate返回日期的一个组件对象,用于初始化年份

    return components.year; //3 返回日期组件
}
  • 在viewDidLoad的下方插入可设置组和行的数量的日期源方法,在这里,最大年份是当前年份
- (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView  {
    return 1;
}
- (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component {
    NSInteger maxYear = [self getYearFromDate:[NSDate date]];

    maxYear += 1;

    return (maxYear - kModelTYear);
}
  • 返回每行所展示的值,方法是实现字符串的基于标题的委托方法。将该方法置于刚创建的两个方法的下面:
- (NSString *)pickerView:(UIPickerView *)pickerView
             titleForRow:(NSInteger)row
            forComponent:(NSInteger)component {
    NSInteger totalRows = [pickerView numberOfRowsInComponent:component];

    NSInteger displayVal = ((kModelTYear + totalRows) - 1) -row;

    return [NSString stringWithFormat:@"%d", displayVal];
}
  • 使用故事板将dataSource和delegate的旋转指针的outlet于编辑控制器的年份连接起来
    IOS开发入门(12)-表视图I:基础知识_第26张图片

      在开始测试之前,需要创建一个segue以打开年份编辑器:

  • 将一个展开的附件指示器(disclose accessory indicator)添加到年份单元格

  • 从年份单元格将一个模态选择segue拖入年份编辑器,命名该segue为YearEditSegue
    IOS开发入门(12)-表视图I:基础知识_第27张图片
    IOS开发入门(12)-表视图I:基础知识_第28张图片

运行如图:
IOS开发入门(12)-表视图I:基础知识_第29张图片

添加年份编辑协议

  协议非常简单,年份编辑器需要年份用展示,委托需要知道在按钮Done被选中时所选择的年份

  添加委托需要两个关键的公式。第一个公式设置初始选择器的行,用于当前的年份。最大的年份选中最顶上的那条,索引为0。最初的想法可能是选中目标年份,然后减去最小的年份数以获得那行。然而,这并不适用于:年份越大,差异越大,也就是索引数。我们要让最大年份数减去目标年份:Row=MaximumYear-TargetYear

  第二个公式将选中的行转为年份。再次,这是个相反的问题。索引为0的那行是最大年份:索引越大,年份越小。在此,年份是最大年份减去当前的行数:SelectedYear=MaximumYear-Row

  这仅仅得到的是获得的最大年份,并且是单纯的总行数加上最小年份数后减一:MaximumYear=(MinimumYear+TotalRows)-1

  最后一种选择是如何为选择器展示默认的选中的年份,如果没有设置值,即汽车对象将年份设置为1908。既然大多数人驾驶的汽车为当前年份前后左右的那些年份,最后的选择是展示这一点。可以展示默认年份为1908,但是驾驶1908年汽车的人概率太小了

遵循下列步骤以创建协议和相关的属性与方法:

  • 添加新的协议,命名为YearEditProtocol,并添加下列方法声明:
#import 

@protocol YearEditProtocol <NSObject>

- (NSInteger) editYearValue;

- (void) editYearDone:(NSInteger)yearValue;


@end
  • 导入协议到YearEditViewController.h文件中并添加一个属性以遵从委托,使用下列代码:
@property (weak, nonatomic) id  delegate;
  • 在.m文件中,从方法中初始化年份选择器。可以通过使用selectRow:inComponent:animated方法设置选择器的选中内容:
- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view
    NSInteger yearValue = [self.delegate editYearValue];

    if (yearValue == kModelTYear) {
        yearValue = [self getYearFromDate:[NSDate date]];
    }

    NSInteger rows = [self.editPicker numberOfRowsInComponent:0];
    NSInteger maxYear = (kModelTYear + rows) - 1;
    NSInteger row = maxYear -yearValue;

    [self.editPicker selectRow:row inComponent:0 animated:YES];
}
  • 填写好editCancel:和editDone
- (IBAction)editCancel:(id)sender {
    [self dismissViewControllerAnimated:YES completion:nil];
}

- (IBAction)editDone:(id)sender {
    NSInteger rows = [self.editPicker numberOfRowsInComponent:0];
    NSInteger maxYear = (kModelTYear + rows) -1;
    NSInteger year = maxYear - [self.editPicker selectedRowInComponent:0];

    [self.delegate editYearDone:year];

    [self dismissViewControllerAnimated:YES completion:nil];
}
@end
  • 导入年份编辑协议到ViewCarTableViewController.h文件中并添加该协议到已有的协议中
<MakeModelEditProtocol, UINavigationControllerDelegate,YearEditProtocol>
  • 打开ViewCarTableViewController.m文件并导入YearEditViewController.h
  • 将最后的else if条件语句添加到 prepareForSegue:sender:方法中
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
    if ([segue.identifier isEqualToString:@"MakeEditSegue"]) {
        MakeModelEditViewController *nextController;
        nextController = segue.destinationViewController;
        nextController.delegate = self;
        currentEdutType = kCurrentEditMake;
    } else if ([segue.identifier isEqualToString:@"ModelEditSegue"]) {
        MakeModelEditViewController *nextController;
        nextController = segue.destinationViewController;
        nextController.delegate = self;
        currentEdutType = kCurrentEditModel;
    } else if ([segue.identifier isEqualToString:@"YearEditSegue"]) {//下面是新加的
        YearEditViewController *nextController;
        nextController = segue.destinationViewController;

        nextController.delegate = self;
    }
}
  • 将协议方法添加到ViewCarTableViewController.m文件中,当年份改变时,记住更新汽车的值、标签和用于更新状态变量的数据
- (NSInteger)editYearValue {
    return self.myCar.year;
}

- (void)editYearDone:(NSInteger)yearValue {
    if(yearValue != self.myCar.year) {
        self.myCar.year = yearValue;

        self.yearLabel.text = [NSString stringWithFormat:@"%d",self.myCar.year];

        dataUpdated = YES;
    }

最后的运行结果如图:

IOS开发入门(12)-表视图I:基础知识_第30张图片

今天就介绍到这里咯
可以到ArnoldYU ,我的git上下载代码
我的另一个博客站点:Arnold-你们好啊

你可能感兴趣的:(IOS开发)