Created
March 28, 2012 22:08
-
-
Save 0xc010d/2230973 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // | |
| // Flipper.h | |
| // FlipLib | |
| // | |
| // Created by Eugene Solodovnykov on 11/30/10. | |
| // Copyright 2010 . All rights reserved. | |
| // | |
| #import <Foundation/Foundation.h> | |
| #import "FLMultiStepAnimation.h" | |
| typedef enum { | |
| FLSwipeDirectionRight = UISwipeGestureRecognizerDirectionRight, | |
| FLSwipeDirectionLeft = UISwipeGestureRecognizerDirectionLeft, | |
| FLSwipeDirectionUp = UISwipeGestureRecognizerDirectionUp, | |
| FLSwipeDirectionDown = UISwipeGestureRecognizerDirectionDown | |
| } FLSwipeDirection; | |
| @protocol FLFlipperDelegate; | |
| @interface FLFlipper : NSObject <FLMultiStepAnimationDelegate> { | |
| @private | |
| NSMutableArray *_gestureRecognizers; | |
| UIViewController *_previousViewController; | |
| UIViewController *_currentViewController; | |
| UIViewController *_nextViewController; | |
| UIImage *_currentImage; | |
| UIImage *_nextImage; | |
| CALayer *_currentLayer; | |
| CALayer *_nextLayer; | |
| UIView *_superview; | |
| FLSwipeDirection _swipeDirection; | |
| } | |
| @property (nonatomic, assign) id<FLFlipperDelegate> delegate; | |
| @property (nonatomic, readonly, getter=isEnabled) BOOL enabled; | |
| @property (nonatomic, readonly, getter=isAnimating) BOOL animating; | |
| @property (nonatomic, assign) UIView *listener; | |
| @property (nonatomic, assign) NSUInteger numberOfTouchesRequired; | |
| @property (nonatomic, assign) FLSwipeDirection direction; | |
| - (id)initWithListener:(UIView *)listener; | |
| - (void)enable; | |
| - (void)disable; | |
| - (void)flip:(FLSwipeDirection)direction; | |
| @end | |
| @protocol FLFlipperDelegate | |
| @required | |
| - (UIViewController *)nextViewController:(FLFlipper *)flipper; | |
| - (UIViewController *)currentViewController:(FLFlipper *)flipper; | |
| - (UIViewController *)previousViewController:(FLFlipper *)flipper; | |
| @end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // | |
| // FLFlipper.m | |
| // FlipLib | |
| // | |
| // Created by Eugene Solodovnykov on 11/30/10. | |
| // Copyright 2010 . All rights reserved. | |
| // | |
| #import "FLFlipper.h" | |
| #import <QuartzCore/QuartzCore.h> | |
| #import <CoreGraphics/CoreGraphics.h> | |
| @interface FLFlipper () | |
| - (UIImage *)screenshotForView:(UIView *)view; | |
| - (UIImage *)flippedHorizontallyImage:(UIImage *)originalImage; | |
| - (UIImage *)flippedVerticallyImage:(UIImage *)originalImage; | |
| - (NSUInteger)directionsCount; | |
| @property (nonatomic, retain) UIViewController *_previousViewController; | |
| @property (nonatomic, retain) UIViewController *_currentViewController; | |
| @property (nonatomic, retain) UIViewController *_nextViewController; | |
| @property (nonatomic, retain) UIImage *_currentImage; | |
| @property (nonatomic, retain) UIImage *_nextImage; | |
| @end | |
| @implementation FLFlipper | |
| @synthesize delegate; | |
| @synthesize animating; | |
| @synthesize enabled; | |
| @synthesize listener; | |
| @synthesize numberOfTouchesRequired; | |
| @synthesize direction; | |
| @synthesize _previousViewController; | |
| @synthesize _currentViewController; | |
| @synthesize _nextViewController; | |
| @synthesize _currentImage; | |
| @synthesize _nextImage; | |
| - (void)dealloc { | |
| self._previousViewController = nil; | |
| self._currentViewController = nil; | |
| self._nextViewController = nil; | |
| self._currentImage = nil; | |
| self._nextImage = nil; | |
| for (UISwipeGestureRecognizer *gestureRecognizer in _gestureRecognizers) { | |
| [self.listener removeGestureRecognizer:gestureRecognizer]; | |
| } | |
| [_gestureRecognizers release]; | |
| [super dealloc]; | |
| } | |
| #pragma mark - | |
| #pragma mark Public | |
| - (id)init { | |
| if (self = [super init]) { | |
| //create gesture recogniser for each direction | |
| _gestureRecognizers = [NSMutableArray new]; | |
| for (short i=0; i<4; i++) { | |
| UISwipeGestureRecognizer *gestureRecognizer = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(swipeWasDone:)]; | |
| gestureRecognizer.direction = (1 << i); | |
| [_gestureRecognizers addObject:gestureRecognizer]; | |
| [gestureRecognizer release]; | |
| } | |
| self.numberOfTouchesRequired = 1; | |
| self.direction = 0xff;//(FLSwipeDirectionLeft | FLSwipeDirectionRight | FLSwipeDirectionDown | FLSwipeDirectionUp); | |
| } | |
| return self; | |
| } | |
| - (id)initWithListener:(UIView *)theListener { | |
| if (self = [self init]) { | |
| self.listener = theListener; | |
| } | |
| return self; | |
| } | |
| - (void)enable { | |
| enabled = YES; | |
| for (UISwipeGestureRecognizer *gestureRecognizer in _gestureRecognizers) { | |
| gestureRecognizer.enabled = YES; | |
| } | |
| } | |
| - (void)disable { | |
| enabled = NO; | |
| for (UISwipeGestureRecognizer *gestureRecognizer in _gestureRecognizers) { | |
| gestureRecognizer.enabled = NO; | |
| } | |
| } | |
| - (void)flip:(FLSwipeDirection)theDirection { | |
| animating = YES; | |
| _swipeDirection = theDirection; | |
| self._currentImage = [self screenshotForView:self._currentViewController.view]; | |
| switch (_swipeDirection) { | |
| case FLSwipeDirectionLeft: | |
| case FLSwipeDirectionDown: | |
| self._nextImage = [self screenshotForView:self._nextViewController.view]; | |
| break; | |
| case FLSwipeDirectionRight: | |
| case FLSwipeDirectionUp: | |
| self._nextImage = [self screenshotForView:self._previousViewController.view]; | |
| break; | |
| } | |
| _currentLayer = [CALayer layer]; | |
| _nextLayer = [CALayer layer]; | |
| CGRect layerFrame = _superview.bounds; | |
| switch (_swipeDirection) { | |
| case FLSwipeDirectionLeft: | |
| layerFrame.size.width /= 2; | |
| layerFrame.origin.x = layerFrame.size.width; | |
| _currentLayer.anchorPoint = CGPointMake(0, 0.5); | |
| break; | |
| case FLSwipeDirectionRight: | |
| layerFrame.size.width /= 2; | |
| _currentLayer.anchorPoint = CGPointMake(1, 0.5); | |
| break; | |
| case FLSwipeDirectionDown: | |
| layerFrame.size.height /= 2; | |
| _currentLayer.anchorPoint = CGPointMake(0.5, 1); | |
| break; | |
| case FLSwipeDirectionUp: | |
| layerFrame.size.height /= 2; | |
| layerFrame.origin.y = layerFrame.size.height; | |
| _currentLayer.anchorPoint = CGPointMake(0.5, 0); | |
| break; | |
| } | |
| _nextLayer.frame = layerFrame; | |
| _currentLayer.frame = layerFrame; | |
| _nextLayer.contents = (id)CGImageCreateWithImageInRect(self._nextImage.CGImage, layerFrame); | |
| _currentLayer.contents = (id)CGImageCreateWithImageInRect(self._currentImage.CGImage, layerFrame); | |
| CATransform3D aTransform = CATransform3DIdentity; | |
| float zDistance = 2500; | |
| aTransform.m34 = 1.0 / -zDistance; | |
| self._currentViewController.view.layer.sublayerTransform = aTransform; | |
| [self._currentViewController.view.layer addSublayer:_nextLayer]; | |
| [self._currentViewController.view.layer addSublayer:_currentLayer]; | |
| //TODO: make it more clean | |
| FLMultiStepAnimation *anim = [[FLMultiStepAnimation new] autorelease]; | |
| anim.keyPath = @"transform"; | |
| anim.delegate = self; | |
| anim.duration = .3; | |
| switch (_swipeDirection) { | |
| case FLSwipeDirectionLeft: | |
| anim.fromValue = [NSValue valueWithCATransform3D:CATransform3DMakeRotation(0, 0, 1, 0)]; | |
| [anim addToValue:[NSValue valueWithCATransform3D:CATransform3DMakeRotation(-M_PI/2, 0, 1, 0)]]; | |
| [anim addToValue:[NSValue valueWithCATransform3D:CATransform3DMakeRotation(-M_PI, 0, 1, 0)]]; | |
| break; | |
| case FLSwipeDirectionRight: | |
| anim.fromValue = [NSValue valueWithCATransform3D:CATransform3DMakeRotation(0, 0, 1, 0)]; | |
| [anim addToValue:[NSValue valueWithCATransform3D:CATransform3DMakeRotation(M_PI/2, 0, 1, 0)]]; | |
| [anim addToValue:[NSValue valueWithCATransform3D:CATransform3DMakeRotation(M_PI, 0, 1, 0)]]; | |
| break; | |
| case FLSwipeDirectionDown: | |
| anim.fromValue = [NSValue valueWithCATransform3D:CATransform3DMakeRotation(0, 1, 0, 0)]; | |
| [anim addToValue:[NSValue valueWithCATransform3D:CATransform3DMakeRotation(-M_PI/2, 1, 0, 0)]]; | |
| [anim addToValue:[NSValue valueWithCATransform3D:CATransform3DMakeRotation(-M_PI, 1, 0, 0)]]; | |
| break; | |
| case FLSwipeDirectionUp: | |
| anim.fromValue = [NSValue valueWithCATransform3D:CATransform3DMakeRotation(0, 1, 0, 0)]; | |
| [anim addToValue:[NSValue valueWithCATransform3D:CATransform3DMakeRotation(M_PI/2, 1, 0, 0)]]; | |
| [anim addToValue:[NSValue valueWithCATransform3D:CATransform3DMakeRotation(M_PI, 1, 0, 0)]]; | |
| break; | |
| } | |
| [anim animateLayer:_currentLayer]; | |
| } | |
| #pragma mark - | |
| #pragma mark Setters | |
| - (void)setDelegate:(id <FLFlipperDelegate>)theDelegate { | |
| delegate = theDelegate; | |
| if (delegate) { | |
| self._currentViewController = [delegate currentViewController:self]; | |
| if (!self._currentViewController.view.superview) { | |
| [self.listener addSubview:self._currentViewController.view]; | |
| _superview = self.listener; | |
| } else { | |
| _superview = self._currentViewController.view.superview; | |
| } | |
| self._previousViewController = [delegate previousViewController:self]; | |
| self._nextViewController = [delegate nextViewController:self]; | |
| } | |
| } | |
| - (void)setNumberOfTouchesRequired:(NSUInteger)theNumberOfTouchesRequired { | |
| numberOfTouchesRequired = theNumberOfTouchesRequired; | |
| for (UISwipeGestureRecognizer *gestureRecognizer in _gestureRecognizers) { | |
| gestureRecognizer.numberOfTouchesRequired = numberOfTouchesRequired; | |
| } | |
| } | |
| - (void)setListener:(UIView *)theListener { | |
| listener = theListener; | |
| for (UISwipeGestureRecognizer *gestureRecognizer in _gestureRecognizers) { | |
| [listener addGestureRecognizer:gestureRecognizer]; | |
| } | |
| } | |
| #pragma mark - | |
| #pragma mark UISwipeGestureRecognizer action | |
| - (void)swipeWasDone:(UISwipeGestureRecognizer *)gestureRecognizer { | |
| NSLog(@"%@", gestureRecognizer); | |
| if (!self.animating && (self.direction & gestureRecognizer.direction)) { | |
| [self flip:(FLSwipeDirection)gestureRecognizer.direction]; | |
| } | |
| } | |
| #pragma mark - | |
| #pragma mark FLMultiStepAnimationDelegate | |
| - (void)animation:(FLMultiStepAnimation *)animation didStopStep:(NSUInteger)step { | |
| if (step == 1) { | |
| CGRect layerFrame = _superview.bounds; | |
| UIImage *nextImageFlipped; | |
| switch (_swipeDirection) { | |
| case FLSwipeDirectionLeft: | |
| layerFrame.size.width /= 2; | |
| layerFrame.origin.x = layerFrame.size.width; | |
| nextImageFlipped = [self flippedHorizontallyImage:self._nextImage]; | |
| break; | |
| case FLSwipeDirectionRight: | |
| layerFrame.size.width /= 2; | |
| nextImageFlipped = [self flippedHorizontallyImage:self._nextImage]; | |
| break; | |
| case FLSwipeDirectionDown: | |
| layerFrame.size.height /= 2; | |
| nextImageFlipped = [self flippedVerticallyImage:self._nextImage]; | |
| break; | |
| case FLSwipeDirectionUp: | |
| layerFrame.size.height /= 2; | |
| layerFrame.origin.y = layerFrame.size.height; | |
| nextImageFlipped = [self flippedVerticallyImage:self._nextImage]; | |
| break; | |
| } | |
| _currentLayer.contents = (id)CGImageCreateWithImageInRect(nextImageFlipped.CGImage, layerFrame); | |
| } | |
| } | |
| - (void)animationDidFinish:(FLMultiStepAnimation *)animation { | |
| [_currentLayer removeFromSuperlayer]; | |
| [_nextLayer removeFromSuperlayer]; | |
| switch (_swipeDirection) { | |
| case FLSwipeDirectionLeft: | |
| case FLSwipeDirectionDown: | |
| self._previousViewController = self._currentViewController; | |
| [self._previousViewController.view removeFromSuperview]; | |
| self._currentViewController = self._nextViewController; | |
| [_superview addSubview:self._currentViewController.view]; | |
| self._nextViewController = [self.delegate nextViewController:self]; | |
| break; | |
| case FLSwipeDirectionRight: | |
| case FLSwipeDirectionUp: | |
| self._nextViewController = self._currentViewController; | |
| [self._nextViewController.view removeFromSuperview]; | |
| self._currentViewController = self._previousViewController; | |
| [_superview addSubview:self._currentViewController.view]; | |
| self._previousViewController = [self.delegate previousViewController:self]; | |
| break; | |
| } | |
| animating = NO; | |
| } | |
| #pragma mark - | |
| #pragma mark Private | |
| - (UIImage *)screenshotForView:(UIView *)view { | |
| //TODO: check this! | |
| CGSize imageSize = _superview.bounds.size; | |
| if (NULL != UIGraphicsBeginImageContextWithOptions) | |
| UIGraphicsBeginImageContextWithOptions(imageSize, NO, 0); | |
| else | |
| UIGraphicsBeginImageContext(imageSize); | |
| CGContextRef context = UIGraphicsGetCurrentContext(); | |
| CGContextSaveGState(context); | |
| CGContextTranslateCTM(context, view.center.x, view.center.y); | |
| CGContextConcatCTM(context, view.transform); | |
| CGContextTranslateCTM(context, | |
| -view.bounds.size.width * view.layer.anchorPoint.x, | |
| -view.bounds.size.height * view.layer.anchorPoint.y); | |
| [view.layer renderInContext:context]; | |
| CGContextRestoreGState(context); | |
| UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); | |
| UIGraphicsEndImageContext(); | |
| return image; | |
| } | |
| - (UIImage *)flippedHorizontallyImage:(UIImage *)originalImage { | |
| UIImageView *tempImageView = [[UIImageView alloc] initWithImage:originalImage]; | |
| UIGraphicsBeginImageContext(tempImageView.frame.size); | |
| CGContextRef context = UIGraphicsGetCurrentContext(); | |
| CGAffineTransform flipVertical = CGAffineTransformMake(-1, 0, 0, 1, tempImageView.frame.size.width, 0); | |
| CGContextConcatCTM(context, flipVertical); | |
| [tempImageView.layer renderInContext:context]; | |
| UIImage *flipedImage = UIGraphicsGetImageFromCurrentImageContext(); | |
| UIGraphicsEndImageContext(); | |
| [tempImageView release]; | |
| return flipedImage; | |
| } | |
| - (UIImage *)flippedVerticallyImage:(UIImage *)originalImage { | |
| UIImageView *tempImageView = [[UIImageView alloc] initWithImage:originalImage]; | |
| UIGraphicsBeginImageContext(tempImageView.frame.size); | |
| CGContextRef context = UIGraphicsGetCurrentContext(); | |
| CGAffineTransform flipVertical = CGAffineTransformMake(1.0, 0.0, 0, -1.0, 0.0, tempImageView.frame.size.height); | |
| CGContextConcatCTM(context, flipVertical); | |
| [tempImageView.layer renderInContext:context]; | |
| UIImage *flipedImage = UIGraphicsGetImageFromCurrentImageContext(); | |
| UIGraphicsEndImageContext(); | |
| [tempImageView release]; | |
| return flipedImage; | |
| } | |
| - (NSUInteger)directionsCount { | |
| //GCC function | |
| return (NSUInteger)__builtin_popcount(direction); | |
| //possible algorithm to calculate the count of 1's in a binary number | |
| NSUInteger directionMask = direction, count = 0; | |
| do { | |
| count += directionMask & 1; | |
| } while (directionMask >>= 1); | |
| return count; | |
| } | |
| @end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // | |
| // FLMultiStepAnimation.h | |
| // FlipLib | |
| // | |
| // Created by Eugene Solodovnykov on 12/2/10. | |
| // Copyright 2010 . All rights reserved. | |
| // | |
| #import <Foundation/Foundation.h> | |
| #import <QuartzCore/QuartzCore.h> | |
| @protocol FLMultiStepAnimationDelegate; | |
| @interface FLMultiStepAnimation : NSObject { | |
| @private | |
| CALayer *_layer; | |
| NSMutableArray *_toValues; | |
| NSUInteger _currentStep; | |
| CABasicAnimation *_animation; | |
| } | |
| @property (nonatomic, assign) NSObject<FLMultiStepAnimationDelegate> *delegate; | |
| @property (nonatomic, assign) CFTimeInterval duration; | |
| @property (nonatomic, retain) NSString *keyPath; | |
| @property (nonatomic, retain) NSValue *fromValue; | |
| @property (nonatomic, readonly) NSUInteger stepsCount; | |
| @property (nonatomic, assign) BOOL stopAfterEachStep; | |
| - (void)addToValue:(NSValue *)toValue; | |
| - (void)animateLayer:(CALayer *)layer; | |
| - (void)continueAnimation; | |
| @end | |
| @protocol FLMultiStepAnimationDelegate | |
| @optional | |
| - (void)animationDidBegin:(FLMultiStepAnimation *)animation; | |
| - (void)animation:(FLMultiStepAnimation *)animation didStopStep:(NSUInteger)step; | |
| - (void)animation:(FLMultiStepAnimation *)animation didStartStep:(NSUInteger)step; | |
| - (void)animationDidFinish:(FLMultiStepAnimation *)animation; | |
| @end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // | |
| // FLMultiStepAnimation.m | |
| // FlipLib | |
| // | |
| // Created by Eugene Solodovnykov on 12/2/10. | |
| // Copyright 2010 . All rights reserved. | |
| // | |
| #import "FLMultiStepAnimation.h" | |
| @interface NSString (UUID) | |
| + (NSString *)UUIDString; | |
| @end | |
| @implementation FLMultiStepAnimation | |
| @synthesize delegate; | |
| @synthesize duration; | |
| @synthesize keyPath; | |
| @synthesize fromValue; | |
| @synthesize stepsCount; | |
| @synthesize stopAfterEachStep; | |
| - (void)dealloc { | |
| [_animation release]; | |
| [_toValues release]; | |
| self.keyPath = nil; | |
| self.fromValue = nil; | |
| [super dealloc]; | |
| } | |
| - (id)init { | |
| if (self = [super init]) { | |
| _toValues = [NSMutableArray new]; | |
| _animation = [CABasicAnimation new]; | |
| _animation.delegate = self; | |
| _animation.repeatCount = 1; | |
| //TODO: move this to class level | |
| _animation.removedOnCompletion = NO; | |
| _animation.fillMode = kCAFillModeForwards; | |
| _currentStep = 0; | |
| self.duration = 0.25; | |
| } | |
| return self; | |
| } | |
| - (void)addToValue:(NSValue *)toValue { | |
| [_toValues addObject:toValue]; | |
| } | |
| - (void)animateLayer:(CALayer *)layer { | |
| _animation.duration = self.duration / [self stepsCount]; | |
| _animation.toValue = [_toValues objectAtIndex:_currentStep++]; | |
| _layer = layer; | |
| [_layer addAnimation:_animation forKey:[NSString UUIDString]]; | |
| } | |
| - (void)continueAnimation { | |
| _animation.fromValue = _animation.toValue; | |
| _animation.toValue = [_toValues objectAtIndex:_currentStep++]; | |
| [_layer addAnimation:_animation forKey:[NSString UUIDString]]; | |
| } | |
| #pragma mark - | |
| #pragma mark setters, getters | |
| - (NSUInteger)stepsCount { | |
| return [_toValues count]; | |
| } | |
| - (void)setKeyPath:(NSString *)theKeyPath { | |
| keyPath = [theKeyPath retain]; | |
| _animation.keyPath = keyPath; | |
| } | |
| - (void)setFromValue:(NSValue *)theFromValue { | |
| fromValue = [theFromValue retain]; | |
| _animation.fromValue = fromValue; | |
| } | |
| #pragma mark - | |
| #pragma mark CABasicAnimationDelegate | |
| - (void)animationDidStart:(CAAnimation *)anim { | |
| if (_currentStep == 1) { | |
| if ([self.delegate respondsToSelector:@selector(animationDidBegin:)]) { | |
| [self.delegate animationDidBegin:self]; | |
| } | |
| } | |
| if ([self.delegate respondsToSelector:@selector(animation:didStartStep:)]) { | |
| [self.delegate animation:self didStartStep:_currentStep]; | |
| } | |
| } | |
| - (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag { | |
| //TODO: add flag processing | |
| if ([self.delegate respondsToSelector:@selector(animation:didStopStep:)]) { | |
| [self.delegate animation:self didStopStep:_currentStep]; | |
| } | |
| if (_currentStep == [self stepsCount]) { | |
| if ([self.delegate respondsToSelector:@selector(animationDidFinish:)]) { | |
| [self.delegate animationDidFinish:self]; | |
| } | |
| } | |
| if (_currentStep < [self stepsCount] && !self.stopAfterEachStep) { | |
| [self continueAnimation]; | |
| } | |
| } | |
| @end | |
| @implementation NSString (UUID) | |
| + (NSString *)UUIDString { | |
| CFUUIDRef theUUID = CFUUIDCreate(NULL); | |
| CFStringRef string = CFUUIDCreateString(NULL, theUUID); | |
| CFRelease(theUUID); | |
| return [(NSString *)string autorelease]; | |
| } | |
| @end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment