如何在iOS中呈现半透明(半切)的视图控制器?

9
我使用以下代码来展示一个视图控制器。 我的问题是:动画完成后,透明的主背景变成了不透明的黑色。
如何解决这个问题并使其保持为clearColor?
UIViewController *menuViewController=[[UIViewController alloc]init];
   menuViewController.view.backgroundColor=[UIColor clearColor];
   menuViewController.view.tintColor=[UIColor clearColor];
   menuViewController.view.opaque=NO;

UIView *menuView=[[UIView alloc]initWithFrame:CGRectMake(0,[UIScreen mainScreen].bounds.size.height-200,320,200)];
   menuView.backgroundColor=[UIColor redColor];

[menuViewController.view addSubview:menuView];

[self presentViewController:menuViewController animated:YES completion:nil];

更新:我正在尝试查看“self”(演示文稿视图控制器的视图)的内容。


4
这不是透明度问题。当动画完成时,iOS会将隐藏的视图控制器从屏幕上移除。你看到的黑色是窗口的背景颜色。我相信iOS7有一些选项可以解决这个问题。 - Brian Nickel
@BrianNickel 那么您的意思是Presenter View Controller在被推出之前会一直处于隐藏状态?如果是这样的话,那我需要手动添加视图并使用动画效果将其从屏幕底部带出吗? - frankish
1
没错。你可以跳过 menuViewController,只需按照自己的喜好将 menuView 动画显示在屏幕上即可。 - Brian Nickel
请您发布一个回答,其中提到您刚刚给出的主要解释(即呈现视图控制器无法通过呈现的视图控制器看到)。这样我就可以将其标记为答案,其他用户也可以轻松找到原因。谢谢! - frankish
1
不,添加的视图上的任何“交互”都由其原始视图控制器处理。但是,由于它被添加到另一个视图后,其父视图已经改变,链接链已经断开。为了正确传播方法调用(例如viewDidAppear),将子控制器作为子级添加是非常重要的。 - AC1
显示剩余2条评论
6个回答

26

iOS 15更新

苹果公司推出了一个新的API,UISheetPresentationController,可以轻松实现半高度(.medium())的表单展示。如果您需要更多自定义内容,请参考最初的回答。

let vc = UIViewController()
if let sheet = vc.presentationController as? UISheetPresentationController {
    sheet.detents = [.medium()]
}
self.present(vc, animated: true, completion: nil)

最初的回答

在iOS 7中,您可以呈现一个视图控制器,仍然可以看到原始视图控制器,就像表单一样。要实现这一点,您需要完成两件事:

  1. Set the modal presentation style to custom:

    viewControllerToPresent.modalPresentationStyle = UIModalPresentationCustom;
    
  2. Set the transitioning delegate:

    viewControllerToPresent.transitioningDelegate = self;
    
在这种情况下,我们将委托对象设置为self,但它也可以是另一个对象。委托对象需要实现协议中的两个必需方法,可能是这样的:最初的回答。
- (id <UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source
{
    SemiModalAnimatedTransition *semiModalAnimatedTransition = [[SemiModalAnimatedTransition alloc] init];
    semiModalAnimatedTransition.presenting = YES;
    return semiModalAnimatedTransition;
}

- (id <UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed
{
    SemiModalAnimatedTransition *semiModalAnimatedTransition = [[SemiModalAnimatedTransition alloc] init];
    return semiModalAnimatedTransition;
}

此时,您可能会想,SemiModalAnimatedTransition类是从哪里来的。嗯,它是从teehan+lax博客中采用的自定义实现。

这是该类的头文件:

@interface SemiModalAnimatedTransition : NSObject <UIViewControllerAnimatedTransitioning>
@property (nonatomic, assign) BOOL presenting;
@end

最初的回答

实现方式如下:

#import "SemiModalAnimatedTransition.h"

@implementation SemiModalAnimatedTransition

- (NSTimeInterval)transitionDuration:(id <UIViewControllerContextTransitioning>)transitionContext
{
    return self.presenting ? 0.6 : 0.3;
}

- (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext
{
    UIViewController *fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
    UIViewController *toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
    
    CGRect endFrame = fromViewController.view.bounds;
    
    if (self.presenting) {
        fromViewController.view.userInteractionEnabled = NO;
        
        [transitionContext.containerView addSubview:fromViewController.view];
        [transitionContext.containerView addSubview:toViewController.view];
        
        CGRect startFrame = endFrame;
        startFrame.origin.y = endFrame.size.height;
        
        toViewController.view.frame = startFrame;
        
        [UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{
            fromViewController.view.tintAdjustmentMode = UIViewTintAdjustmentModeDimmed;
            toViewController.view.frame = endFrame;
        } completion:^(BOOL finished) {
            [transitionContext completeTransition:YES];
        }];
    }
    else {
        toViewController.view.userInteractionEnabled = YES;
        
        [transitionContext.containerView addSubview:toViewController.view];
        [transitionContext.containerView addSubview:fromViewController.view];
        
        endFrame.origin.y = endFrame.size.height;
        
        [UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{
            toViewController.view.tintAdjustmentMode = UIViewTintAdjustmentModeAutomatic;
            fromViewController.view.frame = endFrame;
        } completion:^(BOOL finished) {
            [transitionContext completeTransition:YES];
        }];
    }
}

@end

这并不是最直接的解决方案,但避免了hack并且运行良好。自定义转换是必需的,因为iOS默认会在转换结束时删除第一个视图控制器。

iOS 8更新

对于iOS 8,一切又变了。你只需要使用新的展示样式.OverCurrentContext,例如:

viewControllerToPresent.modalPresentationStyle = UIModalPresentationOverCurrentContext;

4
这应该被标记为最佳答案。 - Tankista
1
这在iOS 8中会出现问题。当关闭屏幕时,屏幕会变成白色。 - geekay
在iOS8(和iOS7)中,如果您想要淡入/淡出视图控制器,请像任何其他动画一样将框架内容更改为alpha,并将viewControllerToPresent.modalPresentationStyle更改为UIModalPresentationPopover。(对于此情况,不需要按照@geekay的建议删除这些行)。 - Brett
@aauser 自定义过渡效果在iOS 7上是必需的。 - Ric Santos
1
在iOS8+中,似乎自定义转场在dismiss时会失败,因为它没有预料到presentingViewController的视图在任何时候都会移动到transitionContext.containerView。当该视图被移除时,呈现者也随之消失。删除这些行可以解决8+的问题(并且可能仍然适用于7)。 - Brian Nickel
显示剩余3条评论

8

更新

在大多数情况下,您会希望按照下面Ric的答案的指导方针进行操作。正如他所提到的,menuViewController.modalPresentationStyle = .overCurrentContext是保持呈现视图控制器可见性最简单的现代方法。

我保留这个答案,因为它提供了对OP问题最直接的解决方案,其中他们已经有一个由当前视图控制器管理的视图,并且只是在寻找一种呈现它的方式,以及因为它解释了实际的问题原因。


正如评论中提到的那样,这不是一个透明度问题(否则您会期望背景变为白色)。当presentViewController:animated:completion:动画完成时,呈现视图控制器实际上从视觉堆栈中删除了。您看到的黑色是窗口的背景颜色。

由于您似乎只是使用menuViewController作为menuView的主机来简化动画,因此您可以考虑跳过menuViewController,将menuView添加到现有视图控制器的视图层次结构中,并自己进行动画处理。


4

这是一个相当简单的问题。你只需要设置被呈现的视图控制器的modalPresentationStyle,而不是创建自定义视图过渡。此外,你还应该在故事板/代码中为被呈现的视图控制器设置背景颜色(和alpha值)。

CustomViewController: UIViewController {
    override func viewDidLoad() {
      super.viewDidLoad()
      view.backgroundColor = UIColor.blackColor().colorWithAlphaComponent(0.6)
    }
}

在展示视图控制器的IBAction组件中 -
let vc = storyboard?.instantiateViewControllerWithIdentifier("customViewController") as! CustomViewController
vc.modalPresentationStyle = UIModalPresentationStyle.Custom
presentViewController(vc, animated: true, completion: nil)

@RicSantos 谢谢!是的,这已经在 iOS 8 及以上版本上进行了测试。 - arpwal

1

自iOS 3.2以来,您应该使用属性modalPresentationStyleavailable

例如:

presenterViewController.modalPresentationStyle = UIModalPresentationCurrentContext;
[presenterViewController presentViewController:loginViewController animated:YES completion:NULL];

3
这并没有解决问题。 presenterViewController 仍然从屏幕中移除了。 - Peter
还有一些小问题,当从子视图控制器或其他模态视图呈现时。 Ку。 - user3099609

0

这是一个相当古老的帖子,感谢Ric的回答,它仍然有效,但需要进行一些修复才能在iOS 14上运行。我想它在较低版本的iOS上也可以正常工作,但由于我的部署目标是iOS 14,所以我没有测试过。

好的,这里有一个用Swift更新的解决方案:

final class SemiTransparentPopupAnimator: NSObject, UIViewControllerAnimatedTransitioning {
var presenting = false

func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
    return presenting ? 0.4 : 0.2
}

func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
    guard
        let fromVC = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from),
        let toVC = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to)
        else {
            return
    }
    
    var endFrame = fromVC.view.bounds
    
    if presenting {
        fromVC.view.isUserInteractionEnabled = false
        
        transitionContext.containerView.addSubview(toVC.view)
        
        var startFrame = endFrame
        startFrame.origin.y = endFrame.size.height
        toVC.view.frame = startFrame
        
        UIView.animate(withDuration: transitionDuration(using: transitionContext)) {
            fromVC.view.tintAdjustmentMode = .dimmed
            toVC.view.frame = endFrame
        } completion: { _ in
            transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
        }
    } else {
        toVC.view.isUserInteractionEnabled = true
        
        endFrame.origin.y = endFrame.size.height
        
        UIView.animate(withDuration: transitionDuration(using: transitionContext)) {
            toVC.view.tintAdjustmentMode = .automatic
            fromVC.view.frame = endFrame
        } completion: { _ in
            transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
        }
    }
}
}

0

尝试将顶部视图设置为透明,并在所需视图下方添加另一个视图,使该视图的背景颜色为黑色,并设置 alpha 值为 0.5 或您喜欢的不透明度级别。


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