Skip to content

Instantly share code, notes, and snippets.

@0xc010d
Created March 28, 2012 22:08
Show Gist options
  • Select an option

  • Save 0xc010d/2230973 to your computer and use it in GitHub Desktop.

Select an option

Save 0xc010d/2230973 to your computer and use it in GitHub Desktop.
//
// 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
//
// 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
//
// 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
//
// 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