diff --git a/MDFlickrActivityIndicatorView/MDFlickrActivityIndicatorView.h b/MDFlickrActivityIndicatorView/MDFlickrActivityIndicatorView.h index aff46d3..1939f21 100644 --- a/MDFlickrActivityIndicatorView/MDFlickrActivityIndicatorView.h +++ b/MDFlickrActivityIndicatorView/MDFlickrActivityIndicatorView.h @@ -8,11 +8,63 @@ #import +/*! + @class MDFlickrActivityIndicatorView + @abstract + An activity indicator view that mimicks Flickr loading animation, as + seen in Flickr's iOS app and website. Use it to show that a task is + in progress. + + You control when an activity indicator animates by calling the + startAnimating and stopAnimating methods. To automatically hide the + activity indicator when animation stops, set the hidesWhenStopped property + to YES. + */ @interface MDFlickrActivityIndicatorView : UIView -@property (nonatomic,readonly) BOOL isAnimating; +/*! + @property hidesWhenStopped + @abstract + A Boolean value that controls whether the receiver is hidden when the + animation is stopped. + @discussion + If the value of this property is YES (the default), the receiver sets its + hidden property (UIView) to YES when receiver is not animating. If the + hidesWhenStopped property is NO, the receiver is not hidden when animation + stops. + You stop an animating progress indicator with the stopAnimating method. + */ +@property (nonatomic, assign) BOOL hidesWhenStopped; +/*! + @method isAnimating + @abstract + Returns whether the receiver is animating. + @return + YES if the receiver is animating, otherwise NO. + */ +- (BOOL)isAnimating; + +/*! + @method startAnimating + @abstract + Starts the animation of the progress indicator. + @discussion + When the progress indicator is animated, the gear spins to indicate + indeterminate progress. The indicator is animated until stopAnimating is + called. + */ - (void)startAnimating; + +/*! + @method stopAnimating + @abstract + Stops the animation of the progress indicator. + @discussion + Call this method to stop the animation of the progress indicator started + with a call to startAnimating. When animating is stopped, the indicator is + hidden, unless hidesWhenStopped is NO. + */ - (void)stopAnimating; @end diff --git a/MDFlickrActivityIndicatorView/MDFlickrActivityIndicatorView.m b/MDFlickrActivityIndicatorView/MDFlickrActivityIndicatorView.m index a9ae575..08b6466 100644 --- a/MDFlickrActivityIndicatorView/MDFlickrActivityIndicatorView.m +++ b/MDFlickrActivityIndicatorView/MDFlickrActivityIndicatorView.m @@ -9,212 +9,240 @@ #import "MDFlickrActivityIndicatorView.h" #import "UIColor+MDFlickrActivityIndicatorView.h" +#pragma mark - +#pragma mark Internal category - MDFlickrActivityIndicatorView() + @interface MDFlickrActivityIndicatorView() -@property(nonatomic,assign) BOOL isAnimating; +{ + BOOL _isAnimating; +} +/*! + @property backgroundLayer + @abstract + Layer responsible for drawing the background rounded rectangle. + */ @property (nonatomic, strong) CALayer *backgroundLayer; + +/*! + @property pinkDotLayer + @abstract + Layer responsible for drawing the pink dot. + */ @property (nonatomic, strong) CAShapeLayer *pinkDotLayer; + +/*! + @property blueDotLayer + @abstract + Layer responsible for drawing the blue dot. + */ @property (nonatomic, strong) CAShapeLayer *blueDotLayer; +/*! + @property dotOnTop + @abstract + Weak reference to the top dot layer (either pinkDotLayer or blueDotLayer). + */ +@property (nonatomic, weak) CAShapeLayer *dotOnTop; + +/*! + @property moveLeftRight + @abstract + Animation for moving a dot from left to right. + */ @property (nonatomic, strong) CABasicAnimation *moveLeftRight; -@property (nonatomic, strong) CABasicAnimation *moveRightLeft; -@property (nonatomic, weak) CAShapeLayer *dotOnTop; +/*! + @property moveRightLeft + @abstract + Animation for moving a dot from right to left. + */ +@property (nonatomic, strong) CABasicAnimation *moveRightLeft; +/*! + @property timer + @abstract + Timer that is responsible for changing the Z-index of the pink/blue + dot layers. + */ @property (nonatomic, strong) NSTimer *timer; --(void)swapDots; +/*! + @method swapDots + @abstract + Swap the Z-index of pink/blue dot layers. Bring the background dot to + front, and vice versa. + */ +- (void)swapDots; + +- (void)createLayers; +- (void)createAnimations; @end +#pragma mark - +#pragma mark Implementation of MDFlickrActivityIndicatorView + @implementation MDFlickrActivityIndicatorView +@synthesize hidesWhenStopped = _hidesWhenStopped; + +#pragma mark Internal methods + +-(void)swapDots +{ + if (self.dotOnTop == self.pinkDotLayer) + self.dotOnTop = self.blueDotLayer; + else + self.dotOnTop = self.pinkDotLayer; + + [self.layer addSublayer:self.dotOnTop]; +} + +- (void)setUp +{ + self->_isAnimating = false; + self.hidden = self.hidesWhenStopped; + self.clipsToBounds = YES; +} + +- (void)createLayers +{ + self.pinkDotLayer = [CAShapeLayer layer]; + self.pinkDotLayer.fillColor = [[UIColor MD_flickrPinkColor] CGColor]; + self.blueDotLayer = [CAShapeLayer layer]; + self.blueDotLayer.fillColor = [[UIColor MD_flickrBlueColor] CGColor]; + + self.backgroundLayer = [CALayer layer]; + self.backgroundLayer.backgroundColor = [[UIColor MD_flickrGrayColor] CGColor]; + + self.dotOnTop = self.blueDotLayer; +} + +- (void)createAnimations +{ + self.moveLeftRight = [CABasicAnimation animationWithKeyPath:@"position"]; + self.moveLeftRight.duration = .6f; + self.moveLeftRight.repeatCount = HUGE_VALF; + self.moveLeftRight.autoreverses = YES; + self.moveLeftRight.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]; + self.moveLeftRight.removedOnCompletion = NO; + + self.moveRightLeft = [self.moveLeftRight copy]; + + [self.moveLeftRight setFromValue:[NSValue valueWithCGPoint:self.pinkDotLayer.position]]; + [self.moveLeftRight setToValue:[NSValue valueWithCGPoint:self.blueDotLayer.position]]; + + [self.moveRightLeft setFromValue:[NSValue valueWithCGPoint:self.blueDotLayer.position]]; + [self.moveRightLeft setToValue:[NSValue valueWithCGPoint:self.pinkDotLayer.position]]; +} + +#pragma mark Public methods + +- (id)init +{ + return [self initWithFrame:CGRectZero]; +} + +- (id)initWithCoder:(NSCoder *)aDecoder +{ + if (self = [super initWithCoder:aDecoder]) + { + [self setUp]; + } + return self; +} + - (id)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; if (self) { - self.isAnimating = false; - self.clipsToBounds = YES; - - + [self setUp]; } return self; } +-(void) setHidesWhenStopped:(BOOL)hidesWhenStopped +{ + _hidesWhenStopped = hidesWhenStopped; + self.hidden = self.isAnimating || hidesWhenStopped; +} + +- (BOOL)isAnimating +{ + return self->_isAnimating; +} + -(void)setFrame:(CGRect)frame { [super setFrame:frame]; - BOOL shouldAdd = self.pinkDotLayer == nil; - - if (self.pinkDotLayer == nil) - { - CAShapeLayer *shapeLayer = [CAShapeLayer layer]; - - shapeLayer.fillColor = [[UIColor MD_flickrPinkColor] CGColor]; - shapeLayer.lineWidth = 1.5f; - shapeLayer.lineJoin = kCALineJoinRound; - - self.pinkDotLayer = shapeLayer; - } - - if (self.blueDotLayer == nil) - { - CAShapeLayer *shapeLayer = [CAShapeLayer layer]; - - shapeLayer.fillColor = [[UIColor MD_flickrBlueColor] CGColor]; - self.blueDotLayer = shapeLayer; - } + [self createLayers]; - CGRect dotBounds = CGRectMake(0, 0, self.frame.size.height/2.0f, self.frame.size.height/2.0f); - self.pinkDotLayer.path = [[UIBezierPath bezierPathWithOvalInRect:dotBounds] CGPath]; - self.blueDotLayer.path = [[UIBezierPath bezierPathWithOvalInRect:dotBounds] CGPath]; - + // Set background layer frame and bounds + self.backgroundLayer.frame = CGRectMake(0, 0, + frame.size.width, + frame.size.height); + self.backgroundLayer.bounds = self.backgroundLayer.frame; + self.backgroundLayer.cornerRadius = frame.size.height/4; + + // Set dot layer paths and bounds + CGRect dotBounds = CGRectMake(0, 0, + self.frame.size.height/2.0f, + self.frame.size.height/2.0f); + CGPathRef dotPath = [[UIBezierPath bezierPathWithOvalInRect:dotBounds] CGPath]; + self.pinkDotLayer.path = dotPath; + self.blueDotLayer.path = dotPath; self.pinkDotLayer.bounds = dotBounds; self.blueDotLayer.bounds = dotBounds; - CGRect dotFrame = CGRectMake( + + // Create dot layer frames at the center of this view + CGRect pinkDotFrame = CGRectMake( frame.size.width/2.0f - dotBounds.size.width/2.0f, frame.size.height/2.0f - dotBounds.size.height/2.0f, dotBounds.size.width, dotBounds.size.height); + CGRect blueDotFrame = pinkDotFrame; - CGRect pinkDotFrame = dotFrame; - CGRect blueDotFrame = dotFrame; - + // Shift pink to the left and blue to the right pinkDotFrame.origin.x += (dotBounds.size.width/2.0 + dotBounds.size.width/10.f); - self.pinkDotLayer.frame = pinkDotFrame; - blueDotFrame.origin.x -= (dotBounds.size.width/2.0 + dotBounds.size.width/10.0f); - self.blueDotLayer.frame = blueDotFrame; - - - - if (self.backgroundLayer == nil) - { - CALayer *lay = [[CALayer alloc] init]; - lay.backgroundColor = [[UIColor MD_flickrGrayColor] CGColor]; - lay.cornerRadius = frame.size.height/4; - self.backgroundLayer = lay; - } - - self.backgroundLayer.frame = CGRectMake(0, 0, frame.size.width, frame.size.height); - self.backgroundLayer.bounds = self.backgroundLayer.frame; + self.pinkDotLayer.frame = pinkDotFrame; + self.blueDotLayer.frame = blueDotFrame; - if (shouldAdd) - { - [self.layer addSublayer:self.backgroundLayer]; - [self.layer addSublayer:self.pinkDotLayer]; - [self.layer addSublayer:self.blueDotLayer]; - - self.dotOnTop = self.blueDotLayer; - } + [self createAnimations]; -// self.layer.backgroundColor = [[UIColor orangeColor] CGColor]; -// self.layer.cornerRadius = 20.0; -// self.layer.frame = CGRectMake(0, 0, frame.size.width, frame.size.height); -} - - -- (id)init -{ - return [self initWithFrame:CGRectZero]; + [self.layer addSublayer:self.backgroundLayer]; + [self.layer addSublayer:self.pinkDotLayer]; + [self.layer addSublayer:self.blueDotLayer]; } - - (void)startAnimating; { - self.isAnimating = YES; - - if (self.moveLeftRight == nil) - { - self.moveLeftRight = [CABasicAnimation animationWithKeyPath:@"position"]; - // [self.movePinkAnimation setDelegate:self]; - [self.moveLeftRight setDuration:.6f]; - [self.moveLeftRight setRepeatCount:HUGE_VALF]; - self.moveLeftRight.autoreverses = YES; - } - - self.moveLeftRight.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]; - + self->_isAnimating = YES; + self.hidden = NO; - [self.moveLeftRight setFromValue:[NSValue valueWithCGPoint:self.pinkDotLayer.position]]; - [self.moveLeftRight setToValue:[NSValue valueWithCGPoint:self.blueDotLayer.position]]; - self.moveLeftRight.removedOnCompletion = NO; - - // copy and reverse - - self.moveRightLeft = [self.moveLeftRight copy]; - [self.moveRightLeft setFromValue:[NSValue valueWithCGPoint:self.blueDotLayer.position]]; - [self.moveRightLeft setToValue:[NSValue valueWithCGPoint:self.pinkDotLayer.position]]; - - // set delegates - -// self.moveLeftRight.delegate = self; - self.moveRightLeft.delegate = self; - - self.timer = [NSTimer scheduledTimerWithTimeInterval:self.moveLeftRight.duration target:self selector:@selector(swapDots) userInfo:NULL repeats:YES]; - + self.timer = [NSTimer scheduledTimerWithTimeInterval:self.moveLeftRight.duration + target:self + selector:@selector(swapDots) + userInfo:NULL + repeats:YES]; [self.pinkDotLayer addAnimation:self.moveLeftRight forKey:@"position"]; [self.blueDotLayer addAnimation:self.moveRightLeft forKey:@"position"]; - - // [self.pathLayer addAnimation:pathAnimation forKey:@"strokeEnd"]; } --(void)swapDots -{ - if (self.dotOnTop == self.pinkDotLayer) - self.dotOnTop = self.blueDotLayer; - else - self.dotOnTop = self.pinkDotLayer; - - [self.layer addSublayer:self.dotOnTop]; -} - -//- (void)animationDidStop:(CAAnimation *)theAnimation finished:(BOOL)flag -//{ -// if (flag) -// { -// [CATransaction begin]; -// [CATransaction setValue:(id)kCFBooleanTrue -// forKey:kCATransactionDisableActions]; -// -// CGPoint oldBluePos = self.blueDotLayer.position; -// [self.blueDotLayer setPosition:self.pinkDotLayer.position]; -// [self.pinkDotLayer setPosition:oldBluePos]; -// -// -// // ended rightToLeft -// CALayer *prevTopLayer = self.dotOnTop; -// -// if (self.dotOnTop == self.pinkDotLayer) -// self.dotOnTop = self.blueDotLayer; -// else -// self.dotOnTop = self.pinkDotLayer; -// -// [self.layer addSublayer:self.dotOnTop]; -// [prevTopLayer addAnimation:self.moveLeftRight forKey:@"position"]; -// [self.dotOnTop addAnimation:self.moveRightLeft forKey:@"position"]; -// -// -// [CATransaction commit]; -// -// } -//} - - (void)stopAnimating { - self.isAnimating = NO; + self->_isAnimating = NO; + self.hidden = self.hidesWhenStopped; + [self.timer invalidate]; self.timer = nil; + [self.pinkDotLayer removeAllAnimations]; [self.blueDotLayer removeAllAnimations]; } -- (UIBezierPath *)samplePath -{ - return [UIBezierPath bezierPathWithRect:CGRectMake(0.0f, 0.0f, 100.0f, 100.0f)]; -} - @end diff --git a/MDFlickrActivityIndicatorView/UIColor+MDFlickrActivityIndicatorView.h b/MDFlickrActivityIndicatorView/UIColor+MDFlickrActivityIndicatorView.h index 5fde7e5..3ecff5f 100644 --- a/MDFlickrActivityIndicatorView/UIColor+MDFlickrActivityIndicatorView.h +++ b/MDFlickrActivityIndicatorView/UIColor+MDFlickrActivityIndicatorView.h @@ -8,10 +8,29 @@ #import +/*! + @category Flickr (UIColor) + @abstract + Adds static Flickr color accessors to UIColor. + */ @interface UIColor (Flickr) +/*! + @property MD_flickrBlueColor + @brief Flickr blue color + */ +(UIColor*)MD_flickrBlueColor; + +/*! + @property MD_flickrGrayColor + @brief Gray color for background behind the pink and bue dots. + */ +(UIColor*)MD_flickrGrayColor; + +/*! + @property MD_flickrPinkColor + @brief Flickr pink color + */ +(UIColor*)MD_flickrPinkColor; @end diff --git a/MDFlickrActivityIndicatorView/UIColor+MDFlickrActivityIndicatorView.m b/MDFlickrActivityIndicatorView/UIColor+MDFlickrActivityIndicatorView.m index 76dba21..f728609 100644 --- a/MDFlickrActivityIndicatorView/UIColor+MDFlickrActivityIndicatorView.m +++ b/MDFlickrActivityIndicatorView/UIColor+MDFlickrActivityIndicatorView.m @@ -12,17 +12,17 @@ @implementation UIColor (Flickr) -+(UIColor*)MD_flickrBlueColor ++ (UIColor*)MD_flickrBlueColor { return UIColorFromRGB(0x1065CB); } -+(UIColor*)MD_flickrPinkColor ++ (UIColor*)MD_flickrPinkColor { return UIColorFromRGB(0xFB007C); } -+(UIColor*)MD_flickrGrayColor ++ (UIColor*)MD_flickrGrayColor { return UIColorFromRGB(0x2E302D); } diff --git a/MDFlickrActivityIndicatorViewDemo/MDFlickrActivityIndicatorViewDemo/Base.lproj/Main.storyboard b/MDFlickrActivityIndicatorViewDemo/MDFlickrActivityIndicatorViewDemo/Base.lproj/Main.storyboard index 5f8a284..7e8f296 100644 --- a/MDFlickrActivityIndicatorViewDemo/MDFlickrActivityIndicatorViewDemo/Base.lproj/Main.storyboard +++ b/MDFlickrActivityIndicatorViewDemo/MDFlickrActivityIndicatorViewDemo/Base.lproj/Main.storyboard @@ -14,12 +14,12 @@ - + + @@ -47,15 +62,16 @@ - - + + + - + diff --git a/MDFlickrActivityIndicatorViewDemo/MDFlickrActivityIndicatorViewDemo/MDFlickrActivityIndicatorViewDemo-Info.plist b/MDFlickrActivityIndicatorViewDemo/MDFlickrActivityIndicatorViewDemo/MDFlickrActivityIndicatorViewDemo-Info.plist index 1bf3927..22f3ac7 100644 --- a/MDFlickrActivityIndicatorViewDemo/MDFlickrActivityIndicatorViewDemo/MDFlickrActivityIndicatorViewDemo-Info.plist +++ b/MDFlickrActivityIndicatorViewDemo/MDFlickrActivityIndicatorViewDemo/MDFlickrActivityIndicatorViewDemo-Info.plist @@ -9,7 +9,7 @@ CFBundleExecutable ${EXECUTABLE_NAME} CFBundleIdentifier - lv.montadigital.${PRODUCT_NAME:rfc1034identifier} + lv.openid.test.${PRODUCT_NAME:rfc1034identifier} CFBundleInfoDictionaryVersion 6.0 CFBundleName diff --git a/MDFlickrActivityIndicatorViewDemo/MDFlickrActivityIndicatorViewDemo/MDViewController.m b/MDFlickrActivityIndicatorViewDemo/MDFlickrActivityIndicatorViewDemo/MDViewController.m index fcea618..d89697b 100644 --- a/MDFlickrActivityIndicatorViewDemo/MDFlickrActivityIndicatorViewDemo/MDViewController.m +++ b/MDFlickrActivityIndicatorViewDemo/MDFlickrActivityIndicatorViewDemo/MDViewController.m @@ -14,6 +14,21 @@ @interface MDViewController () @implementation MDViewController +- (IBAction)toggleHideWhenStopped:(id)sender { + UIButton *btnSender = (UIButton*)sender; + + if ([[btnSender titleForState:UIControlStateNormal] isEqualToString:@"Hide when stopped"]) + { + self.spinner.hidesWhenStopped = YES; + [btnSender setTitle:@"Show when stopped" forState:UIControlStateNormal]; + } + else + { + self.spinner.hidesWhenStopped = NO; + [btnSender setTitle:@"Hide when stopped" forState:UIControlStateNormal]; + } +} + - (IBAction)animationButtonTapped:(id)sender { if([self.spinner isAnimating]) { diff --git a/images/MDFlickrActivityIndicatorView_Preview.png b/images/MDFlickrActivityIndicatorView_Preview.png index 7f2c63f..fa4aa61 100644 Binary files a/images/MDFlickrActivityIndicatorView_Preview.png and b/images/MDFlickrActivityIndicatorView_Preview.png differ