警告:尝试在其视图不在窗口层次结构中的ViewController上显示ViewController

我已经看过相关的问题,但没有任何解决我的问题。

我试图使用dismissViewControllerAnimated:animated:completionpresentViewControllerAnimated:animated:completion连续presentViewControllerAnimated:animated:completion 。 使用故事板,我通过部分curlanimation模式地呈现InfoController。

部分curl显示InfoController上的一个button,我想启动MFMailComposeViewController 。 由于部分curl部分隐藏了MFMailComposeViewController ,我首先要通过非animation部分curl来消除InfoController。 然后,我想要MFMailComposeViewController来animation。

目前,当我尝试这个,部分curl不animation,但MFMailComposeViewController没有得到呈现。 我也有一个警告:

警告:尝试在InfoController上显示MFMailComposeViewController:其视图不在窗口层次结构中!

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)emailMeMFMailComposeViewControlleranimation的,但部分隐藏在部分curl后面。 我怎样才能首先消除curl,然后在MFMailComposeViewControlleranimation?

非常感谢!

编辑:下面是什么样的视图,如果我注释掉[self dismissViewControllerAnimated:YES completion:^{}];

在这里输入图像说明

这是视图控制器之间的一个通信问题,由于不明确的父子视图 – 控制器关系而导致的…不使用协议和委派,这将无法正常工作。

经验法则是:

  • 父母知道他们的孩子,但孩子不需要知道他们的父母。

(听起来无情,但是如果你仔细想想,这是有道理的)。

翻译成ViewController关系:呈现视图控制器需要知道他们的子视图控制器,但子视图控制器不必知道他们的父(呈现)视图控制器:子视图控制器使用他们的代表发送消息回到他们(未知)父母。

如果你必须在头文件中添加@Class声明来修正链接的#import编译器警告,你就知道有什么地方是错误的。 交叉引用总是一件坏事(顺便说一下,这也是代表应该总是(分配)和从不(强)的原因,因为这会导致交叉引用循环和一组僵尸)


那么,让我们看看你的项目的这些关系:

正如你没有说的,我假设调用控制器被命名为MainController 。 所以我们会有:

  • MainController,父级,拥有并呈现InfoController
  • 一个InfoController(在MainController下部分显示),拥有并呈现一个:
  • MailComposer,因为它将显示在MainController下方,所以无法显示。

所以你想拥有这个:

  • MainController,父级,拥有并呈现InfoController&MFMailController
  • 一个InfoController(在MainController下部分显示)
  • InfoController视图中的“电子邮件button”。 点击它会通知MainController(它是未知的委托),它应该closuresInfoController(本身)并呈现MailComposer
  • 一个MailComposer将由MainController而不是InfoController拥有(呈现和解散)

1. InfoController:定义一个@protocol InfoControllerDelegate:

子控制器定义一个协议,并有一个未指定types的委托符合其协议(换言之:委托可以是任何对象,但它必须有这一个方法)

 @protocol InfoControllerDelegate - (void)returnAndSendMail; @end @interface InfoControllerDelegate : UIViewController // … @property (assign) id<InfoControllerDelegate> delegate // ... @end 

2. MainController拥有并创buildInfoController和MFMailController

…并且MainController同时采用InfoControllerDelegate和MFMailComposeDelegate协议,所以它可以再次closuresMFMailComposer(注意,这并不需要是强大的属性,只是在这里显示来清楚)

 @interface MainController <InfoControllerDelegate, MFMailComposeViewControllerDelegate> @property (strong) InfoController *infoController; @property (strong) MFMailComposeViewController *mailComposer; 

3. MainController显示其InfoViewController并将其自身设置为委托

 // however you get the reference to InfoController, just assuming it's there infoController.delegate = self; [self presentViewController:infoController animated:YES completion:nil]; 

'infoController.delegate = self'是关键的一步。 这使得infoController有可能在不知道ObjectType(Class)的情况下将消息发送回MainController。 不需要#import。 所有它知道,它是一个对象,有方法-returnAndSendMail; 这就是我们所需要知道的。

通常你会用alloc / init创build你的viewController,并且让它延迟加载它的xib。 或者,如果您正在使用Storyboard和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; } } 

4.在InfoController中,按下电子邮件button:

当按下电子邮件button时,委托(MainController)被调用。 请注意,self.delegate是MainController并不相关,它只是相关的,它有这种方法-returnAndSendMail

 - (IBAction)sendEmailButtonPressed:(id)sender { // this method dismisses ourself and sends an eMail [self.delegate returnAndSendMail]; } 

…在这里( 在MainController中! ),你将closuresInfoController(清理,因为它是MainController的责任),并呈现MFMailController:

 - (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实际上是一样的。 他们都有他们不知名的代表,所以他们可以回信,如果他们这样做,你可以解雇他们,继续做你应该做的。

笔记

  • -dismissViewControllerAnimated:完成:不应该从子视图控制器调用。 在文档中,它表示:“ 呈现视图控制器负责解除其呈现的视图控制器。 ” 这就是为什么我们仍然需要授权。 这是有用的,因为父母的关系和责任是重要的! 确实。 你不能创造一些东西,然后放弃它。 那么,你可以,但你不应该。
  • 如果你不使用暴露的视图控制器animation,你可以链接这些父(采用儿童协议) – 子(定义协议的父母和采用协议的孙子) – 孙子(定义协议… …
  • 再一次:一个MainController拥有并呈现所有子viewController的devise实际上是一个糟糕的devise。 所以提出的解决scheme是关于协议和通信,而不是将所有内容放在一个MainController中
  • 我不认为块作为编码技术使我们免于定义关系和声明协议的需要
  • 希望有所帮助

当你展示一个viewController的时候,你所展示的viewController需要在视图层次结构中。 两个VC在它们的属性中保持着指向presentingViewController和PresentViewController的指针,所以两个控制器都需要在内存中。

通过解散,然后运行正在被解雇的视图控制器的代码,你打破了这种关系。

相反,你应该在infoController被解散之后,从显示InfoController的viewController中呈现InfoControllerinfoController

传统的做法是通过委托callback底层的viewController,然后处理解散和呈现的两个步骤。 但现在我们使用块

将您的sendMeMail方法移到提供了infoController的VC中

然后 – 在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的本地指针,因为在控制器被解散之后你不能引用该属性)

或者,通过将sendMeMail代码放在完成块中,将所有代码保存在infoController中:

 - (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 VC,而不是self 。 然后处理呈现viewController中的委托方法。

更新2
@Auro提供了一个使用委托方法的详细解决scheme,并在他的评论中指出,这最好地expression了angular色的分离。 在我的传统主义者同意,我认为dismissViewController:animated:completion作为一个kludgy和容易被误解的一块API。

苹果的文档有这样的说法 :

拒绝提出的视图控制器

当需要closures一个呈现的视图控制器时,首选的方法是让呈现视图控制器closures它。 换句话说,只要有可能,呈现视图控制器的视图控制器也应该负责解除视图控制器。 虽然有几种技术用于通知呈现视图控制器它的呈现的视图控制器应该被解散,但是优选的技术是委派。 有关更多信息,请参阅“使用委派与其他控制器通信”。

注意,他们甚至没有提到dismissViewController:animated:completion:在这里,好像他们没有太多的尊重他们自己的API。

但代表团似乎是一个人们经常会遇到的问题,而且可能需要很长时间的答案 ……我认为这是苹果推动这么难的原因之一。 在代码只需要在一个地方执行的情况下,代表模式通常被认为是一个非常复杂的解决scheme,看似简单的问题。

我想最好的答案是,如果你正在学习这个东西,就是要两种方式来实现。 那么你将真正掌握devise模式。

对这个,

 - (IBAction)emailMe:(id)sender { [self dismissViewControllerAnimated:YES completion:^{ [self sendMeMail]; }]; } 

closures自我 viewController后,你不能从自我呈现视图控制器。

那你可以做什么?

1)改变button的按下方法,

 - (IBAction)emailMe:(id)sender { [self sendMeMail]; } 

2)你可以closures自我viewController,当mailViewController被解雇。

 - (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]; }]; } 

如果您使用故事板,请尝试检查您在游戏中使用的过渡types。 你会有问题,解散你转换到模态的视图控制器层。 这可能是你的问题的根源。 尝试切换它们来推动。 在这里输入图像说明 虽然你可以写代表来完成dismissing viewcontrollers它确实不需要。 这是一个过于复杂的解决scheme。 如果你有一个视图控制器转换到几十个不同的故事板,你会有几十名代表控制解雇? 这似乎并不理想。

使用这个代码

 [[[[[UIApplication sharedApplication] delegate] window] rootViewController] presentViewController:composer animated:YES completion:nil]; 

代替

 [self presentViewController:picker animated:YES completion:NULL];