导航控制器上的透明模态视图

71

我试图在导航控制器的顶部创建一个透明的模态视图。有人知道这是否可能吗?


1
注意:对于2014年,一个绝对的解决方案就是使用容器视图,全屏 - 然后你拥有绝对、完全的控制权,可以做任何你想做的事情(动画效果、滑动等)。在某些情况下,这是最好的解决方案。 - Fattie
19个回答

109

模态视图将覆盖其顶部的视图以及导航控制器的导航栏。但是,如果您使用-presentModalViewController:animated:方法,则一旦动画完成,实际上就会消失所覆盖的视图,这使得您的模态视图的任何透明度都毫无意义。(您可以通过在根视图控制器中实现-viewWillDisappear:和-viewDidDisappear:方法来验证此操作)。

您可以像下面这样直接将模态视图添加到视图层次结构中:

UIView *modalView =
    [[[UIView alloc] initWithFrame:[[UIScreen mainScreen] bounds]] autorelease];
modalView.opaque = NO;
modalView.backgroundColor =
    [[UIColor blackColor] colorWithAlphaComponent:0.5f];

UILabel *label = [[[UILabel alloc] init] autorelease];
label.text = @"Modal View";
label.textColor = [UIColor whiteColor];
label.backgroundColor = [UIColor clearColor];
label.opaque = NO;
[label sizeToFit];
[label setCenter:CGPointMake(modalView.frame.size.width / 2,
                                modalView.frame.size.height / 2)];
[modalView addSubview:label];

[self.view addSubview:modalView];

将modalView作为子视图添加到根视图中,像这样做实际上不会覆盖导航栏,但它会覆盖其下方的整个视图。我尝试了调整用于初始化modalView的frame的origin值,但负值会导致它不显示。我发现覆盖整个屏幕(除了状态栏)最好的方法是将modalView作为窗口本身的子视图添加:

TransparentModalViewAppDelegate *delegate = (TransparentModalViewAppDelegate *)[UIApplication sharedApplication].delegate;
[delegate.window addSubview:modalView];

我发现这个讨论对于这个问题非常有用。 http://www.iphonedevsdk.com/forum/iphone-sdk-development/14315-modal-view-semi-transparent-background.html对我有效的方法是手动动画化子视图的出现,而不是使用苹果的模态视图。 - CVertex
4
最简单的方法是将modalView直接添加到navigationController的视图层级中:[self.navigationController.view addSubview:modalView] - Engin Kurutepe
看看这个类别,它可以简化这种方法,并且还可以进行动画过渡效果 - https://dev59.com/uVLTa4cB1Zd3GeqPeu_A#10826685 - BadPirate
但是当我尝试这样做时,我的文本字段委托方法根本不起作用...我们有解决方案吗。 注意:我将viewController2.view添加为子视图。 - iOSManiac

18

最简单的方法是使用navigationControllermodalPresentationStyle属性(但您需要自己制作动画):

self.navigationController.modalPresentationStyle = UIModalPresentationCurrentContext;
[self presentModalViewController:modalViewController animated:NO];
modalViewController.view.alpha = 0;
[UIView animateWithDuration:0.5 animations:^{
    modalViewController.view.alpha = 1;
}];

工作正常!并且覆盖导航栏。 - surfrider
秘密配方 navigationController.modalPresentationStyle = UIModalPresentationCurrentContext - LolaRun
无法在选项卡视图控制器中呈现。在iOS7中根本无法工作。 - Radu Simionescu
这对我的使用情况非常有效。对于iOS7,只需将 presentModalViewController 切换为 presentViewController 即可。 - John Erck

12

我最容易实现这个功能的方法是设置一个"OverlayViewController",它位于窗口或根视图的所有其他子视图之上。在您的应用程序委托或根视图控制器中设置此项,并将OverlayViewController设置为单例,以便可以从代码或视图控制器层次结构中的任何位置访问它。然后,您可以调用显示模态视图、显示活动指示器等方法,每当需要时,它们可能会覆盖任何选项卡栏或导航控制器。

根视图控制器的示例代码:

- (void)viewDidLoad {
  OverlayViewController *o = [OverlayViewController sharedOverlayViewController];
  [self.view addSubview:o.view];
}

以下是您可以使用的示例代码来显示您的模态视图:

[[OverlayViewController sharedOverlayViewController] presentModalViewController:myModalViewController animated:YES];

我实际上并没有使用-presentModalViewController:animated:与我的OverlayViewController,但我认为这应该可以正常工作。

另请参见:你的Objective-C单例是什么样子的?


这种方法非常有效 - 奇怪的是,我在IB中创建了我的叠加视图并将其视图属性设置为隐藏,但当加载视图(并添加到子视图)时,它始终是隐藏的 == NO - 我必须在初始时显式地将其设置为YES,例如在-viewDidLoad:中。 - petert
好的,现在我明白了如何加载和初始化 NIB。由于叠加视图很容易设置,现在我正在我的类中实现 -loadView 方法。 - petert

8
我遇到了同样的问题,解决方法是使用addSubview:添加模态视图,并使用UIView的animateWithDuration:delay:options:animations:completion:动画改变视图层次结构。
我在UIViewController的子类(FRRViewController)中添加了一个属性和2个方法,其中包括其他功能。 我很快就会将整个内容发布在gitHub上,但在此之前,您可以查看下面的相关代码。 有关更多信息,请查看我的博客:如何显示透明的模态视图控制器
#pragma mark - Transparent Modal View
-(void) presentTransparentModalViewController: (UIViewController *) aViewController 
                                     animated: (BOOL) isAnimated 
                                    withAlpha: (CGFloat) anAlpha{

    self.transparentModalViewController = aViewController;
    UIView *view = aViewController.view;

    view.opaque = NO;
    view.alpha = anAlpha;
    [view.subviews enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
        UIView *each = obj;
        each.opaque = NO;
        each.alpha = anAlpha;
    }];

    if (isAnimated) {
        //Animated
        CGRect mainrect = [[UIScreen mainScreen] bounds];
        CGRect newRect = CGRectMake(0, mainrect.size.height, mainrect.size.width, mainrect.size.height);


        [self.view addSubview:view];
        view.frame = newRect;

        [UIView animateWithDuration:0.8
                         animations:^{
                             view.frame = mainrect;
                         } completion:^(BOOL finished) {
                             //nop
                         }];

    }else{
        view.frame = [[UIScreen mainScreen] bounds];
        [self.view addSubview:view];
    }






}

-(void) dismissTransparentModalViewControllerAnimated:(BOOL) animated{

    if (animated) {
        CGRect mainrect = [[UIScreen mainScreen] bounds];
        CGRect newRect = CGRectMake(0, mainrect.size.height, mainrect.size.width, mainrect.size.height);
        [UIView animateWithDuration:0.8
                         animations:^{
                             self.transparentModalViewController.view.frame = newRect;
                         } completion:^(BOOL finished) {
                             [self.transparentModalViewController.view removeFromSuperview];
                             self.transparentModalViewController = nil;
                         }];
    }


}

7
以下是我的解决方案 - 谷歌了一下细节,但这种方法对我非常有效:
  • 截取底层视图的屏幕截图。 https://devforums.apple.com/message/266836 - 这导致了一个现成的方法,返回当前屏幕的UIView。
  • 将屏幕截图交给模态视图(我使用了一个属性)
  • 呈现模态视图
  • 在模态视图控制器的viewDidAppear中,将图像设置为索引0处的UIImageView。通过状态栏的高度调整图像的垂直位置。
  • 在模态视图控制器的viewWillDisappear中,再次删除图像

效果如下:

  • 视图以任何模态视图的方式动画显示 - 模态视图的半透明部分滑过现有视图
  • 动画停止后,背景设置为屏幕截图 - 这使得它看起来好像旧视图仍然在下面,尽管实际上不是这样。
  • 当模态视图的消失动画开始时,图像被移除。与此同时,操作系统显示旧的导航视图,因此模态视图会像预期的那样透明地滑动并消失在视野之外。

我尝试过自己动画叠加视图,但效果不太好。我遇到了一个崩溃,没有任何指示表明已经崩溃。与其追踪这个问题,我做了背景视图,效果非常好。

模态视图中的代码 - 我想你可以解决其他部分,即设置属性modalView.bgImage...

- (void)viewDidAppear:(BOOL)animated {
    // background
    // Get status bar frame dimensions
    CGRect statusBarRect = [[UIApplication sharedApplication] statusBarFrame];
    UIImageView *imageView = [[UIImageView alloc] initWithImage:self.bgImage];
    imageView.tag = 5;
    imageView.center = CGPointMake(imageView.center.x, imageView.center.y - statusBarRect.size.height);
    [self.view insertSubview:imageView atIndex:0];
}

- (void)viewWillDisappear:(BOOL)animated {
    [[self.view viewWithTag:5] removeFromSuperview];
}

4
self.modalPresentationStyle = UIModalPresentationCurrentContext;
[self presentModalViewController:newview animated:YES];

请确保将模态视图的背景设置为透明。

self.view.background = .... alpha:0.x;


这太完美了。比被接受的答案容易得多,而且仍然给了我想要的结果。 - Ted Kulp
1
不支持iOS8。背景视图控制器被隐藏,你会看到窗口的背景颜色(在我的情况下是黑色)。 - mahboudz
对于iOS8,您应该为presented视图控制器设置模态呈现样式。请查看下面的答案。 - xZenon

4
如果您将模态视图控制器的modalPresentationStyle设置为17:viewController.modalPresentationStyle = 17; 背景中的视图不会被移除。(TWTweetComposeViewController使用它)。我没有尝试使用这段代码通过App Store审核。

你是否将模态视图的背景视图设置为透明?我刚试了一下,它可以工作。我使用了默认的Utility应用程序模板,并将flipsideviewcontroller的背景颜色更改为clearColor,并将mainviewcontroller中的代码行从controller.modalTransitionStyle = UIModalTransitionStyleFlipHorizontal;更改为controller.modalPresentationStyle = 17; - Mindaugas
1
我可以确认这个有效!应用程序审核可接受吗?不知道。 - mahboudz

3

是的,你必须手动添加视图,如果你想从底部滑入或者其他方式进入,你也需要自己做动画。

我写了一个类来实现这个功能,并使用该类作为示例编写了一个半模态日期选择器。

你可以在这篇博客文章中找到文档,代码在github上


3

2
我从https://gist.github.com/1279713得到了这个想法。
准备工作: 在模态视图xib(或使用storyboard的场景)中,我设置了全屏背景UIImageView(将其与.h文件挂钩并给它一个属性“backgroundImageView”),透明度为0.3。然后我将视图(UIView)的背景颜色设置为纯黑色。
想法: 然后,在模态视图控制器的“viewDidLoad”中,我从原始状态中捕获截图,并将该图像设置为背景UIImageView。将初始Y点设置为-480,并使用EaseInOut动画选项使其滑动到Y点0,持续时间为0.4秒。当我们关闭视图控制器时,只需执行相反的操作。
模态视图控制器类的代码:
.h文件:
@property (weak, nonatomic) IBOutlet UIImageView *backgroundImageView;
- (void) backgroundInitialize;
- (void) backgroundAnimateIn;
- (void) backgroundAnimateOut;

.m file:

- (void) backgroundInitialize{
    UIGraphicsBeginImageContextWithOptions(((UIViewController *)delegate).view.window.frame.size, YES, 0.0);
    [((UIViewController *)delegate).view.window.layer renderInContext:UIGraphicsGetCurrentContext()];
    UIImage * screenshot = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    backgroundImageView.image=screenshot;
}
- (void) backgroundAnimateIn{
    CGRect backgroundImageViewRect = backgroundImageView.frame;
    CGRect backgroundImageViewRectTemp = backgroundImageViewRect;
    backgroundImageViewRectTemp.origin.y=-480;
    backgroundImageView.frame=backgroundImageViewRectTemp;

    [UIView animateWithDuration:0.4 delay:0.0 options:UIViewAnimationCurveEaseInOut animations:^{
        backgroundImageView.frame=backgroundImageViewRect;
    } completion:^(BOOL finished) {

    }];
}
- (void) backgroundAnimateOut{
    CGRect backgroundImageViewRect = backgroundImageView.frame;
    backgroundImageViewRect.origin.y-=480;

    [UIView animateWithDuration:0.4 delay:0.0 options:UIViewAnimationCurveEaseInOut animations:^{
        backgroundImageView.frame=backgroundImageViewRect;
    } completion:^(BOOL finished) {

    }];
}

在viewDidLoad方法中,只需调用以下内容:
[self backgroundInitialize];
[self backgroundAnimateIn];

在任何地方我们关闭模态视图控制器,都需要调用:

[self backgroundAnimateOut];

请注意,这将始终动画化背景图像。 因此,如果此模态视图控制器转换样式(或segue转换样式)未设置为“Cover Vertical”,则您可能不需要调用动画方法。

这是一个好主意。出于好奇,如果视图控制器需要支持旋转,是否有办法使此解决方案工作?也就是说,半透明的弹出窗口和下面的视图控制器将一起旋转? - Matthew Gillingham
我不确定你的应用程序如何“旋转”。我想你是说要旋转主视图。如果是这种情况,我有两个建议:第一个更容易:创建第二个视图,并将其设置为与主视图相同的大小,但不要将此视图设置为主视图的子视图。相反,将该第二个视图设置在与主视图相同的层次结构级别上。下一个重要的事情是将背景图像视图放入此第二个视图中。因此,如果您对主视图进行动画处理,则不会影响第二个视图。第二个建议是:(请参见下一个评论,因为字符限制超过了) - Wayne Liu
第二个建议:仍然将此背景保留在主视图中作为子视图。每当您旋转主视图时,我假设您正在更新其“transform”属性并将旋转角度添加到其中。如果是这种情况,则可以将相同数量的旋转角度“但是负数”添加到背景图像视图的变换中。这应该使背景图像视图保持在其固定位置。 - Wayne Liu

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