2013-04-04 6 views
10

子モーダルビューから親ビューコントローラにデータを渡す最も良い方法は何ですか?Child Modal VCから親View Controllerにデータを渡す最も良い方法は?

私のiPadアプリには親のスプリットビューコントローラにユーザー情報を渡したい子モーダルログイン画面があります。

私はNSNotificationを使用することを考えていますが、これがデータを親に戻す最も簡単で効率的な方法であるかどうかはわかりません。

ありがとうございます! アラン

+0

私はこの目的のためにNSNotificationsをたくさん使用しています。私は子供が親の方法について知る必要があるので、この場合のプロトコルのファンではありませんが、それはデザインの好みのものです... –

答えて

14

私はあなたの問題を解決するためにdelegationを使用して、as iPatel didをお勧めします。親ビューコントローラとログインビューコントローラの関係により、このパターンが適切になります。あるオブジェクトが特定の責任を果たすために別のオブジェクトを作成する場合、作成されたオブジェクトを作成者と通信させる方法として委任を考慮する必要があります。委譲を選択する特に魅力的な理由は、達成されるタスクが、オブジェクト間の高レベルの相互作用を必要とする複数のステップを潜在的に有する場合であろう。あなたはNSURLConnectionDelegate protocolをこれの例として見ることができます。 URLへの接続は、レスポンスの処理、認証の問題の解決、ダウンロードしたデータの保存、エラーの処理などの段階を含む複雑な作業です。接続とデリゲートは、接続の存続期間にわたってこれらを一緒に処理します。

Objective-Cプロトコルでは、作成されたオブジェクト(この場合はログインビューコントローラ)を作成したオブジェクト(親ビューコントローラ)に強固に結合することなく、委任を達成するためにプロトコルが使用されます。ログインビューコントローラは、特定のクラス実装に頼るのではなく、プロトコルで定義されたメッセージを受け取ることができる任意のオブジェクトと対話できます。明日、ビューコントローラにログインビューを表示させる必要がある場合、ログインビューコントローラは変更する必要はありません。他のビューコントローラは、デリゲートプロトコルを実装し、ログインビューを作成して表示し、ログインビューコントローラが存在を知らずにデリゲートとして自分自身を割り当てることができます。

スタックオーバーフローで表示される一部の委任の例は、非常に混乱し、組み込みのフレームワークに見られるようなものではありません。コードの再利用が最大化され、コードの目的が達成されるように、各オブジェクトに割り当てられた責任だけでなく、プロトコルの名前とインタフェースを慎重に選択する必要があります。

まず、組み込みフレームワーク内の多くのデリゲートプロトコルを見て、コードで表現された関係がどのように見えるかを調べる必要があります。あなたのログインユースケースに基づいたもう少し小さな例があります。私は、代表団の目的がはっきりしており、関係するオブジェクトの役割と責任がコード内の名前を通じて明確で表現されていることがわかってほしいです。

まずは、LoginViewControllerのデリゲートプロトコルを見てみましょう:

#import <UIKit/UIKit.h> 

@protocol LoginViewControllerDelegate; 

@interface LoginViewController : UIViewController 

// We choose a name here that expresses what object is doing the delegating 
@property (nonatomic, weak) id<LoginViewControllerDelegate> delegate; 

@end 

@protocol LoginViewControllerDelegate <NSObject> 

// The methods declared here are all optional 
@optional 

// We name the methods here in a way that explains what the purpose of each message is 
// Each takes a LoginViewController as the first argument, allowing one object to serve 
// as the delegate of many LoginViewControllers 
- (void)loginViewControllerDidLoginSuccessfully:(LoginViewController *)lvc; 
- (void)loginViewController:(LoginViewController *)lvc didFailWithError:(NSError *)error; 
- (void)loginViewControllerDidReceivePasswordResetRequest:(LoginViewController *)lvc; 
- (void)loginViewControllerDiDReceiveSignupRequest:(LoginViewController *)lvc; 
- (BOOL)loginViewControllerShouldAllowAnonymousLogin:(LoginViewController *)lvc; 

@end 

ログインコントローラはそのデリゲートへのイベントの数を伝えるだけでなく、その動作をカスタマイズするために使用される情報のためにそのデリゲートを求めることができます。これは、ユーザのアクションへの対応の一環として、その実装にデリゲートにイベントを伝達する:

#import "LoginViewController.h" 

@interface LoginViewController() 

@property (weak, nonatomic) IBOutlet UIButton *anonSigninButton; 

@end 

@implementation LoginViewController 

- (void)viewDidLoad 
{ 
    [super viewDidLoad]; 

    // Here we ask the delegate for information used to layout the view 
    BOOL anonymousLoginAllowed = NO; 
    // All our protocol methods are @optional, so we must check they are actually implemented before calling. 
    if ([self.delegate respondsToSelector:@selector(loginViewControllerShouldAllowAnonymousLogin:)]) { 
     // self is passed as the LoginViewController argument to the delegate methods 
     // in this way our delegate can serve as the delegate of multiple login view controllers, if needed 
     anonymousLoginAllowed = [self.delegate loginViewControllerShouldAllowAnonymousLogin:self]; 
    } 
    self.anonSigninButton.hidden = !anonymousLoginAllowed; 
} 

- (IBAction)loginButtonAction:(UIButton *)sender 
{ 
    // We're preteneding our password is always bad. So we assume login succeeds when allowed anonmously 
    BOOL loginSuccess = [self isAnonymousLoginEnabled]; 
    NSError *loginError = [self isAnonymousLoginEnabled] ? nil : [NSError errorWithDomain:@"domain" code:0 userInfo:nil]; 

    // Fake concurrency 
    double delayInSeconds = 1.0; 
    dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC)); 
    dispatch_after(popTime, dispatch_get_main_queue(), ^(void){ 
     // Notify delegate of failure or success 
     if (loginSuccess) { 
      if ([self.delegate respondsToSelector:@selector(loginViewControllerDidLoginSuccessfully:)]) { 
       [self.delegate loginViewControllerDidLoginSuccessfully:self]; 
      } 
     } 
     else { 
      if ([self.delegate respondsToSelector:@selector(loginViewController:didFailWithError:)]) { 
       [self.delegate loginViewController:self didFailWithError:loginError]; 
      } 
     } 
    }); 
} 

- (IBAction)forgotPasswordButtonAction:(id)sender 
{ 
    // Notify delegate to handle forgotten password request. 
    if ([self.delegate respondsToSelector:@selector(loginViewControllerDidReceivePasswordResetRequest:)]) { 
     [self.delegate loginViewControllerDidReceivePasswordResetRequest:self]; 
    } 
} 

- (IBAction)signupButtonAction:(id)sender 
{ 
    // Notify delegate to handle signup request. 
    if ([self.delegate respondsToSelector:@selector(loginViewControllerDiDReceiveSignupRequest:)]) { 
     [self.delegate loginViewControllerDiDReceiveSignupRequest:self]; 
    } 
} 

- (BOOL)isAnonymousLoginEnabled 
{ 
    BOOL anonymousLoginAllowed = NO; 

    if ([self.delegate respondsToSelector:@selector(loginViewControllerShouldAllowAnonymousLogin:)]) { 
     anonymousLoginAllowed = [self.delegate loginViewControllerShouldAllowAnonymousLogin:self]; 
    } 
    return anonymousLoginAllowed; 
} 

@end 

メインビューコントローラをインスタンス化し、提示ログインビューコントローラを、そのデリゲートメッセージを処理します。

#import "MainViewController.h" 
#import "LoginViewController.h" 

#define LOGGED_IN NO 

@interface MainViewController() <LoginViewControllerDelegate> 

@end 

@implementation MainViewController 

- (void)viewDidLoad 
{ 
    [super viewDidLoad]; 

    // Fake loading time to show the modal cleanly 
    if (!LOGGED_IN) { 
     double delayInSeconds = 1.0; 
     dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC)); 
     dispatch_after(popTime, dispatch_get_main_queue(), ^(void){ 
      // Create a login view controller, assign its delegate, and present it 
      LoginViewController *lvc = [[LoginViewController alloc] init]; 
      lvc.delegate = self; 
      [self presentViewController:lvc animated:YES completion:^{ 
       NSLog(@"modal completion finished."); 
      }]; 
     }); 
    } 
} 

#pragma mark - LoginViewControllerDelegate 


- (void)loginViewControllerDidLoginSuccessfully:(LoginViewController *)lvc 
{ 
    NSLog(@"Login VC delegate - Login success!"); 
    [self dismissViewControllerAnimated:YES completion:NULL]; 
} 

- (void)loginViewController:(LoginViewController *)lvc didFailWithError:(NSError *)error 
{ 
    // Maybe show an alert... 
    // UIAlertView *alert = ... 
} 

- (void)loginViewControllerDidReceivePasswordResetRequest:(LoginViewController *)lvc 
{ 
    // Take the user to safari to reset password maybe 
    NSLog(@"Login VC delegate - password reset!"); 
} 

- (void)loginViewControllerDiDReceiveSignupRequest:(LoginViewController *)lvc 
{ 
    // Take the user to safari to open signup form maybe 
    NSLog(@"Login VC delegate - signup requested!"); 
} 

- (BOOL)loginViewControllerShouldAllowAnonymousLogin:(LoginViewController *)lvc 
{ 
    return YES; 
} 

@end 

ログインは複雑なインタラクティブなプロセスになることがありますので、通知の代わりに委任を真剣に検討することをお勧めします。しかし、問題となる可能性のあることの1つは、代理人が必ずしも単一のオブジェクトであるということです。複数の異種のオブジェクトにログインビューコントローラの進行状況とステータスを知らせる必要がある場合は、通知を使用する必要があります。特に、ログインプロセスが一方向のメッセージやデータを渡す以上のやりとりを必要としないように、非常に単純な制約を受けることができる場合、通知は実行可能なオプションになる可能性があります。 userInfoプロパティ内の任意の変数を通知内に渡すことができます。NSDictionaryは、あなたがそれを入れることを決めるものです。通知はパフォーマンスに影響を与える可能性がありますが、私は現在、オブザーバーが数百に数えられる場合にのみ発生することを理解しています。それでも、子オブジェクトからの更新をサードパーティのオブジェクトに要求する親オブジェクト(子の存続期間を多かれ少なかれ制御する)があるので、それは私の心の中で最も自然なものではありません。

+1

うわー、このような徹底的な説明をしてくれてありがとう、本当にありがとう。私は、デリゲートの作成とその使用時期について多くのことを理解しています。どうもありがとうございました! – Alan

+0

私はすべての代議員がそれ自身を返すことに気付きました。すべての代表者は一般的にそれ自体を返すのか? – Alan

+1

@Alanでは、実際には 'self'を返さないので、メソッドに引数として' self'を渡しています。デリゲートプロトコルには、デリゲートするオブジェクトのパラメータがそのメソッドに含まれていることがよくあります。たとえば、 'UITableViewDelegate'を見てください。すべてのメソッドの最初の引数は' UITableView'です。 Delegatingオブジェクトはデリゲートメソッド呼び出しで自身を渡します。そのため、デリゲートはそのような複数のオブジェクトのデリゲートとして機能し、そのメソッドを誰が呼び出しているかを区別することができます。途中で大きな質問ですが、私はそれを私の答えに明示的に加えるべきです。 –

5

プロトコルを使用すると、それは最良の方法です。

私はあなたに読んで、

また、この質問議定書を作成する方法については、基本的な考え方与える:次のコード How do I create delegates in Objective-C?

は、以下のコードでは、ここで、プロトコルのためのあなたの基本的なアイデアを与えるとしますボタンのタイトルはMasterViewControllerからDetailViewControllerになります。

#DetailViewController.h 

#import <UIKit/UIKit.h> 

@protocol MasterDelegate <NSObject> 
-(void) getButtonTitile:(NSString *)btnTitle; 
@end 


@interface DetailViewController : MasterViewController 

@property (nonatomic, assign) id<MasterDelegate> customDelegate; 

#DetailViewController.m 

if([self.customDelegate respondsToSelector:@selector(getButtonTitile:)]) 
{ 
      [self.customDelegate getButtonTitile:button.currentTitle];  
} 

#MasterViewController.m 

create obj of DetailViewController 

DetailViewController *obj = [[DetailViewController alloc] init]; 
obj.customDelegate = self; 
[self.navigationController pushViewController:reportTypeVC animated:YES]; 

and add delegate method in MasterViewController.m for get button title. 

#pragma mark - 
#pragma mark - Custom Delegate Method 

-(void) getButtonTitile:(NSString *)btnTitle; 
{ 
    NSLog(@"%@", btnTitle); 

} 
+0

素晴らしい!私はこれを試して、あなたに戻ってきます。ありがとうございました。好奇心から外に – Alan

+0

なぜこのメソッドはNSNotificationより良いですか? – Alan

関連する問題