diff --git a/README.md b/README.md index 6349f42..2ca7662 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,12 @@ for push simple use self.navigationController?.radialPushViewController(SecondViewController(nibName: "SecondViewController", bundle: nil),startFrame: CGRectMake(self.view.frame.size.width, 0, 0, 0),duration:0.9,transitionCompletion: { () -> Void in }) +``` +Swift 3: push with storyboards +``` + let vc = self.storyboard?.instantiateViewController(withIdentifier: "myVC") as! MyViewController + + self.navigationController?.radialPushViewController(viewController: vc, duration: 0.3, startFrame: myButton.frame, transitionCompletion: nil) ``` for pop use ``` swift diff --git a/Swift3Implementation/AAPTransactionDirector.swift b/Swift3Implementation/AAPTransactionDirector.swift new file mode 100644 index 0000000..e8c7871 --- /dev/null +++ b/Swift3Implementation/AAPTransactionDirector.swift @@ -0,0 +1,186 @@ +// +// AAPTransactionDirector.swift +// AAPRadialTransaction_swift +// +// Created by Alex Padalko on 9/23/14. +// Copyright (c) 2014 Alex Padalko. All rights reserved. +// +// Converted to Swift 3 by Ben Sullivan on 08/27/16 + +import UIKit + +class AAPTransactionDirector: NSObject, UIViewControllerAnimatedTransitioning, UINavigationControllerDelegate, UIViewControllerInteractiveTransitioning { + + private var _context: UIViewControllerContextTransitioning? + + /** + * animation block.Use transactionContext to get all needed views. toViewController will be above. NOTE: if you use it for animation without interactive YOU MUST RUN complitBlock at end. + * + */ + lazy var animationBlock:((_ transactionContext: UIViewControllerContextTransitioning, _ animationTime: CGFloat, _ transitionCompletion: @escaping () -> Void) -> Void)? = nil + /** + * interactive update block.Use transactionContext to get all needed views.updating after percent changing + * + */ + lazy var interactiveUpdateBlock:((_ transactionContext:UIViewControllerContextTransitioning, _ percent: CGFloat)->Void)? = nil + + lazy var interactiveEndBlock: ( () -> Void )? = nil + + var isInteractive:Bool = false + var duration:CGFloat? + private var displayLink:CADisplayLink? = nil + private var _percent:CGFloat = 0 + + var precent:CGFloat { + + get { + return _percent + } + set { + + _percent = newValue + + self._context?.updateInteractiveTransition(_percent) + + self._context?.containerView.layer.timeOffset = CFTimeInterval(_percent*self.duration!) + if (self._context != nil) { + + self.interactiveUpdateBlock?(_context!,_percent) + + } + } + + } + + var timeOffset:CFTimeInterval{ + + get { + return self._context!.containerView.layer.timeOffset + } + set { + self.precent = CGFloat(CGFloat(newValue)/self.duration!) + } + } + + //MARK: Interactive transaction ending + /** + * run to end interactive transaction + * + */ + + func endInteractiveTranscation(canceled: Bool , endBlock: ( () -> Void) ) { + + self.interactiveEndBlock = endBlock + + if canceled { + + _context?.cancelInteractiveTransition() + + displayLink = CADisplayLink (target: self, selector: #selector(AAPTransactionDirector.updateCancelAnimation)) + + } else { + + displayLink = CADisplayLink (target: self, selector: #selector(AAPTransactionDirector.updateFinishAnimation)) + } + + displayLink?.add(to: RunLoop.current, forMode: RunLoopMode.commonModes) + + } + + + func updateFinishAnimation() { + + let offset = CFTimeInterval(self.timeOffset) + displayLink!.duration + + if offset > CFTimeInterval(self.duration!) { + + transitionFinishedFinishing() + + } else { + + self.timeOffset = offset + } + + + } + + func updateCancelAnimation(){ + + let offset = CFTimeInterval( self.timeOffset) - displayLink!.duration + + if offset < 0 { + transitionFinishedCanceling() + }else{ + self.timeOffset=offset + + } + } + + private func transitionFinishedCanceling(){ + self._context?.containerView.layer.timeOffset=0 + displayLink?.invalidate() + + _context?.completeTransition(false) + + self.interactiveEndBlock?() + + } + + private func transitionFinishedFinishing(){ + displayLink?.invalidate() + _context?.finishInteractiveTransition() + _context?.completeTransition(true) + self.interactiveEndBlock?() + + } + + + + //MARK: init & deinit + + override init() { + + } + + deinit{ + + } + + //MARK: animation transaction delegate + func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval{ + + return TimeInterval(self.duration!) + } + + func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { + + self._context=transitionContext + + self.animationBlock?(self._context!, self.duration!, { () -> Void in + + transitionContext.completeTransition(true) + + }) + } + + //MARK: interactive transaction delegate + func startInteractiveTransition(_ transitionContext: UIViewControllerContextTransitioning){ + + self._context=transitionContext + self.animationBlock?(self._context!, self.duration!, { () -> Void in + + }) + } + + //MARK: navigation controller delegate + func navigationController(_ navigationController: UINavigationController, animationControllerFor operation: UINavigationControllerOperation, from fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? { + + return self + } + + func navigationController(navigationController: UINavigationController, interactionControllerForAnimationController animationController: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? { + + return self.isInteractive ? self : nil + + } +} diff --git a/Swift3Implementation/UINavigationController+RadialTransaction b/Swift3Implementation/UINavigationController+RadialTransaction new file mode 100644 index 0000000..66cc504 --- /dev/null +++ b/Swift3Implementation/UINavigationController+RadialTransaction @@ -0,0 +1,257 @@ +// +// UINavigationController+RadialTransaction.swift +// AAPRadialTransaction_swift +// +// Created by Alex Padalko on 9/23/14. +// Copyright (c) 2014 Alex Padalko. All rights reserved. +// +// Converted to Swift 3 by Ben Sullivan on 08/27/16 + +import UIKit + +let abc = AAPTransactionDirector() + +var defaultRadialDuration: CGFloat = 0.5 + +extension UINavigationController { + + func getLeftRect()->CGRect{ + + return CGRect.zero + } + + //MARK: PUSH + /** + * radial pushing view controller + * + * @param startFrame where circle start + */ + + func radialPushViewController(viewController: UIViewController, duration: CGFloat = 0.33 ,startFrame: CGRect = CGRect.null, transitionCompletion: (() -> Void)? = nil ) { + + var rect = startFrame + + if rect == CGRect.null { + + if let vvc = self.visibleViewController?.view.frame.size { + + rect = CGRect(x: vvc.width, y: vvc.height / 2, width: 0, height: 0) + } + } + + let animatorDirector: AAPTransactionDirector? = AAPTransactionDirector() + animatorDirector?.duration = duration + + self.delegate = animatorDirector + + animatorDirector?.animationBlock = {(transactionContext: UIViewControllerContextTransitioning, animationTime: CGFloat, completion: @escaping () -> Void ) -> Void in + + let toViewController = transactionContext.viewController(forKey: UITransitionContextViewControllerKey.to) + let fromViewController = transactionContext.viewController(forKey: UITransitionContextViewControllerKey.from) + let containerView = transactionContext.containerView + + containerView.insertSubview(toViewController!.view, aboveSubview: fromViewController!.view) + + toViewController?.view .radialAppireanceWithStartFrame(startFrame: rect, duration: animationTime, complitBlock: { () -> Void in + + completion() + + transitionCompletion?() + }) + + } + + self.pushViewController(viewController, animated: true) + + self.delegate = nil + + } + + //MARK: POP + /** + * radial pop view controller + * + * @param startFrame where circle start + */ + + func radialPopViewController(duration: CGFloat = 0.33, startFrame: CGRect = CGRect.null, transitionCompletion: (() -> Void)? = nil ) { + + var rect = startFrame + + if rect == CGRect.null { + + if let vvc = self.visibleViewController?.view.frame.size { + + rect = CGRect(x: vvc.width, y: vvc.height / 2, width: 0, height: 0) + } + } + + let animatorDirector = AAPTransactionDirector() + animatorDirector.duration = duration + self.delegate=animatorDirector + animatorDirector.animationBlock = {(transactionContext:UIViewControllerContextTransitioning, animationTime: CGFloat ,completion:@escaping ()->Void )-> Void in + + let toViewController = transactionContext.viewController(forKey: UITransitionContextViewControllerKey.to) + let fromViewController = transactionContext.viewController(forKey: UITransitionContextViewControllerKey.from) + let containerView = transactionContext.containerView + + containerView.insertSubview(toViewController!.view, aboveSubview: fromViewController!.view) + + toViewController?.view .radialAppireanceWithStartFrame(startFrame: rect, duration: animationTime, complitBlock: { () -> Void in + + completion() + transitionCompletion?() + + }) + + } + + self.popViewController(animated: true) + self.delegate = nil + } + + + //MARK: Swipe + + func enableRadialSwipe() { + + self.enableGesture(enabled: true) + + } + + func disableRadialSwipe() { + self.enableGesture(enabled: false) + + } + + /** + * enabling swipe back gesture. NOTE interactivePopGestureRecognizer will be disabled + * + */ + private func enableGesture(enabled:Bool) { + + struct StaticStruct { + + static var recognizerData = Dictionary() + + } + + if enabled == true { + + if self.responds(to: #selector(getter: self.interactivePopGestureRecognizer)) { + + self.interactivePopGestureRecognizer?.isEnabled = false + } + + let panGesture = UIScreenEdgePanGestureRecognizer(target: self, action: #selector(self.screenPan(sender:))) + panGesture.edges = UIRectEdge.left + + self.view.addGestureRecognizer(panGesture) + + StaticStruct.recognizerData[self.description] = panGesture + + } else { + + self.view.removeGestureRecognizer(StaticStruct.recognizerData[self.description]!) + StaticStruct.recognizerData[self.description] = nil + + } + } + + func screenPan(sender: AnyObject) { + + let pan: UIPanGestureRecognizer = sender as! UIPanGestureRecognizer + + let state: UIGestureRecognizerState = pan.state + + let location:CGPoint = pan.location(in: self.view) + + struct StaticStruct { + static var firstTouch:CGPoint = CGPoint.zero + static var d:CGFloat = 0 + static var animDirector:AAPTransactionDirector? = nil + + static func clean(){ + + d = 0 + firstTouch = CGPoint.zero + animDirector = nil + } + + } + + switch state { + + case UIGestureRecognizerState.began: + + if self.viewControllers.count < 2 { + + StaticStruct.clean() + return + } + + StaticStruct.animDirector = AAPTransactionDirector() + StaticStruct.animDirector?.isInteractive = true + StaticStruct.animDirector?.duration = defaultRadialDuration + self.delegate=StaticStruct.animDirector + + self.popViewController(animated: true) + self.delegate = nil + + if let vvc = self.visibleViewController?.view.frame.size { + + StaticStruct.d = sqrt(pow(vvc.width, 2) + pow(vvc.height, 2) ) * 2 + StaticStruct.firstTouch = location + + StaticStruct.animDirector?.animationBlock={(transactionContext:UIViewControllerContextTransitioning, animationTime: CGFloat ,completion:()->Void)->Void in + + let toViewController = transactionContext.viewController(forKey: UITransitionContextViewControllerKey.to) + let fromViewController = transactionContext.viewController(forKey: UITransitionContextViewControllerKey.from) + let containerView = transactionContext.containerView + + containerView.insertSubview(toViewController!.view, aboveSubview: fromViewController!.view) + + let maskLayer = CAShapeLayer() + let maskRect = CGRect(x: location.x - location.x / 2, y: location.y - location.x / 2, width: 0, height: 0) + + let path = CGPath(ellipseIn: maskRect, transform: nil) + maskLayer.path=path + toViewController?.view.layer.mask=maskLayer + + + } + } + + case UIGestureRecognizerState.changed: + + StaticStruct.animDirector?.interactiveUpdateBlock = {(transactionContext:UIViewControllerContextTransitioning, percent: CGFloat)->Void in + + let maskLayer:CAShapeLayer = transactionContext.viewController(forKey: UITransitionContextViewControllerKey.to)?.view.layer.mask as! CAShapeLayer + + let mainD = percent * StaticStruct.d + + let maskRect = CGRect(x: -mainD / 2, y: location.y - mainD / 2, width: mainD, height: mainD) + + let path = CGPath(ellipseIn: maskRect, transform: nil) + + maskLayer.path = path + + } + + let mainD = location.x * StaticStruct.d / self.view.frame.size.width + + + StaticStruct.animDirector?.precent = mainD / StaticStruct.d + + default: + + let mainD = location.x * StaticStruct.d / self.view.frame.size.width + + let canceled = mainD > StaticStruct.d * 0.5 ? false : true + + StaticStruct.animDirector?.endInteractiveTranscation(canceled: canceled, endBlock: { () -> Void in + StaticStruct.clean() + }) + } + } +} diff --git a/Swift3Implementation/UIView+Radial.swift b/Swift3Implementation/UIView+Radial.swift new file mode 100644 index 0000000..93a0ef3 --- /dev/null +++ b/Swift3Implementation/UIView+Radial.swift @@ -0,0 +1,105 @@ +// +// UIView+Radial.swift +// AAPRadialTransaction_swift +// +// Created by Alex Padalko on 9/23/14. +// Copyright (c) 2014 Alex Padalko. All rights reserved. +// +// Converted to Swift 3 by Ben Sullivan on 08/27/16 + +import UIKit + +class LayerAnimator: NSObject, CAAnimationDelegate { + + var completionBlock: ( () -> Void)? + var animLayer: CALayer? + var caAnimation: CAAnimation? + + init(layer: CALayer , animation:CAAnimation) { + + self.caAnimation = animation + self.animLayer = layer + } + + func startAnimationWithBlock(block: ( () -> Void) ) { + self.caAnimation?.delegate = self + self.completionBlock = block + + self.animLayer?.add(self.caAnimation!, forKey: "anim") + } + + func animationDidStop(_ anim: CAAnimation, finished flag: Bool) { + + self.completionBlock?() + } +} + + +extension UIView { + + func radialAppireanceWithStartFrame(startFrame: CGRect, duration: CGFloat, complitBlock: @escaping () -> Void) { + + let maskLayer = CAShapeLayer() + let maskRect = startFrame + let path = CGPath(ellipseIn: maskRect, transform: nil) + maskLayer.path = path + + let d = sqrt(pow(self.frame.size.width, 2)+pow(self.frame.size.height, 2) ) * 2 + + let newRect = CGRect(x: self.frame.size.width / 2 - d / 2, y: maskRect.origin.y - d / 2, width: d, height: d) + + let newPath = CGPath(ellipseIn: newRect, transform: nil) + + self.layer.mask = maskLayer + + + let revealAnimation = CABasicAnimation(keyPath: "path") + revealAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseIn) + revealAnimation.fromValue = path + revealAnimation.toValue = newPath + + revealAnimation.duration = CFTimeInterval(duration) + + maskLayer.path=newPath + + let animator:LayerAnimator = LayerAnimator(layer: maskLayer, animation: revealAnimation) + + animator.startAnimationWithBlock { () -> Void in + + complitBlock() + } + } + + func radialDissmisWithStartFrame(startFrame: CGRect, duration: CGFloat, complitBlock: @escaping () -> Void ) { + + let maskLayer = CAShapeLayer() + let maskRect = startFrame + let path = CGPath(ellipseIn: maskRect, transform: nil) + maskLayer.path = path + + let d = sqrt(pow(self.frame.size.width, 2)+pow(self.frame.size.height, 2) ) * 2 + + let newRect = CGRect(x: self.frame.size.width / 2 - d / 2, y: maskRect.origin.y - d / 2, width: d, height: d) + + let newPath = CGPath(ellipseIn: newRect, transform: nil) + + self.layer.mask = maskLayer + + let revealAnimation = CABasicAnimation(keyPath: "path") + revealAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseIn) + revealAnimation.fromValue = path + revealAnimation.toValue = newPath + + revealAnimation.duration = CFTimeInterval(duration) + + maskLayer.path = newPath + + let animator:LayerAnimator = LayerAnimator(layer: maskLayer, animation: revealAnimation) + + animator.startAnimationWithBlock { () -> Void in + + complitBlock() + + } + } +}