// // MRNavigationBarProgressView.m // MRNavigationBarProgressView // // Created by Marius Rackwitz on 09.10.13. // Copyright (c) 2013 Marius Rackwitz. All rights reserved. // #import "MRNavigationBarProgressView.h" #import static NSString *const MR_UINavigationControllerDidShowViewControllerNotification = @"UINavigationControllerDidShowViewControllerNotification"; static NSString *const MR_UINavigationControllerLastVisibleViewController = @"UINavigationControllerLastVisibleViewController"; @interface UINavigationController (NavigationBarProgressView_Private) @property (nonatomic, weak) MRNavigationBarProgressView *progressView; @end @implementation UINavigationController (NavigationBarProgressView_Private) - (void)setProgressView:(MRNavigationBarProgressView *)progressView { objc_setAssociatedObject(self, @selector(progressView), progressView, OBJC_ASSOCIATION_ASSIGN); } - (MRNavigationBarProgressView *)progressView { return objc_getAssociatedObject(self, @selector(progressView)); } @end @interface MRNavigationBarProgressView () @property (nonatomic, weak, readwrite) UIView *progressView; @property (nonatomic, weak, readwrite) UIViewController *viewController; @property (nonatomic, weak, readwrite) UIView *barView; @end @implementation MRNavigationBarProgressView @dynamic progress; static NSNumberFormatter *progressNumberFormatter; + (void)initialize { NSNumberFormatter *numberFormatter = [NSNumberFormatter new]; numberFormatter.numberStyle = NSNumberFormatterPercentStyle; numberFormatter.locale = NSLocale.currentLocale; progressNumberFormatter = numberFormatter; } + (instancetype)progressViewForNavigationController:(UINavigationController *)navigationController { // Try to get existing bar MRNavigationBarProgressView *progressView = navigationController.progressView; if (progressView) { return progressView; } // Create new bar UINavigationBar *navigationBar = navigationController.navigationBar; progressView = [MRNavigationBarProgressView new]; progressView.barView = navigationBar; progressView.progressTintColor = navigationBar.tintColor ? navigationBar.tintColor : UIApplication.sharedApplication.delegate.window.tintColor; // Store bar and add to view hierachy navigationController.progressView = progressView; [navigationController.navigationBar addSubview:progressView]; // Observe topItem progressView.viewController = navigationController.topViewController; [progressView registerObserverForNavigationController:navigationController]; return progressView; } - (id)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; if (self) { [self commonInit]; } return self; } - (id)initWithCoder:(NSCoder *)aDecoder { self = [super initWithCoder:aDecoder]; if (self) { [self commonInit]; } return self; } + (BOOL)requiresConstraintBasedLayout { return YES; } - (void)commonInit { self.isAccessibilityElement = YES; self.accessibilityLabel = NSLocalizedString(@"Determinate Progress", @"Accessibility label for navigation bar progress view"); self.accessibilityTraits = UIAccessibilityTraitUpdatesFrequently; self.autoresizingMask = UIViewAutoresizingFlexibleWidth; self.opaque = NO; UIView *progressView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 0, self.bounds.size.height)]; progressView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleRightMargin; progressView.backgroundColor = self.tintColor; [self addSubview:progressView]; self.progressView = progressView; self.progress = 0; [self tintColorDidChange]; } - (void)registerObserverForNavigationController:(UINavigationController *)navigationController { [NSNotificationCenter.defaultCenter addObserver:self selector:@selector(navigationControllerDidShowViewController:) name:MR_UINavigationControllerDidShowViewControllerNotification object:navigationController]; } - (void)unregisterObserverForNavigationController:(UINavigationController *)navigationController { [NSNotificationCenter.defaultCenter removeObserver:self]; } - (void)navigationControllerDidShowViewController:(NSNotification *)notification { UINavigationController *navigationController = notification.object; UIViewController *lastVisibleVC = notification.userInfo[MR_UINavigationControllerLastVisibleViewController]; // Check if our controller will be still the topViewController or was popped if (lastVisibleVC == self.viewController) { // Unregister observer [self unregisterObserverForNavigationController:navigationController]; // Remove reference navigationController.progressView = nil; // Remove receiver from view hierachy [self removeFromSuperview]; } } - (void) dealloc { [NSNotificationCenter.defaultCenter removeObserver:self]; } - (void)setBarView:(UIView *)barView { _barView = barView; [self layoutSubviews]; } #pragma mark - Layout - (void)layoutSubviews { [super layoutSubviews]; CGRect barFrame = self.barView.frame; const CGFloat progressBarHeight = 2; CGRect frame = CGRectMake(barFrame.origin.x, 0, barFrame.size.width, progressBarHeight); if ([self.barView isKindOfClass:UINavigationBar.class]) { const CGFloat barBorderHeight = 0.5; frame.origin.y = barFrame.size.height - progressBarHeight + barBorderHeight; } if (!CGRectEqualToRect(self.frame, frame)) { self.frame = frame; } [self layoutProgressView]; } - (void)layoutProgressView { self.progressView.frame = CGRectMake(0, 0, self.frame.size.width * self.progress, self.frame.size.height); } #pragma mark - Getter and setter for progress tint color - (void)setProgressTintColor:(UIColor *)tintColor { self.progressView.backgroundColor = tintColor; } - (UIColor *)progressTintColor { return self.progressView.backgroundColor; } #pragma mark - Control progress - (void)setProgress:(float)progress { assert(progress >= 0 && progress <= 1); [self _setProgress:progress]; } - (void)_setProgress:(float)progress { _progress = progress; [self progressDidChange]; } - (void)progressDidChange { self.progressView.alpha = self.progress >= 1 ? 0 : 1; [self layoutProgressView]; self.accessibilityValue = [progressNumberFormatter stringFromNumber:@(self.progress)]; UIAccessibilityPostNotification(UIAccessibilityScreenChangedNotification, self); } - (void)setProgress:(float)progress animated:(BOOL)animated { if (animated) { if (progress > 0 && progress < 1.0 && self.progressView.alpha <= CGFLOAT_MIN) { // progressView was hidden. Make it visible first. self.progressView.alpha = 1; } void(^completion)(BOOL) = ^(BOOL finished){ [UIView animateWithDuration:0.3 delay:0 options:UIViewAnimationOptionCurveEaseOut animations:^{ self.progressView.alpha = self.progress >= 1 ? 0 : 1; } completion:nil]; }; if (progress > self.progress || self.progress >= 1) { // Progress increased: ease out. [UIView animateWithDuration:0.3 delay:0 options:UIViewAnimationOptionCurveEaseOut animations:^{ [self _setProgress:progress]; } completion:completion]; } else { // Progress decreased: bounce. [UIView animateWithDuration:0.3 delay:0 usingSpringWithDamping:0.5 initialSpringVelocity:0 options:0 animations:^{ [self _setProgress:progress]; } completion:completion]; } } else { self.progress = progress; } } @end