将数据从子模式视图传递到父视图控制器的最佳方法是什么?
我的iPad应用程序上有一个儿童模态登录屏幕,我想将用户信息传递给父拆分视图控制器。
我正在考虑使用NSNotification,但我不确定这是否是将数据传回父级的最简单/最有效的方法。
谢谢! 艾伦
答案 0 :(得分:14)
我建议as iPatel did使用delegation来解决您的问题。父视图控制器和登录视图控制器之间的关系使该模式适当。当一个对象创建另一个对象以履行特定职责时,应该将委托视为使创建的对象与创建者通信的一种方式。选择委派的一个特别令人信服的理由是,如果要完成的任务可能具有需要在对象之间进行高级别交互的多个步骤。您可以查看NSURLConnectionDelegate
protocol作为对此的说明。连接到URL是一项复杂的任务,涉及处理响应,满足身份验证质询,保存下载数据和处理错误等阶段,连接和委托在连接的整个生命周期内处理这些问题。
正如您可能已经注意到的那样,Objective-C协议用于实现委派,而不会将创建的对象(在本例中为您的登录视图控制器)紧密耦合到创建它的对象(父视图控制器)。然后,登录视图控制器可以与任何可以接收其协议中定义的消息的对象进行交互,而不是依赖于任何特定的类实现。明天,如果您收到允许任何视图控制器显示登录视图的要求,则登录视图控制器不需要更改。您的其他视图控制器可以实现其委托协议,创建和显示登录视图,并将自己分配为委托,而登录视图控制器不知道它们的存在。
您将在Stack Overflow上找到的一些委托示例可能非常令人困惑,而且非常不像内置框架中的内容。必须仔细选择协议的名称和接口,以及分配给每个对象的职责,以便最大化代码重用并实现代码的目标。
您应首先查看内置框架中的许多委托协议,以了解在代码中表达时的关系。这是另一个小例子,基于您的登录用例。我希望你会发现代表团的目的是明确的,所涉对象的角色和责任是明确的,并通过代码中的名称来表达。
首先,让我们看一下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
在某些方面,登录可能是一个复杂的交互式过程,因此我建议您认真考虑使用委派而不是通知。但是,有一点可能有问题的是委托只是一个对象。如果您需要知道登录视图控制器的进度和stae的多个不同对象,则可能需要使用通知。特别是如果登录过程可以被限制为非常简单,除了传递单向消息和数据之外不需要任何交互,那么通知可以成为可行的选择。您可以在userInfo
属性中的通知中传递任意变量,该属性是您决定填入其中的NSDictionary
。通知可以影响性能,但据我所知,现在只有当观察者数量达到数百时才会发生。即便如此,它仍然不是我认为最自然的,因为你有父对象(或多或少地控制了孩子的生命周期),要求第三方对象从子对象进行更新。
答案 1 :(得分: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);
}