【问题标题】:Correct way of showing consecutive modalViews显示连续模态视图的正确方法
【发布时间】:2011-02-10 09:59:09
【问题描述】:

我有两个视图需要模态显示,一个接一个。如果我们连续关闭并显示,这将不起作用,如下所示:

[rootController dismissModalViewControllerAnimated: YES];
[rootController presentModalViewController: psvc animated: YES];

第二个模态视图根本不显示。

我看到过类似这样的修复:

[rootController dismissModalViewControllerAnimated: YES];
[[UIApplication sharedApplication] beginIgnoringInteractionEvents];
[self performSelector: @selector(seekModal) withObject: nil afterDelay: 0.5];
[[UIApplication sharedApplication] endIgnoringInteractionEvents];

问题是这不会一直有效(有时所需的延迟会更好)。

另一个可能的解决方法是消除动画:

[rootController dismissModalViewControllerAnimated: NO];
[rootController presentModalViewController: psvc animated: YES];

但我真的很想保留动画,以保持第一个模态不碍事的感觉。有什么建议吗?

【问题讨论】:

  • 为什么不在模态视图控制器上使用它来改变它的视图呢?连续两个 Modal View Controller 会有点烦人。
  • 如果它们是“连续的”,请考虑使用导航。
  • 您是否 100% 确定第一个模态视图的关闭和第二个模态视图的打开都是在主线程的上下文中执行的?

标签: iphone animated dismiss modalviewcontroller


【解决方案1】:

编辑:在 iOS5+ 中执行此操作的“正确”机制是使用 – dismissViewControllerAnimated:completion: 方法,并从完成块显示顺序视图控制器。


模态显示的视图控制器将在模态关闭动画完成后调用其 viewDidDisappear:animated: 方法。 AFIK 这是唯一可以挂钩以启动后续 presentModalViewController:animated: 调用的地方。

我有一个用于呈现模态视图控制器的类,它通过在解除完成后对呈现视图控制器的回调来实现您正在寻找的逻辑。要使用这个类,只需分配/初始化一个实例并使用普通的 presentViewController:animated: 调用来呈现。在呈现视图控制器上实现以下方法:

- (void) modalViewControllerDidDismiss:(UIViewController *)modalViewController

这将在模态视图控制器消失后立即调用,此时您可以呈现一个新的模态视图控制器。

还有一件好事——因为这个类是 UINavigationController 的一个特化,你可以根据需要配置导航栏的开/关。该类还具有内置逻辑,可以根据需要显示关闭按钮。

这是类定义:

@protocol TSModalViewControllerDelegate

- (void) modalViewControllerDidDismiss: (UIViewController*) modalViewController;

@end

@interface TSModalViewController : UINavigationController 
{
    UIViewController*   _originalParentViewController;
}
@property BOOL dismissButtonHidden;

- (id) initWithViewController: (UIViewController*) vc;
- (id) initWithClass: (Class) c;
- (id) initWithClass: (Class) c nibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil;

@end

以及类的实现:

@implementation TSModalViewController
@synthesize dismissButtonHidden;

- (id) initWithViewController: (UIViewController *)vc
{
    return [super initWithRootViewController: vc];
}

- (id) initWithClass:(Class)c
{
    UIViewController* vc = [[[c alloc] init] autorelease];
    return [self initWithViewController: vc];
}

- (id) initWithClass: (Class) c nibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
    UIViewController* vc = [[[c alloc] initWithNibName:nibNameOrNil bundle:nibBundleOrNil] autorelease];
    return [self initWithViewController: vc];
}

- (void) viewDidAppear: (BOOL) animated
{
    [super viewDidAppear: animated];

    [_originalParentViewController release];
    _originalParentViewController = [self.parentViewController retain];

    if (!self.dismissButtonHidden)
    {
        UIBarButtonItem* dismissButton = [[[UIBarButtonItem alloc] initWithBarButtonSystemItem: UIBarButtonSystemItemStop
                                                                                        target: self 
                                                                                        action: @selector(onDismiss:)] autorelease];

        UIViewController* rootViewController = [self.viewControllers objectAtIndex:0];

        rootViewController.navigationItem.leftBarButtonItem = dismissButton;
        self.navigationBarHidden = NO;
    }   
}

- (void) viewDidDisappear:(BOOL)animated
{
    [super viewDidDisappear: animated];
    if ( [_originalParentViewController respondsToSelector: @selector(modalViewControllerDidDismiss:)] )
    {
        [_originalParentViewController performSelector: @selector(modalViewControllerDidDismiss:) withObject: self];
    }
}

- (void) dismissModalViewControllerAnimated:(BOOL)animated
{
    return [self.parentViewController dismissModalViewControllerAnimated: animated];
}

- (void) onDismiss: (id) sender
{
    [self.parentViewController dismissModalViewControllerAnimated: YES];
}

- (void) didReceiveMemoryWarning 
{
    [super didReceiveMemoryWarning];
}

- (void) viewDidUnload 
{
    [super viewDidUnload];
}

- (void)dealloc 
{
    [_originalParentViewController release];
    [super dealloc];
}

@end

并且,这是你如何使用它(在一些普通视图控制器的上下文中):

- (void) onShowIt:(id)sender
{
    TSModalViewController* mvc = [[[TSModalViewController alloc] initWithClass: [MyModalViewController class] nibName: @"MyModalViewController" bundle:nil] autorelease];
    mvc.dismissButtonHidden = YES;  // set to no if you don't want an "automatic" close button

    [self presentModalViewController: mvc animated: YES];
}

还有,这里是解除回调方法,它提供了一个新的模态视图控制器:

- (void) modalViewControllerDidDismiss:(UIViewController *)modalViewController
{
    MyModalViewController* vc = [[[MyModalViewController alloc] initWithNibName: @"MyModalViewController" bundle:nil] autorelease];
    vc.modalTransitionStyle = UIModalTransitionStyleFlipHorizontal;

    TSModalViewController* mvc = [[[TSModalViewController alloc] initWithViewController: vc] autorelease];

    [self presentModalViewController: mvc animated: YES];
}

【讨论】:

    【解决方案2】:

    rootController 可以知道它上面的最后一个模态视图控制器何时消失,因为它会收到一个 viewDidAppear:。您是否尝试过将后续视图控制器的 presentModalViewController: 链接到该控制器?

    【讨论】:

    • 你仍然遇到时间问题... viewDidAppear 在视图实际出现之前被调用,显然。
    • 总有 performSelector:foo afterDelay:0 可能在 viewDidAppear 中起作用...
    【解决方案3】:

    如果您真的想将多个视图动画链接在一起,我实际上建议您自己处理动画逻辑。这不是太棘手,然后您可以对视图的呈现方式进行细粒度控制。我只是在这里为另一个问题写了类似的东西:

    iOS -- how do you control the size of a modal view controller?

    您可以只为视图设置动画,为视图设置动画,当调用 animationDidStop 选择器时,为您的第二个视图设置动画。这样做的好处是您还可以使用视图不透明度和动画方向,以及准确决定视图应该出现的时间。例如,当第一个视图滑开时,您可以让第二个视图在第一个视图上滑动;无需等待第一个动画完成。

    【讨论】:

    • 有趣的想法。不过,这似乎迫使您退出 MVC,不是吗?就像你最终会得到两个视图和一个控制器一样。
    • MVC 并不一定意味着其中之一;这实际上是将这些组件彼此分离并让每个组件各司其职。一个视图控制器管理多个视图实际上很常见。毕竟,您添加到视图控制器的视图(按钮、图像等)和动画的许多/大部分 UI 组件只是视图本身。最好将每个视图控制器视为代表程序的一个全屏块,以及应用程序该部分背后的大脑。控制器是将模型和视图连接在一起的逻辑。
    【解决方案4】:

    您的问题是否与“在模态视图中显示模态视图”有关? 我在这里发布了一个关于这个的答案: iPhone modal view inside another modal view?

    【讨论】:

    • 并非如此。这是关于关闭一个模式视图并打开另一个模式视图。当您关闭并呈现太靠近的视图时,iOS 似乎会窒息。这对于相机选择器来说尤其是个问题,因为它的加载/卸载时间很长。如果您想关闭相机选择器并显示其他内容,您可能会遇到各种奇怪的行为。
    【解决方案5】:

    我为这样的事情找到的最佳解决方案(如果它们都是父视图的相同子视图)是将它们的视图修补到启用分页的 UIScrollView 上,(您可以在底部添加一个页面控件以使其清晰和导航)然后将控制器的视图添加到页面视图中,当它们出现在屏幕上时,在它们离开屏幕时移除。

    如果控制器依赖于这个被调用,你可能还需要虚拟调用 -viewWillAppear 和 -viewWillDisappear。当你全部编码的时候感觉有点hack-ish,但是一旦你让它工作起来,它看起来很流畅和自然,并且没有任何与动画一个视图相关的等待,然后动画下一个视图一次它没了。

    【讨论】:

      【解决方案6】:

      我发现使用模态视图的 -viewDidDissapear 来调用呈现视图控制器上的方法非常好。一个好处是能够延迟模态视图控制器上的释放。请发布我可以做的任何改进。 我创建这个协议的灵感来自于 iOS 5 的 "dismissViewControllerAnimated:completion:" 除了 UIViewController。我希望在 iOS 4.3 中有这个功能。


      PresentorDelegateProtocol.h

      @protocol PresentorDelegateProtocol <NSObject>
      @optional
      
      /* 
      
      Extra protocol methods defined in protocol for flexibility.  
      Main methods are:
      - (void)dismissPresentingModalViewController:(id)modalView animated:(BOOL)animated; 
      - (void)modalViewDissapeared:(id)modalView;  //used in modal view's -viewDidDissapear
      
      */
      
      - (void)dismissPresentingModalViewController:(id)modalView animated:(BOOL)animated;
      - (void)modalViewDissapeared:(id)modalView; 
      
      // use the block in this method send messages to save state, etc.  This is the one I like to use.
      - (void)dismissPresentingModalViewController:(id)modalView animated:(BOOL)animated withBlock:(void(^)())block;
      
      // use in other classes that are not controlling dismissal of the modal view
      - (void)executeBlockOnModalDissapearance: (void(^)())block;
      
      @end
      

      PresentingViewController.h

      #import "PresentorDelegateProtocol.h"
      @interface PresentingViewController : UIViewController <PresentorDelegateProtocol>
      - (void)showModalVC;
      @end
      

      ModalViewController.h

      #import "PresentorDelegateProtocol.h"
      @interface ModalViewController : UIViewController
      @property (nonatomic, assign) id <PresentorDelegateProtocol> presentorDelegate;
      - (void)close;
      @end
      

      PresentingViewController.m

      #import "PresentingViewController.h"
      #import "ModalViewController.h"
      @implementation PresentingModalViewController
      - (void)showModalVC
      {
          ModalViewController *modalVC = [[ModalViewController alloc] initWithNibName:@"ModalViewController" bundle:nil];
          modalVC.presentorDelegate = self;
          [self presentModalViewController:modalVC animated:YES];
      }
      - (void)dismissPresentingModalViewController:(id)modalView animated:(BOOL)animated
      {
          if ([modalView isKindOfClass:[ModalViewController class]]) {
              NSLog(@"Can invoke based on class"); 
          }
          [self dismissModalViewControllerAnimated:animated];    
      }
      - (void)dismissPresentingModalViewController:(id)modalView animated:(BOOL)animated withBlock:(void(^)())block
      {
          block();  
          /* execute block before or after calling to dismiss modal view */
          [self dismissPresentingModalViewController:modalView animated:animated];
          //block();
      }
      - (void)modalViewDissapeared:(id)modalView
      {
          if ([modalView isKindOfClass:[ModalViewController class]]) {
              NSLog(@"Do stuff based on class.");
          }
      }
      - (void)executeBlockOnModalDissapearance: (void(^)())block
      {
          block();
          NSLog(@"This delay's dealloc on modal view until block completes");
      }
      @end
      

      ModalViewController.m

      #import "ModalViewController.h"
      @implementation ModalViewController
      @synthesize presentorDelegate;
      
      - (void)close
      {
          if (1 == 0 /*need to do something before dealloc*/){
              [self.presentorDelegate dismissPresentingModalViewController:self animated:YES withBlock:^{
                  NSLog(@"Do stuff with block.  Save, animate, etc");
              }];
      
          } else {
              [self.presentorDelegate dismissPresentingModalViewController:self animated:YES];
          }
      }
      
      - (void)viewDidDisappear:(BOOL)animated
      {
          if (1 == 0 /*stuff to do*/){
              [self.presentorDelegate executeBlockOnModalDissapearance:^{
              // do stuff before modal view is deallocated
              }];
          }
          [self.presentorDelegate modalViewDissapeared:self];
      
          presentorDelegate = nil;
          [super viewDidDisappear:animated];
      }
      @end;
      

      【讨论】:

        【解决方案7】:
        // present modal view inside another presented modal view
        
            FirstViewController *firstVC = [[FirstViewController alloc] initWithNibName:@"FirstViewController" bundle:nil];
            UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController: firstVC];
        
            // Note: you can use your viewcontroller instead self.window.rootViewController
        
            [self.window.rootViewController presentViewController:navController animated:YES completion:^{
                        //code...
                            SecondViewController *secondVC = [[SecondViewController alloc] initWithNibName:@"SecondViewController" bundle:nil];
        
                            [navController presentViewController: secondVC animated:YES completion:nil];
        
                        }
                    }];
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2016-09-02
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2015-02-22
          相关资源
          最近更新 更多