我已经查看过相关问题,但没有解决我的问题。
我正在尝试连续使用dismissViewControllerAnimated:animated:completion
和presentViewControllerAnimated:animated:completion
。使用故事板,我通过部分卷曲动画模拟呈现InfoController。
部分卷曲在InfoController上显示我想要发起MFMailComposeViewController
的按钮。因为部分卷曲部分隐藏了MFMailComposeViewController
,所以我首先要通过取消动画部分卷曲来解除InfoController。然后我想让MFMailComposeViewController
动画。
目前,当我尝试这个时,部分卷曲没有动画,但MFMailComposeViewController
没有出现。我也有一个警告:
警告:尝试提供MFMailComposeViewController:on InfoController:其视图不在窗口层次结构中!
InfoController.h:
#import <UIKit/UIKit.h>
#import <MessageUI/MessageUI.h>
#import <MessageUI/MFMailComposeViewController.h>
@interface InfoController : UIViewController <MFMailComposeViewControllerDelegate>
@property (weak, nonatomic) IBOutlet UIButton *emailMeButton;
-(IBAction)emailMe:(id)sender;
@end
InfoController.m
#import "InfoController.h"
@interface InfoController ()
@end
@implementation InfoController
- (void)viewDidLoad
{
[super viewDidLoad];
}
- (IBAction)emailMe:(id)sender {
[self dismissViewControllerAnimated:YES completion:^{
[self sendMeMail];
}];
}
- (void)sendMeMail {
MFMailComposeViewController *mailController = [[MFMailComposeViewController alloc] init];
if([MFMailComposeViewController canSendMail]){
if(mailController)
{
NSLog(@"%@", self); // This returns InfoController
mailController.mailComposeDelegate = self;
[mailController setSubject:@"I have an issue"];
[mailController setMessageBody:@"My issue is ...." isHTML:YES];
[self presentViewController:mailController animated:YES completion:nil];
}
}
}
- (void)mailComposeController:(MFMailComposeViewController*)controller
didFinishWithResult:(MFMailComposeResult)result
error:(NSError*)error;
{
if (result == MFMailComposeResultSent) {
NSLog(@"It's sent!");
}
[self dismissViewControllerAnimated:YES completion:nil];
}
此外,如果我在[self dismissViewControllerAnimated:YES completion:^{}];
中注释掉(IBAction)emailMe
,MFMailComposeViewController
会动画,但它会部分隐藏在部分卷曲之后。我怎样才能首先解除卷曲然后在MFMailComposeViewController
?
非常感谢!
修改:如果我发表评论[self dismissViewControllerAnimated:YES completion:^{}];
答案 0 :(得分:19)
视图控制器之间的通信问题是由于父子视图控制器关系不明确而导致的......如果不使用协议和委派,这将无法正常工作。
经验法则是:
(听起来很无情,但如果你想的话,这是有意义的。)
转换为ViewController关系:呈现视图控制器需要了解其子视图控制器,但子视图控制器必须不知道关于其父(呈现)视图控制器:子视图控制器使用其委托将消息发回给他们(未知)的父母。
如果必须在标头中添加@Class声明来修复链式#import编译器警告,那么您就知道出了问题。交叉引用总是一件坏事(顺便说一下,这也是委托应该总是(赋值)和永远(强)的原因,因为这会导致交叉引用循环和一组僵尸)
所以,让我们看一下你项目的这些关系:
正如您所说,我假设调用控制器名为 MainController 。所以我们有:
所以你想拥有这个:
子控制器定义了一个协议,并且有一个符合其协议的未指定类型的委托(换句话说:委托可以是任何对象,但必须有这一个方法)
@protocol InfoControllerDelegate
- (void)returnAndSendMail;
@end
@interface InfoControllerDelegate : UIViewController // …
@property (assign) id<InfoControllerDelegate> delegate
// ...
@end
...并且MainController同时采用InfoControllerDelegate和MFMailComposeDelegate协议,因此它可以再次关闭MFMailComposer(注意,这并不是,并且可能不应该是强大的属性,只是显示这个在这里说清楚)
@interface MainController <InfoControllerDelegate, MFMailComposeViewControllerDelegate>
@property (strong) InfoController *infoController;
@property (strong) MFMailComposeViewController *mailComposer;
// however you get the reference to InfoController, just assuming it's there
infoController.delegate = self;
[self presentViewController:infoController animated:YES completion:nil];
&#39; infoController.delegate = self&#39;是至关重要的一步。这使infoController有可能在不知道ObjectType(Class)的情况下将消息发送回MainController。不需要#import。它只知道,它是一个具有方法-returnAndSendMail的对象;这就是我们需要知道的全部内容。
通常你会用alloc / init创建你的viewController并让它懒洋洋地加载它的xib。 或者,如果您正在使用Storyboards和Segues,您可能想拦截segue(在MainController中)以便以编程方式设置委托:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
// hook in the segue to set the delegate of the target
if([segue.identifier isEqualToString:@"infoControllerSegue"]) {
InfoController *infoController = (InfoController*)segue.destinationViewController;
infoController.delegate = self;
}
}
按下eMail按钮时,将调用委托(MainController)。请注意,self.delegate是MainController是不相关的,它只是具有此方法的相关性-returnAndSendMail
- (IBAction)sendEmailButtonPressed:(id)sender {
// this method dismisses ourself and sends an eMail
[self.delegate returnAndSendMail];
}
...和这里(在MainController中!),你将解散InfoController(清理,因为它是MainController的责任)并呈现MFMailController:< / p>
- (void)returnAndSendMail {
// dismiss the InfoController (close revealing page)
[self dismissViewControllerAnimated:YES completion:^{
// and present MFMailController from MainController
self.mailComposer.delegate = self;
[self presentViewController:self.mailComposer animated:YES completion:nil];
}];
}
所以,您使用MFMailController做的事情几乎与InfoController相同。两者都有他们未知的代表,所以他们可以回复,如果他们这样做,你可以解雇他们并继续你应该做的任何事情。
答案 1 :(得分:3)
当您呈现viewController时,您要呈现来自的viewController需要位于视图层次结构中。这两个VC在其属性presentingViewController
和presentedViewController
中保持彼此指针,因此两个控制器都需要在内存中。
通过解雇然后从被解雇的视图控制器运行呈现代码,您正在打破这种关系。
相反,您应该在mailController
被解雇后,在显示InfoController
的viewController中展示您的infoController
。
执行此操作的传统方式是通过委托回调到底层的viewController,然后处理解雇和呈现的两个步骤。但现在我们使用块..
将您的sendMeMail
方法移至显示infoController
然后 - 在infoController
- 你可以在完成块中调用它......
- (IBAction)emailMe:(id)sender {
UIViewController* presentingVC = self.presentingViewController;
[presentingVC dismissViewControllerAnimated:YES completion:^{
if ([presentingVC respondsToSelector:@selector(sendMeMail)])
[presentingVC performSelector:@selector(sendMeMail)
withObject:nil];
}];
}
(您需要获取一个指向self.presentingViewController
的本地指针,因为在控制器被解除后您无法引用该属性)
或者通过将infoController
代码放在完成块中来保留sendMeMail
中的所有代码:
- (IBAction)emailMe:(id)sender {
UIViewController* presentingVC = self.presentingViewController;
[presentingVC dismissViewControllerAnimated:YES completion:^{
MFMailComposeViewController *mailController =
[[MFMailComposeViewController alloc] init];
if([MFMailComposeViewController canSendMail]){
if(mailController) {
NSLog(@"%@", self); // This returns InfoController
mailController.mailComposeDelegate = presentingVC; //edited
[mailController setSubject:@"I have an issue"];
[mailController setMessageBody:@"My issue is ...." isHTML:YES];
[presentingVC presentViewController:mailController
animated:YES
completion:nil];
}
}
}];
}
更新/编辑
如果将所有代码放在完成块中,则应将mailController的mailComposeDelegate设置为presentingVC
,而不是self
。然后在呈现viewController中处理委托方法。
更新2
@Auro使用委托方法提供了详细的解决方案,并在他的评论中指出,这最好地表达了角色的分离。我的传统主义者同意,我认为dismissViewController:animated:completion
是一个kludgy and easily misunderstood API。
Apple的文档have this to say:
解雇呈现的视图控制器
当需要关闭呈现的视图控制器时,首选方法是让呈现视图控制器关闭它。换句话说,只要有可能,呈现视图控制器的同一视图控制器也应负责解除它。尽管有几种技术用于通知呈现视图控制器应该解除其呈现的视图控制器,但是优选的技术是委托。有关更多信息,请参阅“使用委派与其他控制器通信。”
请注意,他们在这里甚至没有提到dismissViewController:animated:completion:
,好像他们对自己的API不太尊重。
但代表团似乎是一个人们经常挣扎的问题:并且可能需要a lengthy answer ......我认为这是苹果如此努力推动封锁的原因之一。在代码只需要在一个地方执行的情况下,代理模式通常被初学者认为是对一个看似简单的问题的过于复杂的解决方案。
我认为,如果你正在学习这些东西,最好的答案就是两种方式实现它。然后你将真正掌握游戏中的设计模式。
答案 2 :(得分:1)
关于此,
- (IBAction)emailMe:(id)sender {
[self dismissViewControllerAnimated:YES completion:^{
[self sendMeMail];
}];
}
在解除 self viewController后,您无法从self呈现视图控制器。
然后你能做什么?
1)更改按钮按下方法
- (IBAction)emailMe:(id)sender {
[self sendMeMail];
}
2)当mailViewController被解除时,您可以解除自身的viewController。
- (void)mailComposeController:(MFMailComposeViewController*)controller
didFinishWithResult:(MFMailComposeResult)result
error:(NSError*)error;
{
if (result == MFMailComposeResultSent) {
NSLog(@"It's sent!");
}
[controller dismissViewControllerAnimated:NO completion:^ {
[self dismissViewControllerAnimated:YES completion:nil];
}];
}
答案 3 :(得分:0)
如果您正在使用故事板,请尝试检查您在segue上使用的转换类型。解决您以模态方式转换的视图控制器层时,您将遇到问题。这可能是您的问题的根源。尝试将它们切换为推动。 虽然您可以编写代理来完成解雇视图控制器,但实际上并不需要它。这是一个过于复杂的解决方案。如果你有一个转换到几十个不同故事板的viewcontroller,你会有几十个代表控制解雇吗?这似乎不是最理想的。
答案 4 :(得分:0)
使用此代码
[[[[[UIApplication sharedApplication] delegate] window] rootViewController] presentViewController:composer animated:YES completion:nil];
而不是
[self presentViewController:picker animated:YES completion:NULL];