// // MRProgressOverlayView.m // MRProgress // // Created by Marius Rackwitz on 09.10.13. // Copyright (c) 2013 Marius Rackwitz. All rights reserved. // //#define MRProgress_EnableUIVisualEffectView #if defined(MRProgress_EnableUIVisualEffectView) #define MR_UIEffectViewIsEnabled 1 #else #define MR_UIEffectViewIsEnabled 0 #endif #define MR_UIEffectViewIsAllowed (MR_UIEffectViewIsEnabled && __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000) #define MR_UIEffectViewIsAvailable (MR_UIEffectViewIsAllowed && NSClassFromString(@"UIVisualEffectView") != nil) #import #import #import "MRProgressOverlayView.h" #import "MRActivityIndicatorView.h" #import "MRBlurView.h" #import "MRCircularProgressView.h" #import "MRIconView.h" #import "MRProgressHelper.h" static const CGFloat MRProgressOverlayViewCornerRadius = 7; static const CGFloat MRProgressOverlayViewMotionEffectExtent = 10; @interface MRProgressOverlayView () { NSDictionary *_savedAttributes; } @property (nonatomic, weak, readwrite) UIView *dialogView; @property (nonatomic, weak, readwrite) UIView *blurView; @property (nonatomic, strong, readwrite) UIView *blurMaskView; @property (nonatomic, weak, readwrite) UILabel *titleLabel; - (UIView *)createModeView; - (UIView *)createViewForMode:(MRProgressOverlayViewMode)mode; - (MRActivityIndicatorView *)createActivityIndicatorView; - (MRActivityIndicatorView *)createSmallActivityIndicatorView; - (UIActivityIndicatorView *)createSmallDefaultActivityIndicatorView; - (MRCircularProgressView *)createCircularProgressView; - (UIProgressView *)createHorizontalBarProgressView; - (MRIconView *)createCheckmarkIconView; - (MRIconView *)createCrossIconView; - (UIView *)createCustomView; - (void)showModeView:(UIView *)modeView; - (void)hideModeView:(UIView *)modeView; - (BOOL)mayStop; - (void)setSubviewTransform:(CGAffineTransform)transform alpha:(CGFloat)alpha; - (void)registerForNotificationCenter; - (void)unregisterFromNotificationCenter; - (void)deviceOrientationDidChange:(NSNotification *)notification; - (void)registerForKVO; - (void)unregisterFromKVO; - (NSArray *)observableKeypaths; - (CGAffineTransform)transformForOrientation; - (NSDictionary *)titleTextAttributesToCopy; @end @implementation MRProgressOverlayView static void *MRProgressOverlayViewObservationContext = &MRProgressOverlayViewObservationContext; #pragma mark - Static helper methods + (instancetype)showOverlayAddedTo:(UIView *)view animated:(BOOL)animated { MRProgressOverlayView *overlayView = [self new]; [view addSubview:overlayView]; [overlayView show:animated]; return overlayView; } + (instancetype)showOverlayAddedTo:(UIView *)view title:(NSString *)title mode:(MRProgressOverlayViewMode)mode animated:(BOOL)animated { MRProgressOverlayView *overlayView = [self new]; overlayView.mode = mode; overlayView.titleLabelText = title; [view addSubview:overlayView]; [overlayView show:animated]; return overlayView; } + (instancetype)showOverlayAddedTo:(UIView *)view title:(NSString *)title mode:(MRProgressOverlayViewMode)mode animated:(BOOL)animated stopBlock:(MRProgressOverlayViewStopBlock)stopBlock { MRProgressOverlayView *overlayView = [self new]; overlayView.mode = mode; overlayView.titleLabelText = title; overlayView.stopBlock = stopBlock; [view addSubview:overlayView]; [overlayView show:animated]; return overlayView; } + (BOOL)dismissOverlayForView:(UIView *)view animated:(BOOL)animated { return [self dismissOverlayForView:view animated:animated completion:nil]; } + (BOOL)dismissOverlayForView:(UIView *)view animated:(BOOL)animated completion:(void(^)())completionBlock { MRProgressOverlayView *overlayView = [self overlayForView:view]; if (overlayView) { [overlayView dismiss:animated completion:completionBlock]; return YES; } return NO; } + (NSUInteger)dismissAllOverlaysForView:(UIView *)view animated:(BOOL)animated { return [self dismissAllOverlaysForView:view animated:animated completion:nil]; } + (NSUInteger)dismissAllOverlaysForView:(UIView *)view animated:(BOOL)animated completion:(void(^)())completionBlock { NSArray *views = [self allOverlaysForView:view]; for (MRProgressOverlayView *overlayView in views) { [overlayView dismiss:animated completion:completionBlock]; } return views.count; } + (instancetype)overlayForView:(UIView *)view { NSEnumerator *subviewsEnum = view.subviews.reverseObjectEnumerator; for (UIView *subview in subviewsEnum) { if ([subview isKindOfClass:self]) { return (MRProgressOverlayView *)subview; } } return nil; } + (NSArray *)allOverlaysForView:(UIView *)view { NSMutableArray *overlays = [NSMutableArray new]; NSArray *subviews = view.subviews; for (UIView *view in subviews) { if ([view isKindOfClass:self]) { [overlays addObject:view]; } } return overlays; } #pragma mark - Initialization - (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; } - (void)commonInit { self.accessibilityViewIsModal = YES; self.hidden = YES; self.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; const CGFloat cornerRadius = MRProgressOverlayViewCornerRadius; // Create blurView self.blurView = [self createBlurView]; // Create container with contents UIView *dialogView = [UIView new]; [self addSubview:dialogView]; self.dialogView = dialogView; [self applyMotionEffects]; // Style the dialog to match the iOS7 UIAlertView dialogView.backgroundColor = UIColor.clearColor; dialogView.layer.cornerRadius = cornerRadius; dialogView.layer.shadowRadius = cornerRadius + 5; dialogView.layer.shadowOpacity = 0.1f; dialogView.layer.shadowOffset = CGSizeMake(-(cornerRadius+5)/2.0f, -(cornerRadius+5)/2.0f); // Create titleLabel UILabel *titleLabel = [UILabel new]; self.titleLabel = titleLabel; self.titleLabel.attributedText = [[NSAttributedString alloc] initWithString:@"Loading ..." attributes:@{ NSForegroundColorAttributeName: UIColor.blackColor, NSFontAttributeName: [UIFont preferredFontForTextStyle:UIFontTextStyleHeadline], NSKernAttributeName: NSNull.null, // turn on auto-kerning }]; titleLabel.accessibilityTraits = UIAccessibilityTraitHeader; titleLabel.textAlignment = NSTextAlignmentCenter; titleLabel.numberOfLines = 0; titleLabel.lineBreakMode = NSLineBreakByWordWrapping; [dialogView addSubview:titleLabel]; // Create modeView [self createModeView]; // Observe key paths and notification center [self registerForKVO]; [self registerForNotificationCenter]; [self tintColorDidChange]; } #pragma mark - Clean up - (void)dealloc { [self unregisterFromKVO]; [self unregisterFromNotificationCenter]; } #pragma mark - Notifications - (void)registerForNotificationCenter { NSNotificationCenter *center = NSNotificationCenter.defaultCenter; [center addObserver:self selector:@selector(deviceOrientationDidChange:) name:UIDeviceOrientationDidChangeNotification object:nil]; } - (void)unregisterFromNotificationCenter { NSNotificationCenter *center = NSNotificationCenter.defaultCenter; [center removeObserver:self]; } - (void)deviceOrientationDidChange:(NSNotification *)notification { if ([self.superview isKindOfClass:UIWindow.class]) { [UIView animateWithDuration:0.3 animations:^{ [self manualLayoutSubviews]; }]; } else { [self manualLayoutSubviews]; } } #pragma mark - Key-Value-Observing - (void)registerForKVO { for (NSString *keyPath in self.observableKeypaths) { [self addObserver:self forKeyPath:keyPath options:NSKeyValueObservingOptionPrior context:MRProgressOverlayViewObservationContext]; } } - (void)unregisterFromKVO { for (NSString *keyPath in self.observableKeypaths) { [self removeObserver:self forKeyPath:keyPath]; } } - (NSArray *)observableKeypaths { return @[@"titleLabel.text"]; } - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { if (context == MRProgressOverlayViewObservationContext) { if ([keyPath isEqualToString:@"titleLabel.text"]) { if ([change[NSKeyValueChangeNotificationIsPriorKey] boolValue]) { _savedAttributes = self.titleTextAttributesToCopy; return; } else { if (!_savedAttributes) { self.titleLabelText = self.titleLabel.text; #if DEBUG NSLog(@"** WARNING - Instance of %@ used automatically setTitleLabelText: internally, instead of titleLabel.text, but some text attributes may been lost.", NSStringFromClass(self.class)); #endif } else { self.titleLabelText = (id)[[NSAttributedString alloc] initWithString:self.titleLabel.text attributes:_savedAttributes]; _savedAttributes = nil; } } } [self manualLayoutSubviews]; return; } [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; } #pragma mark - Create subviews - (UIView *)createBlurView { const CGFloat cornerRadius = MRProgressOverlayViewCornerRadius; if (MR_UIEffectViewIsAvailable) { #if MR_UIEffectViewIsAllowed UIVisualEffectView *effectView = [[UIVisualEffectView alloc] initWithEffect:[UIBlurEffect effectWithStyle:UIBlurEffectStyleExtraLight]]; [self addSubview:effectView]; // Setup mask view UIView *maskView = [UIView new]; maskView.backgroundColor = UIColor.whiteColor; maskView.layer.cornerRadius = cornerRadius; self.blurMaskView = maskView; // Memorize for layout changes effectView.maskView = maskView; return effectView; #endif } else { UIView *blurView = [MRBlurView new]; blurView.alpha = 0.98; blurView.layer.cornerRadius = cornerRadius; [self addSubview:blurView]; return blurView; } } - (UIView *)createModeView { UIView *modeView = [self createViewForMode:self.mode]; self.modeView = modeView; modeView.tintColor = self.tintColor; if ([modeView conformsToProtocol:@protocol(MRStopableView)] && [modeView respondsToSelector:@selector(stopButton)]) { UIButton *stopButton = [((id)modeView) stopButton]; [stopButton addTarget:self action:@selector(modeViewStopButtonTouchUpInside) forControlEvents:UIControlEventTouchUpInside]; } return modeView; } - (void)setModeView:(UIView *)modeView { _modeView = modeView; [self.dialogView addSubview:modeView]; } - (UIView *)createViewForMode:(MRProgressOverlayViewMode)mode { switch (mode) { case MRProgressOverlayViewModeIndeterminate: return [self createActivityIndicatorView]; case MRProgressOverlayViewModeIndeterminateSmall: return [self createSmallActivityIndicatorView]; case MRProgressOverlayViewModeIndeterminateSmallDefault: return [self createSmallDefaultActivityIndicatorView]; case MRProgressOverlayViewModeDeterminateCircular: return [self createCircularProgressView]; case MRProgressOverlayViewModeDeterminateHorizontalBar: return [self createHorizontalBarProgressView]; case MRProgressOverlayViewModeCheckmark: return [self createCheckmarkIconView]; case MRProgressOverlayViewModeCross: return [self createCrossIconView]; case MRProgressOverlayViewModeCustom: return [self createCustomView]; } return nil; } #pragma mark - Mode view factory methods - (MRActivityIndicatorView *)createActivityIndicatorView { // Create activity indicator for indeterminate mode MRActivityIndicatorView *activityIndicatorView = [MRActivityIndicatorView new]; return activityIndicatorView; } - (MRActivityIndicatorView *)createSmallActivityIndicatorView { // Create small activity indicator for text mode MRActivityIndicatorView *smallActivityIndicatorView = [MRActivityIndicatorView new]; smallActivityIndicatorView.hidesWhenStopped = YES; return smallActivityIndicatorView; } - (UIActivityIndicatorView *)createSmallDefaultActivityIndicatorView { // Create small default activity indicator for text mode UIActivityIndicatorView *smallDefaultActivityIndicatorView = [UIActivityIndicatorView new]; smallDefaultActivityIndicatorView.hidesWhenStopped = YES; smallDefaultActivityIndicatorView.activityIndicatorViewStyle = UIActivityIndicatorViewStyleGray; return smallDefaultActivityIndicatorView; } - (MRCircularProgressView *)createCircularProgressView { // Create circular progress view for determinate circular mode MRCircularProgressView *circularProgressView = [MRCircularProgressView new]; return circularProgressView; } - (UIProgressView *)createHorizontalBarProgressView { // Create horizontal progress bar for determinate horizontal bar mode UIProgressView *horizontalBarProgressView = [UIProgressView new]; return horizontalBarProgressView; } - (MRIconView *)createCheckmarkIconView { // Create checkmark icon view for checkmark mode MRCheckmarkIconView *checkmarkIconView = [MRCheckmarkIconView new]; return checkmarkIconView; } - (MRIconView *)createCrossIconView { // Create cross icon view for cross mode MRCrossIconView *crossIconView = [MRCrossIconView new]; return crossIconView; } - (UIView *)createCustomView { // Create custom base view return [UIView new]; } #pragma mark - Title label text - (NSDictionary *)titleTextAttributesToCopy { if (self.titleLabel.text.length > 0) { return [self.titleLabel.attributedText attributesAtIndex:0 effectiveRange:NULL]; } else { return @{}; } } - (void)setTitleLabelText:(NSString *)titleLabelText { self.titleLabelAttributedText = [[NSAttributedString alloc] initWithString:titleLabelText attributes:self.titleTextAttributesToCopy]; [self manualLayoutSubviews]; } - (NSString *)titleLabelText { return self.titleLabel.text; } - (void)setTitleLabelAttributedText:(NSAttributedString *)titleLabelAttributedText { self.titleLabel.attributedText = titleLabelAttributedText; [self manualLayoutSubviews]; } - (NSAttributedString *)titleLabelAttributedText { return self.titleLabel.attributedText; } #pragma mark - Tint color - (void)setTintColor:(UIColor *)tintColor { // Implemented to silent warning super.tintColor = tintColor; } - (void)tintColorDidChange { [super tintColorDidChange]; self.modeView.tintColor = self.tintColor; } #pragma mark - Mode - (void)setMode:(MRProgressOverlayViewMode)mode { [self hideModeView:self.modeView]; _mode = mode; [self showModeView:[self createModeView]]; [self updateModeViewMayStop]; if (!self.hidden) { [self manualLayoutSubviews]; } } - (void)showModeView:(UIView *)modeView { modeView.hidden = NO; if ([modeView respondsToSelector:@selector(startAnimating)]) { [modeView performSelector:@selector(startAnimating)]; } } - (void)hideModeView:(UIView *)modeView { modeView.hidden = YES; if ([modeView respondsToSelector:@selector(stopAnimating)]) { [modeView performSelector:@selector(stopAnimating)]; } } #pragma mark - Stop button - (void)setStopBlock:(MRProgressOverlayViewStopBlock)stopBlock { _stopBlock = stopBlock; if (![self updateModeViewMayStop]) { #if DEBUG NSLog(@"** WARNING - %@: %@ is only valid to call when the mode view supports %@ declared in %@!", NSStringFromClass(self.class), NSStringFromSelector(_cmd), NSStringFromSelector(@selector(setMayStop:)), NSStringFromProtocol(@protocol(MRStopableView))); #endif } } - (BOOL)mayStop { return _stopBlock != nil; } - (BOOL)updateModeViewMayStop { if ([self.modeView conformsToProtocol:@protocol(MRStopableView)] && [self.modeView respondsToSelector:@selector(setMayStop:)]) { [((id)self.modeView) setMayStop:self.mayStop]; return YES; } return NO; } - (void)modeViewStopButtonTouchUpInside { if (self.stopBlock) { self.stopBlock(self); } } #pragma mark - A11y - (BOOL)accessibilityPerformEscape { if (self.mayStop) { [self modeViewStopButtonTouchUpInside]; return YES; } else { return NO; } } #pragma mark - Transitions - (void)setSubviewTransform:(CGAffineTransform)transform alpha:(CGFloat)alpha { self.blurView.transform = transform; self.dialogView.transform = transform; self.alpha = alpha; } - (void)show:(BOOL)animated { [self showModeView:self.modeView]; [self manualLayoutSubviews]; if (animated) { [self setSubviewTransform:CGAffineTransformMakeScale(1.3f, 1.3f) alpha:0.5f]; self.backgroundColor = UIColor.clearColor; } self.hidden = NO; void(^animBlock)() = ^{ [self setSubviewTransform:CGAffineTransformIdentity alpha:1.0f]; self.backgroundColor = [UIColor colorWithWhite:0 alpha:0.4f]; }; if (animated) { [UIView animateWithDuration:0.2f delay:0.0 options:UIViewAnimationOptionCurveEaseInOut animations:animBlock completion:nil]; } else { animBlock(); } UIAccessibilityPostNotification(UIAccessibilityScreenChangedNotification, nil); UIAccessibilityPostNotification(UIAccessibilityAnnouncementNotification, self.titleLabelText); } - (void)dismiss:(BOOL)animated { [self dismiss:animated completion:nil]; } - (void)dismiss:(BOOL)animated completion:(void(^)())completionBlock { [self hide:animated completion:^{ [self removeFromSuperview]; if (completionBlock) { completionBlock(); } }]; } - (void)hide:(BOOL)animated { [self hide:animated completion:nil]; } - (void)hide:(BOOL)animated completion:(void(^)())completionBlock { [self setSubviewTransform:CGAffineTransformIdentity alpha:1.0f]; void(^animBlock)() = ^{ [self setSubviewTransform:CGAffineTransformMakeScale(0.6f, 0.6f) alpha:0.0f]; self.backgroundColor = UIColor.clearColor; }; void(^animCompletionBlock)(BOOL) = ^(BOOL finished) { self.hidden = YES; [self hideModeView:self.modeView]; UIAccessibilityPostNotification(UIAccessibilityScreenChangedNotification, nil); if (completionBlock) { completionBlock(); } }; if (animated) { [UIView animateWithDuration:0.2f delay:0.0 options:UIViewAnimationOptionCurveEaseInOut animations:animBlock completion:animCompletionBlock]; } else { animBlock(); animCompletionBlock(YES); } } #pragma mark - Layout - (CGAffineTransform)transformForOrientation { if ([self.superview isKindOfClass:UIWindow.class]) { return CGAffineTransformMakeRotation(MRRotationForStatusBarOrientation()); } return CGAffineTransformIdentity; } // Don't overwrite layoutSubviews here. This would cause issues with animation. - (void)manualLayoutSubviews { if (!MRSystemVersionGreaterThanOrEqualTo8()) { self.transform = self.transformForOrientation; } CGRect bounds = self.superview.bounds; UIEdgeInsets insets = UIEdgeInsetsZero; if ([self.superview isKindOfClass:[UIScrollView class]]) { UIScrollView *scrollView = (UIScrollView *)self.superview; insets = scrollView.contentInset; } self.center = CGPointMake((bounds.size.width - insets.left - insets.right) / 2.0f, (bounds.size.height - insets.top - insets.bottom) / 2.0f); if (MRSystemVersionGreaterThanOrEqualTo8()) { self.bounds = (CGRect){CGPointZero, bounds.size}; } else { if ([self.superview isKindOfClass:UIWindow.class] && UIInterfaceOrientationIsLandscape(UIApplication.sharedApplication.statusBarOrientation)) { // Swap width and height self.bounds = (CGRect){CGPointZero, {bounds.size.height, bounds.size.width}}; } else { self.bounds = (CGRect){CGPointZero, bounds.size}; } } const CGFloat dialogPadding = 15; const CGFloat modePadding = 30; const CGFloat dialogMargin = 10; const CGFloat dialogMinWidth = 150; const BOOL hasSmallIndicator = self.mode == MRProgressOverlayViewModeIndeterminateSmall || self.mode == MRProgressOverlayViewModeIndeterminateSmallDefault; const BOOL isTextNonEmpty = self.titleLabel.text.length > 0; CGFloat dialogWidth = hasSmallIndicator ? CGRectGetWidth(bounds) - dialogMargin * 2 : dialogMinWidth; if (self.mode == MRProgressOverlayViewModeCustom) { dialogWidth = self.modeView.frame.size.width + 2*modePadding; } CGFloat y = (isTextNonEmpty || hasSmallIndicator) ? 7 : modePadding; CGSize modeViewSize; if (hasSmallIndicator) { modeViewSize = CGSizeMake(20, 20); } if (!self.titleLabel.hidden && isTextNonEmpty) { const CGFloat innerViewWidth = dialogWidth - 2*dialogPadding; CGFloat titleLabelMinX = dialogPadding; CGFloat titleLabelMaxWidth = innerViewWidth; CGFloat offset = 0; if (hasSmallIndicator) { offset = modeViewSize.width + 7; } titleLabelMinX += offset; titleLabelMaxWidth -= offset; y += 3; CGSize titleLabelMaxSize = CGSizeMake(titleLabelMaxWidth, self.bounds.size.height); CGRect boundingRect = [self.titleLabel.attributedText boundingRectWithSize:titleLabelMaxSize options:NSStringDrawingUsesLineFragmentOrigin context:nil]; CGSize titleLabelSize = CGSizeMake(MRCGFloatCeil(boundingRect.size.width), MRCGFloatCeil(boundingRect.size.height)); CGPoint titleLabelOrigin; if (hasSmallIndicator) { CGFloat titleLabelMinWidth = dialogMinWidth - 2*dialogPadding - offset; if (titleLabelSize.width > titleLabelMinWidth) { dialogWidth = titleLabelSize.width + offset + 2*dialogPadding; titleLabelOrigin = CGPointMake(titleLabelMinX, y); } else { dialogWidth = dialogMinWidth; titleLabelOrigin = CGPointMake(titleLabelMinX + (titleLabelMinWidth - titleLabelSize.width) / 2.0f, y); } CGPoint modeViewOrigin = CGPointMake(titleLabelOrigin.x - offset, y + (titleLabelSize.height - modeViewSize.height) / 2.0f); CGRect modeViewFrame = {modeViewOrigin, modeViewSize}; self.modeView.frame = modeViewFrame; } else { titleLabelOrigin = CGPointMake(titleLabelMinX + (titleLabelMaxWidth - titleLabelSize.width) / 2.0f, y); } CGRect titleLabelFrame = {titleLabelOrigin, titleLabelSize}; self.titleLabel.frame = titleLabelFrame; y += CGRectGetMaxY(titleLabelFrame); } else if (hasSmallIndicator) { dialogWidth = modeViewSize.width + 2*y; CGPoint modeViewOrigin = CGPointMake(y, y); CGRect modeViewFrame = {modeViewOrigin, modeViewSize}; self.modeView.frame = modeViewFrame; y += CGRectGetMaxY(modeViewFrame); } if (!hasSmallIndicator) { const CGFloat innerViewWidth = dialogWidth - 2*modePadding; CGRect modeViewFrame; CGFloat paddingBottom = 0; if (self.mode != MRProgressOverlayViewModeDeterminateHorizontalBar) { modeViewFrame = CGRectMake(modePadding, y, innerViewWidth, innerViewWidth); paddingBottom = isTextNonEmpty ? 20 : modePadding; } else { modeViewFrame = CGRectMake(10, y, dialogWidth-20, 5); paddingBottom = 15; } self.modeView.frame = modeViewFrame; y += modeViewFrame.size.height + paddingBottom; } { self.dialogView.frame = MRCenterCGSizeInCGRect(CGSizeMake(dialogWidth, y), self.bounds); if (!CGRectEqualToRect(self.blurView.frame, self.dialogView.frame)) { self.blurView.frame = self.dialogView.frame; if (MR_UIEffectViewIsAvailable) { #if MR_UIEffectViewIsAllowed // As the blurMaskView will be copied internally by UIKit, we have to re-assign // it to the blurView, after we change its layout self.blurMaskView.frame = self.dialogView.bounds; self.blurView.maskView = self.blurMaskView; #endif } } } } #pragma mark - Control progress - (void)setProgress:(float)progress { [self setProgress:progress animated:NO]; } - (void)setProgress:(float)progress animated:(BOOL)animated { assert(progress >= 0 && progress <= 1); _progress = progress; [self applyProgressAnimated:(BOOL)animated]; } - (void)applyProgressAnimated:(BOOL)animated { if ([self.modeView respondsToSelector:@selector(setProgress:animated:)]) { [((id)self.modeView) setProgress:self.progress animated:animated]; } else if ([self.modeView respondsToSelector:@selector(setProgress:)]) { if (animated) { #if DEBUG NSLog(@"** WARNING - %@: %@ is only valid to call when receiver is in a determinate mode or custom view supports %@!", NSStringFromClass(self.class), NSStringFromSelector(_cmd), NSStringFromSelector(@selector(setProgress:animated:))); #endif } [((id)self.modeView) setProgress:self.progress]; } else { NSAssert(self.mode == MRProgressOverlayViewModeDeterminateCircular || self.mode == MRProgressOverlayViewModeDeterminateHorizontalBar, @"Mode must support %@, but doesnot!", NSStringFromSelector(@selector(setProgress:animated:))); #if DEBUG NSLog(@"** ERROR - %@: %@ or %@ are only valid to call when receiver is in a determinate mode!", NSStringFromClass(self.class), NSStringFromSelector(@selector(setProgress:)), NSStringFromSelector(_cmd)); #endif } } #pragma mark - Helper to create UIMotionEffects - (UIInterpolatingMotionEffect *)motionEffectWithKeyPath:(NSString *)keyPath type:(UIInterpolatingMotionEffectType)type { UIInterpolatingMotionEffect *effect = [[UIInterpolatingMotionEffect alloc] initWithKeyPath:keyPath type:type]; effect.minimumRelativeValue = @(-MRProgressOverlayViewMotionEffectExtent); effect.maximumRelativeValue = @(MRProgressOverlayViewMotionEffectExtent); return effect; } - (void)applyMotionEffects { UIMotionEffectGroup *motionEffectGroup = [[UIMotionEffectGroup alloc] init]; motionEffectGroup.motionEffects = @[[self motionEffectWithKeyPath:@"center.x" type:UIInterpolatingMotionEffectTypeTiltAlongHorizontalAxis], [self motionEffectWithKeyPath:@"center.y" type:UIInterpolatingMotionEffectTypeTiltAlongVerticalAxis]]; [self.dialogView addMotionEffect:motionEffectGroup]; [self.blurView addMotionEffect:motionEffectGroup]; } @end