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

Posted

技术标签:

【中文标题】如何在 iOS 中呈现一个半透明(半切)的视图控制器?【英文标题】:How to present a semi-transparent (half-cut) viewcontroller in iOS? 【发布时间】:2013-10-01 20:50:17 【问题描述】:

我使用下面的代码来展示一个视图控制器。 我的问题是:动画完成后,透明的主背景变成了不透明的黑色。

如何解决此问题并使其保持为 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”的内容(演示者视图控制器的视图)。

【问题讨论】:

这不是透明度问题。动画完成后,ios 会从屏幕上移除隐藏的视图控制器。您看到的黑色是窗口的背景颜色。我相信 iOS7 对此有一些选择。 @BrianNickel 所以你的意思是演示者视图控制器是隐藏的,直到呈现的一个被解雇?在这种情况下,我是否应该手动添加视图并使用动画将其从屏幕底部拉出来? 没错。您可以跳过menuViewController,而只是将menuView 动画到屏幕上。 您能否发布一个答案,其中提到您刚刚给出的主要解释? (无法通过呈现的视图控制器看到演示者视图控制器)。所以我将其标记为答案,其他用户可以轻松找到原因。谢谢! 不,添加的视图上的任何“交互”都由其原始视图控制器处理。但是由于它的父视图在添加到另一个视图后发生了变化,因此链接链被破坏了。为了正确传播方法调用(例如 viewDidAppear),将子控制器也添加为子控制器也很重要。 【参考方案1】:

iOS 15 更新

Apple 引入了一个新的 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 中,您可以呈现一个视图控制器,并且在下面仍然可以看到原始视图控制器,就像一个表单一样。为此,您需要做两件事:

    将模态展示样式设置为自定义:

    viewControllerToPresent.modalPresentationStyle = UIModalPresentationCustom;
    

    设置过渡委托:

    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

不是最直接的解决方案,但可以避免黑客攻击并且效果很好。自定义转换是必需的,因为默认情况下 iOS 会在转换结束时移除第一个视图控制器。

iOS 8 更新

对于 iOS 8,情况再次发生了变化。您需要做的就是使用新的演示样式.OverCurrentContext,即:

viewControllerToPresent.modalPresentationStyle = UIModalPresentationOverCurrentContext;

【讨论】:

modalPresentationStyletransitionDelegate可以在parepareForSegue中设置 这应该被标记为最佳答案 这会在 iOS 8 中中断。关闭屏幕时会留下白色 删除以下行修复了问题[transitionContext.containerView addSubview:toViewController.view]; [transitionContext.containerView addSubview:fromViewController.view]; 看起来在 iOS8+ 中自定义转换在关闭时失败,因为它不希望 presentingViewController 的视图在任何时候移动到 transitionContext.containerView。当该视图被删除时,演示者会随之而来。删除这些行修复了 8+ 的解决方案(并且可能仍然适用于 7)。【参考方案2】:

更新

在大多数情况下,您需要遵循下面Ric's answer 中的指南。正如他所提到的,menuViewController.modalPresentationStyle = .overCurrentContext 是保持呈现视图控制器可见的最简单的现代方式。

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


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

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

【讨论】:

【参考方案3】:

这是一个相当容易解决的问题。无需创建自定义视图转换,您只需为正在呈现的视图控制器设置 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+ 上进行了测试。【参考方案4】:

您应该使用属性 modalPresentationStyle available 从 iOS 3.2 开始。

例如:

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

【讨论】:

这并不能解决问题。 PresenterViewController 仍然从屏幕上移除。 从子 VC 或其他模态展示时也有一些怪癖。 Ку.【参考方案5】:

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

【讨论】:

【参考方案6】:

这是一篇相当老的帖子,感谢 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)
        
    


【讨论】:

以上是关于如何在 iOS 中呈现一个半透明(半切)的视图控制器?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 iOS 7 自定义转换在顶部呈现半模态视图控制器

如何在 iOS 6 中使用 UIPopoverBackgroundView 呈现透明弹出框

iOS 7 半透明模态视图控制器

在 iOS 9 中呈现透明模式视图

UIPrintInteractionController半透明条问题

iOS Autolayout - 在半透明导航栏下方正确定位视图