IOS開發入門(5)

承接上節

在本節中,將對之前的故事版進行改進

改進故事版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協議中聲明的方法,如selfclassdescription

- (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-你們好啊

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章