diff --git a/.DS_Store b/.DS_Store index 4b4fb4e..7f19b20 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/GmailLikeLoadingView.h b/GmailLikeLoadingView.h index 83537ad..b846c97 100644 --- a/GmailLikeLoadingView.h +++ b/GmailLikeLoadingView.h @@ -8,9 +8,11 @@ #import -@interface GmailLikeLoadingView : UIView +@interface GmailLikeLoadingView : UIView { + NSInteger animationCount_; +} -(void)startAnimating; -(void)stopAnimating; -@property (nonatomic) BOOL isAnimating; - +-(void)allStop; +-(BOOL)isAnimating; @end diff --git a/GmailLikeLoadingView.m b/GmailLikeLoadingView.m index 2edc251..bf985ac 100644 --- a/GmailLikeLoadingView.m +++ b/GmailLikeLoadingView.m @@ -8,7 +8,10 @@ #import "GmailLikeLoadingView.h" #import - +#define COLOR_MEDIUM_SEA_GREEN [UIColor colorWithRed:0.0/255.0f green:147.0/255.0f blue:78.0/255.0f alpha:1.0] +#define COLOR_MEDUIM_BLUE [UIColor colorWithRed:20.0/255.0f green:99.0/255.0f blue:233.0/255.0 alpha:1.0] +#define COLOR_ORANGE [UIColor colorWithRed:255.0/255.0f green:199.0/255.0f blue:12.0/255.0f alpha:1.0] +#define COLOR_MEDIUM_RED [UIColor colorWithRed:221.0/255.0f green:0.0/255.0f blue:31.0/255.0 alpha:1.0] typedef enum { kFlipStop = 0, kFlipTopBottom, @@ -27,16 +30,17 @@ @interface GmailLikeLoadingView (){ UIView *secondHalfFrontLayerView; UIView *firstHalfBackLayerView; UIView *secondHalfBackLayerView; - BOOL horizontal; NSMutableArray *colorsArray; } -(void)animateView; +-(void)arrangeTopFirstAnimation; +-(void)arrangeBottomFirstAnimation; -(NSArray*)splitViewToImages:(UIView*)view forFlipState:(kFlipDirectionState)flipDirection; +-(BOOL)isBottomFirstAnimation; @end @implementation GmailLikeLoadingView -@synthesize isAnimating; - (id)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; @@ -45,7 +49,7 @@ - (id)initWithFrame:(CGRect)frame CGFloat diameter = MIN(self.frame.size.width, self.frame.size.height); - colorsArray = [NSMutableArray arrayWithObjects:[UIColor colorWithRed:0.0/255.0f green:147.0/255.0f blue:78.0/255.0f alpha:1.0],[UIColor colorWithRed:20.0/255.0f green:99.0/255.0f blue:233.0/255.0 alpha:1.0],[UIColor colorWithRed:255.0/255.0f green:199.0/255.0f blue:12.0/255.0f alpha:1.0],[UIColor colorWithRed:221.0/255.0f green:0.0/255.0f blue:31.0/255.0 alpha:1.0], nil]; + colorsArray = [NSMutableArray arrayWithObjects:COLOR_MEDIUM_SEA_GREEN,COLOR_MEDUIM_BLUE,COLOR_ORANGE,COLOR_MEDIUM_RED, nil]; frontLayerView = [[UIView alloc] init]; [frontLayerView setBackgroundColor:[UIColor clearColor]]; @@ -66,8 +70,7 @@ - (id)initWithFrame:(CGRect)frame backLayerView.center = self.center; previousFlipState = kFlipStop; flipState = kFlipBottomTop; - isAnimating = NO; - horizontal = NO; + animationCount_ = 0; } return self; } @@ -89,22 +92,22 @@ -(NSArray*)splitViewToImages:(UIView*)view forFlipState:(kFlipDirectionState)fli UIImage *top = nil; UIImage *bottom = nil; UIGraphicsBeginImageContextWithOptions(size, view.layer.opaque, 0.f); - {{ - [renderedImage drawAtPoint:CGPointZero]; - - top = UIGraphicsGetImageFromCurrentImageContext(); - }} + + [renderedImage drawAtPoint:CGPointZero]; + + top = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); UIGraphicsBeginImageContextWithOptions(size, view.layer.opaque, 0.f); - {{ - if (flipDirection == kFlipBottomTop || flipDirection == kFlipTopBottom) { - [renderedImage drawAtPoint:CGPointMake(CGPointZero.x, -renderedImage.size.height / 2)]; - }else{ - [renderedImage drawAtPoint:CGPointMake(-renderedImage.size.width / 2,CGPointZero.y)]; - } - bottom = UIGraphicsGetImageFromCurrentImageContext(); - }} + + if (flipDirection == kFlipBottomTop || flipDirection == kFlipTopBottom) { + [renderedImage drawAtPoint:CGPointMake(CGPointZero.x, -renderedImage.size.height / 2)]; + }else{ + [renderedImage drawAtPoint:CGPointMake(-renderedImage.size.width / 2,CGPointZero.y)]; + } + bottom = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); UIImageView *topHalfView = [[UIImageView alloc] initWithImage:top]; @@ -118,37 +121,40 @@ -(NSArray*)splitViewToImages:(UIView*)view forFlipState:(kFlipDirectionState)fli } -- (CGPoint)center:(CGPoint)oldCenter movedFromAnchorPoint:(CGPoint)oldAnchorPoint toAnchorPoint:(CGPoint)newAnchorPoint withFrame:(CGRect)frame; -{ - CGPoint anchorPointDiff = CGPointMake(newAnchorPoint.x - oldAnchorPoint.x, newAnchorPoint.y - oldAnchorPoint.y); - CGPoint newCenter = CGPointMake(oldCenter.x + (anchorPointDiff.x * frame.size.width), - oldCenter.y + (anchorPointDiff.y * frame.size.height)); - return newCenter; -} -(void)animateView{ - - NSArray *frontImages = [self splitViewToImages:frontLayerView forFlipState:flipState]; + //If bottom first animation, use backLayerView, otherwise use frontLayerView + NSArray *frontImages = nil; + if ([self isBottomFirstAnimation]) { + frontImages = [self splitViewToImages:backLayerView forFlipState:flipState]; + } + else { + frontImages = [self splitViewToImages:frontLayerView forFlipState:flipState]; + } firstHalfFrontLayerView = [frontImages objectAtIndex:0]; secondHalfFrontLayerView = [frontImages objectAtIndex:1]; - [firstHalfFrontLayerView setFrame:CGRectMake(0, 0, firstHalfFrontLayerView.frame.size.width, firstHalfFrontLayerView.frame.size.height)]; - firstHalfFrontLayerView.frame = CGRectOffset(firstHalfFrontLayerView.frame, 0, 0); + firstHalfFrontLayerView.frame = firstHalfFrontLayerView.bounds; [self addSubview:firstHalfFrontLayerView]; - secondHalfFrontLayerView.frame = firstHalfFrontLayerView.frame; if (flipState == kFlipBottomTop || flipState == kFlipTopBottom) { - secondHalfFrontLayerView.frame = CGRectOffset(secondHalfFrontLayerView.frame, 0.f, firstHalfFrontLayerView.frame.size.height); + secondHalfFrontLayerView.frame = CGRectOffset(firstHalfFrontLayerView.frame, 0.f, firstHalfFrontLayerView.frame.size.height); }else{ - secondHalfFrontLayerView.frame = CGRectOffset(secondHalfFrontLayerView.frame, firstHalfFrontLayerView.frame.size.width, 0.f); + secondHalfFrontLayerView.frame = CGRectOffset(firstHalfFrontLayerView.frame, firstHalfFrontLayerView.frame.size.width, 0.f); } [self addSubview:secondHalfFrontLayerView]; - - [frontLayerView removeFromSuperview]; + - NSArray *backImages = [self splitViewToImages:backLayerView forFlipState:flipState]; + //If bottom first animation, use frontLayerView, otherwise use backLayerView + NSArray *backImages = nil; + if ([self isBottomFirstAnimation]) { + backImages = [self splitViewToImages:frontLayerView forFlipState:flipState]; + } + else { + backImages = [self splitViewToImages:backLayerView forFlipState:flipState]; + } firstHalfBackLayerView = [backImages objectAtIndex:0]; @@ -160,118 +166,256 @@ -(void)animateView{ secondHalfBackLayerView.frame = secondHalfFrontLayerView.frame; [self insertSubview:secondHalfBackLayerView belowSubview:secondHalfFrontLayerView]; - - CATransform3D skewedIdentityTransform = CATransform3DIdentity; - float zDistance = 1000.000000; - skewedIdentityTransform.m34 = 1.0 / -zDistance; CGPoint newTopViewAnchorPoint; CGPoint newAnchorPointBottomHalf; - float x,y,z; if (flipState == kFlipBottomTop || flipState == kFlipTopBottom) { newTopViewAnchorPoint = CGPointMake(0.5, 1.0); newAnchorPointBottomHalf = CGPointMake(0.5f, 0.f); - x = 1.f; - y = 0.f; - z = 0.f; }else{ newTopViewAnchorPoint = CGPointMake(1.0f, 0.5f); newAnchorPointBottomHalf = CGPointMake(0.f,0.5f); - x = 0.f; - y = 1.f; - z = 0.f; } - CGPoint newTopViewCenter = [self center:firstHalfFrontLayerView.center movedFromAnchorPoint:firstHalfFrontLayerView.layer.anchorPoint toAnchorPoint:newTopViewAnchorPoint withFrame:firstHalfFrontLayerView.frame]; - + firstHalfFrontLayerView.layer.anchorPoint = newTopViewAnchorPoint; - if (flipState == kFlipBottomTop || flipState == kFlipTopBottom) { - firstHalfFrontLayerView.center = newTopViewCenter; - }else{ - [firstHalfFrontLayerView setCenter:CGPointMake(self.frame.size.width/2, self.frame.size.height/2)]; - } + + [firstHalfFrontLayerView setCenter:CGPointMake(self.frame.size.width/2, self.frame.size.height/2)]; + [[firstHalfFrontLayerView layer] setOpacity:1.0f]; [[firstHalfFrontLayerView layer] setOpaque:YES]; - CABasicAnimation *topAnim = [CABasicAnimation animationWithKeyPath:@"transform"]; + + + secondHalfBackLayerView.layer.anchorPoint = newAnchorPointBottomHalf; + + [secondHalfBackLayerView setCenter:CGPointMake(self.frame.size.width/2, self.frame.size.height/2)]; + + + [[secondHalfBackLayerView layer] setOpacity:1.0f]; + [[secondHalfBackLayerView layer] setOpaque:YES]; + + if ([self isBottomFirstAnimation]) { + [self arrangeBottomFirstAnimation]; + } + else { + [self arrangeTopFirstAnimation]; + } + +} + +-(BOOL)isBottomFirstAnimation { + if (flipState == kFlipTopBottom || flipState == kFlipLeftRight) { + return NO; + } + else { + return YES; + } +} + + +-(void)arrangeTopFirstAnimation { + CATransform3D skewedIdentityTransform = CATransform3DIdentity; + float zDistance = 1000.000000; + skewedIdentityTransform.m34 = 1.0 / -zDistance; + float x,y,z; + if (flipState == kFlipBottomTop || flipState == kFlipTopBottom) { + + x = 1.f; + y = 0.f; + z = 0.f; + }else{ + + x = 0.f; + y = 1.f; + z = 0.f; + } + CABasicAnimation *topAnim = [CABasicAnimation animationWithKeyPath:@"transform"]; topAnim.beginTime = CACurrentMediaTime(); topAnim.duration = 0.5; topAnim.fromValue = [NSValue valueWithCATransform3D:skewedIdentityTransform]; - if (flipState == kFlipBottomTop || flipState == kFlipTopBottom) { - topAnim.toValue = [NSValue valueWithCATransform3D:CATransform3DRotate(skewedIdentityTransform, -M_PI_2, x, y, z)]; - }else{ - topAnim.toValue = [NSValue valueWithCATransform3D:CATransform3DRotate(skewedIdentityTransform, M_PI_2, x, y, z)]; + + switch (flipState) { + case kFlipBottomTop: + topAnim.toValue = [NSValue valueWithCATransform3D:CATransform3DRotate(skewedIdentityTransform, -M_PI_2, x, y, z)]; + break; + case kFlipTopBottom: + topAnim.toValue = [NSValue valueWithCATransform3D:CATransform3DRotate(skewedIdentityTransform, -M_PI_2, x, y, z)]; + break; + case kFlipLeftRight: + topAnim.toValue = [NSValue valueWithCATransform3D:CATransform3DRotate(skewedIdentityTransform, M_PI_2, x, y, z)]; + break; + case kFlipRightLeft: + topAnim.toValue = [NSValue valueWithCATransform3D:CATransform3DRotate(skewedIdentityTransform, M_PI_2, x, y, z)]; + break; + default: + break; } - topAnim.delegate = self; + + topAnim.delegate = nil; topAnim.removedOnCompletion = NO; - topAnim.fillMode = kCAFillModeForwards; + + topAnim.fillMode = kCAFillModeBoth; topAnim.timingFunction = [CAMediaTimingFunction functionWithControlPoints:0.70 :0.00 :1.00 :1.00]; - [[firstHalfFrontLayerView layer] setOpacity:0.955555f]; + [[firstHalfFrontLayerView layer] setOpacity:1]; [[firstHalfFrontLayerView layer] setOpaque:YES]; [firstHalfFrontLayerView.layer addAnimation:topAnim forKey:@"topDownFlip"]; + [self bringSubviewToFront:firstHalfFrontLayerView]; - CGPoint newBottomHalfCenter = [self center:secondHalfBackLayerView.center movedFromAnchorPoint:secondHalfBackLayerView.layer.anchorPoint toAnchorPoint:newAnchorPointBottomHalf withFrame:secondHalfBackLayerView.frame]; - secondHalfBackLayerView.layer.anchorPoint = newAnchorPointBottomHalf; - if (flipState == kFlipBottomTop || flipState == kFlipTopBottom) { - secondHalfBackLayerView.center = newBottomHalfCenter; - }else{ - [secondHalfBackLayerView setCenter:CGPointMake(self.frame.size.width/2, self.frame.size.height/2)]; - - } - [[secondHalfBackLayerView layer] setOpacity:1.0f]; - [[secondHalfBackLayerView layer] setOpaque:YES]; - - CABasicAnimation *bottomAnim = [CABasicAnimation animationWithKeyPath:@"transform"]; + CABasicAnimation *bottomAnim = [CABasicAnimation animationWithKeyPath:@"transform"]; bottomAnim.beginTime = topAnim.beginTime + topAnim.duration; bottomAnim.duration = topAnim.duration; - if (flipState == kFlipBottomTop || flipState == kFlipTopBottom) { - bottomAnim.fromValue = [NSValue valueWithCATransform3D:CATransform3DRotate(skewedIdentityTransform, M_PI_2, x, y, z)]; - }else{ - bottomAnim.fromValue = [NSValue valueWithCATransform3D:CATransform3DRotate(skewedIdentityTransform, -M_PI_2, x, y, z)]; + + switch (flipState) { + case kFlipBottomTop: + bottomAnim.fromValue = [NSValue valueWithCATransform3D:CATransform3DRotate(skewedIdentityTransform, M_PI_2, x, y, z)]; + break; + case kFlipTopBottom: + bottomAnim.fromValue = [NSValue valueWithCATransform3D:CATransform3DRotate(skewedIdentityTransform, M_PI_2, x, y, z)]; + break; + case kFlipLeftRight: + bottomAnim.fromValue = [NSValue valueWithCATransform3D:CATransform3DRotate(skewedIdentityTransform, -M_PI_2, x, y, z)]; + break; + case kFlipRightLeft: + bottomAnim.fromValue = [NSValue valueWithCATransform3D:CATransform3DRotate(skewedIdentityTransform, -M_PI_2, x, y, z)]; + break; + default: + break; } + bottomAnim.toValue = [NSValue valueWithCATransform3D:skewedIdentityTransform]; bottomAnim.delegate = self; bottomAnim.removedOnCompletion = NO; bottomAnim.fillMode = kCAFillModeBoth; bottomAnim.timingFunction = [CAMediaTimingFunction functionWithControlPoints:0.30 :1.00 :1.00 :1.00]; - [[secondHalfBackLayerView layer] setOpacity:0.955555f]; + [[secondHalfBackLayerView layer] setOpacity:1]; + [[secondHalfBackLayerView layer] setOpaque:YES]; + [secondHalfBackLayerView.layer addAnimation:bottomAnim forKey:@"bottomDownFlip"]; + [self bringSubviewToFront:secondHalfBackLayerView]; +} + +-(void)arrangeBottomFirstAnimation { + CATransform3D skewedIdentityTransform = CATransform3DIdentity; + float zDistance = 1000.000000; + skewedIdentityTransform.m34 = 1.0 / -zDistance; + float x,y,z; + if (flipState == kFlipBottomTop || flipState == kFlipTopBottom) { + + x = 1.f; + y = 0.f; + z = 0.f; + }else{ + + x = 0.f; + y = 1.f; + z = 0.f; + } + + + CABasicAnimation *bottomAnim = [CABasicAnimation animationWithKeyPath:@"transform"]; + bottomAnim.beginTime = CACurrentMediaTime(); + bottomAnim.duration = 0.5; + bottomAnim.fromValue = [NSValue valueWithCATransform3D:skewedIdentityTransform]; + + switch (flipState) { + case kFlipBottomTop: + bottomAnim.toValue = [NSValue valueWithCATransform3D:CATransform3DRotate(skewedIdentityTransform, M_PI_2, x, y, z)]; + break; + case kFlipTopBottom: + bottomAnim.toValue = [NSValue valueWithCATransform3D:CATransform3DRotate(skewedIdentityTransform, M_PI_2, x, y, z)]; + break; + case kFlipLeftRight: + bottomAnim.toValue = [NSValue valueWithCATransform3D:CATransform3DRotate(skewedIdentityTransform, -M_PI_2, x, y, z)]; + break; + case kFlipRightLeft: + bottomAnim.toValue = [NSValue valueWithCATransform3D:CATransform3DRotate(skewedIdentityTransform, -M_PI_2, x, y, z)]; + break; + default: + break; + } + + bottomAnim.delegate = nil; + bottomAnim.removedOnCompletion = NO; + bottomAnim.fillMode = kCAFillModeForwards; + + + + bottomAnim.timingFunction = [CAMediaTimingFunction functionWithControlPoints:0.70 :0.00 :1.00 :1.00]; + [[secondHalfBackLayerView layer] setOpacity:1]; [[secondHalfBackLayerView layer] setOpaque:YES]; [secondHalfBackLayerView.layer addAnimation:bottomAnim forKey:@"bottomDownFlip"]; + [self bringSubviewToFront:secondHalfBackLayerView]; + + + + CABasicAnimation *topAnim = [CABasicAnimation animationWithKeyPath:@"transform"]; + topAnim.beginTime = bottomAnim.beginTime + bottomAnim.duration; + topAnim.duration = bottomAnim.duration; + topAnim.toValue = [NSValue valueWithCATransform3D:skewedIdentityTransform]; + + switch (flipState) { + case kFlipBottomTop: + topAnim.fromValue = [NSValue valueWithCATransform3D:CATransform3DRotate(skewedIdentityTransform, -M_PI_2, x, y, z)]; + break; + case kFlipTopBottom: + topAnim.fromValue = [NSValue valueWithCATransform3D:CATransform3DRotate(skewedIdentityTransform, -M_PI_2, x, y, z)]; + break; + case kFlipLeftRight: + topAnim.fromValue = [NSValue valueWithCATransform3D:CATransform3DRotate(skewedIdentityTransform, M_PI_2, x, y, z)]; + break; + case kFlipRightLeft: + topAnim.fromValue = [NSValue valueWithCATransform3D:CATransform3DRotate(skewedIdentityTransform, M_PI_2, x, y, z)]; + break; + default: + break; + } + + topAnim.delegate = self; + topAnim.removedOnCompletion = NO; + //topAnim.fillMode = kCAFillModeForwards; + topAnim.fillMode = kCAFillModeBoth; + + + topAnim.timingFunction = [CAMediaTimingFunction functionWithControlPoints:0.30 :1.00 :1.00 :1.00]; + [[firstHalfFrontLayerView layer] setOpacity:1]; + [[firstHalfFrontLayerView layer] setOpaque:YES]; + [firstHalfFrontLayerView.layer addAnimation:topAnim forKey:@"topDownFlip"]; + + [self bringSubviewToFront:firstHalfFrontLayerView]; + + } + -(void)checkFlipDirectionState { switch (flipState) { case kFlipBottomTop: { - horizontal = NO; [self animateView]; previousFlipState = kFlipBottomTop; - flipState = kFlipTopBottom; + flipState = kFlipStop; } break; case kFlipTopBottom: { - horizontal = NO; - [secondHalfBackLayerView.superview bringSubviewToFront:secondHalfBackLayerView]; + [self animateView]; previousFlipState = kFlipTopBottom; flipState = kFlipStop; } break; case kFlipLeftRight: { - horizontal = YES; [self animateView]; previousFlipState = kFlipLeftRight; - flipState = kFlipRightLeft; + flipState = kFlipStop; } break; case kFlipRightLeft: { - horizontal = YES; - [secondHalfBackLayerView.superview bringSubviewToFront:secondHalfBackLayerView]; + [self animateView]; previousFlipState = kFlipRightLeft; flipState = kFlipStop; } @@ -286,11 +430,27 @@ -(void)checkFlipDirectionState { CGColorRef color = frontLayerView.layer.backgroundColor; frontLayerView.layer.backgroundColor = backLayerView.layer.backgroundColor; backLayerView.layer.backgroundColor = color; - if (horizontal == NO) { - flipState = kFlipLeftRight; - }else{ - flipState = kFlipBottomTop; + + + + switch (previousFlipState) { + case kFlipBottomTop: + flipState = kFlipRightLeft; + break; + case kFlipTopBottom: + flipState = kFlipLeftRight; + break; + case kFlipLeftRight: + flipState = kFlipBottomTop; + break; + case kFlipRightLeft: + flipState = kFlipTopBottom; + break; + default: + break; } + + UIColor *backColor = [colorsArray objectAtIndex:0]; [self moveObjectsInArray]; backLayerView.layer.backgroundColor = backColor.CGColor; @@ -310,21 +470,68 @@ -(void)checkFlipDirectionState { } } +-(BOOL)isAnimating { + return animationCount_ > 0; +} + + -(void)stopAnimating{ - flipState = kFlipStopAnimating; - isAnimating = NO; - [self checkFlipDirectionState]; + if(![NSThread isMainThread]) + { + [self performSelectorOnMainThread:@selector(stopAnimating) withObject:nil waitUntilDone:NO]; + return; + } + if(animationCount_==1){ + animationCount_ = 0; + flipState = kFlipStopAnimating; + [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(checkFlipDirectionState) object:nil]; + + [self checkFlipDirectionState]; + + } + else { + animationCount_--; + } + } -(void)startAnimating{ - flipState = kFlipBottomTop; - isAnimating = YES; + if(![NSThread isMainThread]) + { + [self performSelectorOnMainThread:@selector(startAnimating) withObject:nil waitUntilDone:NO]; + return; + } + if (animationCount_<1) { + animationCount_ = 1; + flipState = kFlipTopBottom; + [self checkFlipDirectionState]; + } + else { + animationCount_++; + } +} + + +-(void)allStop { + if(![NSThread isMainThread]) + { + [self performSelectorOnMainThread:@selector(allStop) withObject:nil waitUntilDone:NO]; + return; + } + if(![self isAnimating]) + return; + animationCount_ = 0; + flipState = kFlipStopAnimating; + [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(checkFlipDirectionState) object:nil]; + [self checkFlipDirectionState]; + } + #pragma mark - CAAnimation delegate callbacks - (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag; { - [self checkFlipDirectionState]; + [self performSelectorOnMainThread:@selector(checkFlipDirectionState) withObject:nil waitUntilDone:NO]; }