New file |
| | |
| | | // |
| | | // MBProgressHUD.m |
| | | // Version 1.1.0 |
| | | // Created by Matej Bukovinski on 2.4.09. |
| | | // |
| | | |
| | | #import "MBProgressHUD.h" |
| | | #import <tgmath.h> |
| | | |
| | | |
| | | #ifndef kCFCoreFoundationVersionNumber_iOS_7_0 |
| | | #define kCFCoreFoundationVersionNumber_iOS_7_0 847.20 |
| | | #endif |
| | | |
| | | #ifndef kCFCoreFoundationVersionNumber_iOS_8_0 |
| | | #define kCFCoreFoundationVersionNumber_iOS_8_0 1129.15 |
| | | #endif |
| | | |
| | | #define MBMainThreadAssert() NSAssert([NSThread isMainThread], @"MBProgressHUD needs to be accessed on the main thread."); |
| | | |
| | | CGFloat const MBProgressMaxOffset = 1000000.f; |
| | | |
| | | static const CGFloat MBDefaultPadding = 4.f; |
| | | static const CGFloat MBDefaultLabelFontSize = 16.f; |
| | | static const CGFloat MBDefaultDetailsLabelFontSize = 12.f; |
| | | |
| | | |
| | | @interface MBProgressHUD () { |
| | | // Deprecated |
| | | UIColor *_activityIndicatorColor; |
| | | CGFloat _opacity; |
| | | } |
| | | |
| | | @property (nonatomic, assign) BOOL useAnimation; |
| | | @property (nonatomic, assign, getter=hasFinished) BOOL finished; |
| | | @property (nonatomic, strong) UIView *indicator; |
| | | @property (nonatomic, strong) NSDate *showStarted; |
| | | @property (nonatomic, strong) NSArray *paddingConstraints; |
| | | @property (nonatomic, strong) NSArray *bezelConstraints; |
| | | @property (nonatomic, strong) UIView *topSpacer; |
| | | @property (nonatomic, strong) UIView *bottomSpacer; |
| | | @property (nonatomic, weak) NSTimer *graceTimer; |
| | | @property (nonatomic, weak) NSTimer *minShowTimer; |
| | | @property (nonatomic, weak) NSTimer *hideDelayTimer; |
| | | @property (nonatomic, weak) CADisplayLink *progressObjectDisplayLink; |
| | | |
| | | // Deprecated |
| | | @property (assign) BOOL taskInProgress; |
| | | |
| | | @end |
| | | |
| | | |
| | | @interface MBProgressHUDRoundedButton : UIButton |
| | | @end |
| | | |
| | | |
| | | @implementation MBProgressHUD |
| | | |
| | | #pragma mark - Class methods |
| | | |
| | | + (instancetype)showHUDAddedTo:(UIView *)view animated:(BOOL)animated { |
| | | MBProgressHUD *hud = [[self alloc] initWithView:view]; |
| | | hud.removeFromSuperViewOnHide = YES; |
| | | [view addSubview:hud]; |
| | | [hud showAnimated:animated]; |
| | | return hud; |
| | | } |
| | | |
| | | + (BOOL)hideHUDForView:(UIView *)view animated:(BOOL)animated { |
| | | MBProgressHUD *hud = [self HUDForView:view]; |
| | | if (hud != nil) { |
| | | hud.removeFromSuperViewOnHide = YES; |
| | | [hud hideAnimated:animated]; |
| | | return YES; |
| | | } |
| | | return NO; |
| | | } |
| | | |
| | | + (MBProgressHUD *)HUDForView:(UIView *)view { |
| | | NSEnumerator *subviewsEnum = [view.subviews reverseObjectEnumerator]; |
| | | for (UIView *subview in subviewsEnum) { |
| | | if ([subview isKindOfClass:self]) { |
| | | MBProgressHUD *hud = (MBProgressHUD *)subview; |
| | | if (hud.hasFinished == NO) { |
| | | return hud; |
| | | } |
| | | } |
| | | } |
| | | return nil; |
| | | } |
| | | |
| | | #pragma mark - Lifecycle |
| | | |
| | | - (void)commonInit { |
| | | // Set default values for properties |
| | | _animationType = MBProgressHUDAnimationFade; |
| | | _mode = MBProgressHUDModeIndeterminate; |
| | | _margin = 20.0f; |
| | | _opacity = 1.f; |
| | | _defaultMotionEffectsEnabled = YES; |
| | | |
| | | // Default color, depending on the current iOS version |
| | | BOOL isLegacy = kCFCoreFoundationVersionNumber < kCFCoreFoundationVersionNumber_iOS_7_0; |
| | | _contentColor = isLegacy ? [UIColor whiteColor] : [UIColor colorWithWhite:0.f alpha:0.7f]; |
| | | // Transparent background |
| | | self.opaque = NO; |
| | | self.backgroundColor = [UIColor clearColor]; |
| | | // Make it invisible for now |
| | | self.alpha = 0.0f; |
| | | self.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; |
| | | self.layer.allowsGroupOpacity = NO; |
| | | |
| | | [self setupViews]; |
| | | [self updateIndicators]; |
| | | [self registerForNotifications]; |
| | | } |
| | | |
| | | - (instancetype)initWithFrame:(CGRect)frame { |
| | | if ((self = [super initWithFrame:frame])) { |
| | | [self commonInit]; |
| | | } |
| | | return self; |
| | | } |
| | | |
| | | - (instancetype)initWithCoder:(NSCoder *)aDecoder { |
| | | if ((self = [super initWithCoder:aDecoder])) { |
| | | [self commonInit]; |
| | | } |
| | | return self; |
| | | } |
| | | |
| | | - (id)initWithView:(UIView *)view { |
| | | NSAssert(view, @"View must not be nil."); |
| | | return [self initWithFrame:view.bounds]; |
| | | } |
| | | |
| | | - (void)dealloc { |
| | | [self unregisterFromNotifications]; |
| | | } |
| | | |
| | | #pragma mark - Show & hide |
| | | |
| | | - (void)showAnimated:(BOOL)animated { |
| | | MBMainThreadAssert(); |
| | | [self.minShowTimer invalidate]; |
| | | self.useAnimation = animated; |
| | | self.finished = NO; |
| | | // If the grace time is set, postpone the HUD display |
| | | if (self.graceTime > 0.0) { |
| | | NSTimer *timer = [NSTimer timerWithTimeInterval:self.graceTime target:self selector:@selector(handleGraceTimer:) userInfo:nil repeats:NO]; |
| | | [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes]; |
| | | self.graceTimer = timer; |
| | | } |
| | | // ... otherwise show the HUD immediately |
| | | else { |
| | | [self showUsingAnimation:self.useAnimation]; |
| | | } |
| | | } |
| | | |
| | | - (void)hideAnimated:(BOOL)animated { |
| | | MBMainThreadAssert(); |
| | | [self.graceTimer invalidate]; |
| | | self.useAnimation = animated; |
| | | self.finished = YES; |
| | | // If the minShow time is set, calculate how long the HUD was shown, |
| | | // and postpone the hiding operation if necessary |
| | | if (self.minShowTime > 0.0 && self.showStarted) { |
| | | NSTimeInterval interv = [[NSDate date] timeIntervalSinceDate:self.showStarted]; |
| | | if (interv < self.minShowTime) { |
| | | NSTimer *timer = [NSTimer timerWithTimeInterval:(self.minShowTime - interv) target:self selector:@selector(handleMinShowTimer:) userInfo:nil repeats:NO]; |
| | | [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes]; |
| | | self.minShowTimer = timer; |
| | | return; |
| | | } |
| | | } |
| | | // ... otherwise hide the HUD immediately |
| | | [self hideUsingAnimation:self.useAnimation]; |
| | | } |
| | | |
| | | - (void)hideAnimated:(BOOL)animated afterDelay:(NSTimeInterval)delay { |
| | | // Cancel any scheduled hideDelayed: calls |
| | | [self.hideDelayTimer invalidate]; |
| | | |
| | | NSTimer *timer = [NSTimer timerWithTimeInterval:delay target:self selector:@selector(handleHideTimer:) userInfo:@(animated) repeats:NO]; |
| | | [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes]; |
| | | self.hideDelayTimer = timer; |
| | | } |
| | | |
| | | #pragma mark - Timer callbacks |
| | | |
| | | - (void)handleGraceTimer:(NSTimer *)theTimer { |
| | | // Show the HUD only if the task is still running |
| | | if (!self.hasFinished) { |
| | | [self showUsingAnimation:self.useAnimation]; |
| | | } |
| | | } |
| | | |
| | | - (void)handleMinShowTimer:(NSTimer *)theTimer { |
| | | [self hideUsingAnimation:self.useAnimation]; |
| | | } |
| | | |
| | | - (void)handleHideTimer:(NSTimer *)timer { |
| | | [self hideAnimated:[timer.userInfo boolValue]]; |
| | | } |
| | | |
| | | #pragma mark - View Hierrarchy |
| | | |
| | | - (void)didMoveToSuperview { |
| | | [self updateForCurrentOrientationAnimated:NO]; |
| | | } |
| | | |
| | | #pragma mark - Internal show & hide operations |
| | | |
| | | - (void)showUsingAnimation:(BOOL)animated { |
| | | // Cancel any previous animations |
| | | [self.bezelView.layer removeAllAnimations]; |
| | | [self.backgroundView.layer removeAllAnimations]; |
| | | |
| | | // Cancel any scheduled hideDelayed: calls |
| | | [self.hideDelayTimer invalidate]; |
| | | |
| | | self.showStarted = [NSDate date]; |
| | | self.alpha = 1.f; |
| | | |
| | | // Needed in case we hide and re-show with the same NSProgress object attached. |
| | | [self setNSProgressDisplayLinkEnabled:YES]; |
| | | |
| | | if (animated) { |
| | | [self animateIn:YES withType:self.animationType completion:NULL]; |
| | | } else { |
| | | #pragma clang diagnostic push |
| | | #pragma clang diagnostic ignored "-Wdeprecated-declarations" |
| | | self.bezelView.alpha = self.opacity; |
| | | #pragma clang diagnostic pop |
| | | self.backgroundView.alpha = 1.f; |
| | | } |
| | | } |
| | | |
| | | - (void)hideUsingAnimation:(BOOL)animated { |
| | | if (animated && self.showStarted) { |
| | | self.showStarted = nil; |
| | | [self animateIn:NO withType:self.animationType completion:^(BOOL finished) { |
| | | [self done]; |
| | | }]; |
| | | } else { |
| | | self.showStarted = nil; |
| | | self.bezelView.alpha = 0.f; |
| | | self.backgroundView.alpha = 1.f; |
| | | [self done]; |
| | | } |
| | | } |
| | | |
| | | - (void)animateIn:(BOOL)animatingIn withType:(MBProgressHUDAnimation)type completion:(void(^)(BOOL finished))completion { |
| | | // Automatically determine the correct zoom animation type |
| | | if (type == MBProgressHUDAnimationZoom) { |
| | | type = animatingIn ? MBProgressHUDAnimationZoomIn : MBProgressHUDAnimationZoomOut; |
| | | } |
| | | |
| | | CGAffineTransform small = CGAffineTransformMakeScale(0.5f, 0.5f); |
| | | CGAffineTransform large = CGAffineTransformMakeScale(1.5f, 1.5f); |
| | | |
| | | // Set starting state |
| | | UIView *bezelView = self.bezelView; |
| | | if (animatingIn && bezelView.alpha == 0.f && type == MBProgressHUDAnimationZoomIn) { |
| | | bezelView.transform = small; |
| | | } else if (animatingIn && bezelView.alpha == 0.f && type == MBProgressHUDAnimationZoomOut) { |
| | | bezelView.transform = large; |
| | | } |
| | | |
| | | // Perform animations |
| | | dispatch_block_t animations = ^{ |
| | | if (animatingIn) { |
| | | bezelView.transform = CGAffineTransformIdentity; |
| | | } else if (!animatingIn && type == MBProgressHUDAnimationZoomIn) { |
| | | bezelView.transform = large; |
| | | } else if (!animatingIn && type == MBProgressHUDAnimationZoomOut) { |
| | | bezelView.transform = small; |
| | | } |
| | | #pragma clang diagnostic push |
| | | #pragma clang diagnostic ignored "-Wdeprecated-declarations" |
| | | bezelView.alpha = animatingIn ? self.opacity : 0.f; |
| | | #pragma clang diagnostic pop |
| | | self.backgroundView.alpha = animatingIn ? 1.f : 0.f; |
| | | }; |
| | | |
| | | // Spring animations are nicer, but only available on iOS 7+ |
| | | #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 70000 || TARGET_OS_TV |
| | | if (kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iOS_7_0) { |
| | | [UIView animateWithDuration:0.3 delay:0. usingSpringWithDamping:1.f initialSpringVelocity:0.f options:UIViewAnimationOptionBeginFromCurrentState animations:animations completion:completion]; |
| | | return; |
| | | } |
| | | #endif |
| | | [UIView animateWithDuration:0.3 delay:0. options:UIViewAnimationOptionBeginFromCurrentState animations:animations completion:completion]; |
| | | } |
| | | |
| | | - (void)done { |
| | | // Cancel any scheduled hideDelayed: calls |
| | | [self.hideDelayTimer invalidate]; |
| | | [self setNSProgressDisplayLinkEnabled:NO]; |
| | | |
| | | if (self.hasFinished) { |
| | | self.alpha = 0.0f; |
| | | if (self.removeFromSuperViewOnHide) { |
| | | [self removeFromSuperview]; |
| | | } |
| | | } |
| | | MBProgressHUDCompletionBlock completionBlock = self.completionBlock; |
| | | if (completionBlock) { |
| | | completionBlock(); |
| | | } |
| | | id<MBProgressHUDDelegate> delegate = self.delegate; |
| | | if ([delegate respondsToSelector:@selector(hudWasHidden:)]) { |
| | | [delegate performSelector:@selector(hudWasHidden:) withObject:self]; |
| | | } |
| | | } |
| | | |
| | | #pragma mark - UI |
| | | |
| | | - (void)setupViews { |
| | | UIColor *defaultColor = self.contentColor; |
| | | |
| | | MBBackgroundView *backgroundView = [[MBBackgroundView alloc] initWithFrame:self.bounds]; |
| | | backgroundView.style = MBProgressHUDBackgroundStyleSolidColor; |
| | | backgroundView.backgroundColor = [UIColor clearColor]; |
| | | backgroundView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; |
| | | backgroundView.alpha = 0.f; |
| | | [self addSubview:backgroundView]; |
| | | _backgroundView = backgroundView; |
| | | |
| | | MBBackgroundView *bezelView = [MBBackgroundView new]; |
| | | bezelView.translatesAutoresizingMaskIntoConstraints = NO; |
| | | bezelView.layer.cornerRadius = 5.f; |
| | | bezelView.alpha = 0.f; |
| | | [self addSubview:bezelView]; |
| | | _bezelView = bezelView; |
| | | [self updateBezelMotionEffects]; |
| | | |
| | | UILabel *label = [UILabel new]; |
| | | label.adjustsFontSizeToFitWidth = NO; |
| | | label.textAlignment = NSTextAlignmentCenter; |
| | | label.textColor = defaultColor; |
| | | label.font = [UIFont boldSystemFontOfSize:MBDefaultLabelFontSize]; |
| | | label.opaque = NO; |
| | | label.backgroundColor = [UIColor clearColor]; |
| | | _label = label; |
| | | |
| | | UILabel *detailsLabel = [UILabel new]; |
| | | detailsLabel.adjustsFontSizeToFitWidth = NO; |
| | | detailsLabel.textAlignment = NSTextAlignmentCenter; |
| | | detailsLabel.textColor = defaultColor; |
| | | detailsLabel.numberOfLines = 0; |
| | | detailsLabel.font = [UIFont boldSystemFontOfSize:MBDefaultDetailsLabelFontSize]; |
| | | detailsLabel.opaque = NO; |
| | | detailsLabel.backgroundColor = [UIColor clearColor]; |
| | | _detailsLabel = detailsLabel; |
| | | |
| | | UIButton *button = [MBProgressHUDRoundedButton buttonWithType:UIButtonTypeCustom]; |
| | | button.titleLabel.textAlignment = NSTextAlignmentCenter; |
| | | button.titleLabel.font = [UIFont boldSystemFontOfSize:MBDefaultDetailsLabelFontSize]; |
| | | [button setTitleColor:defaultColor forState:UIControlStateNormal]; |
| | | _button = button; |
| | | |
| | | for (UIView *view in @[label, detailsLabel, button]) { |
| | | view.translatesAutoresizingMaskIntoConstraints = NO; |
| | | [view setContentCompressionResistancePriority:998.f forAxis:UILayoutConstraintAxisHorizontal]; |
| | | [view setContentCompressionResistancePriority:998.f forAxis:UILayoutConstraintAxisVertical]; |
| | | [bezelView addSubview:view]; |
| | | } |
| | | |
| | | UIView *topSpacer = [UIView new]; |
| | | topSpacer.translatesAutoresizingMaskIntoConstraints = NO; |
| | | topSpacer.hidden = YES; |
| | | [bezelView addSubview:topSpacer]; |
| | | _topSpacer = topSpacer; |
| | | |
| | | UIView *bottomSpacer = [UIView new]; |
| | | bottomSpacer.translatesAutoresizingMaskIntoConstraints = NO; |
| | | bottomSpacer.hidden = YES; |
| | | [bezelView addSubview:bottomSpacer]; |
| | | _bottomSpacer = bottomSpacer; |
| | | } |
| | | |
| | | - (void)updateIndicators { |
| | | UIView *indicator = self.indicator; |
| | | BOOL isActivityIndicator = [indicator isKindOfClass:[UIActivityIndicatorView class]]; |
| | | BOOL isRoundIndicator = [indicator isKindOfClass:[MBRoundProgressView class]]; |
| | | |
| | | MBProgressHUDMode mode = self.mode; |
| | | if (mode == MBProgressHUDModeIndeterminate) { |
| | | if (!isActivityIndicator) { |
| | | // Update to indeterminate indicator |
| | | [indicator removeFromSuperview]; |
| | | indicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge]; |
| | | [(UIActivityIndicatorView *)indicator startAnimating]; |
| | | [self.bezelView addSubview:indicator]; |
| | | } |
| | | } |
| | | else if (mode == MBProgressHUDModeDeterminateHorizontalBar) { |
| | | // Update to bar determinate indicator |
| | | [indicator removeFromSuperview]; |
| | | indicator = [[MBBarProgressView alloc] init]; |
| | | [self.bezelView addSubview:indicator]; |
| | | } |
| | | else if (mode == MBProgressHUDModeDeterminate || mode == MBProgressHUDModeAnnularDeterminate) { |
| | | if (!isRoundIndicator) { |
| | | // Update to determinante indicator |
| | | [indicator removeFromSuperview]; |
| | | indicator = [[MBRoundProgressView alloc] init]; |
| | | [self.bezelView addSubview:indicator]; |
| | | } |
| | | if (mode == MBProgressHUDModeAnnularDeterminate) { |
| | | [(MBRoundProgressView *)indicator setAnnular:YES]; |
| | | } |
| | | } |
| | | else if (mode == MBProgressHUDModeCustomView && self.customView != indicator) { |
| | | // Update custom view indicator |
| | | [indicator removeFromSuperview]; |
| | | indicator = self.customView; |
| | | [self.bezelView addSubview:indicator]; |
| | | } |
| | | else if (mode == MBProgressHUDModeText) { |
| | | [indicator removeFromSuperview]; |
| | | indicator = nil; |
| | | } |
| | | indicator.translatesAutoresizingMaskIntoConstraints = NO; |
| | | self.indicator = indicator; |
| | | |
| | | if ([indicator respondsToSelector:@selector(setProgress:)]) { |
| | | [(id)indicator setValue:@(self.progress) forKey:@"progress"]; |
| | | } |
| | | |
| | | [indicator setContentCompressionResistancePriority:998.f forAxis:UILayoutConstraintAxisHorizontal]; |
| | | [indicator setContentCompressionResistancePriority:998.f forAxis:UILayoutConstraintAxisVertical]; |
| | | |
| | | [self updateViewsForColor:self.contentColor]; |
| | | [self setNeedsUpdateConstraints]; |
| | | } |
| | | |
| | | - (void)updateViewsForColor:(UIColor *)color { |
| | | if (!color) return; |
| | | |
| | | self.label.textColor = color; |
| | | self.detailsLabel.textColor = color; |
| | | [self.button setTitleColor:color forState:UIControlStateNormal]; |
| | | |
| | | #pragma clang diagnostic push |
| | | #pragma clang diagnostic ignored "-Wdeprecated-declarations" |
| | | if (self.activityIndicatorColor) { |
| | | color = self.activityIndicatorColor; |
| | | } |
| | | #pragma clang diagnostic pop |
| | | |
| | | // UIAppearance settings are prioritized. If they are preset the set color is ignored. |
| | | |
| | | UIView *indicator = self.indicator; |
| | | if ([indicator isKindOfClass:[UIActivityIndicatorView class]]) { |
| | | UIActivityIndicatorView *appearance = nil; |
| | | #if __IPHONE_OS_VERSION_MIN_REQUIRED < 90000 |
| | | appearance = [UIActivityIndicatorView appearanceWhenContainedIn:[MBProgressHUD class], nil]; |
| | | #else |
| | | // For iOS 9+ |
| | | appearance = [UIActivityIndicatorView appearanceWhenContainedInInstancesOfClasses:@[[MBProgressHUD class]]]; |
| | | #endif |
| | | |
| | | if (appearance.color == nil) { |
| | | ((UIActivityIndicatorView *)indicator).color = color; |
| | | } |
| | | } else if ([indicator isKindOfClass:[MBRoundProgressView class]]) { |
| | | MBRoundProgressView *appearance = nil; |
| | | #if __IPHONE_OS_VERSION_MIN_REQUIRED < 90000 |
| | | appearance = [MBRoundProgressView appearanceWhenContainedIn:[MBProgressHUD class], nil]; |
| | | #else |
| | | appearance = [MBRoundProgressView appearanceWhenContainedInInstancesOfClasses:@[[MBProgressHUD class]]]; |
| | | #endif |
| | | if (appearance.progressTintColor == nil) { |
| | | ((MBRoundProgressView *)indicator).progressTintColor = color; |
| | | } |
| | | if (appearance.backgroundTintColor == nil) { |
| | | ((MBRoundProgressView *)indicator).backgroundTintColor = [color colorWithAlphaComponent:0.1]; |
| | | } |
| | | } else if ([indicator isKindOfClass:[MBBarProgressView class]]) { |
| | | MBBarProgressView *appearance = nil; |
| | | #if __IPHONE_OS_VERSION_MIN_REQUIRED < 90000 |
| | | appearance = [MBBarProgressView appearanceWhenContainedIn:[MBProgressHUD class], nil]; |
| | | #else |
| | | appearance = [MBBarProgressView appearanceWhenContainedInInstancesOfClasses:@[[MBProgressHUD class]]]; |
| | | #endif |
| | | if (appearance.progressColor == nil) { |
| | | ((MBBarProgressView *)indicator).progressColor = color; |
| | | } |
| | | if (appearance.lineColor == nil) { |
| | | ((MBBarProgressView *)indicator).lineColor = color; |
| | | } |
| | | } else { |
| | | #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 70000 || TARGET_OS_TV |
| | | if ([indicator respondsToSelector:@selector(setTintColor:)]) { |
| | | [indicator setTintColor:color]; |
| | | } |
| | | #endif |
| | | } |
| | | } |
| | | |
| | | - (void)updateBezelMotionEffects { |
| | | #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 70000 || TARGET_OS_TV |
| | | MBBackgroundView *bezelView = self.bezelView; |
| | | if (![bezelView respondsToSelector:@selector(addMotionEffect:)]) return; |
| | | |
| | | if (self.defaultMotionEffectsEnabled) { |
| | | CGFloat effectOffset = 10.f; |
| | | UIInterpolatingMotionEffect *effectX = [[UIInterpolatingMotionEffect alloc] initWithKeyPath:@"center.x" type:UIInterpolatingMotionEffectTypeTiltAlongHorizontalAxis]; |
| | | effectX.maximumRelativeValue = @(effectOffset); |
| | | effectX.minimumRelativeValue = @(-effectOffset); |
| | | |
| | | UIInterpolatingMotionEffect *effectY = [[UIInterpolatingMotionEffect alloc] initWithKeyPath:@"center.y" type:UIInterpolatingMotionEffectTypeTiltAlongVerticalAxis]; |
| | | effectY.maximumRelativeValue = @(effectOffset); |
| | | effectY.minimumRelativeValue = @(-effectOffset); |
| | | |
| | | UIMotionEffectGroup *group = [[UIMotionEffectGroup alloc] init]; |
| | | group.motionEffects = @[effectX, effectY]; |
| | | |
| | | [bezelView addMotionEffect:group]; |
| | | } else { |
| | | NSArray *effects = [bezelView motionEffects]; |
| | | for (UIMotionEffect *effect in effects) { |
| | | [bezelView removeMotionEffect:effect]; |
| | | } |
| | | } |
| | | #endif |
| | | } |
| | | |
| | | #pragma mark - Layout |
| | | |
| | | - (void)updateConstraints { |
| | | UIView *bezel = self.bezelView; |
| | | UIView *topSpacer = self.topSpacer; |
| | | UIView *bottomSpacer = self.bottomSpacer; |
| | | CGFloat margin = self.margin; |
| | | NSMutableArray *bezelConstraints = [NSMutableArray array]; |
| | | NSDictionary *metrics = @{@"margin": @(margin)}; |
| | | |
| | | NSMutableArray *subviews = [NSMutableArray arrayWithObjects:self.topSpacer, self.label, self.detailsLabel, self.button, self.bottomSpacer, nil]; |
| | | if (self.indicator) [subviews insertObject:self.indicator atIndex:1]; |
| | | |
| | | // Remove existing constraints |
| | | [self removeConstraints:self.constraints]; |
| | | [topSpacer removeConstraints:topSpacer.constraints]; |
| | | [bottomSpacer removeConstraints:bottomSpacer.constraints]; |
| | | if (self.bezelConstraints) { |
| | | [bezel removeConstraints:self.bezelConstraints]; |
| | | self.bezelConstraints = nil; |
| | | } |
| | | |
| | | // Center bezel in container (self), applying the offset if set |
| | | CGPoint offset = self.offset; |
| | | NSMutableArray *centeringConstraints = [NSMutableArray array]; |
| | | [centeringConstraints addObject:[NSLayoutConstraint constraintWithItem:bezel attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeCenterX multiplier:1.f constant:offset.x]]; |
| | | [centeringConstraints addObject:[NSLayoutConstraint constraintWithItem:bezel attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeCenterY multiplier:1.f constant:offset.y]]; |
| | | [self applyPriority:998.f toConstraints:centeringConstraints]; |
| | | [self addConstraints:centeringConstraints]; |
| | | |
| | | // Ensure minimum side margin is kept |
| | | NSMutableArray *sideConstraints = [NSMutableArray array]; |
| | | [sideConstraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"|-(>=margin)-[bezel]-(>=margin)-|" options:0 metrics:metrics views:NSDictionaryOfVariableBindings(bezel)]]; |
| | | [sideConstraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-(>=margin)-[bezel]-(>=margin)-|" options:0 metrics:metrics views:NSDictionaryOfVariableBindings(bezel)]]; |
| | | [self applyPriority:999.f toConstraints:sideConstraints]; |
| | | [self addConstraints:sideConstraints]; |
| | | |
| | | // Minimum bezel size, if set |
| | | CGSize minimumSize = self.minSize; |
| | | if (!CGSizeEqualToSize(minimumSize, CGSizeZero)) { |
| | | NSMutableArray *minSizeConstraints = [NSMutableArray array]; |
| | | [minSizeConstraints addObject:[NSLayoutConstraint constraintWithItem:bezel attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationGreaterThanOrEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.f constant:minimumSize.width]]; |
| | | [minSizeConstraints addObject:[NSLayoutConstraint constraintWithItem:bezel attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationGreaterThanOrEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.f constant:minimumSize.height]]; |
| | | [self applyPriority:997.f toConstraints:minSizeConstraints]; |
| | | [bezelConstraints addObjectsFromArray:minSizeConstraints]; |
| | | } |
| | | |
| | | // Square aspect ratio, if set |
| | | if (self.square) { |
| | | NSLayoutConstraint *square = [NSLayoutConstraint constraintWithItem:bezel attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:bezel attribute:NSLayoutAttributeWidth multiplier:1.f constant:0]; |
| | | square.priority = 997.f; |
| | | [bezelConstraints addObject:square]; |
| | | } |
| | | |
| | | // Top and bottom spacing |
| | | [topSpacer addConstraint:[NSLayoutConstraint constraintWithItem:topSpacer attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationGreaterThanOrEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.f constant:margin]]; |
| | | [bottomSpacer addConstraint:[NSLayoutConstraint constraintWithItem:bottomSpacer attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationGreaterThanOrEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.f constant:margin]]; |
| | | // Top and bottom spaces should be equal |
| | | [bezelConstraints addObject:[NSLayoutConstraint constraintWithItem:topSpacer attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:bottomSpacer attribute:NSLayoutAttributeHeight multiplier:1.f constant:0.f]]; |
| | | |
| | | // Layout subviews in bezel |
| | | NSMutableArray *paddingConstraints = [NSMutableArray new]; |
| | | [subviews enumerateObjectsUsingBlock:^(UIView *view, NSUInteger idx, BOOL *stop) { |
| | | // Center in bezel |
| | | [bezelConstraints addObject:[NSLayoutConstraint constraintWithItem:view attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:bezel attribute:NSLayoutAttributeCenterX multiplier:1.f constant:0.f]]; |
| | | // Ensure the minimum edge margin is kept |
| | | [bezelConstraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"|-(>=margin)-[view]-(>=margin)-|" options:0 metrics:metrics views:NSDictionaryOfVariableBindings(view)]]; |
| | | // Element spacing |
| | | if (idx == 0) { |
| | | // First, ensure spacing to bezel edge |
| | | [bezelConstraints addObject:[NSLayoutConstraint constraintWithItem:view attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:bezel attribute:NSLayoutAttributeTop multiplier:1.f constant:0.f]]; |
| | | } else if (idx == subviews.count - 1) { |
| | | // Last, ensure spacing to bezel edge |
| | | [bezelConstraints addObject:[NSLayoutConstraint constraintWithItem:view attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:bezel attribute:NSLayoutAttributeBottom multiplier:1.f constant:0.f]]; |
| | | } |
| | | if (idx > 0) { |
| | | // Has previous |
| | | NSLayoutConstraint *padding = [NSLayoutConstraint constraintWithItem:view attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:subviews[idx - 1] attribute:NSLayoutAttributeBottom multiplier:1.f constant:0.f]; |
| | | [bezelConstraints addObject:padding]; |
| | | [paddingConstraints addObject:padding]; |
| | | } |
| | | }]; |
| | | |
| | | [bezel addConstraints:bezelConstraints]; |
| | | self.bezelConstraints = bezelConstraints; |
| | | |
| | | self.paddingConstraints = [paddingConstraints copy]; |
| | | [self updatePaddingConstraints]; |
| | | |
| | | [super updateConstraints]; |
| | | } |
| | | |
| | | - (void)layoutSubviews { |
| | | // There is no need to update constraints if they are going to |
| | | // be recreated in [super layoutSubviews] due to needsUpdateConstraints being set. |
| | | // This also avoids an issue on iOS 8, where updatePaddingConstraints |
| | | // would trigger a zombie object access. |
| | | if (!self.needsUpdateConstraints) { |
| | | [self updatePaddingConstraints]; |
| | | } |
| | | [super layoutSubviews]; |
| | | } |
| | | |
| | | - (void)updatePaddingConstraints { |
| | | // Set padding dynamically, depending on whether the view is visible or not |
| | | __block BOOL hasVisibleAncestors = NO; |
| | | [self.paddingConstraints enumerateObjectsUsingBlock:^(NSLayoutConstraint *padding, NSUInteger idx, BOOL *stop) { |
| | | UIView *firstView = (UIView *)padding.firstItem; |
| | | UIView *secondView = (UIView *)padding.secondItem; |
| | | BOOL firstVisible = !firstView.hidden && !CGSizeEqualToSize(firstView.intrinsicContentSize, CGSizeZero); |
| | | BOOL secondVisible = !secondView.hidden && !CGSizeEqualToSize(secondView.intrinsicContentSize, CGSizeZero); |
| | | // Set if both views are visible or if there's a visible view on top that doesn't have padding |
| | | // added relative to the current view yet |
| | | padding.constant = (firstVisible && (secondVisible || hasVisibleAncestors)) ? MBDefaultPadding : 0.f; |
| | | hasVisibleAncestors |= secondVisible; |
| | | }]; |
| | | } |
| | | |
| | | - (void)applyPriority:(UILayoutPriority)priority toConstraints:(NSArray *)constraints { |
| | | for (NSLayoutConstraint *constraint in constraints) { |
| | | constraint.priority = priority; |
| | | } |
| | | } |
| | | |
| | | #pragma mark - Properties |
| | | |
| | | - (void)setMode:(MBProgressHUDMode)mode { |
| | | if (mode != _mode) { |
| | | _mode = mode; |
| | | [self updateIndicators]; |
| | | } |
| | | } |
| | | |
| | | - (void)setCustomView:(UIView *)customView { |
| | | if (customView != _customView) { |
| | | _customView = customView; |
| | | if (self.mode == MBProgressHUDModeCustomView) { |
| | | [self updateIndicators]; |
| | | } |
| | | } |
| | | } |
| | | |
| | | - (void)setOffset:(CGPoint)offset { |
| | | if (!CGPointEqualToPoint(offset, _offset)) { |
| | | _offset = offset; |
| | | [self setNeedsUpdateConstraints]; |
| | | } |
| | | } |
| | | |
| | | - (void)setMargin:(CGFloat)margin { |
| | | if (margin != _margin) { |
| | | _margin = margin; |
| | | [self setNeedsUpdateConstraints]; |
| | | } |
| | | } |
| | | |
| | | - (void)setMinSize:(CGSize)minSize { |
| | | if (!CGSizeEqualToSize(minSize, _minSize)) { |
| | | _minSize = minSize; |
| | | [self setNeedsUpdateConstraints]; |
| | | } |
| | | } |
| | | |
| | | - (void)setSquare:(BOOL)square { |
| | | if (square != _square) { |
| | | _square = square; |
| | | [self setNeedsUpdateConstraints]; |
| | | } |
| | | } |
| | | |
| | | - (void)setProgressObjectDisplayLink:(CADisplayLink *)progressObjectDisplayLink { |
| | | if (progressObjectDisplayLink != _progressObjectDisplayLink) { |
| | | [_progressObjectDisplayLink invalidate]; |
| | | |
| | | _progressObjectDisplayLink = progressObjectDisplayLink; |
| | | |
| | | [_progressObjectDisplayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode]; |
| | | } |
| | | } |
| | | |
| | | - (void)setProgressObject:(NSProgress *)progressObject { |
| | | if (progressObject != _progressObject) { |
| | | _progressObject = progressObject; |
| | | [self setNSProgressDisplayLinkEnabled:YES]; |
| | | } |
| | | } |
| | | |
| | | - (void)setProgress:(float)progress { |
| | | if (progress != _progress) { |
| | | _progress = progress; |
| | | UIView *indicator = self.indicator; |
| | | if ([indicator respondsToSelector:@selector(setProgress:)]) { |
| | | [(id)indicator setValue:@(self.progress) forKey:@"progress"]; |
| | | } |
| | | } |
| | | } |
| | | |
| | | - (void)setContentColor:(UIColor *)contentColor { |
| | | if (contentColor != _contentColor && ![contentColor isEqual:_contentColor]) { |
| | | _contentColor = contentColor; |
| | | [self updateViewsForColor:contentColor]; |
| | | } |
| | | } |
| | | |
| | | - (void)setDefaultMotionEffectsEnabled:(BOOL)defaultMotionEffectsEnabled { |
| | | if (defaultMotionEffectsEnabled != _defaultMotionEffectsEnabled) { |
| | | _defaultMotionEffectsEnabled = defaultMotionEffectsEnabled; |
| | | [self updateBezelMotionEffects]; |
| | | } |
| | | } |
| | | |
| | | #pragma mark - NSProgress |
| | | |
| | | - (void)setNSProgressDisplayLinkEnabled:(BOOL)enabled { |
| | | // We're using CADisplayLink, because NSProgress can change very quickly and observing it may starve the main thread, |
| | | // so we're refreshing the progress only every frame draw |
| | | if (enabled && self.progressObject) { |
| | | // Only create if not already active. |
| | | if (!self.progressObjectDisplayLink) { |
| | | self.progressObjectDisplayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(updateProgressFromProgressObject)]; |
| | | } |
| | | } else { |
| | | self.progressObjectDisplayLink = nil; |
| | | } |
| | | } |
| | | |
| | | - (void)updateProgressFromProgressObject { |
| | | self.progress = self.progressObject.fractionCompleted; |
| | | } |
| | | |
| | | #pragma mark - Notifications |
| | | |
| | | - (void)registerForNotifications { |
| | | #if !TARGET_OS_TV |
| | | NSNotificationCenter *nc = [NSNotificationCenter defaultCenter]; |
| | | |
| | | [nc addObserver:self selector:@selector(statusBarOrientationDidChange:) |
| | | name:UIApplicationDidChangeStatusBarOrientationNotification object:nil]; |
| | | #endif |
| | | } |
| | | |
| | | - (void)unregisterFromNotifications { |
| | | #if !TARGET_OS_TV |
| | | NSNotificationCenter *nc = [NSNotificationCenter defaultCenter]; |
| | | [nc removeObserver:self name:UIApplicationDidChangeStatusBarOrientationNotification object:nil]; |
| | | #endif |
| | | } |
| | | |
| | | #if !TARGET_OS_TV |
| | | - (void)statusBarOrientationDidChange:(NSNotification *)notification { |
| | | UIView *superview = self.superview; |
| | | if (!superview) { |
| | | return; |
| | | } else { |
| | | [self updateForCurrentOrientationAnimated:YES]; |
| | | } |
| | | } |
| | | #endif |
| | | |
| | | - (void)updateForCurrentOrientationAnimated:(BOOL)animated { |
| | | // Stay in sync with the superview in any case |
| | | if (self.superview) { |
| | | self.frame = self.superview.bounds; |
| | | } |
| | | |
| | | // Not needed on iOS 8+, compile out when the deployment target allows, |
| | | // to avoid sharedApplication problems on extension targets |
| | | #if __IPHONE_OS_VERSION_MIN_REQUIRED < 80000 |
| | | // Only needed pre iOS 8 when added to a window |
| | | BOOL iOS8OrLater = kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iOS_8_0; |
| | | if (iOS8OrLater || ![self.superview isKindOfClass:[UIWindow class]]) return; |
| | | |
| | | // Make extension friendly. Will not get called on extensions (iOS 8+) due to the above check. |
| | | // This just ensures we don't get a warning about extension-unsafe API. |
| | | Class UIApplicationClass = NSClassFromString(@"UIApplication"); |
| | | if (!UIApplicationClass || ![UIApplicationClass respondsToSelector:@selector(sharedApplication)]) return; |
| | | |
| | | UIApplication *application = [UIApplication performSelector:@selector(sharedApplication)]; |
| | | UIInterfaceOrientation orientation = application.statusBarOrientation; |
| | | CGFloat radians = 0; |
| | | |
| | | if (UIInterfaceOrientationIsLandscape(orientation)) { |
| | | radians = orientation == UIInterfaceOrientationLandscapeLeft ? -(CGFloat)M_PI_2 : (CGFloat)M_PI_2; |
| | | // Window coordinates differ! |
| | | self.bounds = CGRectMake(0, 0, self.bounds.size.height, self.bounds.size.width); |
| | | } else { |
| | | radians = orientation == UIInterfaceOrientationPortraitUpsideDown ? (CGFloat)M_PI : 0.f; |
| | | } |
| | | |
| | | if (animated) { |
| | | [UIView animateWithDuration:0.3 animations:^{ |
| | | self.transform = CGAffineTransformMakeRotation(radians); |
| | | }]; |
| | | } else { |
| | | self.transform = CGAffineTransformMakeRotation(radians); |
| | | } |
| | | #endif |
| | | } |
| | | |
| | | @end |
| | | |
| | | |
| | | @implementation MBRoundProgressView |
| | | |
| | | #pragma mark - Lifecycle |
| | | |
| | | - (id)init { |
| | | return [self initWithFrame:CGRectMake(0.f, 0.f, 37.f, 37.f)]; |
| | | } |
| | | |
| | | - (id)initWithFrame:(CGRect)frame { |
| | | self = [super initWithFrame:frame]; |
| | | if (self) { |
| | | self.backgroundColor = [UIColor clearColor]; |
| | | self.opaque = NO; |
| | | _progress = 0.f; |
| | | _annular = NO; |
| | | _progressTintColor = [[UIColor alloc] initWithWhite:1.f alpha:1.f]; |
| | | _backgroundTintColor = [[UIColor alloc] initWithWhite:1.f alpha:.1f]; |
| | | } |
| | | return self; |
| | | } |
| | | |
| | | #pragma mark - Layout |
| | | |
| | | - (CGSize)intrinsicContentSize { |
| | | return CGSizeMake(37.f, 37.f); |
| | | } |
| | | |
| | | #pragma mark - Properties |
| | | |
| | | - (void)setProgress:(float)progress { |
| | | if (progress != _progress) { |
| | | _progress = progress; |
| | | [self setNeedsDisplay]; |
| | | } |
| | | } |
| | | |
| | | - (void)setProgressTintColor:(UIColor *)progressTintColor { |
| | | NSAssert(progressTintColor, @"The color should not be nil."); |
| | | if (progressTintColor != _progressTintColor && ![progressTintColor isEqual:_progressTintColor]) { |
| | | _progressTintColor = progressTintColor; |
| | | [self setNeedsDisplay]; |
| | | } |
| | | } |
| | | |
| | | - (void)setBackgroundTintColor:(UIColor *)backgroundTintColor { |
| | | NSAssert(backgroundTintColor, @"The color should not be nil."); |
| | | if (backgroundTintColor != _backgroundTintColor && ![backgroundTintColor isEqual:_backgroundTintColor]) { |
| | | _backgroundTintColor = backgroundTintColor; |
| | | [self setNeedsDisplay]; |
| | | } |
| | | } |
| | | |
| | | #pragma mark - Drawing |
| | | |
| | | - (void)drawRect:(CGRect)rect { |
| | | CGContextRef context = UIGraphicsGetCurrentContext(); |
| | | BOOL isPreiOS7 = kCFCoreFoundationVersionNumber < kCFCoreFoundationVersionNumber_iOS_7_0; |
| | | |
| | | if (_annular) { |
| | | // Draw background |
| | | CGFloat lineWidth = isPreiOS7 ? 5.f : 2.f; |
| | | UIBezierPath *processBackgroundPath = [UIBezierPath bezierPath]; |
| | | processBackgroundPath.lineWidth = lineWidth; |
| | | processBackgroundPath.lineCapStyle = kCGLineCapButt; |
| | | CGPoint center = CGPointMake(CGRectGetMidX(self.bounds), CGRectGetMidY(self.bounds)); |
| | | CGFloat radius = (self.bounds.size.width - lineWidth)/2; |
| | | CGFloat startAngle = - ((float)M_PI / 2); // 90 degrees |
| | | CGFloat endAngle = (2 * (float)M_PI) + startAngle; |
| | | [processBackgroundPath addArcWithCenter:center radius:radius startAngle:startAngle endAngle:endAngle clockwise:YES]; |
| | | [_backgroundTintColor set]; |
| | | [processBackgroundPath stroke]; |
| | | // Draw progress |
| | | UIBezierPath *processPath = [UIBezierPath bezierPath]; |
| | | processPath.lineCapStyle = isPreiOS7 ? kCGLineCapRound : kCGLineCapSquare; |
| | | processPath.lineWidth = lineWidth; |
| | | endAngle = (self.progress * 2 * (float)M_PI) + startAngle; |
| | | [processPath addArcWithCenter:center radius:radius startAngle:startAngle endAngle:endAngle clockwise:YES]; |
| | | [_progressTintColor set]; |
| | | [processPath stroke]; |
| | | } else { |
| | | // Draw background |
| | | CGFloat lineWidth = 2.f; |
| | | CGRect allRect = self.bounds; |
| | | CGRect circleRect = CGRectInset(allRect, lineWidth/2.f, lineWidth/2.f); |
| | | CGPoint center = CGPointMake(CGRectGetMidX(self.bounds), CGRectGetMidY(self.bounds)); |
| | | [_progressTintColor setStroke]; |
| | | [_backgroundTintColor setFill]; |
| | | CGContextSetLineWidth(context, lineWidth); |
| | | if (isPreiOS7) { |
| | | CGContextFillEllipseInRect(context, circleRect); |
| | | } |
| | | CGContextStrokeEllipseInRect(context, circleRect); |
| | | // 90 degrees |
| | | CGFloat startAngle = - ((float)M_PI / 2.f); |
| | | // Draw progress |
| | | if (isPreiOS7) { |
| | | CGFloat radius = (CGRectGetWidth(self.bounds) / 2.f) - lineWidth; |
| | | CGFloat endAngle = (self.progress * 2.f * (float)M_PI) + startAngle; |
| | | [_progressTintColor setFill]; |
| | | CGContextMoveToPoint(context, center.x, center.y); |
| | | CGContextAddArc(context, center.x, center.y, radius, startAngle, endAngle, 0); |
| | | CGContextClosePath(context); |
| | | CGContextFillPath(context); |
| | | } else { |
| | | UIBezierPath *processPath = [UIBezierPath bezierPath]; |
| | | processPath.lineCapStyle = kCGLineCapButt; |
| | | processPath.lineWidth = lineWidth * 2.f; |
| | | CGFloat radius = (CGRectGetWidth(self.bounds) / 2.f) - (processPath.lineWidth / 2.f); |
| | | CGFloat endAngle = (self.progress * 2.f * (float)M_PI) + startAngle; |
| | | [processPath addArcWithCenter:center radius:radius startAngle:startAngle endAngle:endAngle clockwise:YES]; |
| | | // Ensure that we don't get color overlapping when _progressTintColor alpha < 1.f. |
| | | CGContextSetBlendMode(context, kCGBlendModeCopy); |
| | | [_progressTintColor set]; |
| | | [processPath stroke]; |
| | | } |
| | | } |
| | | } |
| | | |
| | | @end |
| | | |
| | | |
| | | @implementation MBBarProgressView |
| | | |
| | | #pragma mark - Lifecycle |
| | | |
| | | - (id)init { |
| | | return [self initWithFrame:CGRectMake(.0f, .0f, 120.0f, 20.0f)]; |
| | | } |
| | | |
| | | - (id)initWithFrame:(CGRect)frame { |
| | | self = [super initWithFrame:frame]; |
| | | if (self) { |
| | | _progress = 0.f; |
| | | _lineColor = [UIColor whiteColor]; |
| | | _progressColor = [UIColor whiteColor]; |
| | | _progressRemainingColor = [UIColor clearColor]; |
| | | self.backgroundColor = [UIColor clearColor]; |
| | | self.opaque = NO; |
| | | } |
| | | return self; |
| | | } |
| | | |
| | | #pragma mark - Layout |
| | | |
| | | - (CGSize)intrinsicContentSize { |
| | | BOOL isPreiOS7 = kCFCoreFoundationVersionNumber < kCFCoreFoundationVersionNumber_iOS_7_0; |
| | | return CGSizeMake(120.f, isPreiOS7 ? 20.f : 10.f); |
| | | } |
| | | |
| | | #pragma mark - Properties |
| | | |
| | | - (void)setProgress:(float)progress { |
| | | if (progress != _progress) { |
| | | _progress = progress; |
| | | [self setNeedsDisplay]; |
| | | } |
| | | } |
| | | |
| | | - (void)setProgressColor:(UIColor *)progressColor { |
| | | NSAssert(progressColor, @"The color should not be nil."); |
| | | if (progressColor != _progressColor && ![progressColor isEqual:_progressColor]) { |
| | | _progressColor = progressColor; |
| | | [self setNeedsDisplay]; |
| | | } |
| | | } |
| | | |
| | | - (void)setProgressRemainingColor:(UIColor *)progressRemainingColor { |
| | | NSAssert(progressRemainingColor, @"The color should not be nil."); |
| | | if (progressRemainingColor != _progressRemainingColor && ![progressRemainingColor isEqual:_progressRemainingColor]) { |
| | | _progressRemainingColor = progressRemainingColor; |
| | | [self setNeedsDisplay]; |
| | | } |
| | | } |
| | | |
| | | #pragma mark - Drawing |
| | | |
| | | - (void)drawRect:(CGRect)rect { |
| | | CGContextRef context = UIGraphicsGetCurrentContext(); |
| | | |
| | | CGContextSetLineWidth(context, 2); |
| | | CGContextSetStrokeColorWithColor(context,[_lineColor CGColor]); |
| | | CGContextSetFillColorWithColor(context, [_progressRemainingColor CGColor]); |
| | | |
| | | // Draw background and Border |
| | | CGFloat radius = (rect.size.height / 2) - 2; |
| | | CGContextMoveToPoint(context, 2, rect.size.height/2); |
| | | CGContextAddArcToPoint(context, 2, 2, radius + 2, 2, radius); |
| | | CGContextAddArcToPoint(context, rect.size.width - 2, 2, rect.size.width - 2, rect.size.height / 2, radius); |
| | | CGContextAddArcToPoint(context, rect.size.width - 2, rect.size.height - 2, rect.size.width - radius - 2, rect.size.height - 2, radius); |
| | | CGContextAddArcToPoint(context, 2, rect.size.height - 2, 2, rect.size.height/2, radius); |
| | | CGContextDrawPath(context, kCGPathFillStroke); |
| | | |
| | | CGContextSetFillColorWithColor(context, [_progressColor CGColor]); |
| | | radius = radius - 2; |
| | | CGFloat amount = self.progress * rect.size.width; |
| | | |
| | | // Progress in the middle area |
| | | if (amount >= radius + 4 && amount <= (rect.size.width - radius - 4)) { |
| | | CGContextMoveToPoint(context, 4, rect.size.height/2); |
| | | CGContextAddArcToPoint(context, 4, 4, radius + 4, 4, radius); |
| | | CGContextAddLineToPoint(context, amount, 4); |
| | | CGContextAddLineToPoint(context, amount, radius + 4); |
| | | |
| | | CGContextMoveToPoint(context, 4, rect.size.height/2); |
| | | CGContextAddArcToPoint(context, 4, rect.size.height - 4, radius + 4, rect.size.height - 4, radius); |
| | | CGContextAddLineToPoint(context, amount, rect.size.height - 4); |
| | | CGContextAddLineToPoint(context, amount, radius + 4); |
| | | |
| | | CGContextFillPath(context); |
| | | } |
| | | |
| | | // Progress in the right arc |
| | | else if (amount > radius + 4) { |
| | | CGFloat x = amount - (rect.size.width - radius - 4); |
| | | |
| | | CGContextMoveToPoint(context, 4, rect.size.height/2); |
| | | CGContextAddArcToPoint(context, 4, 4, radius + 4, 4, radius); |
| | | CGContextAddLineToPoint(context, rect.size.width - radius - 4, 4); |
| | | CGFloat angle = -acos(x/radius); |
| | | if (isnan(angle)) angle = 0; |
| | | CGContextAddArc(context, rect.size.width - radius - 4, rect.size.height/2, radius, M_PI, angle, 0); |
| | | CGContextAddLineToPoint(context, amount, rect.size.height/2); |
| | | |
| | | CGContextMoveToPoint(context, 4, rect.size.height/2); |
| | | CGContextAddArcToPoint(context, 4, rect.size.height - 4, radius + 4, rect.size.height - 4, radius); |
| | | CGContextAddLineToPoint(context, rect.size.width - radius - 4, rect.size.height - 4); |
| | | angle = acos(x/radius); |
| | | if (isnan(angle)) angle = 0; |
| | | CGContextAddArc(context, rect.size.width - radius - 4, rect.size.height/2, radius, -M_PI, angle, 1); |
| | | CGContextAddLineToPoint(context, amount, rect.size.height/2); |
| | | |
| | | CGContextFillPath(context); |
| | | } |
| | | |
| | | // Progress is in the left arc |
| | | else if (amount < radius + 4 && amount > 0) { |
| | | CGContextMoveToPoint(context, 4, rect.size.height/2); |
| | | CGContextAddArcToPoint(context, 4, 4, radius + 4, 4, radius); |
| | | CGContextAddLineToPoint(context, radius + 4, rect.size.height/2); |
| | | |
| | | CGContextMoveToPoint(context, 4, rect.size.height/2); |
| | | CGContextAddArcToPoint(context, 4, rect.size.height - 4, radius + 4, rect.size.height - 4, radius); |
| | | CGContextAddLineToPoint(context, radius + 4, rect.size.height/2); |
| | | |
| | | CGContextFillPath(context); |
| | | } |
| | | } |
| | | |
| | | @end |
| | | |
| | | |
| | | @interface MBBackgroundView () |
| | | |
| | | #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000 || TARGET_OS_TV |
| | | @property UIVisualEffectView *effectView; |
| | | #endif |
| | | #if !TARGET_OS_TV |
| | | @property UIToolbar *toolbar; |
| | | #endif |
| | | |
| | | @end |
| | | |
| | | |
| | | @implementation MBBackgroundView |
| | | |
| | | #pragma mark - Lifecycle |
| | | |
| | | - (instancetype)initWithFrame:(CGRect)frame { |
| | | if ((self = [super initWithFrame:frame])) { |
| | | if (kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iOS_7_0) { |
| | | _style = MBProgressHUDBackgroundStyleBlur; |
| | | #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000 || TARGET_OS_TV |
| | | _blurEffectStyle = UIBlurEffectStyleLight; |
| | | #endif |
| | | if (kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iOS_8_0) { |
| | | _color = [UIColor colorWithWhite:0.8f alpha:0.6f]; |
| | | } else { |
| | | _color = [UIColor colorWithWhite:0.95f alpha:0.6f]; |
| | | } |
| | | } else { |
| | | _style = MBProgressHUDBackgroundStyleSolidColor; |
| | | _color = [[UIColor blackColor] colorWithAlphaComponent:0.8]; |
| | | } |
| | | |
| | | self.clipsToBounds = YES; |
| | | |
| | | [self updateForBackgroundStyle]; |
| | | } |
| | | return self; |
| | | } |
| | | |
| | | #pragma mark - Layout |
| | | |
| | | - (CGSize)intrinsicContentSize { |
| | | // Smallest size possible. Content pushes against this. |
| | | return CGSizeZero; |
| | | } |
| | | |
| | | #pragma mark - Appearance |
| | | |
| | | - (void)setStyle:(MBProgressHUDBackgroundStyle)style { |
| | | if (style == MBProgressHUDBackgroundStyleBlur && kCFCoreFoundationVersionNumber < kCFCoreFoundationVersionNumber_iOS_7_0) { |
| | | style = MBProgressHUDBackgroundStyleSolidColor; |
| | | } |
| | | if (_style != style) { |
| | | _style = style; |
| | | [self updateForBackgroundStyle]; |
| | | } |
| | | } |
| | | |
| | | - (void)setColor:(UIColor *)color { |
| | | NSAssert(color, @"The color should not be nil."); |
| | | if (color != _color && ![color isEqual:_color]) { |
| | | _color = color; |
| | | [self updateViewsForColor:color]; |
| | | } |
| | | } |
| | | |
| | | #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000 || TARGET_OS_TV |
| | | |
| | | - (void)setBlurEffectStyle:(UIBlurEffectStyle)blurEffectStyle { |
| | | if (_blurEffectStyle == blurEffectStyle) { |
| | | return; |
| | | } |
| | | |
| | | _blurEffectStyle = blurEffectStyle; |
| | | |
| | | [self updateForBackgroundStyle]; |
| | | } |
| | | |
| | | #endif |
| | | |
| | | /////////////////////////////////////////////////////////////////////////////////////////// |
| | | #pragma mark - Views |
| | | |
| | | - (void)updateForBackgroundStyle { |
| | | MBProgressHUDBackgroundStyle style = self.style; |
| | | if (style == MBProgressHUDBackgroundStyleBlur) { |
| | | #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000 || TARGET_OS_TV |
| | | if (kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iOS_8_0) { |
| | | UIBlurEffect *effect = [UIBlurEffect effectWithStyle:self.blurEffectStyle]; |
| | | UIVisualEffectView *effectView = [[UIVisualEffectView alloc] initWithEffect:effect]; |
| | | [self addSubview:effectView]; |
| | | effectView.frame = self.bounds; |
| | | effectView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth; |
| | | self.backgroundColor = self.color; |
| | | self.layer.allowsGroupOpacity = NO; |
| | | self.effectView = effectView; |
| | | } else { |
| | | #endif |
| | | #if !TARGET_OS_TV |
| | | UIToolbar *toolbar = [[UIToolbar alloc] initWithFrame:CGRectInset(self.bounds, -100.f, -100.f)]; |
| | | toolbar.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth; |
| | | toolbar.barTintColor = self.color; |
| | | toolbar.translucent = YES; |
| | | [self addSubview:toolbar]; |
| | | self.toolbar = toolbar; |
| | | #endif |
| | | #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000 || TARGET_OS_TV |
| | | } |
| | | #endif |
| | | } else { |
| | | #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000 || TARGET_OS_TV |
| | | if (kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iOS_8_0) { |
| | | [self.effectView removeFromSuperview]; |
| | | self.effectView = nil; |
| | | } else { |
| | | #endif |
| | | #if !TARGET_OS_TV |
| | | [self.toolbar removeFromSuperview]; |
| | | self.toolbar = nil; |
| | | #endif |
| | | #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000 || TARGET_OS_TV |
| | | } |
| | | #endif |
| | | self.backgroundColor = self.color; |
| | | } |
| | | } |
| | | |
| | | - (void)updateViewsForColor:(UIColor *)color { |
| | | if (self.style == MBProgressHUDBackgroundStyleBlur) { |
| | | if (kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iOS_8_0) { |
| | | self.backgroundColor = self.color; |
| | | } else { |
| | | #if !TARGET_OS_TV |
| | | self.toolbar.barTintColor = color; |
| | | #endif |
| | | } |
| | | } else { |
| | | self.backgroundColor = self.color; |
| | | } |
| | | } |
| | | |
| | | @end |
| | | |
| | | |
| | | @implementation MBProgressHUD (Deprecated) |
| | | |
| | | #pragma mark - Class |
| | | |
| | | + (NSUInteger)hideAllHUDsForView:(UIView *)view animated:(BOOL)animated { |
| | | NSArray *huds = [MBProgressHUD allHUDsForView:view]; |
| | | for (MBProgressHUD *hud in huds) { |
| | | hud.removeFromSuperViewOnHide = YES; |
| | | [hud hideAnimated:animated]; |
| | | } |
| | | return [huds count]; |
| | | } |
| | | |
| | | + (NSArray *)allHUDsForView:(UIView *)view { |
| | | NSMutableArray *huds = [NSMutableArray array]; |
| | | NSArray *subviews = view.subviews; |
| | | for (UIView *aView in subviews) { |
| | | if ([aView isKindOfClass:self]) { |
| | | [huds addObject:aView]; |
| | | } |
| | | } |
| | | return [NSArray arrayWithArray:huds]; |
| | | } |
| | | |
| | | #pragma mark - Lifecycle |
| | | |
| | | - (id)initWithWindow:(UIWindow *)window { |
| | | return [self initWithView:window]; |
| | | } |
| | | |
| | | #pragma mark - Show & hide |
| | | |
| | | - (void)show:(BOOL)animated { |
| | | [self showAnimated:animated]; |
| | | } |
| | | |
| | | - (void)hide:(BOOL)animated { |
| | | [self hideAnimated:animated]; |
| | | } |
| | | |
| | | - (void)hide:(BOOL)animated afterDelay:(NSTimeInterval)delay { |
| | | [self hideAnimated:animated afterDelay:delay]; |
| | | } |
| | | |
| | | #pragma mark - Threading |
| | | |
| | | - (void)showWhileExecuting:(SEL)method onTarget:(id)target withObject:(id)object animated:(BOOL)animated { |
| | | [self showAnimated:animated whileExecutingBlock:^{ |
| | | #pragma clang diagnostic push |
| | | #pragma clang diagnostic ignored "-Warc-performSelector-leaks" |
| | | // Start executing the requested task |
| | | [target performSelector:method withObject:object]; |
| | | #pragma clang diagnostic pop |
| | | }]; |
| | | } |
| | | |
| | | - (void)showAnimated:(BOOL)animated whileExecutingBlock:(dispatch_block_t)block { |
| | | dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); |
| | | [self showAnimated:animated whileExecutingBlock:block onQueue:queue completionBlock:NULL]; |
| | | } |
| | | |
| | | - (void)showAnimated:(BOOL)animated whileExecutingBlock:(dispatch_block_t)block completionBlock:(void (^)(void))completion { |
| | | dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); |
| | | [self showAnimated:animated whileExecutingBlock:block onQueue:queue completionBlock:completion]; |
| | | } |
| | | |
| | | - (void)showAnimated:(BOOL)animated whileExecutingBlock:(dispatch_block_t)block onQueue:(dispatch_queue_t)queue { |
| | | [self showAnimated:animated whileExecutingBlock:block onQueue:queue completionBlock:NULL]; |
| | | } |
| | | |
| | | - (void)showAnimated:(BOOL)animated whileExecutingBlock:(dispatch_block_t)block onQueue:(dispatch_queue_t)queue completionBlock:(nullable MBProgressHUDCompletionBlock)completion { |
| | | self.taskInProgress = YES; |
| | | self.completionBlock = completion; |
| | | dispatch_async(queue, ^(void) { |
| | | block(); |
| | | dispatch_async(dispatch_get_main_queue(), ^(void) { |
| | | [self cleanUp]; |
| | | }); |
| | | }); |
| | | [self showAnimated:animated]; |
| | | } |
| | | |
| | | - (void)cleanUp { |
| | | self.taskInProgress = NO; |
| | | [self hideAnimated:self.useAnimation]; |
| | | } |
| | | |
| | | #pragma mark - Labels |
| | | |
| | | - (NSString *)labelText { |
| | | return self.label.text; |
| | | } |
| | | |
| | | - (void)setLabelText:(NSString *)labelText { |
| | | MBMainThreadAssert(); |
| | | self.label.text = labelText; |
| | | } |
| | | |
| | | - (UIFont *)labelFont { |
| | | return self.label.font; |
| | | } |
| | | |
| | | - (void)setLabelFont:(UIFont *)labelFont { |
| | | MBMainThreadAssert(); |
| | | self.label.font = labelFont; |
| | | } |
| | | |
| | | - (UIColor *)labelColor { |
| | | return self.label.textColor; |
| | | } |
| | | |
| | | - (void)setLabelColor:(UIColor *)labelColor { |
| | | MBMainThreadAssert(); |
| | | self.label.textColor = labelColor; |
| | | } |
| | | |
| | | - (NSString *)detailsLabelText { |
| | | return self.detailsLabel.text; |
| | | } |
| | | |
| | | - (void)setDetailsLabelText:(NSString *)detailsLabelText { |
| | | MBMainThreadAssert(); |
| | | self.detailsLabel.text = detailsLabelText; |
| | | } |
| | | |
| | | - (UIFont *)detailsLabelFont { |
| | | return self.detailsLabel.font; |
| | | } |
| | | |
| | | - (void)setDetailsLabelFont:(UIFont *)detailsLabelFont { |
| | | MBMainThreadAssert(); |
| | | self.detailsLabel.font = detailsLabelFont; |
| | | } |
| | | |
| | | - (UIColor *)detailsLabelColor { |
| | | return self.detailsLabel.textColor; |
| | | } |
| | | |
| | | - (void)setDetailsLabelColor:(UIColor *)detailsLabelColor { |
| | | MBMainThreadAssert(); |
| | | self.detailsLabel.textColor = detailsLabelColor; |
| | | } |
| | | |
| | | - (CGFloat)opacity { |
| | | return _opacity; |
| | | } |
| | | |
| | | - (void)setOpacity:(CGFloat)opacity { |
| | | MBMainThreadAssert(); |
| | | _opacity = opacity; |
| | | } |
| | | |
| | | - (UIColor *)color { |
| | | return self.bezelView.color; |
| | | } |
| | | |
| | | - (void)setColor:(UIColor *)color { |
| | | MBMainThreadAssert(); |
| | | self.bezelView.color = color; |
| | | } |
| | | |
| | | - (CGFloat)yOffset { |
| | | return self.offset.y; |
| | | } |
| | | |
| | | - (void)setYOffset:(CGFloat)yOffset { |
| | | MBMainThreadAssert(); |
| | | self.offset = CGPointMake(self.offset.x, yOffset); |
| | | } |
| | | |
| | | - (CGFloat)xOffset { |
| | | return self.offset.x; |
| | | } |
| | | |
| | | - (void)setXOffset:(CGFloat)xOffset { |
| | | MBMainThreadAssert(); |
| | | self.offset = CGPointMake(xOffset, self.offset.y); |
| | | } |
| | | |
| | | - (CGFloat)cornerRadius { |
| | | return self.bezelView.layer.cornerRadius; |
| | | } |
| | | |
| | | - (void)setCornerRadius:(CGFloat)cornerRadius { |
| | | MBMainThreadAssert(); |
| | | self.bezelView.layer.cornerRadius = cornerRadius; |
| | | } |
| | | |
| | | - (BOOL)dimBackground { |
| | | MBBackgroundView *backgroundView = self.backgroundView; |
| | | UIColor *dimmedColor = [UIColor colorWithWhite:0.f alpha:.2f]; |
| | | return backgroundView.style == MBProgressHUDBackgroundStyleSolidColor && [backgroundView.color isEqual:dimmedColor]; |
| | | } |
| | | |
| | | - (void)setDimBackground:(BOOL)dimBackground { |
| | | MBMainThreadAssert(); |
| | | self.backgroundView.style = MBProgressHUDBackgroundStyleSolidColor; |
| | | self.backgroundView.color = dimBackground ? [UIColor colorWithWhite:0.f alpha:.2f] : [UIColor clearColor]; |
| | | } |
| | | |
| | | - (CGSize)size { |
| | | return self.bezelView.frame.size; |
| | | } |
| | | |
| | | - (UIColor *)activityIndicatorColor { |
| | | return _activityIndicatorColor; |
| | | } |
| | | |
| | | - (void)setActivityIndicatorColor:(UIColor *)activityIndicatorColor { |
| | | if (activityIndicatorColor != _activityIndicatorColor) { |
| | | _activityIndicatorColor = activityIndicatorColor; |
| | | UIActivityIndicatorView *indicator = (UIActivityIndicatorView *)self.indicator; |
| | | if ([indicator isKindOfClass:[UIActivityIndicatorView class]]) { |
| | | [indicator setColor:activityIndicatorColor]; |
| | | } |
| | | } |
| | | } |
| | | |
| | | @end |
| | | |
| | | @implementation MBProgressHUDRoundedButton |
| | | |
| | | #pragma mark - Lifecycle |
| | | |
| | | - (instancetype)initWithFrame:(CGRect)frame { |
| | | self = [super initWithFrame:frame]; |
| | | if (self) { |
| | | CALayer *layer = self.layer; |
| | | layer.borderWidth = 1.f; |
| | | } |
| | | return self; |
| | | } |
| | | |
| | | #pragma mark - Layout |
| | | |
| | | - (void)layoutSubviews { |
| | | [super layoutSubviews]; |
| | | // Fully rounded corners |
| | | CGFloat height = CGRectGetHeight(self.bounds); |
| | | self.layer.cornerRadius = ceil(height / 2.f); |
| | | } |
| | | |
| | | - (CGSize)intrinsicContentSize { |
| | | // Only show if we have associated control events |
| | | if (self.allControlEvents == 0) return CGSizeZero; |
| | | CGSize size = [super intrinsicContentSize]; |
| | | // Add some side padding |
| | | size.width += 20.f; |
| | | return size; |
| | | } |
| | | |
| | | #pragma mark - Color |
| | | |
| | | - (void)setTitleColor:(UIColor *)color forState:(UIControlState)state { |
| | | [super setTitleColor:color forState:state]; |
| | | // Update related colors |
| | | [self setHighlighted:self.highlighted]; |
| | | self.layer.borderColor = color.CGColor; |
| | | } |
| | | |
| | | - (void)setHighlighted:(BOOL)highlighted { |
| | | [super setHighlighted:highlighted]; |
| | | UIColor *baseColor = [self titleColorForState:UIControlStateSelected]; |
| | | self.backgroundColor = highlighted ? [baseColor colorWithAlphaComponent:0.1f] : [UIColor clearColor]; |
| | | } |
| | | |
| | | @end |