上回說到直接的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位址