在另一个模态视图控制器解散后立即呈现一个模态视图控制器

38

我正在关闭一个模态视图控制器,然后立即展示另一个,但后者从未出现。这是代码:

[self dismissModalViewControllerAnimated:YES];
UIImagePickerController *picker = [[UIImagePickerController alloc] init]; picker.delegate = self; picker.sourceType = UIImagePickerControllerSourceTypeSavedPhotosAlbum; [self presentModalViewController:picker animated:YES];

第一个模态VC向下滑动,但新的picker从未出现。有什么想法是怎么回事吗?


创建新的UIImagePickerController实例并不是一个好主意,你能使用旧的吗? - h4cky
3
你能详细说明一下吗?为什么这会是一个不好的想法? - Heavy_Bullets
8个回答

52

2012年8月更新:

iOS 5及更高版本引入了使用完成块(completion blocks)进行模态视图动画完成后的操作的更安全的API:

[self presentViewController:myModalVC animated:YES completion:^{}];
[self dismissViewControllerAnimated:YES completion:^{}];

2012年8月之前的答案:

当我快速地关闭模态框一并打开模态框二时,我遇到了类似的问题。有时候模态框二会在模态框一被关闭后显示,有时候模态框二则根本不会出现,这让我很难过。

看起来像是竞争条件的问题...

在调用呈现模态框二的方法 showModalTwo 之前加入1秒钟的延迟可以解决问题,每次关闭模态框一后都能够成功显示模态框二:

- (void)didDismissModalOne {
    [self performSelector:@selector(showModalTwo:)                withObject:someNumber                afterDelay:1.0f];
}

经过确认,发现在关闭模态框1和呈现模态框2之间存在某种竞争条件。然而在调用者上设置延迟是不优雅的,并不能保证竞争条件不会在其他情况下重新出现。

问题

原来,UIViewController有一个公共属性modalViewController,当调用presentModalViewController:animated:时会被设置,当调用dismissModalViewControllerAnimated:时会被解除设置。但问题是这个属性不会同步被解除设置,所以可能会在以下方式中,在移除旧的modalViewController值与建立新值之间创建一个竞赛:

  1. 呈现模态框1。现在myViewController.modalViewController指向模态框1
  2. 关闭模态框1。后台进程开始拆除myViewController.modalViewController,但myViewController.modalViewController仍指向模态框1
  3. 呈现模态框2,myViewController.modalViewController现在指向模态框2
  4. 系统回调触发,将myViewController.modalViewController设置为nil,这打断了模态框2动画的过程,结果用户永远看不到它。

竞争条件始于步骤2,并在步骤4中显现。

解决方案

我的解决方案是在呈现模态框2的方法上设置一个守卫条件,以确保myViewControoler.modalViewController在尝试呈现模态框2之前为nil

-(void)showModalTwo:(NSNumber *)aParameter {

    if (self.modalViewController) {        
            [self performSelector:@selector(showModalTwo:)
                       withObject:aParameter 
                       afterDelay:0.1f];
            return;
    }
    // You can now present the second modal safely.
}

非常顺利地解决了问题。一个更优雅的解决方案可能包括超时。

后续

我真的不喜欢这个解决方案中的轮询部分。在这个问题的被接受的答案中,@Nimrod建议你可以安全地从模态视图一的viewDidDisappear:方法开始呈现模态视图二。我喜欢这种事件驱动的方法,但在我的用例中完全实现后,我确认使用viewDidDisappear:内部的回调呈现模态视图二仍然存在竞争条件。唯一确定模态视图二将被显示的方式是在父视图控制器中轮询,直到你绝对确定self.modalViewControllernil。只有在这种情况下,弹出模态视图二才是“安全”的。


+1 美妙优雅的解决方案;特别喜欢学习performSelector方法...每天都能学到新东西 :-D - Christopher Lightfoot
对于那些想知道为什么在使用iOS 5.0+解决方案时会出现“方法未找到”错误的人,请注意dismissModalViewControllerAnimated...dismissViewControllerAnimated...之间的区别(现在已经没有Modal了)。这是一个容易犯的低级错误。 - Murdock
@prairiedogg,我想知道在“someNumber”中应该传递什么?我的有些问题,如果你有时间,希望你不介意访问我的问题,关于一个类似的问题,链接在这里:http://stackoverflow.com/questions/15893817/ipad-modalviewcontroller-when-dismissed-does-not-go-to-the-viewdidappear-of-the - gdubs
1
使用 dismissViewControllerAnimated:YES completion:^{[self.delegate show2ndVC];} 对我有效。 - Jingshao Chen

16

与其他动画元素一样,dismissModalViewControllerAnimated 不会阻塞直到视图控制器消失。相反,它会启动视图控制器的"解散"。您可能需要在模态控制器1的viewDidDisappear中使用回调函数,调用父视图控制器中的modalViewControllerDisappeared之类的方法。在该方法中,您可以呈现模态控制器2。否则,请参照Robot K所说的方式。


这是个好主意。下次我需要做这件事的时候,我可能会尝试一下。 - Kris Markel
不确定它是否能够正常工作(我没有测试过),但看起来应该可以。 - Nimrod
1
我尝试了在这个答案中推荐的事件驱动方法,将回调放置在viewDidDisappear中,但竞争条件仍然存在。请参阅我的答案,以获取最终对我有效的解决方案。 - prairiedogg

15
[self dismissViewControllerAnimated:YES completion:^{    //Present the new MVC }];

注意:仅限 iOS 5.0 及以上版本。


5
[self dismissModalViewControllerAnimated:NO];

 UIImagePickerController *picker = [[UIImagePickerController alloc] init];
 picker.delegate = self;
 picker.sourceType = UIImagePickerControllerSourceTypeSavedPhotosAlbum;
 [self presentModalViewController:picker animated:YES];

将动画设置为NO以便于关闭它。 - Sid
2
我认为这段代码不会起作用。dismissModalViewControllerAnimated将释放您在VC中拥有的所有内容,然后您尝试在此VC上呈现另一个视图控制器。 - Ilker Baltaci
希望你知道,上面从视图控制器调用的内容不会自动关闭,而是当前以模态方式呈现的视图控制器。那个踩踏是没有必要的,因为你的评论是错误的。无意冒犯,但我必须直言不讳。干杯。 - Sid
另外,IronLeash,我希望你意识到我的答案是在2010年发布的,早于iOS5。你所发布的解决方案从iOS 5开始就可用...请考虑这一点。 - Sid
我在 VC 中尝试了它,但它只是崩溃了。我想在解除视图控制器后调用委托方法。 - Ilker Baltaci
嗯,崩溃可能是由其他原因引起的(也许)。如果您没有解决方案,请发布一份带有代码的详细问题,也许我可以帮助您。 - Sid

2
发生的情况是,视图控制器在解除模态视图控制器后,会删除对它的引用,这发生在调用此代码之后完成解除动画,因此它认为没有新的视图控制器可以模态呈现。
我处理这个问题的方法是,在调用dismissModalViewController后将didDismissModalVC ivar设置为YES。然后在我的viewDidAppear:方法中,检查ivar的值并呈现新的模态视图控制器。(记得将值设置回NO,以免永远解除模态视图控制器。)

0

在Swift中:

  1. 使用dismissViewController来关闭第一个呈现的视图。
  2. 使用dismissViewController的完成块来调用主VC中的函数。该函数应该调用第二个VC。

您的dismissViewController应该如下所示:

var presentingVC_Delegate: mainLists_PopoverDelegation!

@IBAction fund button_Pressed (sender: AnyObject) {
    self.dismissViewControllerAnimated(true, completion: { finished in
        self.presentingVC_Delegate.presentOtherVC()
        print("DismissVC completion block says hello")
    })
}

主视图控制器(mainVC)包含展示其他视图控制器(presentOtherVC)的位置:

func presentSettingsVC () {
    self.performSegueWithIdentifier("present_OtherVC", sender: nil)
}

0
在这种情况下,我创建了一个委托来回调父视图控制器以显示第二个模态视图控制器。
父视图控制器的协议定义:
@protocol ParentViewControllerDelegate
- (void)showModalTwo;
@end

我在父视图控制器中实现了这个协议,以展示第二个模态视图控制器,并在第一个模态视图控制器上创建代理属性@property id<ParentViewControllerDelegate> delegate;

从父视图控制器显示第一个模态视图控制器:

TheFirstModalViewController *controller = ...
controller.delegate = self;
[self presentViewController:controller animated:YES completion:nil];
...

在第一个模态视图控制器的 viewDidDisappear: 方法中,只需调用 delegate.showModalTwo: 即可从父视图控制器显示第二个模态视图。
希望这可以帮到你。

0
这是我的方法,似乎在iOS 10上运行良好。我的情况略有不同,但对大多数情况都适用。我将初始视图控制器呈现为弹出窗口,需要立即呈现模态视图控制器。
首先,在初始视图控制器的viewDidLoad中,只需隐藏其视图:
   view.isHidden = true

接着,在它的viewWillAppear方法中,呈现模态视图控制器,并在完成后取消动画并显示视图:

   present(yourModalViewController, animated: false) { [unowned self]
       self.view.isHidden = false
    }

你可能想要使用一个Bool来控制你的状态,这样后续对viewWillAppear的调用不会重新呈现模态窗口,但你应该明白我的意思。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接