[iOS x Design Pattern] ViewController之間傳遞參數 Delegate Pattern

在iOS中頁面的參數傳遞有幾種作法,最常碰到的是相鄰的兩個頁面傳遞,其中又以把參數往下一頁傳遞的狀況較多,比較少的情況是需要把參數傳遞給前一頁

題外話,一個月前我還什麼都不會的時候,跑去參加iOS Bootcamp 2013那時候真的搞不懂iOS的delegate,本來還以為跟匿名函式一樣:P 但是完全不是,許多用字也與過去的不同,像是interface在iOS中是用Protocol,總之,天下武功大逕相同,殊途同歸,只要了解語言特性去習慣它,勿故步自封便能無招勝有招。

言歸正傳

情境

ViewControllerA -> ViewControllerB

[傳遞給下一頁]

Passing Data Forward

ViewControllerB *viewControllerB = [[ViewControllerB alloc] initWithNib:@"ViewControllerB" bundle:nil];
viewControllerB.isSomethingEnabled = YES;
[self pushViewController:viewControllerB animated:YES];

Passing Data Forward using Segue's

-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{
    if([segue.identifier isEqualToString:@"showDetailSegue"]){
        ViewControllerB *controller = (ViewControllerB *)segue.destinationViewController;
        controller.isSomethingEnabled = YES;
    }
}

tips:你不能直接把值設定給UI物件,因為在prepare segue階段該物件尚未產生實體,會導致你傳資料給一個不存在的物件。

[傳遞給前一頁]

透過Protocol限制delegate的物件

  1. 建立Protocol

ViewControllerB.h

@protocol ViewControllerBDelegate <NSObject>
- (void)addItemViewController:(ViewControllerB *)controller didFinishEnteringItem:(NSString *)item;
@end
@interface ViewControllerB : UIViewController
@property (nonatomic, weak) id <ViewControllerBDelegate> delegate;
@end

ViewControllerB.m

#import "ViewControllerA.h"
//When you need viewControllerA
- (void) setDataBack{
  [self.delegate didFinishEnteringItem:YES];
}

set delegate
加上介面ViewControllerBDelegate
ViewControllerA.h

@interface ViewControllerA : UIViewController <ViewControllerBDelegate>

ViewControllerA.m

-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{
    if([segue.identifier isEqualToString:@"showDetailSegue"]){
        ViewControllerB *controller = (ViewControllerB *)segue.destinationViewController;
        controller.delegate = self;
    }
}

更簡單的方式,把ViewControllerA當成物件傳進來

ViewControllerB.h

@property (nonatomic, strong) id previousViewController;

ViewControllerB.m

#import "ViewControllerA.h"
//When you need viewControllerA
- (void) setDataBack{
  ViewControllerA *controllerA = (ViewControllerA)self.previousViewController;
  [controllerA setText:@"set somthing data"];
}

別忘了在前一頁的時候要把自己傳過去

ViewControllerA.m

-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{
    if([segue.identifier isEqualToString:@"showDetailSegue"]){
        ViewControllerB *controller = (ViewControllerB *)segue.destinationViewController;
        controller.previousViewController = self;
    }
}

這樣也就不用寫什麼Protocol,就懶人來說是比較容易的方式XD

結論

比較後面這兩種方式,一種是正規的Delegate Pattern,一種是偷吃步懶人寫法,前者有較嚴謹的規範,你可以定義好前一頁該做什麼事情並且不用管前一頁是誰(即便是ViewControllerC),只要是符合Protocol的人都能當他的前一頁; 而後面偷吃步的作法,好處是可以偷懶不用寫Protocol,若你今天要回傳的資料很多,寫起來也是挺麻煩的,但如果你是跟別人合作,或是你的ViewControllerB是好幾頁的子頁面,就比較建議用正規寫法囉。

另外值得一提的是,如果你要傳遞的頁面距離該頁面有些距離,比如你要傳遞給你的曾祖父資訊,那就要動用到觀察者模式Observer pattern了,會在下一篇介紹給大家。

refernce:Passing Data between View Controllers