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