You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
542 lines
18 KiB
542 lines
18 KiB
//
|
|
// UINavigationController+M13ProgressViewBar.m
|
|
// M13ProgressView
|
|
//
|
|
|
|
#import "UINavigationController+M13ProgressViewBar.h"
|
|
#import "UIApplication+M13ProgressSuite.h"
|
|
#import <objc/runtime.h>
|
|
|
|
//Keys to set properties since one cannot define properties in a category.
|
|
static char oldTitleKey;
|
|
static char displayLinkKey;
|
|
static char animationFromKey;
|
|
static char animationToKey;
|
|
static char animationStartTimeKey;
|
|
static char progressKey;
|
|
static char progressViewKey;
|
|
static char indeterminateKey;
|
|
static char indeterminateLayerKey;
|
|
static char isShowingProgressKey;
|
|
static char primaryColorKey;
|
|
static char secondaryColorKey;
|
|
static char backgroundColorKey;
|
|
static char backgroundViewKey;
|
|
|
|
@implementation UINavigationController (M13ProgressViewBar)
|
|
|
|
#pragma mark Title
|
|
|
|
- (void)setProgressTitle:(NSString *)title
|
|
{
|
|
//Change the title on screen.
|
|
NSString *oldTitle = [self getOldTitle];
|
|
if (oldTitle == nil) {
|
|
//We haven't changed the navigation bar yet. So store the original before changing it.
|
|
[self setOldTitle:self.visibleViewController.navigationItem.title];
|
|
}
|
|
|
|
if (title != nil) {
|
|
self.visibleViewController.navigationItem.title = title;
|
|
} else {
|
|
self.visibleViewController.navigationItem.title = oldTitle;
|
|
[self setOldTitle:nil];
|
|
}
|
|
}
|
|
|
|
#pragma mark Progress
|
|
|
|
- (void)setProgress:(CGFloat)progress animated:(BOOL)animated
|
|
{
|
|
CADisplayLink *displayLink = [self getDisplayLink];
|
|
if (animated == NO) {
|
|
if (displayLink) {
|
|
//Kill running animations
|
|
[displayLink invalidate];
|
|
[self setDisplayLink:nil];
|
|
}
|
|
[self setProgress:progress];
|
|
} else {
|
|
[self setAnimationStartTime:CACurrentMediaTime()];
|
|
[self setAnimationFromValue:[self getProgress]];
|
|
[self setAnimationToValue:progress];
|
|
if (!displayLink) {
|
|
//Create and setup the display link
|
|
[displayLink invalidate];
|
|
displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(animateProgress:)];
|
|
[self setDisplayLink:displayLink];
|
|
[displayLink addToRunLoop:NSRunLoop.mainRunLoop forMode:NSRunLoopCommonModes];
|
|
} /*else {
|
|
//Reuse the current display link
|
|
}*/
|
|
}
|
|
}
|
|
|
|
- (void)animateProgress:(CADisplayLink *)displayLink
|
|
{
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
CGFloat dt = (displayLink.timestamp - [self getAnimationStartTime]) / [self getAnimationDuration];
|
|
if (dt >= 1.0) {
|
|
//Order is important! Otherwise concurrency will cause errors, because setProgress: will detect an animation in progress and try to stop it by itself. Once over one, set to actual progress amount. Animation is over.
|
|
[displayLink invalidate];
|
|
[self setDisplayLink:nil];
|
|
[self setProgress:[self getAnimationToValue]];
|
|
return;
|
|
}
|
|
|
|
//Set progress
|
|
[self setProgress:[self getAnimationFromValue] + dt * ([self getAnimationToValue] - [self getAnimationFromValue])];
|
|
|
|
});
|
|
}
|
|
|
|
- (void)finishProgress
|
|
{
|
|
UIView *progressView = [self getProgressView];
|
|
UIView *backgroundView = [self getBackgroundView];
|
|
if (progressView && backgroundView) {
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
[UIView animateWithDuration:0.1 animations:^{
|
|
CGRect progressFrame = progressView.frame;
|
|
progressFrame.size.width = self.navigationBar.frame.size.width;
|
|
progressView.frame = progressFrame;
|
|
} completion:^(BOOL finished) {
|
|
[UIView animateWithDuration:0.5 animations:^{
|
|
progressView.alpha = 0;
|
|
backgroundView.alpha = 0;
|
|
} completion:^(BOOL finished) {
|
|
[progressView removeFromSuperview];
|
|
[backgroundView removeFromSuperview];
|
|
backgroundView.alpha = 1;
|
|
progressView.alpha = 1;
|
|
[self setTitle:nil];
|
|
[self setIsShowingProgressBar:NO];
|
|
}];
|
|
}];
|
|
});
|
|
}
|
|
}
|
|
|
|
- (void)cancelProgress
|
|
{
|
|
UIView *progressView = [self getProgressView];
|
|
UIView *backgroundView = [self getBackgroundView];
|
|
|
|
if (progressView && backgroundView) {
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
[UIView animateWithDuration:0.5 animations:^{
|
|
progressView.alpha = 0;
|
|
backgroundView.alpha = 0;
|
|
} completion:^(BOOL finished) {
|
|
[progressView removeFromSuperview];
|
|
[backgroundView removeFromSuperview];
|
|
progressView.alpha = 1;
|
|
backgroundView.alpha = 1;
|
|
[self setTitle:nil];
|
|
[self setIsShowingProgressBar:NO];
|
|
}];
|
|
});
|
|
}
|
|
}
|
|
|
|
#pragma mark Orientation
|
|
|
|
- (UIInterfaceOrientation)currentDeviceOrientation
|
|
{
|
|
UIInterfaceOrientation orientation;
|
|
|
|
if ([UIApplication isM13AppExtension]) {
|
|
if ([UIScreen mainScreen].bounds.size.width < [UIScreen mainScreen].bounds.size.height) {
|
|
orientation = UIInterfaceOrientationPortrait;
|
|
} else {
|
|
orientation = UIInterfaceOrientationLandscapeLeft;
|
|
}
|
|
} else {
|
|
orientation = [UIApplication safeM13SharedApplication].statusBarOrientation;
|
|
}
|
|
|
|
return orientation;
|
|
}
|
|
|
|
#pragma mark Drawing
|
|
|
|
- (void)showProgress
|
|
{
|
|
UIView *progressView = [self getProgressView];
|
|
UIView *backgroundView = [self getBackgroundView];
|
|
|
|
[UIView animateWithDuration:.1 animations:^{
|
|
progressView.alpha = 1;
|
|
backgroundView.alpha = 1;
|
|
}];
|
|
|
|
[self setIsShowingProgressBar:YES];
|
|
}
|
|
|
|
- (void)updateProgress
|
|
{
|
|
[self updateProgressWithInterfaceOrientation:[self currentDeviceOrientation]];
|
|
}
|
|
|
|
- (void)updateProgressWithInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
|
|
{
|
|
//Create the progress view if it doesn't exist
|
|
UIView *progressView = [self getProgressView];
|
|
if(!progressView)
|
|
{
|
|
progressView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 0, 2.5)];
|
|
progressView.clipsToBounds = YES;
|
|
[self setProgressView:progressView];
|
|
}
|
|
|
|
if ([self getPrimaryColor]) {
|
|
progressView.backgroundColor = [self getPrimaryColor];
|
|
} else {
|
|
progressView.backgroundColor = self.navigationBar.tintColor;
|
|
}
|
|
|
|
//Create background view if it doesn't exist
|
|
UIView *backgroundView = [self getBackgroundView];
|
|
if (!backgroundView)
|
|
{
|
|
backgroundView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 0, 2.5)];
|
|
backgroundView.clipsToBounds = YES;
|
|
[self setBackgroundView:backgroundView];
|
|
}
|
|
|
|
if ([self getBackgroundColor]) {
|
|
backgroundView.backgroundColor = [self getBackgroundColor];
|
|
} else {
|
|
backgroundView.backgroundColor = [UIColor clearColor];
|
|
}
|
|
|
|
//Calculate the frame of the navigation bar, based off the orientation.
|
|
UIView *topView = self.topViewController.view;
|
|
CGSize screenSize;
|
|
if (topView) {
|
|
screenSize = topView.bounds.size;
|
|
} else {
|
|
screenSize = [UIScreen mainScreen].bounds.size;
|
|
}
|
|
CGFloat width = 0.0;
|
|
CGFloat height = 0.0;
|
|
//Calculate the width of the screen
|
|
if (UIInterfaceOrientationIsLandscape(interfaceOrientation)) {
|
|
//Use the maximum value
|
|
width = MAX(screenSize.width, screenSize.height);
|
|
if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPhone) {
|
|
height = 32.0; //Hate hardcoding values, but autolayout doesn't work, and cant retreive the new height until after the animation completes.
|
|
} else {
|
|
height = 44.0; //Hate hardcoding values, but autolayout doesn't work, and cant retreive the new height until after the animation completes.
|
|
}
|
|
} else {
|
|
//Use the minimum value
|
|
width = MIN(screenSize.width, screenSize.height);
|
|
height = 44.0; //Hate hardcoding values, but autolayout doesn't work, and cant retreive the new height until after the animation completes.
|
|
}
|
|
|
|
//Check if the progress view is in its superview and if we are showing the bar.
|
|
if (progressView.superview == nil && [self isShowingProgressBar]) {
|
|
[self.navigationBar addSubview:backgroundView];
|
|
[self.navigationBar addSubview:progressView];
|
|
}
|
|
|
|
//Layout
|
|
if (![self getIndeterminate]) {
|
|
//Calculate the width of the progress view;
|
|
float progressWidth = (float)width * (float)[self getProgress];
|
|
//Set the frame of the progress view
|
|
progressView.frame = CGRectMake(0, height - 2.5, progressWidth, 2.5);
|
|
} else {
|
|
//Calculate the width of the progress view
|
|
progressView.frame = CGRectMake(0, height - 2.5, width, 2.5);
|
|
}
|
|
backgroundView.frame = CGRectMake(0, height - 2.5, width, 2.5);
|
|
}
|
|
|
|
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
|
|
{
|
|
[self updateProgressWithInterfaceOrientation:toInterfaceOrientation];
|
|
[self drawIndeterminateWithInterfaceOrientation:toInterfaceOrientation];
|
|
}
|
|
|
|
- (void)drawIndeterminate
|
|
{
|
|
[self drawIndeterminateWithInterfaceOrientation:[self currentDeviceOrientation]];
|
|
}
|
|
|
|
- (void)drawIndeterminateWithInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
|
|
{
|
|
if ([self getIndeterminate]) {
|
|
//Get the indeterminate layer
|
|
CALayer *indeterminateLayer = [self getIndeterminateLayer];
|
|
if (!indeterminateLayer) {
|
|
//Create if needed
|
|
indeterminateLayer = [CALayer layer];
|
|
[self setIndeterminateLayer:indeterminateLayer];
|
|
}
|
|
|
|
//Calculate the frame of the navigation bar, based off the orientation.
|
|
CGSize screenSize = [UIScreen mainScreen].bounds.size;
|
|
CGFloat width = 0.0;
|
|
//Calculate the width of the screen
|
|
if (UIInterfaceOrientationIsLandscape(interfaceOrientation)) {
|
|
//Use the maximum value
|
|
width = MAX(screenSize.width, screenSize.height);
|
|
} else {
|
|
//Use the minimum value
|
|
width = MIN(screenSize.width, screenSize.height);
|
|
}
|
|
|
|
//Create the pattern image
|
|
CGFloat stripeWidth = 2.5;
|
|
//Start the image context
|
|
UIGraphicsBeginImageContextWithOptions(CGSizeMake(stripeWidth * 4.0, stripeWidth * 4.0), NO, [UIScreen mainScreen].scale);
|
|
//Fill the background
|
|
if ([self getPrimaryColor]) {
|
|
[[self getPrimaryColor] setFill];
|
|
} else {
|
|
[self.navigationBar.tintColor setFill];
|
|
}
|
|
UIBezierPath *fillPath = [UIBezierPath bezierPathWithRect:CGRectMake(0, 0, stripeWidth * 4.0, stripeWidth * 4.0)];
|
|
[fillPath fill];
|
|
//Draw the stripes
|
|
//Set the stripe color
|
|
if ([self getSecondaryColor]) {
|
|
[[self getSecondaryColor] setFill];
|
|
} else {
|
|
CGFloat red;
|
|
CGFloat green;
|
|
CGFloat blue;
|
|
CGFloat alpha;
|
|
[self.navigationBar.barTintColor getRed:&red green:&green blue:&blue alpha:&alpha];
|
|
//System set the tint color to a close to, but not non-zero value for each component.
|
|
if (alpha > .05) {
|
|
[self.navigationBar.barTintColor setFill];
|
|
} else {
|
|
[[UIColor whiteColor] setFill];
|
|
}
|
|
|
|
}
|
|
|
|
for (int i = 0; i < 4; i++) {
|
|
//Create the four inital points of the fill shape
|
|
CGPoint bottomLeft = CGPointMake(-(stripeWidth * 4.0), stripeWidth * 4.0);
|
|
CGPoint topLeft = CGPointMake(0, 0);
|
|
CGPoint topRight = CGPointMake(stripeWidth, 0);
|
|
CGPoint bottomRight = CGPointMake(-(stripeWidth * 4.0) + stripeWidth, stripeWidth * 4.0);
|
|
//Shift all four points as needed to draw all four stripes
|
|
bottomLeft.x += i * (2 * stripeWidth);
|
|
topLeft.x += i * (2 * stripeWidth);
|
|
topRight.x += i * (2 * stripeWidth);
|
|
bottomRight.x += i * (2 * stripeWidth);
|
|
//Create the fill path
|
|
UIBezierPath *path = [UIBezierPath bezierPath];
|
|
[path moveToPoint:bottomLeft];
|
|
[path addLineToPoint:topLeft];
|
|
[path addLineToPoint:topRight];
|
|
[path addLineToPoint:bottomRight];
|
|
[path closePath];
|
|
[path fill];
|
|
}
|
|
//Retreive the image
|
|
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
|
|
UIGraphicsEndImageContext();
|
|
//Set the background of the progress layer
|
|
indeterminateLayer.backgroundColor = [UIColor colorWithPatternImage:image].CGColor;
|
|
|
|
//remove any indeterminate layer animations
|
|
[indeterminateLayer removeAllAnimations];
|
|
//Set the indeterminate layer frame and add to the sub view
|
|
indeterminateLayer.frame = CGRectMake(0, 0, width + (4 * 2.5), 2.5);
|
|
UIView *progressView = [self getProgressView];
|
|
[progressView.layer addSublayer:indeterminateLayer];
|
|
//Add the animation
|
|
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"position"];
|
|
animation.duration = .1;
|
|
animation.repeatCount = HUGE_VALF;
|
|
animation.removedOnCompletion = YES;
|
|
animation.fromValue = [NSValue valueWithCGPoint:CGPointMake(- (2 * 2.5) + (width / 2.0), 2.5 / 2.0)];
|
|
animation.toValue = [NSValue valueWithCGPoint:CGPointMake(0 + (width / 2.0), 2.5 / 2.0)];
|
|
[indeterminateLayer addAnimation:animation forKey:@"position"];
|
|
} else {
|
|
CALayer *indeterminateLayer = [self getIndeterminateLayer];
|
|
[indeterminateLayer removeAllAnimations];
|
|
[indeterminateLayer removeFromSuperlayer];
|
|
}
|
|
}
|
|
|
|
#pragma mark properties
|
|
|
|
- (void)setOldTitle:(NSString *)oldTitle
|
|
{
|
|
objc_setAssociatedObject(self, &oldTitleKey, self.visibleViewController.navigationItem.title, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
|
|
}
|
|
|
|
- (NSString *)getOldTitle
|
|
{
|
|
return objc_getAssociatedObject(self, &oldTitleKey);
|
|
}
|
|
|
|
- (void)setDisplayLink:(CADisplayLink *)displayLink
|
|
{
|
|
objc_setAssociatedObject(self, &displayLinkKey, displayLink, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
|
|
}
|
|
|
|
- (CADisplayLink *)getDisplayLink
|
|
{
|
|
return objc_getAssociatedObject(self, &displayLinkKey);
|
|
}
|
|
|
|
- (void)setAnimationFromValue:(CGFloat)animationFromValue
|
|
{
|
|
objc_setAssociatedObject(self, &animationFromKey, [NSNumber numberWithFloat:(float)animationFromValue], OBJC_ASSOCIATION_RETAIN_NONATOMIC);
|
|
}
|
|
|
|
- (CGFloat)getAnimationFromValue
|
|
{
|
|
NSNumber *number = objc_getAssociatedObject(self, &animationFromKey);
|
|
return number.floatValue;
|
|
}
|
|
|
|
- (void)setAnimationToValue:(CGFloat)animationToValue
|
|
{
|
|
objc_setAssociatedObject(self, &animationToKey, [NSNumber numberWithFloat:(float)animationToValue], OBJC_ASSOCIATION_RETAIN_NONATOMIC);
|
|
}
|
|
|
|
- (CGFloat)getAnimationToValue
|
|
{
|
|
NSNumber *number = objc_getAssociatedObject(self, &animationToKey);
|
|
return number.floatValue;
|
|
}
|
|
|
|
- (void)setAnimationStartTime:(NSTimeInterval)animationStartTime
|
|
{
|
|
objc_setAssociatedObject(self, &animationStartTimeKey, [NSNumber numberWithFloat:(float)animationStartTime], OBJC_ASSOCIATION_RETAIN_NONATOMIC);
|
|
}
|
|
|
|
- (NSTimeInterval)getAnimationStartTime
|
|
{
|
|
NSNumber *number = objc_getAssociatedObject(self, &animationStartTimeKey);
|
|
return number.floatValue;
|
|
}
|
|
|
|
- (void)setProgress:(CGFloat)progress
|
|
{
|
|
if (progress > 1.0) {
|
|
progress = 1.0;
|
|
} else if (progress < 0.0) {
|
|
progress = 0.0;
|
|
}
|
|
objc_setAssociatedObject(self, &progressKey, [NSNumber numberWithFloat:(float)progress], OBJC_ASSOCIATION_RETAIN_NONATOMIC);
|
|
//Draw the update
|
|
if ([NSThread isMainThread]) {
|
|
[self updateProgress];
|
|
} else {
|
|
//Sometimes UINavigationController runs in a background thread. And drawing is not thread safe.
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
[self updateProgress];
|
|
});
|
|
}
|
|
}
|
|
|
|
- (void)setIsShowingProgressBar:(BOOL)isShowingProgressBar
|
|
{
|
|
objc_setAssociatedObject(self, &isShowingProgressKey, [NSNumber numberWithBool:isShowingProgressBar], OBJC_ASSOCIATION_RETAIN_NONATOMIC);
|
|
}
|
|
|
|
- (CGFloat)getProgress
|
|
{
|
|
NSNumber *number = objc_getAssociatedObject(self, &progressKey);
|
|
return number.floatValue;
|
|
}
|
|
|
|
- (CGFloat)getAnimationDuration
|
|
{
|
|
return .3;
|
|
}
|
|
|
|
- (void)setProgressView:(UIView *)view
|
|
{
|
|
objc_setAssociatedObject(self, &progressViewKey, view, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
|
|
}
|
|
|
|
- (UIView *)getProgressView
|
|
{
|
|
return objc_getAssociatedObject(self, &progressViewKey);
|
|
}
|
|
|
|
- (void)setBackgroundView:(UIView *)view
|
|
{
|
|
objc_setAssociatedObject(self, &backgroundViewKey, view, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
|
|
}
|
|
|
|
- (UIView *)getBackgroundView
|
|
{
|
|
return objc_getAssociatedObject(self, &backgroundViewKey);
|
|
}
|
|
|
|
|
|
- (void)setIndeterminate:(BOOL)indeterminate
|
|
{
|
|
objc_setAssociatedObject(self, &indeterminateKey, [NSNumber numberWithBool:indeterminate], OBJC_ASSOCIATION_RETAIN_NONATOMIC);
|
|
[self updateProgress];
|
|
[self drawIndeterminate];
|
|
}
|
|
|
|
- (BOOL)getIndeterminate
|
|
{
|
|
NSNumber *number = objc_getAssociatedObject(self, &indeterminateKey);
|
|
return number.boolValue;
|
|
}
|
|
|
|
- (void)setIndeterminateLayer:(CALayer *)indeterminateLayer
|
|
{
|
|
objc_setAssociatedObject(self, &indeterminateLayerKey, indeterminateLayer, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
|
|
}
|
|
|
|
- (CALayer *)getIndeterminateLayer
|
|
{
|
|
return objc_getAssociatedObject(self, &indeterminateLayerKey);
|
|
}
|
|
|
|
- (BOOL)isShowingProgressBar
|
|
{
|
|
return [objc_getAssociatedObject(self, &isShowingProgressKey) boolValue];
|
|
}
|
|
|
|
- (void)setPrimaryColor:(UIColor *)primaryColor
|
|
{
|
|
objc_setAssociatedObject(self, &primaryColorKey, primaryColor, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
|
|
[self getProgressView].backgroundColor = primaryColor;
|
|
[self setIndeterminate:[self getIndeterminate]];
|
|
}
|
|
|
|
- (UIColor *)getPrimaryColor
|
|
{
|
|
return objc_getAssociatedObject(self, &primaryColorKey);
|
|
}
|
|
|
|
- (void)setSecondaryColor:(UIColor *)secondaryColor
|
|
{
|
|
objc_setAssociatedObject(self, &secondaryColorKey, secondaryColor, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
|
|
[self setIndeterminate:[self getIndeterminate]];
|
|
}
|
|
|
|
- (UIColor *)getSecondaryColor
|
|
{
|
|
return objc_getAssociatedObject(self, &secondaryColorKey);
|
|
}
|
|
|
|
- (void)setBackgroundColor:(UIColor *)backgroundColor
|
|
{
|
|
objc_setAssociatedObject(self, &backgroundColorKey, backgroundColor, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
|
|
[self setIndeterminate:[self getIndeterminate]];
|
|
}
|
|
|
|
- (UIColor *)getBackgroundColor
|
|
{
|
|
return objc_getAssociatedObject(self, &backgroundColorKey);
|
|
}
|
|
|
|
@end
|