本文实现使用了Modal转场动画,原因是项目多由导航控制器和标签控制器作为基类,为了不影响导航控制器的代理,转场动画使用模态交互。
代码使用SnapKit进行布局,能够适应屏幕旋转。手势速率大于300或进度超过30%的时候直接完成动画,否则动画回滚取消,具体数值可以修改对应的常量。抽屉出现的时候,主控制有遮罩,对应关键字是mask。

实现文件只有两个
DrawerControl:控制抽屉出现,一行代码即可调用
Animator:负责动画实现,包括了交互式的代理事件和非交互式的代理事件
//
// DrawerControl.swift
// PratiseSwift
//
// Created by EugeneLaw on 2018/7/31.
// Copyright © 2018年 EugeneLaw. All rights reserved.
//
import UIKit
enum DrawerSize {
case Left
case Right
}
class DrawerControl: NSObject {
/**主页面*/
var base: UIViewController?
/**抽屉控制器*/
var drawer: UIViewController?
/**抽屉在左边还是右边,默认左边,没有实现右边,要右边自己去animator里面加判断*/
var whichSize = DrawerSize.Left
/**拖拽手势*/
var panBase: UIPanGestureRecognizer?
var panDrawer: UIPanGestureRecognizer?
/**主页面在抽屉显示时保留的宽度*/
var baseWidth: CGFloat {
get {
return self.animator!.baseWidth
}
set {
self.animator?.baseWidth = newValue
}
}
/**是否应该响应手势*/
var shouldResponseRecognizer = false
/**效果响应*/
var animator: Animator?
init(base: UIViewController, drawer: UIViewController) {
super.init()
self.base = base
self.drawer = drawer
animator = Animator(base: self.base!, drawer: self.drawer!)
self.panBase = UIPanGestureRecognizer(target: self, action: #selector(panBaseAction(pan:)))
base.view.addGestureRecognizer(self.panBase!)
self.panDrawer = UIPanGestureRecognizer(target: self, action: #selector(panDrawerAction(pan:)))
drawer.view.addGestureRecognizer(self.panDrawer!)
self.drawer?.transitioningDelegate = self.animator
}
deinit {
if self.panBase != nil {
self.base?.view.removeGestureRecognizer(self.panBase!)
self.panBase = nil
}
if self.panDrawer != nil {
self.drawer?.view.removeGestureRecognizer(self.panDrawer!)
self.panDrawer = nil
}
}
}
extension DrawerControl {
///显示抽屉
func show() {
if (self.base?.view.frame.origin.x)! > SCREEN_WIDTH/2 {
return
}
self.animator?.interative = false
self.base?.present(self.drawer!, animated: true, completion: nil)
}
///关闭抽屉,或直接dismiss即可
func close() {
self.animator?.interative = false
self.drawer?.dismiss(animated: true, completion: nil)
}
}
extension DrawerControl {
@objc func panBaseAction(pan: UIPanGestureRecognizer) {
let transition = pan.translation(in: self.drawer?.view)
let percentage = CGFloat(transition.x/SCREEN_WIDTH)
let velocity = CGFloat(fabs(pan.velocity(in: self.drawer?.view).x))
switch pan.state {
case .began:
if transition.x < 0 {
shouldResponseRecognizer = false
}else {
shouldResponseRecognizer = true
}
if shouldResponseRecognizer {
self.beginAnimator(showDrawer: true)
}
case .changed:
if shouldResponseRecognizer {
self.updateAnimator(percentage)
}
default:
if shouldResponseRecognizer {
self.cancelAnimator(percentage, velocity: velocity)
}
}
}
@objc func panDrawerAction(pan: UIPanGestureRecognizer) {
let transition = pan.translation(in: self.drawer?.view)
let percentage = CGFloat(-transition.x/SCREEN_WIDTH)
let velocity = CGFloat(fabs(pan.velocity(in: self.drawer?.view).x))
switch pan.state {
case .began:
if transition.x > 0 {
shouldResponseRecognizer = false
}else {
shouldResponseRecognizer = true
}
if shouldResponseRecognizer {
self.beginAnimator(showDrawer: false)
}
case .changed:
if shouldResponseRecognizer {
self.updateAnimator(percentage)
}
default:
if shouldResponseRecognizer {
self.cancelAnimator(percentage, velocity: velocity)
}
}
}
func beginAnimator(showDrawer: Bool) {
self.animator?.interative = true
if showDrawer {
self.base?.transitioningDelegate = self.animator
self.base?.present(self.drawer!, animated: true, completion: nil)
}else {
self.drawer?.transitioningDelegate = self.animator
self.drawer?.dismiss(animated: true, completion: nil)
}
}
func updateAnimator(_ percentage: CGFloat) {
self.animator?.update(percentage)
}
func cancelAnimator(_ percentage: CGFloat, velocity: CGFloat) {
if percentage < 0.3 && velocity < 300 {
self.animator?.cancel()
}else {
self.animator?.finish()
}
}
}
//
// Animator.swift
// PratiseSwift
//
// Created by EugeneLaw on 2018/7/31.
// Copyright © 2018年 EugeneLaw. All rights reserved.
//
import UIKit
let DRAWER_ANIMATION_TIME = 0.3
class Animator: UIPercentDrivenInteractiveTransition, UIViewControllerTransitioningDelegate, UIViewControllerAnimatedTransitioning {
/**是否交互转场*/
var interative = false
var showDrawer = false
vU% M4(聹4(4)4(2'JR#&/***":"fJ3*:"fj4("o:j:"V""b:"fJ3*:"fr"Gj惖/+:"k"f.:"fJ3"fj:""o*:"j^gG*+:"fjb"fOk:"fg>V4(4(Rj^g>r*:"jZW6>惖/"G*+2'Jr>s6W3vN7j^gR"fj6WR6W*:"f4(5|% Qх
4)4(fkR"j>c<4(4(!(AM(4(
14(
Ё14(4(4)U%-(4(5I,胢)Ё%M耍U%M}
MQM锡
M锡U%M锤聙4)ЁA%M耍U%M}
MQM锡
M锡U%M锤聙4)ЁA%M耍U%M}
MQM锡
M锡U%M锤聙4)ЁA%M耍U%M}
MQM锡
M锡U%M锤聙4)Ё%}UMI}%9QI
}%%=4%U%4)Ё%}UMI}%9QI
}%%=4%U%4)ЁA`%M耍U%M}
MQM锡
M锡U%M锤聙4(4(5I,胞V3v4)ЁQ I}!%!PA`)Ё9Y%Q%=9}!%!PA`4)M
I9}]%Q ЁЁM
I9}]%Q!}U94(4)M
I9}!%!PЁЁM
I9}!%!Q}U94(4(4)M
I9}]%Q!}U9
ЁU%M锹4)4)M
I9}!%!Q}U9
ЁU%M锹)4(5I,胦s&)Ё
=1=I}]!%QM5=-4(4(4(6s&6"AU%
(4(4)
MU%
4(4(%(%(%(ЁM%%t4(ЁM%%Хt4(ЁM%%t4(MM!4(M聝M!4(M聉M!4(Ё
СССС4()4(4(n*b4)MY|U%YU%
M锰аФ
((4(4)4(4(_nj4(4)|U%Y
Ёй4)4(4(r"njC4(4)`|U%Y
Ёй4(4(r"njC4(4)|U%Y
ЁйФ4)4(4(r"njC4(4)d|U%Y
Ёй4(4(r"njCc(4)|U%Y
ЁйСФ4)4(4(_njc(4)С|U%Y
Ёй)4(brZj3ro疒j惚r'&*rokkR2 |