效果图: 纯代码
1.自定义继承于UIPresentationController的控制器
import UIKit
class PopoverPresentationController: UIPresentationController{
// 定义一个presentFrame 来动态改变展示视图的frame
var presentFrame = CGRect.zero
/**
实例化负责转场的控制器
:param: presentedViewController 被展现的控制器
:param: presentingViewController 发起转场的控制器, xocde6是nil, xocde7是野指针
:returns: 负责转场的控制器
*/
override init(presentedViewController: UIViewController, presenting presentingViewController: UIViewController?) {
super.init(presentedViewController: presentedViewController, presenting: presentingViewController)
}
/*
即将布局容器视图上得子视图时调用
containerView 容器视图, 放置展现出来的视图
presentedView 被展现的视图
*/
override func containerViewWillLayoutSubviews() {
// 1.添加遮盖
containerView?.addSubview(coverView)
containerView?.insertSubview(coverView, at: 0)
coverView.frame = (containerView?.frame)!
// 把菜单的frame做活
if presentFrame == CGRect.zero {
/*
* 这个是默认菜单高度
*/
// 2.调整被展示视图的大小
presentedView?.frame = CGRect(x:JKscreenW/2.0-100,y:56,width:200,height:200)
// 2.1.被展示视图上面的子视图大小的调整
// 背景图片
presentedView?.subviews[0].frame = CGRect(x:0,y:0,width:200,height:200)
// tableview
presentedView?.subviews[1].frame = CGRect(x:20,y:20,width:presentedView!.width-40,height:presentedView!.height-40)
}else{
/*
* 这个是外面自定义的菜单高度
*/
// 2.调整被展示视图的大小
presentedView?.frame = presentFrame
// 2.1.被展示视图上面的子视图大小的调整
// 背景图片
presentedView?.subviews[0].frame = CGRect(x:0,y:0,width:presentFrame.width,height:presentFrame.height)
// tableview
presentedView?.subviews[1].frame = CGRect(x:20,y:20,width:presentFrame.width-40,height:presentFrame.height-40)
}
}
/**
关闭弹窗
*/
func close(){
presentedViewController.dismiss(animated: true, completion: nil)
}
// MARK: - 懒加载
lazy var coverView: UIView = {
// 1.创建蒙版
let view = UIView()
view.backgroundColor = UIColor(white: 0.0, alpha: 0.2)
// 2.注册监听
let tap = UITapGestureRecognizer(target: self, action: #selector(PopoverPresentationController.close))
view.addGestureRecognizer(tap)
return view
}()
}
2.自定义一个管理动画的对象类PopoverAnimator继承于NSObject,遵守UIViewControllerTransitioningDelegate,UIViewControllerAnimatedTransitioning协议
import UIKit
// 定义常量保存通知的名称
let JKPopoverAnimatorWillshow = "JKPopoverAnimatorWillshow"
let JKPopoverAnimatorWilldismiss = "JKPopoverAnimatorWilldismiss"
/*
* 负责转场的类
*/
class PopoverAnimator: NSObject,UIViewControllerTransitioningDelegate,UIViewControllerAnimatedTransitioning{
// 记录当前菜单是否展开
var isPresent: Bool = false
var presentFrame = CGRect.zero
// MARK: 只要实现了一下方法,那么系统自带的默认动画就没有了,所有东西都需要程序员自己来实现
// 实现代理方法,告诉系统谁来负责转场动画
// UIPopoverPresentationController ios8推出专门负责转场动画的
func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController?{
let pc = PopoverPresentationController(presentedViewController: presented, presenting: presenting)
// 传frame,设置菜单的大小
pc.presentFrame = presentFrame
return pc
}
// 告诉系统谁来负责Modal的展现动画
// presented : 被展现的视图
// presenting : 发起的视图
// returns : 谁来负责
func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning?{
NotificationCenter.default.post(name: NSNotification.Name(rawValue: JKPopoverAnimatorWillshow), object: self)
isPresent = true
return self
}
// 告诉系统谁来负责modal的消失动画
// dismissed 被关闭的视图
func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning?{
NotificationCenter.default.post(name: NSNotification.Name(rawValue: JKPopoverAnimatorWilldismiss), object: self)
isPresent = false
return self
}
/*
* UIViewControllerAnimatedTransitioning 下面的2个方法是这个协议产生的
* 返回动画时长
* transitionContext : 上下文里面包含了所有需要的参数
* returns: 动画时长
*
*/
public func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return 0.5
}
/*
* 告诉系统如何动画,无论是展现还是
*
*
*/
public func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
// 1.拿到展现的视图
// let toVc = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to)
// let fromVc = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from)
// 通过打印发现需要修改的是 toVc上面的view
print(isPresent)
if isPresent {
let toView = transitionContext.view(forKey: UITransitionContextViewKey.to)
// 注意: 一定要把视图添加到view上
transitionContext.containerView.addSubview(toView!)
// 把 transform设置为 x:1.0,y: 0
toView?.transform = CGAffineTransform(scaleX: 1.0, y: 0)
// 设置锚点
toView?.layer.anchorPoint = CGPoint(x:0.5, y: 0)
//print(toView!.layer.anchorPoint)
// 2.执行动画
UIView.animate(withDuration: transitionDuration(using: transitionContext), animations: {
// 2.1.清空transform
toView?.transform = CGAffineTransform.identity
}) { (_) in
// 2.2.动画执行完毕,一定要告诉系统,如果不写可能产生一些未知的错误
transitionContext.completeTransition(true)
}
}else{
let fromView = transitionContext.view(forKey: UITransitionContextViewKey.from)
// 2.执行动画
UIView.animate(withDuration: transitionDuration(using: transitionContext)-3, animations: {
// 把 transform设置为 x:1.0,y: 0,由于CGFloat是不准确的,所以写0.0是没有动画的
fromView?.transform = CGAffineTransform(scaleX: 1.0, y: 0.000001)
}) { (_) in
// 2.2.动画执行完毕,一定要告诉系统,如果不写可能产生一些未知的错误
transitionContext.completeTransition(true)
}
}
}
}
提醒动画执行完毕,一定要告诉系统,如果不写可能产生一些未知的错误
transitionContext.completeTransition(true)
3.动画实现的操作
// 1.创建控制器对象
let popoverViewController = PopoverViewController()
// 2.设置转场代理, 告诉系统谁来负责转场 popoverAnimator: 是负责转场的
popoverViewController.transitioningDelegate = popoverAnimator
// 3.设置转场模式
popoverViewController.modalPresentationStyle = UIModalPresentationStyle.custom
present(popoverViewController, animated: true, completion: nil)