Shifty
目的
这个库旨在作为现有 UIViewController 转换 API 的补充。虽然 Shifty 不会取代 UIKit 视图控制器转换代理和动画器,但它大大简化了帧转换的实现,同时让您能够自定义许多动画部分以创建独特的效果。
关键概念
TransitionDriving
- 一个协议,表示任何可以在其生命周期内响应过渡动画器的各种回调的对象。Shift.Target
- 包含移动视图的源和目标状态。ShiftTransitioning
- 一个协议,表示任何可以提供State
对象给动画器的对象(通常是 UIViewController)。ShiftAnimator
- 管理在源和目标之间匹配和协调State
对象的动画器对象。
使用
过渡驱动
TransitionDriving
协议可以用来创建各种不同的过渡效果。它允许您将视图控制器特定的效果和动画从 UIViewControllerAnimatedTransitioning
对象本身中分离出来。这样可以创建更多可重用的动画器,同时保留动画的自定义特性。
为了在两个背景相同的视图控制器之间创建一个简单的过渡(比如两者都是蓝色),我们可能需要创建一个 UIViewControllerAnimatedTransitioning
对象
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
let container = transitionContext.containerView
guard let sourceController = transitionContext.viewController(forKey: .from), let destinationController = transitionContext.viewController(forKey: .to) else { return }
guard let destinationView = transitionContext.view(forKey: .to) else { return }
guard let source = sourceController as? TransitionDriving, let destination = destinationController as? TransitionDriving else { return }
destination.prepareForTransition(from: source)
source.prepareForTransition(to: destination, withDuration: transitionDuration(using: transitionContext)) { finished in
container.addSubview(destinationView)
destinationView.frame = transitionContext.finalFrame(for: destinationController)
source.completeTransition(to: destination)
destination.completeTransition(from: source)
transitionContext.completeTransition(finished)
}
该动画器遵循简单的事件序列。在确保 UIViewControllerContextTransitioning
正确配置后,它将指示源视图控制器执行任何必要的动画以促进向目标视图控制器的过渡,同时给目标视图控制器一个在它可见于窗口之前准备自己的机会。
一旦源动画完成,动画器将把 destinationView
添加到容器中,并配置它到最终框架。
最后,现在目标视图是可见的(并且遮挡源视图),它会指示源进行清理,目标执行以完成过渡所需的任何动画或工作,并将回调到 UIViewControllerContextTransitioning
对象,以表示过渡结束。
但为了完成这两个屏幕连续的效果,源上的所有内容都必须被清除,目标上的所有内容也必须在交换发生之前被清除。为了实现这一点,我们可以在我们的源和目标视图控制器上实现 TransitionDriving
extension ViewController: TransitionDriving {
func completeTransition(from source: TransitionDriving?) {
UIView.animate(withDuration: 0.3, delay: 0.0, usingSpringWithDamping: 1.0, initialSpringVelocity: 0.0, options: [], animations: {
animatingViews.forEach { $0.transform = .identity }
}, completion: nil)
}
func completeTransition(to destination: TransitionDriving?) {
animatingViews.forEach { $0.transform = .identity }
}
func prepareForTransition(from source: TransitionDriving?) {
animatingViews.forEach { $0.transform = CGAffineTransform(translationX: -self.view.bounds.width, y: 0) }
}
func prepareForTransition(to destination: TransitionDriving?, withDuration duration: TimeInterval, completion: @escaping (Bool) -> Void) {
UIView.animate(withDuration: duration - delay, delay: delay, usingSpringWithDamping: 1.0, initialSpringVelocity: 0.0, options: [], animations: {
animatingViews.forEach { $0.transform = CGAffineTransform(translationX: -self.view.bounds.width, y: 0) }
}, completion: completion)
}
}
这将产生在作为源时将所有意向视图从屏幕的边缘动画出去的效果,在作为目标时从边缘动画回来的效果。在 UIViewControllerAnimatedTransitioning
对象的源和目标上实现这一功能将产生它是单个连续屏幕的错觉。
平移转换
有时在这样的转换中,两个屏幕之间有内容是一致的——如果不是确切的尺寸或位置。在这种情况下,最好只是动画离开屏幕的内容,然后再次将其动画出来。相反,我们可以使用 ShiftTransitioning
协议将其移动到新位置。首先,我们必须告诉源和目标哪些视图有资格移动
extension ViewController: ShiftTransitioning {
/* This empty conformance is enough to inform the animator to search through this controller's subviews for eligible shiftables. */
//The default value of this variable is true, setting to false will short-circuit the search. */
var isShiftingEnabled: Bool { return true }
//The default value of this variable is an empty array, but allows you to short-circuit search in more complicated view hierarchies.
var shiftExclusions: [UIView] { return [] }
}
extension ViewController {
override func viewDidLoad() {
super.viewDidLoad()
/* ...other work... */
yellowView.shiftID = "yellow"
orangeView.shiftID = "orange"
}
}
在本例中,我们有一个黄色和橙色视图,它们在屏幕之间保持一致。因为它们的标识符(可以是 AnyHashable
)相等,动画师会将它们匹配成一对。它将每个 Target
附带的 UIView
从源状态移动到目标状态。这将产生内容从一个地方移动到另一个地方的错觉(类似于 Keynote 中的神奇移动效果)。
组成 Target
且自动可动化的 UIView
和 CALayer
属性列表如下:
bounds
center
transform
和layer.transform3d
layer.cornerRadius
为了完成效果并使用这种新的移动能力,我们还需要在动画器中做一些额外的工作
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
let container = transitionContext.containerView
guard let sourceController = transitionContext.viewController(forKey: .from), let destinationController = transitionContext.viewController(forKey: .to) else { return }
guard let destinationView = transitionContext.view(forKey: .to) else { return }
guard let source = sourceController as? TransitionDriving, let destination = destinationController as? TransitionDriving else { return }
guard let shiftSource = sourceController as? ShiftTransitioning, let shiftDestination = destinationController as? ShiftTransitioning else { return }
shiftAnimator = ShiftAnimator(source: shiftSource, destination: shiftDestination)
destination.prepareForTransition(from: source)
source.prepareForTransition(to: destination, withDuration: transitionDuration(using: transitionContext)) { finished in
container.addSubview(destinationView)
destinationView.frame = transitionContext.finalFrame(for: destinationController)
destinationView.layoutIfNeeded()
source.completeTransition(to: destination)
destination.completeTransition(from: source)
self.shiftAnimator?.animate(with: 0.3, inContainer: container) { position in
transitionContext.completeTransition(position == .end)
}
}
}
这个动画方法几乎与前一个相同,增加了 ShiftAnimator
。这个对象使用特定的源和目标创建。在某段过渡期间,它将被指示在源和目标状态之间寻找匹配项进行动画。这种动画可以是过渡的一部分(在帧转换完成时结束)或单独进行(转换将在移动开始时结束)。
此外,提供自定义的 ShiftCoordinator
对象将允许你提供自定义的 UITimingCurveProvider
对象以及每个移动的不同相对起始时间和结束时间。示例项目中还有许多更复杂的例子可供运行。
示例
要运行示例项目,首先克隆仓库,然后从 Example 目录运行 pod install
要求
需要 iOS 10.0 +,Swift 4.0
CocoaPods 安装
将以下内容添加到您的 Podfile
pod 'Shifty'
同时确保您已选择使用框架
use_frameworks!
然后使用 CocoaPods 0.36 或更高版本运行 pod install
贡献
请参阅 CONTRIBUTING 文档。感谢,贡献者!
)
- 扩展默认的
Action
集合 - 提供一个方法,允许在稍后执行(隐藏原生视图)之前预先
Action
- 为
Action
提供添加“并列”动画的途径(交叉溶解、旋转等)