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

看效果先:

实现上面的效果需要用到一个类(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了。

下一篇继续

Github地址

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值