如何在 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;
【讨论】:
modalPresentationStyle
和transitionDelegate
可以在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 6 中使用 UIPopoverBackgroundView 呈现透明弹出框