具有模糊背景的模态导航控制器,在推送动画期间无缝?
Modal Navigation Controller with Blurred Background, Seamless During Push Animation?
我以模态方式呈现导航控制器,以 table 视图控制器作为根,并在用户点击单元格时推送更多视图控制器。现在,我想对整个事物应用 blur/vibrancy 效果。
情节提要如下所示:
现在,想要对整个导航控制器应用模糊效果,以便可以在下方看到初始视图控制器中的图像。
通过对 table 视图控制器和详细视图控制器应用相同的模糊效果,我取得了一些成功,如下所示:
Table 视图控制器
class TableViewController: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.tableFooterView = UIView(frame: .zero)
tableView.backgroundColor = UIColor.clear
let blurEffect = UIBlurEffect(style: .dark)
let blurEffectView = UIVisualEffectView(effect: blurEffect)
tableView.backgroundView = blurEffectView
tableView.separatorEffect = UIVibrancyEffect(blurEffect: blurEffect)
}
}
详细视图控制器
class DetailViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .clear
let blurEffect = UIBlurEffect(style: .dark)
let blurView = UIVisualEffectView(effect: blurEffect)
blurView.translatesAutoresizingMaskIntoConstraints = false
view.insertSubview(blurView, at: 0)
NSLayoutConstraint.activate([
blurView.heightAnchor.constraint(equalTo: view.heightAnchor),
blurView.widthAnchor.constraint(equalTo: view.widthAnchor),
])
}
}
(我也把导航的模态呈现样式设置为.overFullScreen
).
但是,在导航过渡到详细视图控制器的最后,在模糊的背景中可以看到一个烦人的工件 :
我觉得这跟iOS7之后pushed view controller不能有透明背景有关,否则pushing view controller会被看到,因为它会在结束时立即被移除过渡。
我尝试将模糊效果应用于导航控制器,并将两个子视图改为透明,但效果也不佳。
无缝地将背景 blur/vibrancy 效果应用到导航控制器的内容的正确方法是什么?
尝试以下步骤。我没试过,但应该可以。
- 将window背景图片添加到
didFinishLaunchingWithOptions
中,默认隐藏。
- 将相同的图像添加到呈现模态视图控制器中。
- 确保清除视图控制器的背景。
- 因此,当您呈现时,它会显示模态视图控制器中的图像。
- 完成后取消隐藏 window 图像并隐藏视图控制器图像。
- 当您点击后退按钮关闭视图时,首先取消隐藏您的视图控制器图像并隐藏您的 window 图像。所以你的动画看起来很正常。
如果您需要任何帮助,请告诉我。
正如我在问题中发现的那样,自 iOS 7 以来引入的 push/pop 动画过渡(其中导航下方的视图控制器堆栈 "slides out at half the speed",以便它在 pushed/popped) 的下方重叠 使得无法在没有工件的情况下在透明视图控制器之间进行转换。
所以我决定采用协议UINavigationControllerDelegate
,更准确地说是方法:navigationController(_:animationControllerFor:from:to:)
,基本上从头开始复制 UINavigationController 的动画过渡。
这让我可以添加一个 mask view 到在推送操作期间转换离开的视图,以便剪掉重叠部分,并且不会出现可见的伪影。
目前我只实现了push操作(最后还要做pop操作,还有交互过渡form swipe gesture-based pop), 我做了一个自定义的 UINavigationController
子类 和 put it on GitHub。
(目前,它支持动画推送和弹出,但不支持交互。此外,我还没有想出如何复制导航栏标题的 "slide" 动画 - 它只是交叉溶解.)
归结为以下步骤:
- 最初,将
.to
视图向右变换,off-screen。在动画期间,逐渐将其转换回身份(屏幕中心)。
- 将 white UIView 设置为
mask
到 .from
视图,最初的边界等于 .from
视图(无遮罩) .在动画过程中,逐渐将遮罩的 frame
属性 的宽度减小到其初始值的一半。
- 在动画播放期间,逐渐将
from
视图从屏幕中移到左侧。
在代码中:
class AnimationController: NSObject, UIViewControllerAnimatedTransitioning {
// Super slow for debugging:
let duration: TimeInterval = 1
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return duration
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
guard let fromView = transitionContext.view(forKey: .from) else {
return
}
guard let toView = transitionContext.view(forKey: .to) else {
return
}
guard let toViewController = transitionContext.viewController(forKey: .to) else {
return
}
//
// (Code below assumes navigation PUSH operation. For POP,
// use similar code but with view roles and direction
// reversed)
//
// Add target view to container:
transitionContext.containerView.addSubview(toView)
// Set tagret view frame, centered on screen
let toViewFinalFrame = transitionContext.finalFrame(for: toViewController)
toView.frame = toViewFinalFrame
let containerBounds = transitionContext.containerView.bounds
toView.center = CGPoint(x: containerBounds.midX, y: containerBounds.midY)
// But translate it to fully the RIGHT, for now:
toView.transform = CGAffineTransform(translationX: containerBounds.width, y: 0)
// We will slide the source view out, to the LEFT, by half as much:
let fromTransform = CGAffineTransform(translationX: -0.5*containerBounds.width, y: 0)
// Apply a white UIView as mask to the source view:
let maskView = UIView(frame: CGRect(origin: .zero, size: fromView.frame.size))
maskView.backgroundColor = .white
fromView.mask = maskView
// The mask will shrink to half the width during the animation:
let maskViewNewFrame = CGRect(origin: .zero, size: CGSize(width: 0.5*fromView.frame.width, height: fromView.frame.height))
// Animate:
UIView.animate(withDuration: duration, delay: 0, options: [.curveEaseOut], animations: {
fromView.transform = fromTransform // off-screen to the left (half)
toView.transform = .identity // Back on screen, fully centered
maskView.frame = maskViewNewFrame // Mask to half width
}, completion: {(completed) in
// Remove mask, or funny things will happen to a
// table view or scroll view if the user
// "rubber-bands":
fromView.mask = nil
transitionContext.completeTransition(completed)
})
}
结果:
(为了清晰起见,我在细节视图控制器中添加了一个文本视图)
我以模态方式呈现导航控制器,以 table 视图控制器作为根,并在用户点击单元格时推送更多视图控制器。现在,我想对整个事物应用 blur/vibrancy 效果。
情节提要如下所示:
现在,想要对整个导航控制器应用模糊效果,以便可以在下方看到初始视图控制器中的图像。
通过对 table 视图控制器和详细视图控制器应用相同的模糊效果,我取得了一些成功,如下所示:
Table 视图控制器
class TableViewController: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.tableFooterView = UIView(frame: .zero)
tableView.backgroundColor = UIColor.clear
let blurEffect = UIBlurEffect(style: .dark)
let blurEffectView = UIVisualEffectView(effect: blurEffect)
tableView.backgroundView = blurEffectView
tableView.separatorEffect = UIVibrancyEffect(blurEffect: blurEffect)
}
}
详细视图控制器
class DetailViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .clear
let blurEffect = UIBlurEffect(style: .dark)
let blurView = UIVisualEffectView(effect: blurEffect)
blurView.translatesAutoresizingMaskIntoConstraints = false
view.insertSubview(blurView, at: 0)
NSLayoutConstraint.activate([
blurView.heightAnchor.constraint(equalTo: view.heightAnchor),
blurView.widthAnchor.constraint(equalTo: view.widthAnchor),
])
}
}
(我也把导航的模态呈现样式设置为.overFullScreen
).
但是,在导航过渡到详细视图控制器的最后,在模糊的背景中可以看到一个烦人的工件 :
我觉得这跟iOS7之后pushed view controller不能有透明背景有关,否则pushing view controller会被看到,因为它会在结束时立即被移除过渡。
我尝试将模糊效果应用于导航控制器,并将两个子视图改为透明,但效果也不佳。
无缝地将背景 blur/vibrancy 效果应用到导航控制器的内容的正确方法是什么?
尝试以下步骤。我没试过,但应该可以。
- 将window背景图片添加到
didFinishLaunchingWithOptions
中,默认隐藏。 - 将相同的图像添加到呈现模态视图控制器中。
- 确保清除视图控制器的背景。
- 因此,当您呈现时,它会显示模态视图控制器中的图像。
- 完成后取消隐藏 window 图像并隐藏视图控制器图像。
- 当您点击后退按钮关闭视图时,首先取消隐藏您的视图控制器图像并隐藏您的 window 图像。所以你的动画看起来很正常。
如果您需要任何帮助,请告诉我。
正如我在问题中发现的那样,自 iOS 7 以来引入的 push/pop 动画过渡(其中导航下方的视图控制器堆栈 "slides out at half the speed",以便它在 pushed/popped) 的下方重叠 使得无法在没有工件的情况下在透明视图控制器之间进行转换。
所以我决定采用协议UINavigationControllerDelegate
,更准确地说是方法:navigationController(_:animationControllerFor:from:to:)
,基本上从头开始复制 UINavigationController 的动画过渡。
这让我可以添加一个 mask view 到在推送操作期间转换离开的视图,以便剪掉重叠部分,并且不会出现可见的伪影。
目前我只实现了push操作(最后还要做pop操作,还有交互过渡form swipe gesture-based pop), 我做了一个自定义的 UINavigationController
子类 和 put it on GitHub。
(目前,它支持动画推送和弹出,但不支持交互。此外,我还没有想出如何复制导航栏标题的 "slide" 动画 - 它只是交叉溶解.)
归结为以下步骤:
- 最初,将
.to
视图向右变换,off-screen。在动画期间,逐渐将其转换回身份(屏幕中心)。 - 将 white UIView 设置为
mask
到.from
视图,最初的边界等于.from
视图(无遮罩) .在动画过程中,逐渐将遮罩的frame
属性 的宽度减小到其初始值的一半。 - 在动画播放期间,逐渐将
from
视图从屏幕中移到左侧。
在代码中:
class AnimationController: NSObject, UIViewControllerAnimatedTransitioning {
// Super slow for debugging:
let duration: TimeInterval = 1
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return duration
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
guard let fromView = transitionContext.view(forKey: .from) else {
return
}
guard let toView = transitionContext.view(forKey: .to) else {
return
}
guard let toViewController = transitionContext.viewController(forKey: .to) else {
return
}
//
// (Code below assumes navigation PUSH operation. For POP,
// use similar code but with view roles and direction
// reversed)
//
// Add target view to container:
transitionContext.containerView.addSubview(toView)
// Set tagret view frame, centered on screen
let toViewFinalFrame = transitionContext.finalFrame(for: toViewController)
toView.frame = toViewFinalFrame
let containerBounds = transitionContext.containerView.bounds
toView.center = CGPoint(x: containerBounds.midX, y: containerBounds.midY)
// But translate it to fully the RIGHT, for now:
toView.transform = CGAffineTransform(translationX: containerBounds.width, y: 0)
// We will slide the source view out, to the LEFT, by half as much:
let fromTransform = CGAffineTransform(translationX: -0.5*containerBounds.width, y: 0)
// Apply a white UIView as mask to the source view:
let maskView = UIView(frame: CGRect(origin: .zero, size: fromView.frame.size))
maskView.backgroundColor = .white
fromView.mask = maskView
// The mask will shrink to half the width during the animation:
let maskViewNewFrame = CGRect(origin: .zero, size: CGSize(width: 0.5*fromView.frame.width, height: fromView.frame.height))
// Animate:
UIView.animate(withDuration: duration, delay: 0, options: [.curveEaseOut], animations: {
fromView.transform = fromTransform // off-screen to the left (half)
toView.transform = .identity // Back on screen, fully centered
maskView.frame = maskViewNewFrame // Mask to half width
}, completion: {(completed) in
// Remove mask, or funny things will happen to a
// table view or scroll view if the user
// "rubber-bands":
fromView.mask = nil
transitionContext.completeTransition(completed)
})
}
结果:
(为了清晰起见,我在细节视图控制器中添加了一个文本视图)