看效果先:
实现上面的效果需要用到一个类(UIPercentDrivenInteractiveTransition)和两个协议(UINavigationControllerDelegate,UIViewControllerAnimatedTransitioning)
首先 UIPercentDrivenInteractiveTransition类,官方文档说明:
一个控制两个控制器交互动画的对象,
继承自NSObject。
百分比过渡交互动画需要配合着UIViewControllerAnimatedTransitioning这个代理来完成。当需要由交互来过渡到一个控制器的时候,
我们需要在控制器的代理里用到UIPercentDrivenInteractiveTransition的实例。用户的交互事件会影响动画的过渡进程,在这期间可以调用 update( _ : ),cancel(),和finish()三个方法来响应当前过渡到哪个阶段,例如,你可以在手势识别响应里用这些方法来反应手势完成到哪一步。
也可以通过创建一个UIPercentDrivenInteractiveTransition的子类,不过使用时必须重写父类声明的方法。
@available(iOS 7.0, *)
open class UIPercentDrivenInteractiveTransition : NSObject, UIViewControllerInteractiveTransitioning {
/// This is the non-interactive duration that was returned when the
/// animators transitionDuration: method was called when the transition started.
open var duration: CGFloat { get }
public protocol UIViewControllerInteractiveTransitioning : NSObjectProtocol {
func startInteractiveTransition(_ transitionContext: UIViewControllerContextTransitioning)
optional var completionSpeed: CGFloat { get }
@available(iOS 2.0, *)
optional var completionCurve: UIView.AnimationCurve { get }
/// In 10.0, if an object conforming to UIViewControllerAnimatedTransitioning is
/// known to be interruptible, it is possible to start it as if it was not
/// interactive and then interrupt the transition and interact with it. In this
/// case, implement this method and return NO. If an interactor does not
/// implement this method, YES is assumed.
@available(iOS 10.0, *)
optional var wantsInteractiveStart: Bool { get }
}
通过文档可以看出,UIPercentDrivenInteractiveTransition类遵循协议UIViewControllerInteractiveTransitioning,主要负责过渡效果的开始、进行、完成和取消。
接下来看一个协议:UIViewControllerAnimatedTransitioning,控制ViewController过渡的协议,包括过渡时间和过渡过程
public protocol UIViewControllerAnimatedTransitioning : NSObjectProtocol {
//控制过渡时间
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval
//控制过渡过程中toVC和fromVC的变化情况
func animateTransition(using transitionContext: UIViewControllerContextTransitioning)
/// A conforming object implements this method if the transition it creates can
/// be interrupted. For example, it could return an instance of a
/// UIViewPropertyAnimator. It is expected that this method will return the same
/// instance for the life of a transition.
@available(iOS 10.0, *)
optional func interruptibleAnimator(using transitionContext: UIViewControllerContextTransitioning) -> UIViewImplicitlyAnimating
// This is a convenience and if implemented will be invoked by the system when the transition context's completeTransition: method is invoked.
optional func animationEnded(_ transitionCompleted: Bool)
}
注意:UIPercentDrivenInteractiveTransition控制的是交互过渡过程,而UIViewControllerAnimatedTransitioning里的方法控制的是直接过渡过程。
另一个协议:UINavigationControllerDelegate,这个想必都比较熟悉,导航控制器push、pop和手势滑动时的代理方法都在里面。
public protocol UINavigationControllerDelegate : NSObjectProtocol {
// Called when the navigation controller shows a new top view controller via a push, pop or setting of the view controller stack.
@available(iOS 2.0, *)
optional func navigationController(_ navigationController: UINavigationController, willShow viewController: UIViewController, animated: Bool)
@available(iOS 2.0, *)
optional func navigationController(_ navigationController: UINavigationController, didShow viewController: UIViewController, animated: Bool)
@available(iOS 7.0, *)
optional func navigationControllerSupportedInterfaceOrientations(_ navigationController: UINavigationController) -> UIInterfaceOrientationMask
@available(iOS 7.0, *)
optional func navigationControllerPreferredInterfaceOrientationForPresentation(_ navigationController: UINavigationController) -> UIInterfaceOrientation
@available(iOS 7.0, *)
optional func navigationController(_ navigationController: UINavigationController, interactionControllerFor animationController: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning?
@available(iOS 7.0, *)
optional func navigationController(_ navigationController: UINavigationController, animationControllerFor operation: UINavigationController.Operation, from fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning?
}
一个类和两个协议的结合使用:
让导航控制器遵循UINavigationControllerDelegate代理,可以拦截到push、pop、interaction(交互)的方法。在这些方法里来处理各自的处理事件。
class BaseNaviViewController: UINavigationController,UINavigationControllerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = .black
self.navigationBar.isHidden = true
self.delegate = self //遵循代理
}
}
//MARK: nav delegate
extension BaseNaviViewController {
//处理push/pop过渡动画 method one
func navigationController(_ navigationController: UINavigationController, animationControllerFor operation: UINavigationController.Operation, from fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
}
//pop手势百分比 method two
func navigationController(_ navigationController: UINavigationController, interactionControllerFor animationController: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
}
}
导航控制器在push和pop操作时,method one返回一个UIViewControllerAnimatedTransitioning对象,method two返回UIViewControllerInteractiveTransitioning对象。由上面对UIPercentDrivenInteractiveTransition类的分析可知,创建一个UIPercentDrivenInteractiveTransition子类TransitionAnimation并遵循UIViewControllerAnimatedTransitioning即可。
class TransitionAnimation: UIPercentDrivenInteractiveTransition, UIViewControllerAnimatedTransitioning {
}
在导航控制器中创建控制动画实例,代理方法中返回:
//自定义滑动动画类
lazy var transitionAnimation:TransitionAnimation = {
var transitionAnimation = TransitionAnimation()
return transitionAnimation
}()
//处理push/pop过渡动画
func navigationController(_ navigationController: UINavigationController, animationControllerFor operation: UINavigationController.Operation, from fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
if operation == .push {
self.transitionAnimation.animationType = .push
} else if operation == .pop {
self.transitionAnimation.animationType = .pop
}
return self.transitionAnimation
}
//pop手势百分比
func navigationController(_ navigationController: UINavigationController, interactionControllerFor animationController: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
//在手势交互且是pop操作时,返回transitionAnimation实例,处理pop手势进程
if self.transitionAnimation.animationType == .pop {
let gestureMove = isInteractive == true ? self.transitionAnimation : nil
return gestureMove
}
return nil
}
transitionAnimation中,控制过渡时间:
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return timeInterval
}
控制过渡过程,直接push或pop走该方法:
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
storedContext = transitionContext
switch animationType {
case .push?:
pushAnimation(transitionContext: transitionContext)
default:
popAnimation(transitionContext: transitionContext)
}
}
func pushAnimation(transitionContext:UIViewControllerContextTransitioning) {
//通过viewControllerForKey取出转场前后的两个控制器,这里toVC就是转场后的VC,fromVC就是转场前的VC
if transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from) is BaseNaviViewController {
let baseNav = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from) as? BaseNaviViewController
fromVC = (baseNav?.viewControllers.last)!
} else {
fromVC = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from)!
}
//引入containerView
let containerView = transitionContext.containerView
toVC = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to)!
containerView.addSubview(toVC.view)
containerView.insertSubview(fromVC.view, belowSubview: toVC.view)
var fromView:UIView!
var toView:UIView!
fromView = fromVC.view
toView = toVC.view
let frame = CGRect(x: 0, y: 0, width: kWidth, height: kHeight)
fromView.frame = frame
toView.frame = CGRect(x: kWidth, y: 0, width: kWidth, height: kHeight)
//对目标控制器缩放一个比例
UIView.animate(withDuration: timeInterval, animations: {
fromView.transform = CGAffineTransform(scaleX: default_scale, y: default_scale)
toView.frame = frame
}) { (finished) in
transitionContext.completeTransition(true)
//注意:transform还原,一定要在过渡完成后把transform还原,不然下次fromVC的frame会发生变化
self.fromVC.view.transform = .identity
}
}
func popAnimation(transitionContext:UIViewControllerContextTransitioning) {
//通过viewControllerForKey取出转场前后的两个控制器,这里toVC就是转场后的VC,fromVC就是转场前的VC
if transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from) is BaseNaviViewController {
let baseNav = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from) as? BaseNaviViewController
fromVC = (baseNav?.viewControllers.last)!
} else {
fromVC = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from)!
}
toVC = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to)!
toVC.tabBarController?.tabBar.isHidden = true
//引入containerView
let containerView = transitionContext.containerView
var fromView:UIView!
var toView:UIView!
containerView.insertSubview(toVC.view, belowSubview:fromVC.view)
fromView = fromVC.view
toView = toVC.view
let frame = CGRect(x: 0, y: 0, width: kWidth, height: kHeight)
fromView.frame = frame
toView.frame = frame
toView.transform = CGAffineTransform(scaleX: default_scale, y: default_scale)
//对目标控制器还原缩放比例
UIView.animate(withDuration: timeInterval, animations: {
toView.transform = .identity
fromView.frame = CGRect(x: kWidth, y: 0, width: kWidth, height: kHeight)
}) { (finished) in
transitionContext.completeTransition(true)
}
}
到这里可以看到直接push和pop的放大缩小效果已经有了。
然而,当要用手势右滑pop时,却不是我们想要的效果,而是直接pop了。
下一篇继续