上回说到直接的pop和push效果已经有了,没有随手势滑动pop时的过渡效果,这篇继续记录说明。
//pop手势百分比
func navigationController(_ navigationController: UINavigationController, interactionControllerFor animationController: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
if self.transitionAnimation.animationType == .pop {
let gestureMove = isInteractive == true ? self.transitionAnimation : nil
return gestureMove
}
return nil
}
还记得导航控制器的这个代理方法吧,它就是处理手势的时候要用的。
首先添加手势,
//添加侧滑手势
private func addPanGestureAction() {
let ges = UIPanGestureRecognizer(target: self, action: #selector(edgTapAction))
self.view.addGestureRecognizer(ges)
}
实现手势事件:
//MARK: add tap action
extension BaseNaviViewController {
//侧滑事件
@objc func edgTapAction(ges:UIPanGestureRecognizer) {
//找到当前点
let translation = ges.translation(in: ges.view)
percentComplete = abs(translation.x/kWidth)
//滑动比例
percentComplete = min(max(percentComplete, 0.01), 0.99)
if translation.x < 0 { //手势左滑的状态相当于滑动比例为0,
percentComplete = 0.0
}
switch ges.state {
case .began:
isInteractive = true
let currentVCArray = self.viewControllers
if (currentVCArray.count) > 1 {
self.popViewController(animated: true)
}
case .changed:
self.transitionAnimation.update(CGFloat(percentComplete))
case .ended,.cancelled:
isInteractive = false
ges.isEnabled = false
if percentComplete >= 0.5 {
self.transitionAnimation.finish {
ges.isEnabled = true //响应完成方可操作
}
} else {
self.transitionAnimation.cancel {
ges.isEnabled = true //响应完成方可操作
}
}
default:
break
}
}
}
isInteractive作为一个标记,记录手势的开始和结束,并在导航栏代理里一同作为是否进入自定义pop手势的依据。
//pop手势百分比
func navigationController(_ navigationController: UINavigationController, interactionControllerFor animationController: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
if self.transitionAnimation.animationType == .pop {
let gestureMove = isInteractive == true ? self.transitionAnimation : nil
return gestureMove
}
return nil
}
transitionAnimation实例里实现以下方法以响应手势事件:
//检测到右手势操作就会走这个方法,直接push或pop则不会
//在此方法里对fromVC和toVC做转场动画前的准备工作
override func startInteractiveTransition(_ transitionContext: UIViewControllerContextTransitioning) {
let containerView = transitionContext.containerView
guard let fromViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from),let toViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to) else { return }
storedContext = transitionContext
containerView.insertSubview(toViewController.view, belowSubview: fromViewController.view)
fromVC = fromViewController
toVC = toViewController
self.toView = toViewController.view
self.fromView = fromViewController.view
self.toView.transform = CGAffineTransform(scaleX: default_scale, y: default_scale)
}
由前一篇的对UIPercentDrivenInteractiveTransition分析了解到,重写进度更新方法
//更新进度的方法,根据当前进度百分比,对正在过渡的视图进行处理
override func update(_ percentComplete: CGFloat) {
if storedContext == nil {
return
}
guard self.toView != nil,self.fromView != nil else {
return
}
self.fromView?.frame = CGRect(x: kWidth * percentComplete, y: 0, width: kWidth, height: kHeight)
let gap_scale = (1 - default_scale) * percentComplete
let scale = default_scale + gap_scale
self.toView.transform = CGAffineTransform(scaleX: scale, y: scale)
storedContext?.updateInteractiveTransition(percentComplete)
}
func cancel(completedClosure:@ escaping ()->()) {
guard self.toView != nil,self.fromView != nil else {
completedClosure()
return
}
UIView.animate(withDuration: 0.2, animations: {
self.fromView?.frame = CGRect(x: 0, y: 0, width: kWidth, height: kHeight)
self.toView.transform = CGAffineTransform(scaleX: default_scale, y: default_scale)
}) { (completed) in
self.storedContext?.cancelInteractiveTransition()
self.storedContext?.completeTransition(false)
self.storedContext = nil
self.toView = nil
self.fromView = nil
self.toVC.view.transform = .identity
completedClosure()
}
}
func finish(completedClosure:@ escaping ()->()) {
guard self.toView != nil,self.fromView != nil else {
completedClosure()
return
}
UIView.animate(withDuration: 0.2, animations: {
self.fromView?.frame = CGRect(x: kWidth, y: 0, width: kWidth, height: kHeight)
self.toView.transform = CGAffineTransform.init(scaleX: 1.0, y: 1.0)
}) { (completed) in
self.storedContext?.finishInteractiveTransition()
self.storedContext?.completeTransition(true)
self.storedContext = nil
self.toView = nil
self.fromView = nil
completedClosure()
}
}
这里的进度取消和完成我没有进行重写而是自己写了个方法,原因是在手势滑动过程中,很可能出现UIView动画还未完成,滑动手势还在触发update更新方法,导致toVC.view一直在后面躲来躲去,所以这里写了一个闭包在取消/完成操作结束后回调给手势,手势isEnabled属性解锁为true。
其中self.storedContext?.cancelInteractiveTransition() self.storedContext?.finishInteractiveTransition()作用相当于调用了取消和完成操作,不用重写父类方法也可以。
到这里仿今日头条转场效果就结束了。水平有限,如果你看完我写的文章有不同意见或建议,欢迎留言一起讨论。
Github地址