Swift -- 仿今日头条转场效果 (二)

上回说到直接的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地址

你可能感兴趣的:(移动开发,Swift,转场动画,导航控制器,p.p1,{margin:,0.0px,0.0p)