Swift——自定义转场动画(一)

Swift——自定义转场动画(一),第1张

d窗转场/过度动画(Popover效果)

避免浪费大家时间,快速查看运行效果可以直接拉到最后看 【五.完整代码】 部分,如果要看递推逻辑,可以从前往后看。

一.基本设置

d出一个控制器:系统提供了以下的方法

 @available(iOS 5.0, *)
    open func present(_ viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)? = nil)

但是如果想自定义d出控制器的大小及动画的展示形式,就需要自定义转场
首先UIModalPresentationStyle定义了控制器d出的方式

@available(iOS 3.2, *)
    open var modalPresentationStyle: UIModalPresentationStyle

要设置为**.custom**,这样d出控制器后,背后的控制器不会消失,如下:

二.调整大小
//popoverVc是将要d出来的控制器
popoverVc.modalPresentationStyle = .custom

其次要使控制器遵守转场动画的协议:UIViewControllerTransitioningDelegate

    @available(iOS 2.0, *)
    optional func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning?

    
    @available(iOS 2.0, *)
    optional func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning?

    
    optional func interactionControllerForPresentation(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning?

    
    optional func interactionControllerForDismissal(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning?

    
    @available(iOS 8.0, *)
    optional func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController?

协议中先看下第五个方法,此方法中,我们可以拿到要d出的控制器和被d出的控制器,返回值是UIPresentationController
可以认为,系统就是通过这个类型的控制器将我们要d出的控制器,d出来的。所以我们自定义一个控制器,继承自UIPresentationController,然后就改变一些东西,比如d出控制器view的大小:

    open var presentingViewController: UIViewController { get }

    open var presentedViewController: UIViewController { get }
    
    // The view in which a presentation occurs. It is an ancestor of both the presenting and presented view controller's views.
    // This view is being passed to the animation controller.
    open var containerView: UIView? { get }
        // A view that's going to be animated during the presentation. Must be an ancestor of a presented view controller's view
    // or a presented view controller's view itself.
    // (Default: presented view controller's view)
    open var presentedView: UIView? { get }

可以看到,我们可以拿到要d出来的控制器presentedViewController和它的view presentedView,那么我们改变presentedView的frame,就可以调整d出控制器的位置大小了。自定义示例如下:

import UIKit

class WXPresentationController: UIPresentationController {
    
    //提供属性,frame由外界传入
    var presentedFrame:CGRect = CGRect(x: 0, y: 0, width: 0, height: 0)
    
    override func containerViewWillLayoutSubviews() {
        super.containerViewWillLayoutSubviews()
        presentedView!.frame = presentedFrame
        setupCoverView()
    }
}

/MARK:- 添加蒙版
extension WXPresentationController{
    private func setupCoverView(){
        let coverView = UIView(frame: containerView!.bounds)
        coverView.backgroundColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0.2)
        let tapGesture = UITapGestureRecognizer(target: self, action: #selector(clickCoverView))
        coverView.addGestureRecognizer(tapGesture)
        containerView!.insertSubview(coverView, at: 0)
    }
}

/MARK:- 点击蒙版,view消失
extension WXPresentationController{
    @objc func clickCoverView(){
        presentedViewController.dismiss(animated: true, completion: nil)
    }
}
extension HomeViewController:UIViewControllerTransitioningDelegate{
    func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? {
        let presentationController = WXPresentationController(presentedViewController: presented, presenting: presenting)
        presentationController.presentedFrame = presentedFrame
        return presentationController
    }
}

截止到此处位置,动画依然用的是系统的动画,frame是我们来定义的

三.调整动画

此时再回头看UIViewControllerTransitioningDelegate 中的前两个方法

    @available(iOS 2.0, *)
    optional func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning?

    
    @available(iOS 2.0, *)
    optional func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning?

见名知意,这两个方法,提供两个控制器,forPresented 和 forDismissed,也就是执行d出动画的控制器和执行消失动画的控制器,看返回值:UIViewControllerAnimatedTransitioning?
这是个什么类型呢?进去看下:

public protocol UIViewControllerAnimatedTransitioning : NSObjectProtocol {

    func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval
    // This method can only be a no-op if the transition is interactive and not a percentDriven interactive transition.
    func animateTransition(using transitionContext: UIViewControllerContextTransitioning)

很明显,这里是一个协议,那么返回值是一个协议是是什么意思呢?是指的返回一个遵守了这个协议的对象即可。
所以,可以直接返回当前控制器,也就是self,然后在当前控制器中,实现这个协议的两个方法即可。

extension HomeViewController:UIViewControllerAnimatedTransitioning{
    func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {

    }
    
    func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
        
    }
 }

但是d出动画和消失动画走的是同样的两个代理方法,所以在此通过定义 isPresented变量来加以区分

extension HomeViewController:UIViewControllerTransitioningDelegate{

    func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        isPresented = true
        return self
    }
    
    func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        isPresented = false
        return self
    }
}
extension HomeViewController:UIViewControllerAnimatedTransitioning{
    func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
        //动画时长
        0.5
    }
    
    func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {

        isPresented ? presentedTransitioin(using: transitionContext) : dismissedTransitioin(using: transitionContext)
        
    }
    
    //封装d出动画
    func presentedTransitioin(using transitionContext: UIViewControllerContextTransitioning){
        let presentedView = transitionContext.view(forKey: UITransitionContextViewKey.to)!
        presentedView.layer.anchorPoint = CGPoint(x: 0.5, y: 0)
        transitionContext.containerView.addSubview(presentedView)
        presentedView.transform = CGAffineTransform.init(scaleX: 1, y: 0.0001)
        UIView.animate(withDuration: transitionDuration(using: transitionContext), delay: 0.0, options: .curveEaseOut) {
            presentedView.transform = CGAffineTransform.identity
        } completion: { _ in
            transitionContext.completeTransition(true)
        }
    }
    
    //封装消失动画
    func dismissedTransitioin(using transitionContext: UIViewControllerContextTransitioning){
        let dismissedView = transitionContext.view(forKey: UITransitionContextViewKey.from)!
        UIView.animate(withDuration: transitionDuration(using: transitionContext), delay: 0.0, options: .curveEaseOut) {
        	//y:设置为0.00001,不要直接设置为0,防止突变
            dismissedView.transform = CGAffineTransform.init(scaleX: 1.0, y: 0.0001)
        } completion: { _ in
            dismissedView.removeFromSuperview()
            transitionContext.completeTransition(true)
        }
    }
}
四.优化工作

代理方法及动画逻辑都在HomeViewController中,耦合性太强,可以自定义一个PopoverAnimator,继承自NSObject,然后实现相应的代理逻辑,这样就可以将代理逻辑抽取出来加以复用,代理就成了PopoverAnimator的一个实例对象。

extension HomeViewController{
    @objc func titleBtnClick(sender:TitleBtn){
        let popoverVc = PopoverViewController()
        popoverVc.modalPresentationStyle = .custom
        //设置代理
        popoverVc.transitioningDelegate = popoverAnimator
        popoverAnimator.presentedFrame = CGRect(x: view.frame.width/2 - 75, y: 100, width: 150, height: 250)

        present(popoverVc, animated: true, completion: nil)
        
    }
}

至此:我们定义了一个WXPresentationController 来调整d出控制器的view的层级,加蒙版等,以及一个PopoverAnimator 来调整d出控制的大小及动画的类。

五.完整代码
import UIKit

class WXPresentationController: UIPresentationController {
    
    //提供属性,frame由外界传入
    var presentedFrame:CGRect = CGRect(x: 0, y: 0, width: 0, height: 0)
    
    override func containerViewWillLayoutSubviews() {
        super.containerViewWillLayoutSubviews()
        presentedView!.frame = presentedFrame
        setupCoverView()
    }
}

/MARK:- 添加蒙版
extension WXPresentationController{
    private func setupCoverView(){
        let coverView = UIView(frame: containerView!.bounds)
        coverView.backgroundColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0.2)
        let tapGesture = UITapGestureRecognizer(target: self, action: #selector(clickCoverView))
        coverView.addGestureRecognizer(tapGesture)

        containerView!.insertSubview(coverView, at: 0)
    }
}

/MARK:- 点击蒙版,view消失
extension WXPresentationController{
    @objc func clickCoverView(){
        presentedViewController.dismiss(animated: true, completion: nil)
    }

}

//
//  PopoverAnimator.swift
//  PopoverViewController
//
//  Created by xiao on 2021/11/25.
//

import UIKit

class PopoverAnimator: NSObject {
    var presentedFrame:CGRect = CGRect(x: 0, y: 0, width: 0, height: 0)
    var isPresented:Bool = false
    var presentedCallBack:((_ isPresented:Bool)->())?

}

//自定义转场动画遵守的协议
//改变d出控制器的大小
extension PopoverAnimator:UIViewControllerTransitioningDelegate{
    func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? {
        let presentationController = WXPresentationController(presentedViewController: presented, presenting: presenting)
        presentationController.presentedFrame = presentedFrame
        return presentationController
    }
    
    func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        isPresented = true
        //提供回调状态,通知外界
        if let callBack = presentedCallBack{
            callBack(isPresented)
        }
        return self
    }
    
    func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        isPresented = false
        //提供回调状态,通知外界
        if let callBack = presentedCallBack{
            callBack(isPresented)
        }
        return self
    }
}

extension PopoverAnimator:UIViewControllerAnimatedTransitioning{
    func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
        //动画时长
        0.5
    }
    
    func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {

        isPresented ? presentedTransitioin(using: transitionContext) : dismissedTransitioin(using: transitionContext)
        
    }
    
    //封装d出动画
    func presentedTransitioin(using transitionContext: UIViewControllerContextTransitioning){
        //通过UITransitionContextViewKey获取做动画的view
        let presentedView = transitionContext.view(forKey: UITransitionContextViewKey.to)!
        presentedView.layer.anchorPoint = CGPoint(x: 0.5, y: 0)
        transitionContext.containerView.addSubview(presentedView)
        presentedView.transform = CGAffineTransform.init(scaleX: 1, y: 0.0001)
        UIView.animate(withDuration: transitionDuration(using: transitionContext), delay: 0.0, options: .curveEaseOut) {
            presentedView.transform = CGAffineTransform.identity
        } completion: { _ in
            transitionContext.completeTransition(true)
        }
    }
    
    //封装消失动画
    func dismissedTransitioin(using transitionContext: UIViewControllerContextTransitioning){
        let dismissedView = transitionContext.view(forKey: UITransitionContextViewKey.from)!
        UIView.animate(withDuration: transitionDuration(using: transitionContext), delay: 0.0, options: .curveEaseOut) {
            dismissedView.transform = CGAffineTransform.init(scaleX: 1.0, y: 0.0001)
        } completion: { _ in
            dismissedView.removeFromSuperview()
            transitionContext.completeTransition(true)
        }
    }
   
}

调用

import UIKit

class PopoverViewController: UIViewController {
    
    lazy var tableView:UITableView = {
        let tableview = UITableView(frame: CGRect(x: 0, y: 0, width: 150, height: 250), style: .plain)
        return tableview
    }()

    override func viewDidLoad() {
        super.viewDidLoad()
        setupUI()
    }
}

///UI
extension PopoverViewController{
    private func setupUI(){
        view.addSubview(tableView)
    }
}
extension HomeViewController{
    @objc func titleBtnClick(sender:TitleBtn){
        let popoverVc = PopoverViewController()
        popoverVc.modalPresentationStyle = .custom
        popoverVc.transitioningDelegate = popoverAnimator
        popoverAnimator.presentedFrame = CGRect(x: view.frame.width/2 - 75, y: 100, width: 150, height: 250)
        popoverAnimator.presentedCallBack = { [weak self]isPresented in
            self?.titleBtn.isSelected = isPresented
        }
        present(popoverVc, animated: true, completion: nil)
        
    }

over

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/web/994726.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-21
下一篇 2022-05-21

发表评论

登录后才能评论

评论列表(0条)

保存