效果图: 纯代码
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)