Swift4.1轉(zhuǎn)場(chǎng)動(dòng)畫實(shí)現(xiàn)側(cè)滑抽屜效果
本文實(shí)現(xiàn)使用了Modal轉(zhuǎn)場(chǎng)動(dòng)畫,原因是項(xiàng)目多由導(dǎo)航控制器和標(biāo)簽控制器作為基類,為了不影響導(dǎo)航控制器的代理,轉(zhuǎn)場(chǎng)動(dòng)畫使用模態(tài)交互。
代碼使用SnapKit進(jìn)行布局,能夠適應(yīng)屏幕旋轉(zhuǎn)。手勢(shì)速率大于300或進(jìn)度超過(guò)30%的時(shí)候直接完成動(dòng)畫,否則動(dòng)畫回滾取消,具體數(shù)值可以修改對(duì)應(yīng)的常量。抽屜出現(xiàn)的時(shí)候,主控制有遮罩,對(duì)應(yīng)關(guān)鍵字是mask。
實(shí)現(xiàn)文件只有兩個(gè)
DrawerControl:控制抽屜出現(xiàn),一行代碼即可調(diào)用
Animator:負(fù)責(zé)動(dòng)畫實(shí)現(xiàn),包括了交互式的代理事件和非交互式的代理事件
//
// 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 {
/**主頁(yè)面*/
var base: UIViewController?
/**抽屜控制器*/
var drawer: UIViewController?
/**抽屜在左邊還是右邊,默認(rèn)左邊,沒(méi)有實(shí)現(xiàn)右邊,要右邊自己去animator里面加判斷*/
var whichSize = DrawerSize.Left
/**拖拽手勢(shì)*/
var panBase: UIPanGestureRecognizer?
var panDrawer: UIPanGestureRecognizer?
/**主頁(yè)面在抽屜顯示時(shí)保留的寬度*/
var baseWidth: CGFloat {
get {
return self.animator!.baseWidth
}
set {
self.animator?.baseWidth = newValue
}
}
/**是否應(yīng)該響應(yīng)手勢(shì)*/
var shouldResponseRecognizer = false
/**效果響應(yīng)*/
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)
}
///關(guān)閉抽屜,或直接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 {
/**是否交互轉(zhuǎn)場(chǎng)*/
var interative = false
var showDrawer = false
var base: UIViewController?
var drawer:UIViewController?
/**主頁(yè)面在抽屜顯示時(shí)保留的寬度*/
var baseWidth: CGFloat = 100
lazy var mask = { () -> UIButton in
let mask = UIButton()
mask.addTarget(self, action: #selector(maskClicked(_:)), for: .touchUpInside)
return mask
}()
init(base: UIViewController, drawer: UIViewController) {
super.init()
self.base = base
self.drawer = drawer
UIDevice.current.beginGeneratingDeviceOrientationNotifications()
NotificationCenter.default.addObserver(self, selector: #selector(observeDeviceOrientation(_:)), name: .UIDeviceOrientationDidChange, object: nil)
}
@objc func observeDeviceOrientation(_ notification: NSObject) {
if let superView = self.base?.view.superview {
if showDrawer {
self.base?.view.snp.remakeConstraints({ (make) in
make.width.equalTo(SCREEN_WIDTH)
make.left.equalTo(superView.snp.right).offset(-self.baseWidth)
make.top.bottom.equalTo(superView)
})
}else {
self.base?.view.snp.remakeConstraints({ (make) in
make.edges.equalTo(superView)
})
}
superView.layoutIfNeeded()
}
}
deinit {
NotificationCenter.default.removeObserver(self)
}
}
extension Animator {
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
if showDrawer {
let fromView = transitionContext.view(forKey: .from)
addShadowToView(fromView!, color: .black, offset: CGSize(width: -1, height: 0), radius: 3, opacity: 0.1)
let toView = transitionContext.view(forKey: .to)
let containerView = transitionContext.containerView
containerView.addSubview(toView!)
containerView.addSubview(fromView!)
fromView?.snp.remakeConstraints({ (make) in
make.edges.equalTo(containerView)
})
toView?.snp.remakeConstraints({ (make) in
make.edges.equalTo(containerView)
})
containerView.layoutIfNeeded()
UIView.animate(withDuration: DRAWER_ANIMATION_TIME, animations: {
fromView?.snp.remakeConstraints({ (make) in
make.left.equalTo((toView?.snp.right)!).offset(-self.baseWidth)
make.width.top.bottom.equalTo(toView!)
})
containerView.layoutIfNeeded()
}) { (finish) in
let cancel = transitionContext.transitionWasCancelled
transitionContext.completeTransition(!cancel)
if !cancel {//取消狀態(tài)下區(qū)分添加到哪一個(gè)父視圖,弄錯(cuò)會(huì)導(dǎo)致黑屏
if self.drawer?.view.superview != nil {
self.drawer?.view?.snp.remakeConstraints({ (make) in
make.edges.equalTo((self.drawer?.view?.superview)!)
})
}
self.showPartOfView()
}else {
fromView?.snp.remakeConstraints({ (make) in
make.edges.equalTo((fromView?.superview)!)
})
}
}
}else {
let fromView = transitionContext.view(forKey: .from)
let toView = transitionContext.view(forKey: .to)
addShadowToView(toView!, color: .black, offset: CGSize(width: -1, height: 0), radius: 3, opacity: 0.1)
let containerView = transitionContext.containerView
containerView.addSubview(fromView!)
containerView.addSubview(toView!)
fromView?.snp.remakeConstraints({ (make) in
make.edges.equalTo(containerView)
})
toView?.snp.remakeConstraints({ (make) in
make.left.equalTo(containerView.snp.right).offset(-self.baseWidth)
make.width.equalTo(SCREEN_WIDTH)
make.height.equalTo(SCREEN_HEIGHT)
make.top.bottom.equalTo(containerView)
})
containerView.layoutIfNeeded()
UIView.animate(withDuration: DRAWER_ANIMATION_TIME, animations: {
toView?.snp.remakeConstraints({ (make) in
make.edges.equalTo(containerView)
})
containerView.layoutIfNeeded()
}) { (finish) in
let cancel = transitionContext.transitionWasCancelled
transitionContext.completeTransition(!cancel)
toView?.snp.remakeConstraints({ (make) in
make.edges.equalTo((toView?.superview)!)
})
if minX((self.base?.view)!) <= 0 {//判斷結(jié)束時(shí)候是否回到主視圖
self.base?.view.isUserInteractionEnabled = true
}
}
}
}
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return DRAWER_ANIMATION_TIME
}
override func startInteractiveTransition(_ transitionContext: UIViewControllerContextTransitioning) {
super.startInteractiveTransition(transitionContext)
}
func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
self.showDrawer = true
return self
}
func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
self.showDrawer = false
return self
}
func interactionControllerForPresentation(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
if interative {
return self
}else {
return nil
}
}
func interactionControllerForDismissal(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
if interative {
return self
}else {
return nil
}
}
}
extension Animator {
func showPartOfView() {
self.drawer?.view.addSubview((self.base?.view)!)
self.base?.view.snp.remakeConstraints({ (make) in
make.left.equalTo((self.drawer?.view.snp.right)!).offset(-self.baseWidth)
make.top.bottom.equalTo((self.drawer?.view)!)
make.width.equalTo(SCREEN_WIDTH)
})
//遮罩
self.drawer?.view.insertSubview(mask, aboveSubview: (self.base?.view)!)
self.base?.view.isUserInteractionEnabled = false//阻止交互
mask.snp.remakeConstraints { (make) in
make.left.equalTo((mask.superview?.snp.right)!).offset(-baseWidth)
make.top.width.bottom.equalTo(mask.superview!);
}
self.drawer?.view.superview?.layoutIfNeeded()
}
@objc func maskClicked(_ button: UIButton) {
button.removeFromSuperview()
self.drawer?.dismiss(animated: true, completion: nil)
}
}
按鈕調(diào)用例子:(手勢(shì)控制已經(jīng)自動(dòng)添加到主控制器和抽屜控制器的view上)
創(chuàng)建推出抽屜的控制類,參數(shù)分別是主控制器和抽屜控制器。在我自己的練習(xí)工程中,把這個(gè)控制類定義為總控制器(包括了導(dǎo)航控制器和標(biāo)簽控制器的控制類)的一個(gè)屬性。創(chuàng)建這個(gè)抽屜控制類的時(shí)候,我把導(dǎo)航控制器(它的root是標(biāo)簽控制器)當(dāng)做主控制器傳給第一個(gè)參數(shù)。
self.drawer = DrawerControl(base: self.navigation!, drawer: self.drawerPage)
調(diào)用的時(shí)候只需要使用抽屜控制類的show方法即可,練習(xí)工程中我把該按鈕封裝在導(dǎo)航菜單里面,它響應(yīng)的時(shí)候會(huì)調(diào)用總控制器的單例,調(diào)用單例記錄的抽屜控制器屬性。
@objc func btnMenuClicked(_ button: UIButton) {
TotalControl.instance().drawer?.show()
}
附錄:用到的一些變量
//
// Headers.swift
// PratiseSwift
//
// Created by EugeneLaw on 2018/7/23.
// Copyright © 2018年 EugeneLaw. All rights reserved.
//
import UIKit
//MARK: 設(shè)備
let isRetina = (UIScreen.instancesRespond(to: #selector(getter: UIScreen.currentMode)) ? __CGSizeEqualToSize(CGSize(width: 640, height: 960), (UIScreen.main.currentMode?.size)!) : false)
let iPhone5 = (UIScreen.instancesRespond(to: #selector(getter: UIScreen.currentMode)) ? __CGSizeEqualToSize(CGSize(width: 640, height: 1136), (UIScreen.main.currentMode?.size)!) : false)
let iPhone6 = (UIScreen.instancesRespond(to: #selector(getter: UIScreen.currentMode)) ? __CGSizeEqualToSize(CGSize(width: 750, height: 1334), (UIScreen.main.currentMode?.size)!) : false)
let iPhone6Plus = (UIScreen.instancesRespond(to: #selector(getter: UIScreen.currentMode)) ? __CGSizeEqualToSize(CGSize(width: 1242, height: 2208), (UIScreen.main.currentMode?.size)!) : false)
let isPad = (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiom.pad)
let isPhone = (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiom.phone)
let isiPhoneX = (UIScreen.instancesRespond(to: #selector(getter: UIScreen.currentMode)) ? __CGSizeEqualToSize(CGSize(width: 1125, height: 2436), (UIScreen.main.currentMode?.size)!) : false)
//MARK: 界面
let TABBAR_HEIGHT = (isiPhoneX ? 83 : 49)
let NAVIGATION_HEIGHT = (isiPhoneX ? 88 : 64)
var SCREEN_WIDTH: CGFloat {
get {
return SCREEN_WIDTH_FUNC()
}
}
var SCREEN_HEIGHT: CGFloat {
get {
return SCREEN_HEIGHT_FUNC()
}
}
func SCREEN_WIDTH_FUNC() -> CGFloat {
return UIScreen.main.bounds.size.width
}
func SCREEN_HEIGHT_FUNC() -> CGFloat {
return UIScreen.main.bounds.size.height
}
//MARK: 顏色
let COLOR_WHITESMOKE = ColorHex("#F5F5F5")
/**
*十六進(jìn)制顏色值轉(zhuǎn)換成UIColor
*@param "#000000"
*/
func ColorHex(_ color: String) -> UIColor? {
if color.count <= 0 || color.count != 7 || color == "(null)" || color == "<null>" {
return nil
}
var red: UInt32 = 0x0
var green: UInt32 = 0x0
var blue: UInt32 = 0x0
let redString = String(color[color.index(color.startIndex, offsetBy: 1)...color.index(color.startIndex, offsetBy: 2)])
let greenString = String(color[color.index(color.startIndex, offsetBy: 3)...color.index(color.startIndex, offsetBy: 4)])
let blueString = String(color[color.index(color.startIndex, offsetBy: 5)...color.index(color.startIndex, offsetBy: 6)])
Scanner(string: redString).scanHexInt32(&red)
Scanner(string: greenString).scanHexInt32(&green)
Scanner(string: blueString).scanHexInt32(&blue)
let hexColor = UIColor.init(red: CGFloat(red)/255.0, green: CGFloat(green)/255.0, blue: CGFloat(blue)/255.0, alpha: 1)
return hexColor
}
/**
*給圖層添加陰影
*/
func addShadowToView(_ view: UIView, color: UIColor, offset: CGSize, radius: CGFloat, opacity: Float) {
view.layer.shadowColor = color.cgColor
view.layer.shadowOffset = offset
view.layer.shadowOpacity = opacity
view.layer.shadowRadius = radius
}
/**
*計(jì)算圖層的寬度
*/
func width(_ object: UIView) -> CGFloat {
return object.frame.width
}
/**
*在父視圖中的x坐標(biāo)
*/
func minX(_ object: UIView) -> CGFloat {
return object.frame.origin.x
}
/**
*在父視圖中的x坐標(biāo)+自身寬度
*/
func maxX(_ object: UIView) -> CGFloat {
return object.frame.origin.x+width(object)
}
/**
*在父視圖中的y坐標(biāo)
*/
func minY(_ object: UIView) -> CGFloat {
return object.frame.origin.y
}
/**
*在父視圖中的y坐標(biāo)+自身高度
*/
func maxY(_ object: UIView) -> CGFloat {
return object.frame.origin.y+height(object)
}
/**
*計(jì)算圖層的高度
*/
func height(_ object: UIView) -> CGFloat {
return object.frame.height
}
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持我們。
欄 目:Swift
本文標(biāo)題:Swift4.1轉(zhuǎn)場(chǎng)動(dòng)畫實(shí)現(xiàn)側(cè)滑抽屜效果
本文地址:http://www.jygsgssxh.com/a1/Swift/11927.html
您可能感興趣的文章


閱讀排行
- 1C語(yǔ)言 while語(yǔ)句的用法詳解
- 2java 實(shí)現(xiàn)簡(jiǎn)單圣誕樹(shù)的示例代碼(圣誕
- 3利用C語(yǔ)言實(shí)現(xiàn)“百馬百擔(dān)”問(wèn)題方法
- 4C語(yǔ)言中計(jì)算正弦的相關(guān)函數(shù)總結(jié)
- 5c語(yǔ)言計(jì)算三角形面積代碼
- 6什么是 WSH(腳本宿主)的詳細(xì)解釋
- 7C++ 中隨機(jī)函數(shù)random函數(shù)的使用方法
- 8正則表達(dá)式匹配各種特殊字符
- 9C語(yǔ)言十進(jìn)制轉(zhuǎn)二進(jìn)制代碼實(shí)例
- 10C語(yǔ)言查找數(shù)組里數(shù)字重復(fù)次數(shù)的方法
本欄相關(guān)
- 01-11Swift利用Decodable解析JSON的一個(gè)小問(wèn)題
- 01-11swift中defer幾個(gè)簡(jiǎn)單的使用場(chǎng)景詳解
- 01-11Swift中初始化init的方法小結(jié)
- 01-11Swift中defer關(guān)鍵字推遲執(zhí)行示例詳解
- 01-11Swift利用純代碼實(shí)現(xiàn)時(shí)鐘效果實(shí)例代碼
- 01-11Swift中定義單例的方法實(shí)例
- 01-11Swift中排序算法的簡(jiǎn)單取舍詳解
- 01-11Swift Json實(shí)例詳細(xì)解析
- 01-11Swift如何為設(shè)置中心添加常用功能
- 01-11Swift利用指紋識(shí)別或面部識(shí)別為應(yīng)用添
隨機(jī)閱讀
- 01-10delphi制作wav文件的方法
- 08-05織夢(mèng)dedecms什么時(shí)候用欄目交叉功能?
- 08-05dedecms(織夢(mèng))副欄目數(shù)量限制代碼修改
- 04-02jquery與jsp,用jquery
- 01-10使用C語(yǔ)言求解撲克牌的順子及n個(gè)骰子
- 01-11Mac OSX 打開(kāi)原生自帶讀寫NTFS功能(圖文
- 01-10SublimeText編譯C開(kāi)發(fā)環(huán)境設(shè)置
- 01-10C#中split用法實(shí)例總結(jié)
- 08-05DEDE織夢(mèng)data目錄下的sessions文件夾有什
- 01-11ajax實(shí)現(xiàn)頁(yè)面的局部加載


