Swift 3 中的自定义转换未正确翻译
Custom transition in Swift 3 does not translate correctly
我已经实现了一个导航控制器来合并一个旋转圆盘类型的布局(想象每个 VC 布置成一个圆圈,作为一个整体旋转,依次进入视图。导航控制器被配置使用自定义转换 class,如下所示:-
import UIKit
class RotaryTransition: NSObject, UIViewControllerAnimatedTransitioning {
let isPresenting :Bool
let duration :TimeInterval = 0.5
let animationDuration: TimeInterval = 0.7
let delay: TimeInterval = 0
let damping: CGFloat = 1.4
let spring: CGFloat = 6.0
init(isPresenting: Bool) {
self.isPresenting = isPresenting
super.init()
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
//Get references to the view hierarchy
let fromViewController: UIViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from)!
let toViewController: UIViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to)!
let sourceRect: CGRect = transitionContext.initialFrame(for: fromViewController)
let containerView: UIView = transitionContext.containerView
if self.isPresenting { // Push
//1. Settings for the fromVC ............................
// fromViewController.view.frame = sourceRect
fromViewController.view.layer.anchorPoint = CGPoint(x: 0.5, y: 3);
fromViewController.view.layer.position = CGPoint(x: fromViewController.view.frame.size.width/2, y: fromViewController.view.frame.size.height * 3);
//2. Setup toVC view...........................
containerView.insertSubview(toViewController.view, belowSubview:fromViewController.view)
toViewController.view.layer.anchorPoint = CGPoint(x: 0.5, y: 3);
toViewController.view.layer.position = CGPoint(x: toViewController.view.frame.size.width/2, y: toViewController.view.frame.size.height * 3);
toViewController.view.transform = CGAffineTransform(rotationAngle: 15 * CGFloat(M_PI / 180));
//3. Perform the animation...............................
UIView.animate(withDuration: animationDuration, delay:delay, usingSpringWithDamping: damping, initialSpringVelocity: spring, options: [], animations: {
fromViewController.view.transform = CGAffineTransform(rotationAngle: -15 * CGFloat(M_PI / 180));
toViewController.view.transform = CGAffineTransform(rotationAngle: 0);
}, completion: {
(animated: Bool) -> () in transitionContext.completeTransition(true)
})
} else { // Pop
//1. Settings for the fromVC ............................
fromViewController.view.frame = sourceRect
fromViewController.view.layer.anchorPoint = CGPoint(x: 0.5, y: 3);
fromViewController.view.layer.position = CGPoint(x: fromViewController.view.frame.size.width/2, y: fromViewController.view.frame.size.height * 3);
//2. Setup toVC view...........................
// toViewController.view.frame = transitionContext.finalFrame(for: toViewController)
toViewController.view.layer.anchorPoint = CGPoint(x: 0.5, y: 3);
toViewController.view.layer.position = CGPoint(x: toViewController.view.frame.size.width/2, y: toViewController.view.frame.size.height * 3);
toViewController.view.transform = CGAffineTransform(rotationAngle: -15 * CGFloat(M_PI / 180));
containerView.insertSubview(toViewController.view, belowSubview:fromViewController.view)
//3. Perform the animation...............................
UIView.animate(withDuration: animationDuration, delay:delay, usingSpringWithDamping: damping, initialSpringVelocity: spring, options: [], animations: {
fromViewController.view.transform = CGAffineTransform(rotationAngle: 15 * CGFloat(M_PI / 180));
toViewController.view.transform = CGAffineTransform(rotationAngle: 0);
}, completion: {
//When the animation is completed call completeTransition
(animated: Bool) -> () in transitionContext.completeTransition(true)
})
}
}
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return duration;
}
}
视图如何移动的表示如下图所示...两个红色区域是问题所在,稍后解释。
呈现(推送)翻译工作正常 - 2 移动到 1,3 移动到 2。但是,解雇(pop)翻译没有,因此解雇 VC 似乎正确地移出视野( 2 移动到 3),但是呈现(上一个)VC 到达错误的位置,或者框架大小不正确...
使用 class 原样,转换结果是 2 移动到 3(正确)但 1 然后移动到 4,视图大小正确,但似乎偏移了一个看似任意的距离,从预定位置。从那以后,我尝试了各种不同的解决方案。
在 pop 部分我尝试添加以下行(在代码中注释):-
toViewController.view.frame = transitionContext.finalFrame(for: toViewController)
...但是 VC 现在最终缩小了(1 移动到 5)。我希望有人能看到我正在犯的可能是愚蠢的错误。我尝试简单地将推送部分复制到弹出部分(并反转所有内容),但它不起作用!
仅供参考...那些需要知道如何连接过渡到 UINavigationController 的人 - 将 UINavigationControllerDelegate 添加到您的导航控制器,以及以下功能...
func navigationController(_ navigationController: UINavigationController, animationControllerFor operation: UINavigationControllerOperation, from fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
let transition: SwingTransition = SwingTransition.init(isPresenting: ( operation == .push ? true : false ))
return transition;
}
下图显示了所有视图如何共享相同的起点(用于翻译)。 objective 是为了给人一种左轮手枪枪管将每个 VC 移动到视野中的错觉。顶部中心视图代表视图 window,显示堆栈中的第三个视图。为糟糕的视觉效果道歉...
问题是恢复的视图控制器视图中的属性之一没有正确重置。我建议在动画完成后重置它(您可能不想保留非标准的 transform
和 anchorPoint
以防您稍后执行其他假定视图未转换的动画) .因此,在动画的 completion
块中,重置视图的 position
、anchorPoint
和 transform
。
class RotaryTransition: NSObject, UIViewControllerAnimatedTransitioning {
let isPresenting: Bool
let duration: TimeInterval = 0.5
let delay: TimeInterval = 0
let damping: CGFloat = 1.4
let spring: CGFloat = 6
init(isPresenting: Bool) {
self.isPresenting = isPresenting
super.init()
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
let from = transitionContext.viewController(forKey: .from)!
let to = transitionContext.viewController(forKey: .to)!
let frame = transitionContext.initialFrame(for: from)
let height = frame.size.height
let width = frame.size.width
let angle: CGFloat = 15.0 * .pi / 180.0
let rotationCenterOffset: CGFloat = width / 2 / tan(angle / 2) / height + 1 // use fixed value, e.g. 3, if you want, or use this to ensure that the corners of the two views just touch, but don't overlap
let rightTransform = CATransform3DMakeRotation(angle, 0, 0, 1)
let leftTransform = CATransform3DMakeRotation(-angle, 0, 0, 1)
transitionContext.containerView.insertSubview(to.view, aboveSubview: from.view)
// save the anchor and position
let anchorPoint = from.view.layer.anchorPoint
let position = from.view.layer.position
// prepare `to` layer for rotation
to.view.layer.anchorPoint = CGPoint(x: 0.5, y: rotationCenterOffset)
to.view.layer.position = CGPoint(x: width / 2, y: height * rotationCenterOffset)
to.view.layer.transform = self.isPresenting ? rightTransform : leftTransform
//to.view.layer.opacity = 0
// prepare `from` layer for rotation
from.view.layer.anchorPoint = CGPoint(x: 0.5, y: rotationCenterOffset)
from.view.layer.position = CGPoint(x: width / 2, y: height * rotationCenterOffset)
// rotate
UIView.animate(withDuration: duration, delay: delay, usingSpringWithDamping: damping, initialSpringVelocity: spring, animations: {
from.view.layer.transform = self.isPresenting ? leftTransform : rightTransform
to.view.layer.transform = CATransform3DIdentity
//to.view.layer.opacity = 1
//from.view.layer.opacity = 0
}, completion: { finished in
// restore the layers to their default configuration
for view in [to.view, from.view] {
view?.layer.transform = CATransform3DIdentity
view?.layer.anchorPoint = anchorPoint
view?.layer.position = position
//view?.layer.opacity = 1
}
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
})
}
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return duration
}
}
我在这里的时候还做了一些其他杂七杂八的改动:
- 消除了分号;
- 消除了持续时间属性之一;
- 将
animate
方法的 completion
闭包的参数名称固定为 finished
而不是 animated
以更准确地反映其真正目的.. . 你也可以使用 _
;
- 根据动画是否被取消设置
completeTransition
(因为如果你曾经做过这个 interactive/cancelable,你不想总是使用 true
);
- 使用
.pi
而不是M_PI
;
- 我注释掉了我对
opacity
的调整,但我通常这样做是为了让效果更加精致,并确保如果你调整角度使视图重叠,你不会感到任何奇怪动画开始或结束时其他视图的伪影;我实际上已经计算了参数,因此无论屏幕尺寸如何,都没有重叠,所以这不是必需的,我注释掉了 opacity
行,但您可以考虑使用它们,具体取决于所需的效果。
之前我展示了如何稍微简化这个过程,但最终的效果并不是您想要的,如果您有兴趣,请参阅 previous rendition of this answer。
您的问题很常见,发生在您进行自定义视图控制器转换时。我知道这一点,因为我已经做了很多:)
您正在查找弹出转换中的问题,但实际问题出在推送中。如果您在过渡后检查堆栈中第一个控制器的视图,您会发现它有一个不寻常的框架,因为您弄乱了它的变换、锚点和图层位置等。真的,你需要在结束过渡之前清理所有这些,否则它稍后会咬你,就像你在流行音乐中看到的那样。
进行自定义转换的一种更简单、更安全的方法是添加一个 "canvas" 视图,然后向该 canvas 添加传出和传入视图的快照并对其进行操作。这意味着您在过渡结束时没有清理 - 只需删除 canvas 视图。我已经写过有关此技术的文章 here。对于您的情况,我添加了以下便捷方法:
extension UIView {
func snapshot(view: UIView, afterUpdates: Bool) -> UIView? {
guard let snapshot = view.snapshotView(afterScreenUpdates: afterUpdates) else { return nil }
self.addSubview(snapshot)
snapshot.frame = convert(view.bounds, from: view)
return snapshot
}
}
然后更新您的转换代码以在 canvas 视图上四处移动快照:
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
//Get references to the view hierarchy
let fromViewController: UIViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from)!
let toViewController: UIViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to)!
let sourceRect: CGRect = transitionContext.initialFrame(for: fromViewController)
let containerView: UIView = transitionContext.containerView
// The canvas is used for all animation and discarded at the end
let canvas = UIView(frame: containerView.bounds)
containerView.addSubview(canvas)
let fromView = transitionContext.view(forKey: .from)!
let toView = transitionContext.view(forKey: .to)!
toView.frame = transitionContext.finalFrame(for: toViewController)
toView.layoutIfNeeded()
let toSnap = canvas.snapshot(view: toView, afterUpdates: true)!
if self.isPresenting { // Push
//1. Settings for the fromVC ............................
// fromViewController.view.frame = sourceRect
let fromSnap = canvas.snapshot(view: fromView, afterUpdates: false)!
fromView.removeFromSuperview()
fromSnap.layer.anchorPoint = CGPoint(x: 0.5, y: 3);
fromSnap.layer.position = CGPoint(x: fromViewController.view.frame.size.width/2, y: fromViewController.view.frame.size.height * 3);
//2. Setup toVC view...........................
toSnap.layer.anchorPoint = CGPoint(x: 0.5, y: 3);
toSnap.layer.position = CGPoint(x: toViewController.view.frame.size.width/2, y: toViewController.view.frame.size.height * 3);
toSnap.transform = CGAffineTransform(rotationAngle: 15 * CGFloat(M_PI / 180));
//3. Perform the animation...............................
UIView.animate(withDuration: animationDuration, delay:delay, usingSpringWithDamping: damping, initialSpringVelocity: spring, options: [], animations: {
fromSnap.transform = CGAffineTransform(rotationAngle: -15 * CGFloat(M_PI / 180));
toSnap.transform = CGAffineTransform(rotationAngle: 0);
}, completion: {
(animated: Bool) -> () in
containerView.insertSubview(toViewController.view, belowSubview:canvas)
canvas.removeFromSuperview()
transitionContext.completeTransition(true)
})
} else { // Pop
//1. Settings for the fromVC ............................
fromViewController.view.frame = sourceRect
fromViewController.view.layer.anchorPoint = CGPoint(x: 0.5, y: 3);
fromViewController.view.layer.position = CGPoint(x: fromViewController.view.frame.size.width/2, y: fromViewController.view.frame.size.height * 3);
//2. Setup toVC view...........................
let toSnap = canvas.snapshot(view: toView, afterUpdates: true)!
toSnap.layer.anchorPoint = CGPoint(x: 0.5, y: 3);
toSnap.layer.position = CGPoint(x: toViewController.view.frame.size.width/2, y: toViewController.view.frame.size.height * 3);
toSnap.transform = CGAffineTransform(rotationAngle: -15 * CGFloat(M_PI / 180));
//3. Perform the animation...............................
UIView.animate(withDuration: animationDuration, delay:delay, usingSpringWithDamping: damping, initialSpringVelocity: spring, options: [], animations: {
fromViewController.view.transform = CGAffineTransform(rotationAngle: 15 * CGFloat(M_PI / 180));
toSnap.transform = CGAffineTransform(rotationAngle: 0);
}, completion: {
//When the animation is completed call completeTransition
(animated: Bool) -> () in
containerView.insertSubview(toViewController.view, belowSubview: canvas)
canvas.removeFromSuperview()
transitionContext.completeTransition(true)
})
}
}
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return duration;
}
}
这个特定的过渡非常简单,因此重置视图框架的属性并不难,但如果你做任何更复杂的事情,那么 canvas 和快照方法非常有效,所以我倾向于随处使用它。
我已经实现了一个导航控制器来合并一个旋转圆盘类型的布局(想象每个 VC 布置成一个圆圈,作为一个整体旋转,依次进入视图。导航控制器被配置使用自定义转换 class,如下所示:-
import UIKit class RotaryTransition: NSObject, UIViewControllerAnimatedTransitioning { let isPresenting :Bool let duration :TimeInterval = 0.5 let animationDuration: TimeInterval = 0.7 let delay: TimeInterval = 0 let damping: CGFloat = 1.4 let spring: CGFloat = 6.0 init(isPresenting: Bool) { self.isPresenting = isPresenting super.init() } func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { //Get references to the view hierarchy let fromViewController: UIViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from)! let toViewController: UIViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to)! let sourceRect: CGRect = transitionContext.initialFrame(for: fromViewController) let containerView: UIView = transitionContext.containerView if self.isPresenting { // Push //1. Settings for the fromVC ............................ // fromViewController.view.frame = sourceRect fromViewController.view.layer.anchorPoint = CGPoint(x: 0.5, y: 3); fromViewController.view.layer.position = CGPoint(x: fromViewController.view.frame.size.width/2, y: fromViewController.view.frame.size.height * 3); //2. Setup toVC view........................... containerView.insertSubview(toViewController.view, belowSubview:fromViewController.view) toViewController.view.layer.anchorPoint = CGPoint(x: 0.5, y: 3); toViewController.view.layer.position = CGPoint(x: toViewController.view.frame.size.width/2, y: toViewController.view.frame.size.height * 3); toViewController.view.transform = CGAffineTransform(rotationAngle: 15 * CGFloat(M_PI / 180)); //3. Perform the animation............................... UIView.animate(withDuration: animationDuration, delay:delay, usingSpringWithDamping: damping, initialSpringVelocity: spring, options: [], animations: { fromViewController.view.transform = CGAffineTransform(rotationAngle: -15 * CGFloat(M_PI / 180)); toViewController.view.transform = CGAffineTransform(rotationAngle: 0); }, completion: { (animated: Bool) -> () in transitionContext.completeTransition(true) }) } else { // Pop //1. Settings for the fromVC ............................ fromViewController.view.frame = sourceRect fromViewController.view.layer.anchorPoint = CGPoint(x: 0.5, y: 3); fromViewController.view.layer.position = CGPoint(x: fromViewController.view.frame.size.width/2, y: fromViewController.view.frame.size.height * 3); //2. Setup toVC view........................... // toViewController.view.frame = transitionContext.finalFrame(for: toViewController) toViewController.view.layer.anchorPoint = CGPoint(x: 0.5, y: 3); toViewController.view.layer.position = CGPoint(x: toViewController.view.frame.size.width/2, y: toViewController.view.frame.size.height * 3); toViewController.view.transform = CGAffineTransform(rotationAngle: -15 * CGFloat(M_PI / 180)); containerView.insertSubview(toViewController.view, belowSubview:fromViewController.view) //3. Perform the animation............................... UIView.animate(withDuration: animationDuration, delay:delay, usingSpringWithDamping: damping, initialSpringVelocity: spring, options: [], animations: { fromViewController.view.transform = CGAffineTransform(rotationAngle: 15 * CGFloat(M_PI / 180)); toViewController.view.transform = CGAffineTransform(rotationAngle: 0); }, completion: { //When the animation is completed call completeTransition (animated: Bool) -> () in transitionContext.completeTransition(true) }) } } func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval { return duration; } }
视图如何移动的表示如下图所示...两个红色区域是问题所在,稍后解释。
呈现(推送)翻译工作正常 - 2 移动到 1,3 移动到 2。但是,解雇(pop)翻译没有,因此解雇 VC 似乎正确地移出视野( 2 移动到 3),但是呈现(上一个)VC 到达错误的位置,或者框架大小不正确...
使用 class 原样,转换结果是 2 移动到 3(正确)但 1 然后移动到 4,视图大小正确,但似乎偏移了一个看似任意的距离,从预定位置。从那以后,我尝试了各种不同的解决方案。
在 pop 部分我尝试添加以下行(在代码中注释):-
toViewController.view.frame = transitionContext.finalFrame(for: toViewController)
...但是 VC 现在最终缩小了(1 移动到 5)。我希望有人能看到我正在犯的可能是愚蠢的错误。我尝试简单地将推送部分复制到弹出部分(并反转所有内容),但它不起作用!
仅供参考...那些需要知道如何连接过渡到 UINavigationController 的人 - 将 UINavigationControllerDelegate 添加到您的导航控制器,以及以下功能...
func navigationController(_ navigationController: UINavigationController, animationControllerFor operation: UINavigationControllerOperation, from fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? { let transition: SwingTransition = SwingTransition.init(isPresenting: ( operation == .push ? true : false )) return transition; }
下图显示了所有视图如何共享相同的起点(用于翻译)。 objective 是为了给人一种左轮手枪枪管将每个 VC 移动到视野中的错觉。顶部中心视图代表视图 window,显示堆栈中的第三个视图。为糟糕的视觉效果道歉...
问题是恢复的视图控制器视图中的属性之一没有正确重置。我建议在动画完成后重置它(您可能不想保留非标准的 transform
和 anchorPoint
以防您稍后执行其他假定视图未转换的动画) .因此,在动画的 completion
块中,重置视图的 position
、anchorPoint
和 transform
。
class RotaryTransition: NSObject, UIViewControllerAnimatedTransitioning {
let isPresenting: Bool
let duration: TimeInterval = 0.5
let delay: TimeInterval = 0
let damping: CGFloat = 1.4
let spring: CGFloat = 6
init(isPresenting: Bool) {
self.isPresenting = isPresenting
super.init()
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
let from = transitionContext.viewController(forKey: .from)!
let to = transitionContext.viewController(forKey: .to)!
let frame = transitionContext.initialFrame(for: from)
let height = frame.size.height
let width = frame.size.width
let angle: CGFloat = 15.0 * .pi / 180.0
let rotationCenterOffset: CGFloat = width / 2 / tan(angle / 2) / height + 1 // use fixed value, e.g. 3, if you want, or use this to ensure that the corners of the two views just touch, but don't overlap
let rightTransform = CATransform3DMakeRotation(angle, 0, 0, 1)
let leftTransform = CATransform3DMakeRotation(-angle, 0, 0, 1)
transitionContext.containerView.insertSubview(to.view, aboveSubview: from.view)
// save the anchor and position
let anchorPoint = from.view.layer.anchorPoint
let position = from.view.layer.position
// prepare `to` layer for rotation
to.view.layer.anchorPoint = CGPoint(x: 0.5, y: rotationCenterOffset)
to.view.layer.position = CGPoint(x: width / 2, y: height * rotationCenterOffset)
to.view.layer.transform = self.isPresenting ? rightTransform : leftTransform
//to.view.layer.opacity = 0
// prepare `from` layer for rotation
from.view.layer.anchorPoint = CGPoint(x: 0.5, y: rotationCenterOffset)
from.view.layer.position = CGPoint(x: width / 2, y: height * rotationCenterOffset)
// rotate
UIView.animate(withDuration: duration, delay: delay, usingSpringWithDamping: damping, initialSpringVelocity: spring, animations: {
from.view.layer.transform = self.isPresenting ? leftTransform : rightTransform
to.view.layer.transform = CATransform3DIdentity
//to.view.layer.opacity = 1
//from.view.layer.opacity = 0
}, completion: { finished in
// restore the layers to their default configuration
for view in [to.view, from.view] {
view?.layer.transform = CATransform3DIdentity
view?.layer.anchorPoint = anchorPoint
view?.layer.position = position
//view?.layer.opacity = 1
}
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
})
}
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return duration
}
}
我在这里的时候还做了一些其他杂七杂八的改动:
- 消除了分号;
- 消除了持续时间属性之一;
- 将
animate
方法的completion
闭包的参数名称固定为finished
而不是animated
以更准确地反映其真正目的.. . 你也可以使用_
; - 根据动画是否被取消设置
completeTransition
(因为如果你曾经做过这个 interactive/cancelable,你不想总是使用true
); - 使用
.pi
而不是M_PI
; - 我注释掉了我对
opacity
的调整,但我通常这样做是为了让效果更加精致,并确保如果你调整角度使视图重叠,你不会感到任何奇怪动画开始或结束时其他视图的伪影;我实际上已经计算了参数,因此无论屏幕尺寸如何,都没有重叠,所以这不是必需的,我注释掉了opacity
行,但您可以考虑使用它们,具体取决于所需的效果。
之前我展示了如何稍微简化这个过程,但最终的效果并不是您想要的,如果您有兴趣,请参阅 previous rendition of this answer。
您的问题很常见,发生在您进行自定义视图控制器转换时。我知道这一点,因为我已经做了很多:)
您正在查找弹出转换中的问题,但实际问题出在推送中。如果您在过渡后检查堆栈中第一个控制器的视图,您会发现它有一个不寻常的框架,因为您弄乱了它的变换、锚点和图层位置等。真的,你需要在结束过渡之前清理所有这些,否则它稍后会咬你,就像你在流行音乐中看到的那样。
进行自定义转换的一种更简单、更安全的方法是添加一个 "canvas" 视图,然后向该 canvas 添加传出和传入视图的快照并对其进行操作。这意味着您在过渡结束时没有清理 - 只需删除 canvas 视图。我已经写过有关此技术的文章 here。对于您的情况,我添加了以下便捷方法:
extension UIView {
func snapshot(view: UIView, afterUpdates: Bool) -> UIView? {
guard let snapshot = view.snapshotView(afterScreenUpdates: afterUpdates) else { return nil }
self.addSubview(snapshot)
snapshot.frame = convert(view.bounds, from: view)
return snapshot
}
}
然后更新您的转换代码以在 canvas 视图上四处移动快照:
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
//Get references to the view hierarchy
let fromViewController: UIViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from)!
let toViewController: UIViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to)!
let sourceRect: CGRect = transitionContext.initialFrame(for: fromViewController)
let containerView: UIView = transitionContext.containerView
// The canvas is used for all animation and discarded at the end
let canvas = UIView(frame: containerView.bounds)
containerView.addSubview(canvas)
let fromView = transitionContext.view(forKey: .from)!
let toView = transitionContext.view(forKey: .to)!
toView.frame = transitionContext.finalFrame(for: toViewController)
toView.layoutIfNeeded()
let toSnap = canvas.snapshot(view: toView, afterUpdates: true)!
if self.isPresenting { // Push
//1. Settings for the fromVC ............................
// fromViewController.view.frame = sourceRect
let fromSnap = canvas.snapshot(view: fromView, afterUpdates: false)!
fromView.removeFromSuperview()
fromSnap.layer.anchorPoint = CGPoint(x: 0.5, y: 3);
fromSnap.layer.position = CGPoint(x: fromViewController.view.frame.size.width/2, y: fromViewController.view.frame.size.height * 3);
//2. Setup toVC view...........................
toSnap.layer.anchorPoint = CGPoint(x: 0.5, y: 3);
toSnap.layer.position = CGPoint(x: toViewController.view.frame.size.width/2, y: toViewController.view.frame.size.height * 3);
toSnap.transform = CGAffineTransform(rotationAngle: 15 * CGFloat(M_PI / 180));
//3. Perform the animation...............................
UIView.animate(withDuration: animationDuration, delay:delay, usingSpringWithDamping: damping, initialSpringVelocity: spring, options: [], animations: {
fromSnap.transform = CGAffineTransform(rotationAngle: -15 * CGFloat(M_PI / 180));
toSnap.transform = CGAffineTransform(rotationAngle: 0);
}, completion: {
(animated: Bool) -> () in
containerView.insertSubview(toViewController.view, belowSubview:canvas)
canvas.removeFromSuperview()
transitionContext.completeTransition(true)
})
} else { // Pop
//1. Settings for the fromVC ............................
fromViewController.view.frame = sourceRect
fromViewController.view.layer.anchorPoint = CGPoint(x: 0.5, y: 3);
fromViewController.view.layer.position = CGPoint(x: fromViewController.view.frame.size.width/2, y: fromViewController.view.frame.size.height * 3);
//2. Setup toVC view...........................
let toSnap = canvas.snapshot(view: toView, afterUpdates: true)!
toSnap.layer.anchorPoint = CGPoint(x: 0.5, y: 3);
toSnap.layer.position = CGPoint(x: toViewController.view.frame.size.width/2, y: toViewController.view.frame.size.height * 3);
toSnap.transform = CGAffineTransform(rotationAngle: -15 * CGFloat(M_PI / 180));
//3. Perform the animation...............................
UIView.animate(withDuration: animationDuration, delay:delay, usingSpringWithDamping: damping, initialSpringVelocity: spring, options: [], animations: {
fromViewController.view.transform = CGAffineTransform(rotationAngle: 15 * CGFloat(M_PI / 180));
toSnap.transform = CGAffineTransform(rotationAngle: 0);
}, completion: {
//When the animation is completed call completeTransition
(animated: Bool) -> () in
containerView.insertSubview(toViewController.view, belowSubview: canvas)
canvas.removeFromSuperview()
transitionContext.completeTransition(true)
})
}
}
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return duration;
}
}
这个特定的过渡非常简单,因此重置视图框架的属性并不难,但如果你做任何更复杂的事情,那么 canvas 和快照方法非常有效,所以我倾向于随处使用它。