您提到了子视图的动画,但没有谈论整体动画。我倾向于使用容器视图来进行动画,以避免在同时对子视图和主视图进行动画时出现任何潜在的混淆/问题。我的做法如下:
- 制作“from”视图中子视图位置的快照,然后隐藏子视图;
- 制作“to”视图中子视图位置的快照,然后隐藏子视图;
- 将所有这些
frame
值转换为容器的坐标空间,并将所有这些快照添加到容器视图中;
- 将“to”快照的
alpha
从零开始(以便它们淡入);
- 将“to”快照的更改同时动画化到其最终目的地,将它们的
alpha
更改回1
。
- 同时将“from”快照动画到“to”视图最终目的地的位置,并将它们的
alpha
动画到零(这样它们就会淡出,在与第4点相结合时产生一种交叉溶解效果)。
- 完成后,删除快照并取消隐藏其快照被动画化的子视图。
其效果是标签从一个位置滑动到另一个位置,如果初始和最终内容不同,那么在它们移动时会产生一种交叉溶解效果。
例如:
![enter image description here](https://istack.dev59.com/nDFtQ.gif)
通过使用容器视图来进行快照的动画,它独立于您可能正在对目标场景的主视图进行的任何动画。在这种情况下,我将其从右侧滑入,但您可以按照自己的喜好进行操作。
或者,您可以对多个子视图进行此操作:
![enter image description here](https://istack.dev59.com/BShLK.gif)
(个人认为,如果情况是这样的,即几乎所有内容都在滑动,那么我会失去主视图的滑动动画,因为它现在变得令人分心,但它给了您基本的想法。同时,在我的退出动画中,我交换了要传递给另一个视图的视图,您永远不会这样做,但我只是想说明它的灵活性和淡入淡出效果。)
要呈现上述内容,我在Swift 4中使用了以下内容:
protocol CustomTransitionOriginator {
var fromAnimatedSubviews: [UIView] { get }
}
protocol CustomTransitionDestination {
var toAnimatedSubviews: [UIView] { get }
}
class Animator: NSObject, UIViewControllerAnimatedTransitioning {
enum TransitionType {
case present
case dismiss
}
let type: TransitionType
init(type: TransitionType) {
self.type = type
super.init()
}
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return 1.0
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
let fromVC = transitionContext.viewController(forKey: .from) as! CustomTransitionOriginator & UIViewController
let toVC = transitionContext.viewController(forKey: .to) as! CustomTransitionDestination & UIViewController
let container = transitionContext.containerView
toVC.view.frame = fromVC.view.frame
if type == .present {
container.addSubview(toVC.view)
} else {
container.insertSubview(toVC.view, belowSubview: fromVC.view)
}
toVC.view.layoutIfNeeded()
let fromSnapshots = fromVC.fromAnimatedSubviews.map { subview -> UIView in
let snapshot = subview.snapshotView(afterScreenUpdates: false)!
snapshot.frame = container.convert(subview.frame, from: subview.superview)
return snapshot
}
let toSnapshots = toVC.toAnimatedSubviews.map { subview -> UIView in
let snapshot = subview.snapshotView(afterScreenUpdates: true)!// UIImageView(image: subview.snapshot())
snapshot.frame = container.convert(subview.frame, from: subview.superview)
return snapshot
}
let frames = zip(fromSnapshots, toSnapshots).map { ($0.frame, $1.frame) }
zip(toSnapshots, frames).forEach { snapshot, frame in
snapshot.frame = frame.0
snapshot.alpha = 0
container.addSubview(snapshot)
}
fromSnapshots.forEach { container.addSubview($0) }
fromVC.fromAnimatedSubviews.forEach { $0.alpha = 0 }
toVC.toAnimatedSubviews.forEach { $0.alpha = 0 }
if type == .present {
toVC.view.transform = .init(translationX: toVC.view.frame.width, y: 0)
} else {
toVC.view.alpha = 0.5
}
UIView.animate(withDuration: transitionDuration(using: transitionContext), animations: {
zip(toSnapshots, frames).forEach { snapshot, frame in
snapshot.frame = frame.1
snapshot.alpha = 1
}
zip(fromSnapshots, frames).forEach { snapshot, frame in
snapshot.frame = frame.1
snapshot.alpha = 0
}
if self.type == .present {
toVC.view.transform = .identity
fromVC.view.alpha = 0.5
} else {
fromVC.view.transform = .init(translationX: fromVC.view.frame.width, y: 0)
toVC.view.alpha = 1
}
}, completion: { _ in
fromSnapshots.forEach { $0.removeFromSuperview() }
toSnapshots.forEach { $0.removeFromSuperview() }
fromVC.fromAnimatedSubviews.forEach { $0.alpha = 1 }
toVC.toAnimatedSubviews.forEach { $0.alpha = 1 }
fromVC.view.alpha = 1
fromVC.view.transform = .identity
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
})
}
}
class PresentationController: UIPresentationController {
override var shouldRemovePresentersView: Bool { return true }
}
那么,为了使上述所有内容正常运行,如果我从ViewController
转换到SecondViewController
,我需要指定我正在从哪些子视图移动以及我正在移动到哪些子视图:
extension ViewController: CustomTransitionOriginator {
var fromAnimatedSubviews: [UIView] { return [label] }
}
extension SecondViewController: CustomTransitionDestination {
var toAnimatedSubviews: [UIView] { return [label] }
}
同时为了支持dismiss操作,我会添加相应的协议遵循:
extension ViewController: CustomTransitionDestination {
var toAnimatedSubviews: [UIView] { return [label] }
}
extension SecondViewController: CustomTransitionOriginator {
var fromAnimatedSubviews: [UIView] { return [label] }
}
现在,我不希望你被所有这些代码迷惑了,所以我建议你着重关注高层设计(我在顶部列举的前七个要点)。但愿这足以让你理解基本思路。
UIView
的快照以移动它,将其位置设为原始UIView
的位置,然后将其动画化到目标位置。虽然我同意这种逻辑,但我更喜欢不创建快照。相反,我想将第一个视图控制器中的原始UILabel
移动到由第二个视图控制器中相应标签的位置所决定的位置。 - Aleksander