Swift--弹框动画组件

本文介绍了如何创建一个自定义的对话框动画组件,支持底部、中心和顶部弹出,简化了不同方向弹框的需求,通过UIViewControllerTransitioningDelegate实现动画效果。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在工作中我们经常会用到自定义的弹框,弹出的方向有时也不只一个,为了开发方便,写了一个比较能适应各个方向弹框的动画组件。记录下来以便后续使用:



import UIKit


public class DialogPresentationController: UIPresentationController, UIViewControllerTransitioningDelegate, PresentationControllerDeleate {
    
    enum DialogType {
        case Bottom
        case Center
        case Top
    }
    
    public class func showFromBottom(_ presented: UIViewController, presenting: UIViewController, didDismiss dismiss: (() -> Void)?) {
        let dialog = DialogPresentationController(presentedViewController: presented, presenting: presenting, dialogType: .Bottom)
        dialog.cancel = dismiss
        presenting.present(presented, animated: true, completion: nil)
    }
    
    public class func showFromTop(_ presented: UIViewController, presenting: UIViewController, didDismiss dismiss: (() -> Void)?) {
        let dialog = DialogPresentationController(presentedViewController: presented, presenting: presenting, dialogType: .Top)
        dialog.cancel = dismiss
        presenting.present(presented, animated: true, completion: nil)
    }
    
    public class func showFromCenter(_ presented: UIViewController, presenting: UIViewController, didDismiss dismiss: (() -> Void)?) {
        let dialog = DialogPresentationController(presentedViewController: presented, presenting: presenting, dialogType: .Center)
        dialog.cancel = dismiss
        presenting.present(presented, animated: true, completion: nil)
    }
    
    //presented: 可改变preferredContentSize属性去改变视图控制大小
    //presenting: 要呈现presented的视图控制器
    public class func show(_ presented: UIViewController, presenting: UIViewController, didDismiss dismiss: (() -> Void)?) {
        showFromBottom(presented, presenting: presenting, didDismiss: dismiss)
    }
    
    init(presentedViewController: UIViewController, presenting presentingViewController: UIViewController?, dialogType type: DialogType) {
        presentedViewController.modalPresentationStyle = .custom
        super.init(presentedViewController: presentedViewController, presenting: presentingViewController)
        switch type {
        case .Bottom:
            presentationController = BottomPresentationController()
        case .Center:
            presentationController = CenterPresentationController()
        case .Top:
            presentationController = TopPresentationController()
        }
        presentedViewController.transitioningDelegate = self
        presentationController.delegate = self
    }
    
    fileprivate var presentationController: PresentationController!
        
    fileprivate var isPresenting: Bool = false
        
    fileprivate lazy var overlay: UIControl = {
        let view = UIControl()
        view.backgroundColor = .red
        view.frame = UIScreen.main.bounds
        view.backgroundColor = UIColor.init(white: 0, alpha: 0.5)
        view.addTarget(self, action: #selector(overlayAction), for: .touchUpInside)
        return view
    }()
    
    fileprivate var cancel: (() -> Void)?
    
    override public func presentationTransitionWillBegin() {
        overlay.layer.opacity = 0
        self.containerView?.addSubview(overlay)
    }
    
    override public var frameOfPresentedViewInContainerView: CGRect {
        guard let containerView = self.containerView else {
            fatalError()
        }
        let size = self.presentedViewController.preferredContentSize
        var frame = CGRect(x: 0, y: 0, width: size.width, height: size.height)
        frame.origin.y = containerView.bounds.height - frame.height
        return frame
    }
    
    //MARK: Action
    @objc func overlayAction() {
        self.presentedViewController.dismiss(animated: true) { [unowned self] in
            self.cancel?()
        }
    }
    
    //MARK: PresentationControllerDeleate
    func overlayView() -> UIView? {
        return self.overlay
    }
    
    //MARK: UIViewControllerTransitioningDelegate
    public func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        presentationController.isPresenting = true
        return presentationController
    }
    
    public func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        presentationController.isPresenting = false
        return presentationController
    }
    
    public func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? {
        return self
    }
}

protocol PresentationControllerDeleate: class {
    func overlayView() -> UIView?
}

protocol PresentationController: UIViewControllerAnimatedTransitioning {
    var isPresenting: Bool { get set }
    var delegate: PresentationControllerDeleate? { get set }
}


//MARK: --- 从低部弹出
class BottomPresentationController: NSObject, PresentationController {
    var isPresenting: Bool = false
    weak var delegate: PresentationControllerDeleate?
    
    //MARK: UIViewControllerAnimatedTransitioning
    public func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
        return 0.25
    }
    
    public func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
        guard let desViewController = transitionContext.viewController(forKey: .to),
            let fromViewController = transitionContext.viewController(forKey: .from) else {
                fatalError()
        }
        //        let isPresenting = (self.presentingViewController == fromViewController)
        let key = isPresenting ? UITransitionContextViewKey.to : UITransitionContextViewKey.from
        guard let desView = transitionContext.view(forKey: key) else {
            fatalError()
        }
        
        let containerView = transitionContext.containerView
        var fromFinalFrame = transitionContext.finalFrame(for: fromViewController)
        var toInitFrame = transitionContext.initialFrame(for: desViewController)
        let toFinalFrame = transitionContext.finalFrame(for: desViewController)
        
        if isPresenting { //present            
            toInitFrame.size = toFinalFrame.size
            toInitFrame.origin = CGPoint(x: 0, y: fromFinalFrame.height)
            desView.frame = toInitFrame
            containerView.addSubview(desView)
        } else { //dismiss
            fromFinalFrame.origin = CGPoint(x: 0, y: toFinalFrame.height)
        }
        
        let duration = self.transitionDuration(using: transitionContext)
        var overlay = self.delegate?.overlayView()
        UIView.animate(withDuration: duration, animations: {
            if self.isPresenting {
                desView.frame = toFinalFrame
                overlay?.layer.opacity = 1
            } else {
                overlay?.layer.opacity = 0
                desView.frame = fromFinalFrame
            }
        }, completion: { result in
            let wasCancelled = transitionContext.transitionWasCancelled
            transitionContext.completeTransition(!wasCancelled)
            if !self.isPresenting {
                transitionContext.view(forKey: .from)?.removeFromSuperview()
                overlay?.removeFromSuperview()
                overlay = nil
            }
        })
    }
}


//MARK: --- 从顶部弹出
class TopPresentationController: NSObject, PresentationController {
    var isPresenting: Bool = false
    weak var delegate: PresentationControllerDeleate?
    //MARK: UIViewControllerAnimatedTransitioning
    public func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
        return 0.25 }
    public func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { guard let desViewController = transitionContext.viewController(forKey: .to),
        let fromViewController = transitionContext.viewController(forKey: .from) else { fatalError()
        }
        // let isPresenting = (self.presentingViewController == fromViewController)
        let key = isPresenting ? UITransitionContextViewKey.to : UITransitionContextViewKey.from
        guard let desView = transitionContext.view(forKey: key) else {
            fatalError()
            
        }
        let containerView = transitionContext.containerView
        var fromFinalFrame = transitionContext.finalFrame(for: fromViewController)
        var toInitFrame = transitionContext.initialFrame(for: desViewController)
        let toFinalFrame = transitionContext.finalFrame(for: desViewController)
        if isPresenting { //present
            toInitFrame.size = toFinalFrame.size
            toInitFrame.origin = CGPoint(x: 0, y: -toFinalFrame.height)
            desView.frame = toInitFrame
            containerView.addSubview(desView)
            
        } else { //dismiss
            fromFinalFrame.origin = CGPoint(x: 0, y: -toFinalFrame.height)
        }
        let duration = self.transitionDuration(using: transitionContext)
        var overlay = self.delegate?.overlayView()
        
        UIView.animate(withDuration: duration, animations: {
            if self.isPresenting {
                desView.frame.origin.y = 0
                overlay?.layer.opacity = 1
                
            } else {
                overlay?.layer.opacity = 0
                desView.frame = fromFinalFrame
                
            }
        }, completion: { result in
            let wasCancelled = transitionContext.transitionWasCancelled
            transitionContext.completeTransition(!wasCancelled)
            if !self.isPresenting {
                transitionContext.view(forKey: .from)?.removeFromSuperview()
                overlay?.removeFromSuperview()
                overlay = nil
            }
        })
        
    }
}

//MARK: --- 从中心弹出
class CenterPresentationController: NSObject, PresentationController {
    var isPresenting: Bool = false
    weak var delegate: PresentationControllerDeleate?
    
    //MARK: UIViewControllerAnimatedTransitioning
    public func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
        return 0.4
    }
    
    public func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
        guard let desViewController = transitionContext.viewController(forKey: .to),
            let _ = transitionContext.viewController(forKey: .from) else {
                fatalError()
        }
        //        let isPresenting = (self.presentingViewController == fromViewController)
        let key = isPresenting ? UITransitionContextViewKey.to : UITransitionContextViewKey.from
        guard let desView = transitionContext.view(forKey: key) else {
            fatalError()
        }
        
        let containerView = transitionContext.containerView
        let toFinalFrame = transitionContext.finalFrame(for: desViewController)
        
        if isPresenting {
            desView.frame = toFinalFrame
            containerView.addSubview(desView)
        }
        
        let duration = self.transitionDuration(using: transitionContext)
        var overlay = self.delegate?.overlayView()
        
        if isPresenting {
            let presentAnimation = CAKeyframeAnimation(keyPath: "transform")
            presentAnimation.duration = duration
            presentAnimation.values = [
                NSValue(caTransform3D: CATransform3DMakeScale(0.1, 0.1, 1.0)),
                NSValue(caTransform3D: CATransform3DMakeScale(1.1, 1.1, 1.0)),
                NSValue(caTransform3D: CATransform3DMakeScale(0.9, 0.9, 1.0)),
                NSValue(caTransform3D: CATransform3DIdentity)
            ]
            presentAnimation.keyTimes = [0.0, 0.5, 0.75, 1.0]
            presentAnimation.timingFunctions = [
                CAMediaTimingFunction(name: CAMediaTimingFunctionName(rawValue: "easeInEaseOut")),
                CAMediaTimingFunction(name: CAMediaTimingFunctionName(rawValue: "easeInEaseOut")),
                CAMediaTimingFunction(name: CAMediaTimingFunctionName(rawValue: "easeInEaseOut"))
            ]
            desView.layer.add(presentAnimation, forKey: nil)
        }
        UIView.animate(withDuration: duration, animations: {
            if self.isPresenting {
                overlay?.layer.opacity = 1
            } else {
                overlay?.layer.opacity = 0
                desView.layer.opacity = 0
            }
        }, completion: { result in
            let wasCancelled = transitionContext.transitionWasCancelled
            transitionContext.completeTransition(!wasCancelled)
            if !self.isPresenting {
                transitionContext.view(forKey: .from)?.removeFromSuperview()
                overlay?.removeFromSuperview()
                overlay = nil
                
            }
        })        
    }
}

使用方法:

需要我们新建一个UIVIiewController,在此视图控制器里面去自定义弹出的视图样式,然后使用类似于DialogPresentationController.showFromBottom(controller, presenting: self, didDismiss: nil)的方法去调用

class SettingViewController: UIViewController {

   
    override func viewDidLoad() {
        super.viewDidLoad()
        // 此方法是设置我们弹框的大小的
        self.preferredContentSize = CGSize(width: 300, height: 200)
        self.setUI()
    }

self.preferredContentSize = CGSize(width: 300, height: 200)不能省略,要不视图会显示不完整。

调用方法:

let controller = SettingViewController()
DialogPresentationController.showFromBottom(controller, presenting: self, didDismiss: nil)`
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值