承接上节
在本节中,将对之前的故事版进行改进
改进故事版I
先大致介绍一下协议
在大多数应用程序中,都会存在一些需要与其他对象中的触发行为者进行交换的数据,这导致互相关联、不独立的代码难以维护和复用。相应的解决办法,就是使用协议(protocol)。
协议增加了代码的可维护性、复用性和灵活性。可以认为协议是请求者和供应者之间的合同。供应者常被叫做代理(delegate),统一实现一些方法,从而请求者可以调用。每个方法通常用于如下三个目的之一
- 从代理请求数据,如Car对象
- 在改变时通知代理,以允许存储、取消或再次显示数据
- 指向特定的行为,如呼叫某人前来取车
协议的主要优点在于复用性,并且每个实现了所规定的方法的类,都可以成为代理。相似的是,任何类都可以使用协议方法,请求信息或发起行为。
要了解更多关于协议的信息,查看Programming in Objective-C 2.0,第二版,Stephen G Kochan著,有Addison We是可以出版,翻看该书中第231页关于协议的内容。也可以阅读苹果公司Objective-C文档中关于协议的章节。
使用协议交换数据
协议的常见用法是交换数据。编辑者或请求者需要信息,由代理提供并且或许由代理更新。
在这种情况下,汽车编辑者需要知道汽车对象和拍照。Add/View场景或代理,在汽车对象有改变时需要被通知到,从而可以更新显示内容。这要求协议有三个方法:
- carToEdit返回汽车对象以进行编辑
- carNumber返回被编辑汽车的拍照(不是索引数)
- editedCarUpdated告诉代理,编辑操作已经完成
现在创建新的Objective-C协议,命名为CarEditViewControllerProtocol,并将它添加到项目中。
需要注意的是,需要选择Objective-C File中,File Type 选择protocol
代码如下:
#import <Foundation/Foundation.h>//在协议中个导入用到的类的所有头文件。Foundation.h定义了许多Cocoa类型,包括NSInteger类型
@class Car;
@protocol CarEditViewControllerProtocol <NSObject>//@protocol是声明协议的指令。接下来的部分是协议的名称,接着是协议要包含的内容。在这种情况下,CarEditViewControllerProtocol有权限访问任何在NSObject协议中声明的方法,如self、class和description等
- (Car*)carToEdit;//指定方法声明的方式如同公共类方法
- (NSInteger)carNumber;
- (void)editedCarUpdated;
@end
在定义好协议后,改变要使用该协议的请求者。在CarValet应用中,从改变导入文件开始,进行修改。在CarEditViewController.h文件中,移除carNumber属性,然后按下列代码进行更改
#import <UIKit/UIKit.h>
#import "CarEditViewControllerProtocol.h"//修改
@class Car;
@interface CarEditViewController : UIViewController
//@property (nonatomic) NSInteger carNumber;//修改
@property (weak, nonatomic) id <CarEditViewControllerProtocol> delegate;//修改
@property (strong,nonatomic) Car *currentCar;
@property (weak, nonatomic) IBOutlet UILabel *carNumberLabel;
@property (weak, nonatomic) IBOutlet UITextField *makeField;
@property (weak, nonatomic) IBOutlet UITextField *modelField;
@property (weak, nonatomic) IBOutlet UITextField *yearField;
@property (weak, nonatomic) IBOutlet UITextField *fuelField;
@end
最后一个修改是协议中十分重要的一部分。id是占位符,能代表任意类的对象。这意味着任意类,包含哪些我们还没有想到过的类,都可以成为该协议的代理。虽然Edit场景要求Car对象拥有某些特定属性,但我们依然拥有设置该代理的灵活性。可以在其他的项目中重用到Edit场景。另一处要注意的地方是,在声明id变量类型时并不适用星号。id变量的声明应该是delegate
而非*delegate
。
在类型之后,用尖括号括起的CarEditViewControllerProtocol,这表明无论delegate所属的类是什么,他都必须遵从协议。也就是说,它必须实现协议要求的所有方法。
现在更新CarEditViewController.m中的viewDidLoad方法,代码如下
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
NSString *carNumberText;
carNumberText = [NSString stringWithFormat:@"Car Number:%d",[self.delegate carNumber]];//修改 启辰的拍照,现在从代理返回
self.carNumberLabel.text = carNumberText;
self.currentCar = [self.delegate carToEdit];//修改 因已编辑过Car对象,故而通知代理。
self.makeField.text = self.currentCar.make;
self.modelField.text = self.currentCar.model;
self.yearField.text = [NSString stringWithFormat:@"%d",self.currentCar.year];
self.fuelField.text = [NSString stringWithFormat:@"%0.2f",self.currentCar.fuelAmount];
最后一处修改是通知Add/View场景进行自我更新。在viewWillDisappear:的结尾处添加对[self.delegate editedCarUpdated]
的调用
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
[self.delegate editedCarUpdated];
}
现在已经完成请求者部分,是时候更新代理部分了。
修改ViewController.h文件顶部的代码
#import <UIKit/UIKit.h>
#import "CarEditViewControllerProtocol.h"//修改
@interface ViewController : UIViewController
<CarEditViewControllerProtocol>//修改
@property (weak, nonatomic) IBOutlet UILabel *totalCarsLabel;
@property (weak, nonatomic) IBOutlet UILabel *CarNumberLabel;
@property (weak, nonatomic) IBOutlet UILabel *CarInfoLabel;
- (IBAction)newCar:(id)sender;
- (IBAction)previousCar:(id)sender;
- (IBAction)nextCar:(id)sender;
@end
<CarEditViewControllerProtocol>
的意思是ViewController支持——也即是说,遵从——我们的协议。当然,我们还未实现任何支持。此处,Xcode展现了它帮助你检查代码完整的方法之一。查看工具栏中间部分的状态栏,可以看到右边有黄色警告三角形。表明在编译代码时已导致警告。单击警告可以找到更多细节。因为协议的方法部分不存在,该警告表明存在不完整的实现。
在newCar:方法的上方添加协议方法:
- (Car*)carToEdit {
return arrayOfCars[displayedCarIndex];
}
- (NSInteger)carNumber {
return displayedCarIndex + 1;
}
- (void)editedCarUpdated {
[self displayCurrentCarInfo];
}
最后还有一个错误提示需要解决
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if([segue.identifier isEqualToString:@"EditSegue"]){
CarEditViewController *nextCOntroller;
nextCOntroller = segue.destinationViewController;
nextCOntroller.delegate = self;//修改
Car *currentCar = arrayOfCars[displayedCarIndex];
nextCOntroller.currentCar = currentCar;
}
}
然后就是成功运行了。
改进故事版2
IOS 6 引入了一种更好的方法来将数据返回到激发segue的视图控制器:简单地将一个特殊类型的IBAction添加到视图控制器中即可。同城,动作方法有个看似为(id)sender的参数,或者根本没有参数。通过将参数修改为(UIStoryboard*)segue,可以创建特殊类型的动作——一种能接受segue对象的动作。
仅当一个场景激发另一个场景的切换时才会发送prepareForSegue:sender:。它是向前segue。在IOS 6中,存在另一种类型的segue,其动作是相反的:在一个changing想要返回到之前的场景时发送prepareForSeguesender:。它会回退(unwinds)首次打开场景的segue,并且在打开之前的场景时不会创建新场景。
要查看这个回退动作,修改如下代码:
在ViewController.m底部添加
- (IBAction)editingDone:(UIStoryboardSegue*)segue {
[self displayCurrentCarInfo];
}
现在需要激发这个回退动作,方法如同Edit的修改
添加按钮(这里换了个样式)
Ctrl将Done拉到下图位置,选择editingDone
在CarEditViewController.m中修改如下方法
- (IBAction)editingDone:(UIStoryboardSegue*)segue {
NSLog(@"\neditedCarUpdated called!\n");
[self displayCurrentCarInfo];
}
每次Done之后,会在控制台看到
运行的时候发现,还是没能立即刷新,然而你对同一个再次编辑,再次返回,会发现他保存的是上一个的变化。
讲道理,我们要的应该是点返回只有立即更新才对,那么做如下修改
在CarEditViewController.m中添加方法,这个跟之前跳转到类似,我就不多解释了
添加segue标签
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{
if([segue.identifier isEqualToString:@"EditDoneSegue"]){
self.currentCar.make = self.makeField.text;
self.currentCar.model = self.modelField.text;
self.currentCar.year = [self.yearField.text integerValue];
self.currentCar.fuelAmount = [self.fuelField.text floatValue];
}
}
现在运行之后,一旦更更久能立即显示啦
今天的介绍就到这里咯
我的另一个博客站点:Arnold-你们好啊