// // LoadingView.m // LoadingView // // Created by Matt Gallagher on 12/04/09. // Copyright Matt Gallagher 2009. All rights reserved. // // Permission is given to use this source code file without charge in any // project, commercial or otherwise, entirely at your risk, with the condition // that any redistribution (in part or whole) of source code must retain // this copyright and permission notice. Attribution in compiled projects is // appreciated but not required. // // Modifications by Shazron Abdullah, Nitobi Software Inc. // #import "LoadingView.h" #import // // NewPathWithRoundRect // // Creates a CGPathRect with a round rect of the given radius. // CGPathRef NewPathWithRoundRect(CGRect rect, CGFloat cornerRadius) { // // Create the boundary path // CGMutablePathRef path = CGPathCreateMutable(); CGPathMoveToPoint(path, NULL, rect.origin.x, rect.origin.y + rect.size.height - cornerRadius); // Top left corner CGPathAddArcToPoint(path, NULL, rect.origin.x, rect.origin.y, rect.origin.x + rect.size.width, rect.origin.y, cornerRadius); // Top right corner CGPathAddArcToPoint(path, NULL, rect.origin.x + rect.size.width, rect.origin.y, rect.origin.x + rect.size.width, rect.origin.y + rect.size.height, cornerRadius); // Bottom right corner CGPathAddArcToPoint(path, NULL, rect.origin.x + rect.size.width, rect.origin.y + rect.size.height, rect.origin.x, rect.origin.y + rect.size.height, cornerRadius); // Bottom left corner CGPathAddArcToPoint(path, NULL, rect.origin.x, rect.origin.y + rect.size.height, rect.origin.x, rect.origin.y, cornerRadius); // Close the path at the rounded rect CGPathCloseSubpath(path); return path; } // // NewPathWithRect // // Creates a CGPathRect // CGPathRef NewPathWithRect(CGRect rect) { // // Create the boundary path // CGMutablePathRef path = CGPathCreateMutable(); // start at origin CGPathMoveToPoint (path, NULL, CGRectGetMinX(rect), CGRectGetMinY(rect)); // add bottom edge CGPathAddLineToPoint (path, NULL, CGRectGetMaxX(rect), CGRectGetMinY(rect)); // add right edge CGPathAddLineToPoint (path, NULL, CGRectGetMaxX(rect), CGRectGetMaxY(rect)); // add top edge CGPathAddLineToPoint (path, NULL, CGRectGetMinX(rect), CGRectGetMaxY(rect)); // add left edge and close CGPathCloseSubpath (path); return path; } @implementation LoadingView @synthesize boxLength; @synthesize strokeOpacity; @synthesize backgroundOpacity; @synthesize strokeColor; @synthesize minDuration; @synthesize timestamp; @synthesize fullScreen; @synthesize textLabel; @synthesize bounceAnimation; + (CGFloat) defaultStrokeOpacity { return 0.65; } + (CGFloat) defaultBackgroundOpacity { return 0.9; } + (CGFloat) defaultBoxLength { return 150.0; } + (UIColor*) defaultStrokeColor { return [UIColor whiteColor]; } + (NSString*) defaultLabelText { return NSLocalizedString(@"Loading…", nil); } // // loadingViewInView: // // Constructor for this view. Creates and adds a loading view for covering the // provided aSuperview. // // Parameters: // aSuperview - the superview that will be covered by the loading view // // returns the constructed view, already added as a subview of the aSuperview // (and hence retained by the superview) // + (id)loadingViewInView:(UIView *)aSuperview strokeOpacity:(CGFloat)strokeOpacity backgroundOpacity:(CGFloat)backgroundOpacity strokeColor:(UIColor*)strokeColor fullScreen:(BOOL)fullScreen labelText:(NSString*)labelText bounceAnimation:(BOOL)bounceAnimation boxLength:(CGFloat)boxLength { LoadingView *loadingView = [[[LoadingView alloc] initWithFrame:[aSuperview bounds]] autorelease]; if (!loadingView) { return nil; } loadingView.boxLength = boxLength; loadingView.strokeOpacity = strokeOpacity; loadingView.backgroundOpacity = backgroundOpacity; loadingView.strokeColor = strokeColor; loadingView.fullScreen = fullScreen; loadingView.bounceAnimation = bounceAnimation; loadingView.opaque = NO; loadingView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; [aSuperview addSubview:loadingView]; const CGFloat DEFAULT_LABEL_WIDTH = 280.0; const CGFloat DEFAULT_LABEL_HEIGHT = 50.0; CGRect labelFrame = CGRectMake(0, 0, DEFAULT_LABEL_WIDTH, DEFAULT_LABEL_HEIGHT); loadingView.textLabel = [[[UILabel alloc] initWithFrame:labelFrame] autorelease]; loadingView.textLabel.text = labelText; loadingView.textLabel.textColor = strokeColor; loadingView.textLabel.backgroundColor = [UIColor clearColor]; loadingView.textLabel.textAlignment = UITextAlignmentCenter; loadingView.textLabel.font = [UIFont boldSystemFontOfSize:[UIFont labelFontSize]]; loadingView.textLabel.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin; [loadingView addSubview:loadingView.textLabel]; UIActivityIndicatorView *activityIndicatorView = [[[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge] autorelease]; [loadingView addSubview:activityIndicatorView]; activityIndicatorView.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin; [activityIndicatorView startAnimating]; CGFloat totalHeight = loadingView.textLabel.frame.size.height + activityIndicatorView.frame.size.height; labelFrame.origin.x = floor(0.5 * (loadingView.frame.size.width - DEFAULT_LABEL_WIDTH)); labelFrame.origin.y = floor(0.5 * (loadingView.frame.size.height - totalHeight)); loadingView.textLabel.frame = labelFrame; CGRect activityIndicatorRect = activityIndicatorView.frame; activityIndicatorRect.origin.x = 0.5 * (loadingView.frame.size.width - activityIndicatorRect.size.width); activityIndicatorRect.origin.y = loadingView.textLabel.frame.origin.y + loadingView.textLabel.frame.size.height; activityIndicatorView.frame = activityIndicatorRect; // Set up the fade-in animation if (!loadingView.bounceAnimation) { CATransition *animation = [CATransition animation]; [animation setType:kCATransitionFade]; [[aSuperview layer] addAnimation:animation forKey:@"layerAnimation"]; } else { CALayer *viewLayer = [loadingView layer]; CAKeyframeAnimation* animation = [CAKeyframeAnimation animationWithKeyPath:@"transform.scale"]; animation.duration = 0.5; animation.values = [NSArray arrayWithObjects: [NSNumber numberWithFloat:0.6], [NSNumber numberWithFloat:.7], [NSNumber numberWithFloat:1.1], [NSNumber numberWithFloat:.9], [NSNumber numberWithFloat:1], nil]; animation.keyTimes = [NSArray arrayWithObjects: [NSNumber numberWithFloat:0.0], [NSNumber numberWithFloat:0.4], [NSNumber numberWithFloat:0.5], [NSNumber numberWithFloat:0.7], [NSNumber numberWithFloat:1.0], nil]; [viewLayer addAnimation:animation forKey:@"transform.scale"]; } loadingView.timestamp = [NSDate date]; return loadingView; } + (id)loadingViewInView:(UIView *)aSuperview { return [self loadingViewInView:aSuperview strokeOpacity:[LoadingView defaultStrokeOpacity] backgroundOpacity:[LoadingView defaultBackgroundOpacity] strokeColor:[LoadingView defaultStrokeColor] fullScreen:NO labelText:[LoadingView defaultLabelText] bounceAnimation:NO boxLength:[LoadingView defaultBoxLength]]; } // // removeView // // Animates the view out from the superview. As the view is removed from the // superview, it will be released. // - (void)removeView { if (!self.bounceAnimation) { UIView *aSuperview = [self superview]; [super removeFromSuperview]; // Set up the animation CATransition *animation = [CATransition animation]; [animation setType:kCATransitionFade]; [[aSuperview layer] addAnimation:animation forKey:@"layerAnimation"]; } else { CALayer *viewLayer = [self layer]; CAKeyframeAnimation* animation = [CAKeyframeAnimation animationWithKeyPath:@"transform.scale"]; animation.duration = 0.5; animation.values = [NSArray arrayWithObjects: [NSNumber numberWithFloat:1], [NSNumber numberWithFloat:.7], [NSNumber numberWithFloat:.5], [NSNumber numberWithFloat:.3], [NSNumber numberWithFloat:.1], [NSNumber numberWithFloat:0], nil]; animation.keyTimes = [NSArray arrayWithObjects: [NSNumber numberWithFloat:0.0], [NSNumber numberWithFloat:0.4], [NSNumber numberWithFloat:0.5], [NSNumber numberWithFloat:0.7], [NSNumber numberWithFloat:0.8], [NSNumber numberWithFloat:1.0], nil]; [viewLayer addAnimation:animation forKey:@"transform.scale"]; } [super performSelector:@selector(removeFromSuperview) withObject:nil afterDelay:0.2]; } - (void)drawRect:(CGRect)rect { if (fullScreen) { CGPathRef roundRectPath = NewPathWithRect(rect); CGContextRef context = UIGraphicsGetCurrentContext(); CGContextSetRGBFillColor(context, 0, 0, 0, backgroundOpacity); CGContextAddPath(context, roundRectPath); CGContextFillPath(context); CGContextSetRGBStrokeColor(context, 1, 1, 1, strokeOpacity); CGContextAddPath(context, roundRectPath); CGContextStrokePath(context); CGPathRelease(roundRectPath); } else { const CGFloat RECT_PADDING = 8.0; rect = CGRectInset(rect, RECT_PADDING, RECT_PADDING); const CGFloat ROUND_RECT_CORNER_RADIUS = 5.0; rect.size.width = self.boxLength; rect.size.height = self.boxLength; rect.origin.x = (0.5 * self.frame.size.width) - (rect.size.width / 2); rect.origin.y = (0.5 * self.frame.size.height) - (rect.size.height / 2); CGPathRef roundRectPath = NewPathWithRoundRect(rect, ROUND_RECT_CORNER_RADIUS); CGContextRef context = UIGraphicsGetCurrentContext(); CGContextSetRGBFillColor(context, 0, 0, 0, backgroundOpacity); CGContextAddPath(context, roundRectPath); CGContextFillPath(context); CGContextSetRGBStrokeColor(context, 1, 1, 1, strokeOpacity); CGContextAddPath(context, roundRectPath); CGContextStrokePath(context); CGPathRelease(roundRectPath); } } // // dealloc // // Release instance memory. // - (void)dealloc { self.timestamp = nil; self.strokeColor = nil; self.textLabel = nil; [super dealloc]; } @end