- (IBAction)baseTap:(id)sender { NLMailComposerViewController *mail = [[NLMailComposerViewController alloc] init]; mail.delegate = self; [self.view addSubview: mail.view]; NSArray *toRecipients = [NSArray arrayWithObject:@"[email protected]"]; NSString *subject = @"this is subject"; NSString *emailBody = @"this is body!"; [mail showMailPickerToRecipients:toRecipients subject:subject emailBody:emailBody isHTML:NO]; } - (void)emailDidFinished:(NLMailComposerViewController *)mailComposerViewController{ NSLog(@"邮件发送后代理回调"); // mailComposerViewController. [mailComposerViewController.view removeFromSuperview]; }
在baseTap方法中我要present出邮件view,由于以上代码用了arc,所以在离开baseTap方法后mail.view的controller释放了,导致emailDidFinished回调时crash。
当然我们可以在这个类中定义一个NLMailComposerViewController属性来解决这个办法。但总觉的这个不是理想解决方案。
Each view controller object is the sole owner of its view. You must not associate the same view object with multiple view controller objects. The only exception to this rule is that a container view controller implementation may add this view as a subview in its own view hierarchy. Before adding the subview, the container must first call its addChildViewController: method to create a parent-child relationship between the two view controller objects.
addChildViewController可以解决view controller无法获得相应回调事件的问题!
代码更正:
- (IBAction)baseTap:(id)sender { NLMailComposerViewController *mail = [[NLMailComposerViewController alloc] init]; mail.delegate = self; [self.view addSubview: mail.view]; [self addChildViewController:mail]; NSArray *toRecipients = [NSArray arrayWithObject:@"[email protected]"]; NSString *subject = @"this is subject"; NSString *emailBody = @"this is body!"; [mail showMailPickerToRecipients:toRecipients subject:subject emailBody:emailBody isHTML:NO]; } - (void)emailDidFinished:(NLMailComposerViewController *)mailComposerViewController{ NSLog(@"邮件发送后代理回调"); // mailComposerViewController. [mailComposerViewController removeFromParentViewController]; [mailComposerViewController.view removeFromSuperview]; }
PS:
[mailComposerViewController removeFromParentViewController]; 这个是删除 self addChildViewController加进去的view controller实例。 其view实例还是需要用 [mailComposerViewController.view removeFromSuperview]来删除。)
BTW:
在苹果的WWDC2011大会视频的 《Session 101 - What’s New in Cocoa》 和 《Session 102 - Implementing UIViewController Containment》 中介绍了苹果在iOS5中给UIViewController新增加的5方法以及一个属性:
// 方法
addChildViewController:
removeFromParentViewController: transitionFromViewController:toViewController:duration:options:animations:completion: willMoveToParentViewController:
didMoveToParentViewController:
// 属性
@property(nonatomic,readonly) NSArray *childViewControllers
原来的问题:
这些新增的方法和属性用于改进我们的编程方式。那么让我们先看看以前的对于UIViewController的使用有什么潜在的问题,认清问题,我们才能理解苹果改变的意义。
在以前,一个UIViewController的View可能有很多小的子view。这些子view很多时候被盖在最后,我们在最外层ViewController的viewDidLoad方法中,用addSubview增加了大量的子view。这些子view大多数不会一直处于界面上,只是在某些情况下才会出现,例如登陆失败的提示view,上传附件成功的提示view,网络失败的提示view等。但是虽然这些view很少出现,但是我们却常常一直把它们放在内存中。另外,当收到内存警告时,我们只能自己手工把这些view从super view中去掉。
改变:
苹果新的API增加了addChildViewController方法,并且希望我们在使用addSubview时,同时调用[self addChildViewController:child]方法将sub view对应的viewController也加到当前ViewController的管理中。对于那些当前暂时不需要显示的subview,只通过addChildViewController把subViewController加进去。需要显示时再调用transitionFromViewController:toViewController:duration:options:animations:completion方法。
另外,当收到系统的Memory Warning的时候,系统也会自动把当前没有显示的subview unload掉,以节省内存。
参考:https://developer.apple.com/library/ios/#documentation/UIKit/Reference/UIViewController_Class/Reference/Reference.html
https://developer.apple.com/library/ios/#featuredarticles/ViewControllerPGforiPhoneOS/ViewLoadingandUnloading/ViewLoadingandUnloading.html#//apple_ref/doc/uid/TP40007457-CH10
关于addChildViewController使用的例子:
ios5中UIViewController addChildViewController等新方法
联想:在category中使用iOS扩展机制 - associative