// // SVProgressHUD.h // SVProgressHUD, https://github.com/SVProgressHUD/SVProgressHUD // // Copyright (c) 2011-2019 Sam Vermette and contributors. All rights reserved. // #if !__has_feature(objc_arc) #error SVProgressHUD is ARC only. Either turn on ARC for the project or use -fobjc-arc flag #endif #import "SVProgressHUD.h" #import "SVIndefiniteAnimatedView.h" #import "SVProgressAnimatedView.h" #import "SVRadialGradientLayer.h" NSString * const SVProgressHUDDidReceiveTouchEventNotification = @"SVProgressHUDDidReceiveTouchEventNotification"; NSString * const SVProgressHUDDidTouchDownInsideNotification = @"SVProgressHUDDidTouchDownInsideNotification"; NSString * const SVProgressHUDWillDisappearNotification = @"SVProgressHUDWillDisappearNotification"; NSString * const SVProgressHUDDidDisappearNotification = @"SVProgressHUDDidDisappearNotification"; NSString * const SVProgressHUDWillAppearNotification = @"SVProgressHUDWillAppearNotification"; NSString * const SVProgressHUDDidAppearNotification = @"SVProgressHUDDidAppearNotification"; NSString * const SVProgressHUDStatusUserInfoKey = @"SVProgressHUDStatusUserInfoKey"; static const CGFloat SVProgressHUDParallaxDepthPoints = 10.0f; static const CGFloat SVProgressHUDUndefinedProgress = -1; static const CGFloat SVProgressHUDDefaultAnimationDuration = 0.15f; static const CGFloat SVProgressHUDVerticalSpacing = 12.0f; static const CGFloat SVProgressHUDHorizontalSpacing = 12.0f; static const CGFloat SVProgressHUDLabelSpacing = 8.0f; @interface SVProgressHUD () @property (nonatomic, strong) NSTimer *graceTimer; @property (nonatomic, strong) NSTimer *fadeOutTimer; @property (nonatomic, strong) UIControl *controlView; @property (nonatomic, strong) UIView *backgroundView; @property (nonatomic, strong) SVRadialGradientLayer *backgroundRadialGradientLayer; @property (nonatomic, strong) UIVisualEffectView *hudView; @property (nonatomic, strong) UIBlurEffect *hudViewCustomBlurEffect; @property (nonatomic, strong) UILabel *statusLabel; @property (nonatomic, strong) UIImageView *imageView; @property (nonatomic, strong) UIView *indefiniteAnimatedView; @property (nonatomic, strong) SVProgressAnimatedView *ringView; @property (nonatomic, strong) SVProgressAnimatedView *backgroundRingView; @property (nonatomic, readwrite) CGFloat progress; @property (nonatomic, readwrite) NSUInteger activityCount; @property (nonatomic, readonly) CGFloat visibleKeyboardHeight; @property (nonatomic, readonly) UIWindow *frontWindow; #if TARGET_OS_IOS && __IPHONE_OS_VERSION_MAX_ALLOWED >= 100000 @property (nonatomic, strong) UINotificationFeedbackGenerator *hapticGenerator NS_AVAILABLE_IOS(10_0); #endif @end @implementation SVProgressHUD { BOOL _isInitializing; } + (SVProgressHUD*)sharedView { static dispatch_once_t once; static SVProgressHUD *sharedView; #if !defined(SV_APP_EXTENSIONS) if (@available(iOS 13.0, *)) { dispatch_once(&once, ^{ NSPredicate *foregroundActivePredict = [NSPredicate predicateWithBlock:^BOOL(id _Nullable evaluatedObject, NSDictionary * _Nullable bindings) { return ([evaluatedObject activationState] == UISceneActivationStateForegroundActive); }]; NSSet *setOfScene = [[[UIApplication sharedApplication] connectedScenes] filteredSetUsingPredicate:foregroundActivePredict]; UIWindowScene *windowScene = (UIWindowScene *)[[setOfScene allObjects] firstObject]; UIWindow *current = [[UIWindow alloc] initWithWindowScene:windowScene]; sharedView = [[self alloc] initWithFrame:current.bounds]; }); }else{ // 创建单例对象 dispatch_once(&once, ^{ sharedView = [[self alloc] initWithFrame:[[[UIApplication sharedApplication] delegate] window].bounds]; }); } #else dispatch_once(&once, ^{ sharedView = [[self alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; }); #endif return sharedView; } #pragma mark - Setters + (void)setStatus:(NSString*)status { [[self sharedView] setStatus:status]; } + (void)setDefaultStyle:(SVProgressHUDStyle)style { [self sharedView].defaultStyle = style; } + (void)setDefaultMaskType:(SVProgressHUDMaskType)maskType { [self sharedView].defaultMaskType = maskType; } + (void)setDefaultAnimationType:(SVProgressHUDAnimationType)type { [self sharedView].defaultAnimationType = type; } + (void)setContainerView:(nullable UIView*)containerView { [self sharedView].containerView = containerView; } + (void)setMinimumSize:(CGSize)minimumSize { [self sharedView].minimumSize = minimumSize; } + (void)setRingThickness:(CGFloat)ringThickness { [self sharedView].ringThickness = ringThickness; } + (void)setRingRadius:(CGFloat)radius { [self sharedView].ringRadius = radius; } + (void)setRingNoTextRadius:(CGFloat)radius { [self sharedView].ringNoTextRadius = radius; } + (void)setCornerRadius:(CGFloat)cornerRadius { [self sharedView].cornerRadius = cornerRadius; } + (void)setBorderColor:(nonnull UIColor*)color { [self sharedView].hudView.layer.borderColor = color.CGColor; } + (void)setBorderWidth:(CGFloat)width { [self sharedView].hudView.layer.borderWidth = width; } + (void)setFont:(UIFont*)font { [self sharedView].font = font; } + (void)setForegroundColor:(UIColor*)color { [self sharedView].foregroundColor = color; [self setDefaultStyle:SVProgressHUDStyleCustom]; } + (void)setForegroundImageColor:(UIColor *)color { [self sharedView].foregroundImageColor = color; [self setDefaultStyle:SVProgressHUDStyleCustom]; } + (void)setBackgroundColor:(UIColor*)color { [self sharedView].backgroundColor = color; [self setDefaultStyle:SVProgressHUDStyleCustom]; } + (void)setHudViewCustomBlurEffect:(UIBlurEffect*)blurEffect { [self sharedView].hudViewCustomBlurEffect = blurEffect; [self setDefaultStyle:SVProgressHUDStyleCustom]; } + (void)setBackgroundLayerColor:(UIColor*)color { [self sharedView].backgroundLayerColor = color; } + (void)setImageViewSize:(CGSize)size { [self sharedView].imageViewSize = size; } + (void)setShouldTintImages:(BOOL)shouldTintImages { [self sharedView].shouldTintImages = shouldTintImages; } + (void)setInfoImage:(UIImage*)image { [self sharedView].infoImage = image; } + (void)setSuccessImage:(UIImage*)image { [self sharedView].successImage = image; } + (void)setErrorImage:(UIImage*)image { [self sharedView].errorImage = image; } + (void)setViewForExtension:(UIView*)view { [self sharedView].viewForExtension = view; } + (void)setGraceTimeInterval:(NSTimeInterval)interval { [self sharedView].graceTimeInterval = interval; } + (void)setMinimumDismissTimeInterval:(NSTimeInterval)interval { [self sharedView].minimumDismissTimeInterval = interval; } + (void)setMaximumDismissTimeInterval:(NSTimeInterval)interval { [self sharedView].maximumDismissTimeInterval = interval; } + (void)setFadeInAnimationDuration:(NSTimeInterval)duration { [self sharedView].fadeInAnimationDuration = duration; } + (void)setFadeOutAnimationDuration:(NSTimeInterval)duration { [self sharedView].fadeOutAnimationDuration = duration; } + (void)setMaxSupportedWindowLevel:(UIWindowLevel)windowLevel { [self sharedView].maxSupportedWindowLevel = windowLevel; } + (void)setHapticsEnabled:(BOOL)hapticsEnabled { [self sharedView].hapticsEnabled = hapticsEnabled; } + (void)setMotionEffectEnabled:(BOOL)motionEffectEnabled { [self sharedView].motionEffectEnabled = motionEffectEnabled; } #pragma mark - Show Methods + (void)show { [self showWithStatus:nil]; } + (void)showWithMaskType:(SVProgressHUDMaskType)maskType { SVProgressHUDMaskType existingMaskType = [self sharedView].defaultMaskType; [self setDefaultMaskType:maskType]; [self show]; [self setDefaultMaskType:existingMaskType]; } + (void)showWithStatus:(NSString*)status { [self showProgress:SVProgressHUDUndefinedProgress status:status]; } + (void)showWithStatus:(NSString*)status maskType:(SVProgressHUDMaskType)maskType { SVProgressHUDMaskType existingMaskType = [self sharedView].defaultMaskType; [self setDefaultMaskType:maskType]; [self showWithStatus:status]; [self setDefaultMaskType:existingMaskType]; } + (void)showProgress:(float)progress { [self showProgress:progress status:nil]; } + (void)showProgress:(float)progress maskType:(SVProgressHUDMaskType)maskType { SVProgressHUDMaskType existingMaskType = [self sharedView].defaultMaskType; [self setDefaultMaskType:maskType]; [self showProgress:progress]; [self setDefaultMaskType:existingMaskType]; } + (void)showProgress:(float)progress status:(NSString*)status { [[self sharedView] showProgress:progress status:status]; } + (void)showProgress:(float)progress status:(NSString*)status maskType:(SVProgressHUDMaskType)maskType { SVProgressHUDMaskType existingMaskType = [self sharedView].defaultMaskType; [self setDefaultMaskType:maskType]; [self showProgress:progress status:status]; [self setDefaultMaskType:existingMaskType]; } #pragma mark - Show, then automatically dismiss methods + (void)showInfoWithStatus:(NSString*)status { [self showImage:[self sharedView].infoImage status:status]; #if TARGET_OS_IOS && __IPHONE_OS_VERSION_MAX_ALLOWED >= 100000 if (@available(iOS 10.0, *)) { dispatch_async(dispatch_get_main_queue(), ^{ [[self sharedView].hapticGenerator notificationOccurred:UINotificationFeedbackTypeWarning]; }); } #endif } + (void)showInfoWithStatus:(NSString*)status maskType:(SVProgressHUDMaskType)maskType { SVProgressHUDMaskType existingMaskType = [self sharedView].defaultMaskType; [self setDefaultMaskType:maskType]; [self showInfoWithStatus:status]; [self setDefaultMaskType:existingMaskType]; } + (void)showSuccessWithStatus:(NSString*)status { [self showImage:[self sharedView].successImage status:status]; #if TARGET_OS_IOS && __IPHONE_OS_VERSION_MAX_ALLOWED >= 100000 if (@available(iOS 10, *)) { dispatch_async(dispatch_get_main_queue(), ^{ [[self sharedView].hapticGenerator notificationOccurred:UINotificationFeedbackTypeSuccess]; }); } #endif } + (void)showSuccessWithStatus:(NSString*)status maskType:(SVProgressHUDMaskType)maskType { SVProgressHUDMaskType existingMaskType = [self sharedView].defaultMaskType; [self setDefaultMaskType:maskType]; [self showSuccessWithStatus:status]; [self setDefaultMaskType:existingMaskType]; #if TARGET_OS_IOS && __IPHONE_OS_VERSION_MAX_ALLOWED >= 100000 if (@available(iOS 10.0, *)) { dispatch_async(dispatch_get_main_queue(), ^{ [[self sharedView].hapticGenerator notificationOccurred:UINotificationFeedbackTypeSuccess]; }); } #endif } + (void)showErrorWithStatus:(NSString*)status { [self showImage:[self sharedView].errorImage status:status]; #if TARGET_OS_IOS && __IPHONE_OS_VERSION_MAX_ALLOWED >= 100000 if (@available(iOS 10.0, *)) { dispatch_async(dispatch_get_main_queue(), ^{ [[self sharedView].hapticGenerator notificationOccurred:UINotificationFeedbackTypeError]; }); } #endif } + (void)showErrorWithStatus:(NSString*)status maskType:(SVProgressHUDMaskType)maskType { SVProgressHUDMaskType existingMaskType = [self sharedView].defaultMaskType; [self setDefaultMaskType:maskType]; [self showErrorWithStatus:status]; [self setDefaultMaskType:existingMaskType]; #if TARGET_OS_IOS && __IPHONE_OS_VERSION_MAX_ALLOWED >= 100000 if (@available(iOS 10.0, *)) { dispatch_async(dispatch_get_main_queue(), ^{ [[self sharedView].hapticGenerator notificationOccurred:UINotificationFeedbackTypeError]; }); } #endif } + (void)showImage:(UIImage*)image status:(NSString*)status { NSTimeInterval displayInterval = [self displayDurationForString:status]; [[self sharedView] showImage:image status:status duration:displayInterval]; } + (void)showImage:(UIImage*)image status:(NSString*)status maskType:(SVProgressHUDMaskType)maskType { SVProgressHUDMaskType existingMaskType = [self sharedView].defaultMaskType; [self setDefaultMaskType:maskType]; [self showImage:image status:status]; [self setDefaultMaskType:existingMaskType]; } #pragma mark - Dismiss Methods + (void)popActivity { if([self sharedView].activityCount > 0) { [self sharedView].activityCount--; } if([self sharedView].activityCount == 0) { [[self sharedView] dismiss]; } } + (void)dismiss { [self dismissWithDelay:0.0 completion:nil]; } + (void)dismissWithCompletion:(SVProgressHUDDismissCompletion)completion { [self dismissWithDelay:0.0 completion:completion]; } + (void)dismissWithDelay:(NSTimeInterval)delay { [self dismissWithDelay:delay completion:nil]; } + (void)dismissWithDelay:(NSTimeInterval)delay completion:(SVProgressHUDDismissCompletion)completion { [[self sharedView] dismissWithDelay:delay completion:completion]; } #pragma mark - Offset + (void)setOffsetFromCenter:(UIOffset)offset { [self sharedView].offsetFromCenter = offset; } + (void)resetOffsetFromCenter { [self setOffsetFromCenter:UIOffsetZero]; } #pragma mark - Instance Methods - (instancetype)initWithFrame:(CGRect)frame { if((self = [super initWithFrame:frame])) { _isInitializing = YES; self.userInteractionEnabled = NO; self.activityCount = 0; self.backgroundView.alpha = 0.0f; self.imageView.alpha = 0.0f; self.statusLabel.alpha = 0.0f; self.indefiniteAnimatedView.alpha = 0.0f; self.ringView.alpha = self.backgroundRingView.alpha = 0.0f; _backgroundColor = [UIColor whiteColor]; _foregroundColor = [UIColor blackColor]; _backgroundLayerColor = [UIColor colorWithWhite:0 alpha:0.4]; // Set default values _defaultMaskType = SVProgressHUDMaskTypeNone; _defaultStyle = SVProgressHUDStyleLight; _defaultAnimationType = SVProgressHUDAnimationTypeFlat; _minimumSize = CGSizeZero; _font = [UIFont preferredFontForTextStyle:UIFontTextStyleSubheadline]; _imageViewSize = CGSizeMake(28.0f, 28.0f); _shouldTintImages = YES; NSBundle *bundle = [NSBundle bundleForClass:[SVProgressHUD class]]; NSURL *url = [bundle URLForResource:@"SVProgressHUD" withExtension:@"bundle"]; NSBundle *imageBundle = [NSBundle bundleWithURL:url]; _infoImage = [UIImage imageWithContentsOfFile:[imageBundle pathForResource:@"info" ofType:@"png"]]; _successImage = [UIImage imageWithContentsOfFile:[imageBundle pathForResource:@"success" ofType:@"png"]]; _errorImage = [UIImage imageWithContentsOfFile:[imageBundle pathForResource:@"error" ofType:@"png"]]; _ringThickness = 2.0f; _ringRadius = 18.0f; _ringNoTextRadius = 24.0f; _cornerRadius = 14.0f; _graceTimeInterval = 0.0f; _minimumDismissTimeInterval = 5.0; _maximumDismissTimeInterval = CGFLOAT_MAX; _fadeInAnimationDuration = SVProgressHUDDefaultAnimationDuration; _fadeOutAnimationDuration = SVProgressHUDDefaultAnimationDuration; _maxSupportedWindowLevel = UIWindowLevelNormal; _hapticsEnabled = NO; _motionEffectEnabled = YES; // Accessibility support self.accessibilityIdentifier = @"SVProgressHUD"; self.isAccessibilityElement = YES; _isInitializing = NO; } return self; } - (void)updateHUDFrame { // Check if an image or progress ring is displayed BOOL imageUsed = (self.imageView.image) && !(self.imageView.hidden); BOOL progressUsed = self.imageView.hidden; // Calculate size of string CGRect labelRect = CGRectZero; CGFloat labelHeight = 0.0f; CGFloat labelWidth = 0.0f; if(self.statusLabel.text) { CGSize constraintSize = CGSizeMake(200.0f, 300.0f); labelRect = [self.statusLabel.text boundingRectWithSize:constraintSize options:(NSStringDrawingOptions)(NSStringDrawingUsesFontLeading | NSStringDrawingTruncatesLastVisibleLine | NSStringDrawingUsesLineFragmentOrigin) attributes:@{NSFontAttributeName: self.statusLabel.font} context:NULL]; labelHeight = ceilf(CGRectGetHeight(labelRect)); labelWidth = ceilf(CGRectGetWidth(labelRect)); } // Calculate hud size based on content // For the beginning use default values, these // might get update if string is too large etc. CGFloat hudWidth; CGFloat hudHeight; CGFloat contentWidth = 0.0f; CGFloat contentHeight = 0.0f; if(imageUsed || progressUsed) { contentWidth = CGRectGetWidth(imageUsed ? self.imageView.frame : self.indefiniteAnimatedView.frame); contentHeight = CGRectGetHeight(imageUsed ? self.imageView.frame : self.indefiniteAnimatedView.frame); } // |-spacing-content-spacing-| hudWidth = SVProgressHUDHorizontalSpacing + MAX(labelWidth, contentWidth) + SVProgressHUDHorizontalSpacing; // |-spacing-content-(labelSpacing-label-)spacing-| hudHeight = SVProgressHUDVerticalSpacing + labelHeight + contentHeight + SVProgressHUDVerticalSpacing; if(self.statusLabel.text && (imageUsed || progressUsed)){ // Add spacing if both content and label are used hudHeight += SVProgressHUDLabelSpacing; } // Update values on subviews self.hudView.bounds = CGRectMake(0.0f, 0.0f, MAX(self.minimumSize.width, hudWidth), MAX(self.minimumSize.height, hudHeight)); // Animate value update [CATransaction begin]; [CATransaction setDisableActions:YES]; // Spinner and image view CGFloat centerY; if(self.statusLabel.text) { CGFloat yOffset = MAX(SVProgressHUDVerticalSpacing, (self.minimumSize.height - contentHeight - SVProgressHUDLabelSpacing - labelHeight) / 2.0f); centerY = yOffset + contentHeight / 2.0f; } else { centerY = CGRectGetMidY(self.hudView.bounds); } self.indefiniteAnimatedView.center = CGPointMake(CGRectGetMidX(self.hudView.bounds), centerY); if(self.progress != SVProgressHUDUndefinedProgress) { self.backgroundRingView.center = self.ringView.center = CGPointMake(CGRectGetMidX(self.hudView.bounds), centerY); } self.imageView.center = CGPointMake(CGRectGetMidX(self.hudView.bounds), centerY); // Label if(imageUsed || progressUsed) { centerY = CGRectGetMaxY(imageUsed ? self.imageView.frame : self.indefiniteAnimatedView.frame) + SVProgressHUDLabelSpacing + labelHeight / 2.0f; } else { centerY = CGRectGetMidY(self.hudView.bounds); } self.statusLabel.frame = labelRect; self.statusLabel.center = CGPointMake(CGRectGetMidX(self.hudView.bounds), centerY); [CATransaction commit]; } #if TARGET_OS_IOS - (void)updateMotionEffectForOrientation:(UIInterfaceOrientation)orientation { UIInterpolatingMotionEffectType xMotionEffectType = UIInterfaceOrientationIsPortrait(orientation) ? UIInterpolatingMotionEffectTypeTiltAlongHorizontalAxis : UIInterpolatingMotionEffectTypeTiltAlongVerticalAxis; UIInterpolatingMotionEffectType yMotionEffectType = UIInterfaceOrientationIsPortrait(orientation) ? UIInterpolatingMotionEffectTypeTiltAlongVerticalAxis : UIInterpolatingMotionEffectTypeTiltAlongHorizontalAxis; [self updateMotionEffectForXMotionEffectType:xMotionEffectType yMotionEffectType:yMotionEffectType]; } #endif - (void)updateMotionEffectForXMotionEffectType:(UIInterpolatingMotionEffectType)xMotionEffectType yMotionEffectType:(UIInterpolatingMotionEffectType)yMotionEffectType { UIInterpolatingMotionEffect *effectX = [[UIInterpolatingMotionEffect alloc] initWithKeyPath:@"center.x" type:xMotionEffectType]; effectX.minimumRelativeValue = @(-SVProgressHUDParallaxDepthPoints); effectX.maximumRelativeValue = @(SVProgressHUDParallaxDepthPoints); UIInterpolatingMotionEffect *effectY = [[UIInterpolatingMotionEffect alloc] initWithKeyPath:@"center.y" type:yMotionEffectType]; effectY.minimumRelativeValue = @(-SVProgressHUDParallaxDepthPoints); effectY.maximumRelativeValue = @(SVProgressHUDParallaxDepthPoints); UIMotionEffectGroup *effectGroup = [UIMotionEffectGroup new]; effectGroup.motionEffects = @[effectX, effectY]; // Clear old motion effect, then add new motion effects self.hudView.motionEffects = @[]; [self.hudView addMotionEffect:effectGroup]; } - (void)updateViewHierarchy { // Add the overlay to the application window if necessary if(!self.controlView.superview) { if(self.containerView){ [self.containerView addSubview:self.controlView]; } else { #if !defined(SV_APP_EXTENSIONS) [self.frontWindow addSubview:self.controlView]; #else // If SVProgressHUD is used inside an app extension add it to the given view if(self.viewForExtension) { [self.viewForExtension addSubview:self.controlView]; } #endif } } else { // The HUD is already on screen, but maybe not in front. Therefore // ensure that overlay will be on top of rootViewController (which may // be changed during runtime). [self.controlView.superview bringSubviewToFront:self.controlView]; } // Add self to the overlay view if(!self.superview) { [self.controlView addSubview:self]; } } - (void)setStatus:(NSString*)status { self.statusLabel.text = status; self.statusLabel.hidden = status.length == 0; [self updateHUDFrame]; } - (void)setGraceTimer:(NSTimer*)timer { if(_graceTimer) { [_graceTimer invalidate]; _graceTimer = nil; } if(timer) { _graceTimer = timer; } } - (void)setFadeOutTimer:(NSTimer*)timer { if(_fadeOutTimer) { [_fadeOutTimer invalidate]; _fadeOutTimer = nil; } if(timer) { _fadeOutTimer = timer; } } #pragma mark - Notifications and their handling - (void)registerNotifications { #if TARGET_OS_IOS [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(positionHUD:) name:UIApplicationDidChangeStatusBarOrientationNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(positionHUD:) name:UIKeyboardWillHideNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(positionHUD:) name:UIKeyboardDidHideNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(positionHUD:) name:UIKeyboardWillShowNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(positionHUD:) name:UIKeyboardDidShowNotification object:nil]; #endif [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(positionHUD:) name:UIApplicationDidBecomeActiveNotification object:nil]; } - (NSDictionary*)notificationUserInfo { return (self.statusLabel.text ? @{SVProgressHUDStatusUserInfoKey : self.statusLabel.text} : nil); } - (void)positionHUD:(NSNotification*)notification { CGFloat keyboardHeight = 0.0f; double animationDuration = 0.0; #if !defined(SV_APP_EXTENSIONS) && TARGET_OS_IOS self.frame = [self getCurrentWindow].bounds; UIInterfaceOrientation orientation = UIApplication.sharedApplication.statusBarOrientation; #elif !defined(SV_APP_EXTENSIONS) && !TARGET_OS_IOS self.frame= [UIApplication sharedApplication].keyWindow.bounds; #else if (self.viewForExtension) { self.frame = self.viewForExtension.frame; } else { self.frame = UIScreen.mainScreen.bounds; } #if TARGET_OS_IOS UIInterfaceOrientation orientation = CGRectGetWidth(self.frame) > CGRectGetHeight(self.frame) ? UIInterfaceOrientationLandscapeLeft : UIInterfaceOrientationPortrait; #endif #endif #if TARGET_OS_IOS // Get keyboardHeight in regard to current state if(notification) { NSDictionary* keyboardInfo = [notification userInfo]; CGRect keyboardFrame = [keyboardInfo[UIKeyboardFrameBeginUserInfoKey] CGRectValue]; animationDuration = [keyboardInfo[UIKeyboardAnimationDurationUserInfoKey] doubleValue]; if(notification.name == UIKeyboardWillShowNotification || notification.name == UIKeyboardDidShowNotification) { keyboardHeight = CGRectGetWidth(keyboardFrame); if(UIInterfaceOrientationIsPortrait(orientation)) { keyboardHeight = CGRectGetHeight(keyboardFrame); } } } else { keyboardHeight = self.visibleKeyboardHeight; } #endif // Get the currently active frame of the display (depends on orientation) CGRect orientationFrame = self.bounds; #if !defined(SV_APP_EXTENSIONS) && TARGET_OS_IOS CGRect statusBarFrame = UIApplication.sharedApplication.statusBarFrame; #else CGRect statusBarFrame = CGRectZero; #endif if (_motionEffectEnabled) { #if TARGET_OS_IOS // Update the motion effects in regard to orientation [self updateMotionEffectForOrientation:orientation]; #else [self updateMotionEffectForXMotionEffectType:UIInterpolatingMotionEffectTypeTiltAlongHorizontalAxis yMotionEffectType:UIInterpolatingMotionEffectTypeTiltAlongHorizontalAxis]; #endif } // Calculate available height for display CGFloat activeHeight = CGRectGetHeight(orientationFrame); if(keyboardHeight > 0) { activeHeight += CGRectGetHeight(statusBarFrame) * 2; } activeHeight -= keyboardHeight; CGFloat posX = CGRectGetMidX(orientationFrame); CGFloat posY = floorf(activeHeight*0.45f); CGFloat rotateAngle = 0.0; CGPoint newCenter = CGPointMake(posX, posY); if(notification) { // Animate update if notification was present [UIView animateWithDuration:animationDuration delay:0 options:(UIViewAnimationOptions) (UIViewAnimationOptionAllowUserInteraction | UIViewAnimationOptionBeginFromCurrentState) animations:^{ [self moveToPoint:newCenter rotateAngle:rotateAngle]; [self.hudView setNeedsDisplay]; } completion:nil]; } else { [self moveToPoint:newCenter rotateAngle:rotateAngle]; } } - (void)moveToPoint:(CGPoint)newCenter rotateAngle:(CGFloat)angle { self.hudView.transform = CGAffineTransformMakeRotation(angle); if (self.containerView) { self.hudView.center = CGPointMake(self.containerView.center.x + self.offsetFromCenter.horizontal, self.containerView.center.y + self.offsetFromCenter.vertical); } else { self.hudView.center = CGPointMake(newCenter.x + self.offsetFromCenter.horizontal, newCenter.y + self.offsetFromCenter.vertical); } } #pragma mark - Event handling - (void)controlViewDidReceiveTouchEvent:(id)sender forEvent:(UIEvent*)event { [[NSNotificationCenter defaultCenter] postNotificationName:SVProgressHUDDidReceiveTouchEventNotification object:self userInfo:[self notificationUserInfo]]; UITouch *touch = event.allTouches.anyObject; CGPoint touchLocation = [touch locationInView:self]; if(CGRectContainsPoint(self.hudView.frame, touchLocation)) { [[NSNotificationCenter defaultCenter] postNotificationName:SVProgressHUDDidTouchDownInsideNotification object:self userInfo:[self notificationUserInfo]]; } } #pragma mark - Master show/dismiss methods - (void)showProgress:(float)progress status:(NSString*)status { __weak SVProgressHUD *weakSelf = self; [[NSOperationQueue mainQueue] addOperationWithBlock:^{ __strong SVProgressHUD *strongSelf = weakSelf; if(strongSelf){ if(strongSelf.fadeOutTimer) { strongSelf.activityCount = 0; } // Stop timer strongSelf.fadeOutTimer = nil; strongSelf.graceTimer = nil; // Update / Check view hierarchy to ensure the HUD is visible [strongSelf updateViewHierarchy]; // Reset imageView and fadeout timer if an image is currently displayed strongSelf.imageView.hidden = YES; strongSelf.imageView.image = nil; // Update text and set progress to the given value strongSelf.statusLabel.hidden = status.length == 0; strongSelf.statusLabel.text = status; strongSelf.progress = progress; // Choose the "right" indicator depending on the progress if(progress >= 0) { // Cancel the indefiniteAnimatedView, then show the ringLayer [strongSelf cancelIndefiniteAnimatedViewAnimation]; // Add ring to HUD if(!strongSelf.ringView.superview){ [strongSelf.hudView.contentView addSubview:strongSelf.ringView]; } if(!strongSelf.backgroundRingView.superview){ [strongSelf.hudView.contentView addSubview:strongSelf.backgroundRingView]; } // Set progress animated [CATransaction begin]; [CATransaction setDisableActions:YES]; strongSelf.ringView.strokeEnd = progress; [CATransaction commit]; // Update the activity count if(progress == 0) { strongSelf.activityCount++; } } else { // Cancel the ringLayer animation, then show the indefiniteAnimatedView [strongSelf cancelRingLayerAnimation]; // Add indefiniteAnimatedView to HUD [strongSelf.hudView.contentView addSubview:strongSelf.indefiniteAnimatedView]; if([strongSelf.indefiniteAnimatedView respondsToSelector:@selector(startAnimating)]) { [(id)strongSelf.indefiniteAnimatedView startAnimating]; } // Update the activity count strongSelf.activityCount++; } // Fade in delayed if a grace time is set if (self.graceTimeInterval > 0.0 && self.backgroundView.alpha == 0.0f) { strongSelf.graceTimer = [NSTimer timerWithTimeInterval:self.graceTimeInterval target:strongSelf selector:@selector(fadeIn:) userInfo:nil repeats:NO]; [[NSRunLoop mainRunLoop] addTimer:strongSelf.graceTimer forMode:NSRunLoopCommonModes]; } else { [strongSelf fadeIn:nil]; } // Tell the Haptics Generator to prepare for feedback, which may come soon #if TARGET_OS_IOS && __IPHONE_OS_VERSION_MAX_ALLOWED >= 100000 if (@available(iOS 10.0, *)) { [strongSelf.hapticGenerator prepare]; } #endif } }]; } - (void)showImage:(UIImage*)image status:(NSString*)status duration:(NSTimeInterval)duration { __weak SVProgressHUD *weakSelf = self; [[NSOperationQueue mainQueue] addOperationWithBlock:^{ __strong SVProgressHUD *strongSelf = weakSelf; if(strongSelf){ // Stop timer strongSelf.fadeOutTimer = nil; strongSelf.graceTimer = nil; // Update / Check view hierarchy to ensure the HUD is visible [strongSelf updateViewHierarchy]; // Reset progress and cancel any running animation strongSelf.progress = SVProgressHUDUndefinedProgress; [strongSelf cancelRingLayerAnimation]; [strongSelf cancelIndefiniteAnimatedViewAnimation]; // Update imageView if (self.shouldTintImages) { if (image.renderingMode != UIImageRenderingModeAlwaysTemplate) { strongSelf.imageView.image = [image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; } strongSelf.imageView.tintColor = strongSelf.foregroundImageColorForStyle; } else { strongSelf.imageView.image = image; } strongSelf.imageView.hidden = NO; // Update text strongSelf.statusLabel.hidden = status.length == 0; strongSelf.statusLabel.text = status; // Fade in delayed if a grace time is set // An image will be dismissed automatically. Thus pass the duration as userInfo. if (self.graceTimeInterval > 0.0 && self.backgroundView.alpha == 0.0f) { strongSelf.graceTimer = [NSTimer timerWithTimeInterval:self.graceTimeInterval target:strongSelf selector:@selector(fadeIn:) userInfo:@(duration) repeats:NO]; [[NSRunLoop mainRunLoop] addTimer:strongSelf.graceTimer forMode:NSRunLoopCommonModes]; } else { [strongSelf fadeIn:@(duration)]; } } }]; } - (void)fadeIn:(id)data { // Update the HUDs frame to the new content and position HUD [self updateHUDFrame]; [self positionHUD:nil]; // Update accessibility as well as user interaction // \n cause to read text twice so remove "\n" new line character before setting up accessiblity label NSString *accessibilityString = [[self.statusLabel.text componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]] componentsJoinedByString:@" "]; if(self.defaultMaskType != SVProgressHUDMaskTypeNone) { self.controlView.userInteractionEnabled = YES; self.accessibilityLabel = accessibilityString ?: NSLocalizedString(@"Loading", nil); self.isAccessibilityElement = YES; self.controlView.accessibilityViewIsModal = YES; } else { self.controlView.userInteractionEnabled = NO; self.hudView.accessibilityLabel = accessibilityString ?: NSLocalizedString(@"Loading", nil); self.hudView.isAccessibilityElement = YES; self.controlView.accessibilityViewIsModal = NO; } // Get duration id duration = [data isKindOfClass:[NSTimer class]] ? ((NSTimer *)data).userInfo : data; // Show if not already visible if(self.backgroundView.alpha != 1.0f) { // Post notification to inform user [[NSNotificationCenter defaultCenter] postNotificationName:SVProgressHUDWillAppearNotification object:self userInfo:[self notificationUserInfo]]; // Shrink HUD to to make a nice appear / pop up animation self.hudView.transform = self.hudView.transform = CGAffineTransformScale(self.hudView.transform, 1/1.5f, 1/1.5f); __block void (^animationsBlock)(void) = ^{ // Zoom HUD a little to make a nice appear / pop up animation self.hudView.transform = CGAffineTransformIdentity; // Fade in all effects (colors, blur, etc.) [self fadeInEffects]; }; __block void (^completionBlock)(void) = ^{ // Check if we really achieved to show the HUD (<=> alpha) // and the change of these values has not been cancelled in between e.g. due to a dismissal if(self.backgroundView.alpha == 1.0f){ // Register observer <=> we now have to handle orientation changes etc. [self registerNotifications]; // Post notification to inform user [[NSNotificationCenter defaultCenter] postNotificationName:SVProgressHUDDidAppearNotification object:self userInfo:[self notificationUserInfo]]; // Update accessibility UIAccessibilityPostNotification(UIAccessibilityScreenChangedNotification, nil); UIAccessibilityPostNotification(UIAccessibilityAnnouncementNotification, self.statusLabel.text); // Dismiss automatically if a duration was passed as userInfo. We start a timer // which then will call dismiss after the predefined duration if(duration){ self.fadeOutTimer = [NSTimer timerWithTimeInterval:[(NSNumber *)duration doubleValue] target:self selector:@selector(dismiss) userInfo:nil repeats:NO]; [[NSRunLoop mainRunLoop] addTimer:self.fadeOutTimer forMode:NSRunLoopCommonModes]; } } }; // Animate appearance if (self.fadeInAnimationDuration > 0) { // Animate appearance [UIView animateWithDuration:self.fadeInAnimationDuration delay:0 options:(UIViewAnimationOptions) (UIViewAnimationOptionAllowUserInteraction | UIViewAnimationCurveEaseIn | UIViewAnimationOptionBeginFromCurrentState) animations:^{ animationsBlock(); } completion:^(BOOL finished) { completionBlock(); }]; } else { animationsBlock(); completionBlock(); } // Inform iOS to redraw the view hierarchy [self setNeedsDisplay]; } else { // Update accessibility UIAccessibilityPostNotification(UIAccessibilityScreenChangedNotification, nil); UIAccessibilityPostNotification(UIAccessibilityAnnouncementNotification, self.statusLabel.text); // Dismiss automatically if a duration was passed as userInfo. We start a timer // which then will call dismiss after the predefined duration if(duration){ self.fadeOutTimer = [NSTimer timerWithTimeInterval:[(NSNumber *)duration doubleValue] target:self selector:@selector(dismiss) userInfo:nil repeats:NO]; [[NSRunLoop mainRunLoop] addTimer:self.fadeOutTimer forMode:NSRunLoopCommonModes]; } } } - (void)dismiss { [self dismissWithDelay:0.0 completion:nil]; } - (void)dismissWithDelay:(NSTimeInterval)delay completion:(SVProgressHUDDismissCompletion)completion { __weak SVProgressHUD *weakSelf = self; [[NSOperationQueue mainQueue] addOperationWithBlock:^{ __strong SVProgressHUD *strongSelf = weakSelf; if(strongSelf){ // Post notification to inform user [[NSNotificationCenter defaultCenter] postNotificationName:SVProgressHUDWillDisappearNotification object:nil userInfo:[strongSelf notificationUserInfo]]; // Reset activity count strongSelf.activityCount = 0; __block void (^animationsBlock)(void) = ^{ // Shrink HUD a little to make a nice disappear animation strongSelf.hudView.transform = CGAffineTransformScale(strongSelf.hudView.transform, 1/1.3f, 1/1.3f); // Fade out all effects (colors, blur, etc.) [strongSelf fadeOutEffects]; }; __block void (^completionBlock)(void) = ^{ // Check if we really achieved to dismiss the HUD (<=> alpha values are applied) // and the change of these values has not been cancelled in between e.g. due to a new show if(self.backgroundView.alpha == 0.0f){ // Clean up view hierarchy (overlays) [strongSelf.controlView removeFromSuperview]; [strongSelf.backgroundView removeFromSuperview]; [strongSelf.hudView removeFromSuperview]; [strongSelf removeFromSuperview]; // Reset progress and cancel any running animation strongSelf.progress = SVProgressHUDUndefinedProgress; [strongSelf cancelRingLayerAnimation]; [strongSelf cancelIndefiniteAnimatedViewAnimation]; // Remove observer <=> we do not have to handle orientation changes etc. [[NSNotificationCenter defaultCenter] removeObserver:strongSelf]; // Post notification to inform user [[NSNotificationCenter defaultCenter] postNotificationName:SVProgressHUDDidDisappearNotification object:strongSelf userInfo:[strongSelf notificationUserInfo]]; // Tell the rootViewController to update the StatusBar appearance #if !defined(SV_APP_EXTENSIONS) && TARGET_OS_IOS UIViewController *rootController = [self getCurrentWindow].rootViewController; [rootController setNeedsStatusBarAppearanceUpdate]; #endif // Run an (optional) completionHandler if (completion) { completion(); } } }; // UIViewAnimationOptionBeginFromCurrentState AND a delay doesn't always work as expected // When UIViewAnimationOptionBeginFromCurrentState is set, animateWithDuration: evaluates the current // values to check if an animation is necessary. The evaluation happens at function call time and not // after the delay => the animation is sometimes skipped. Therefore we delay using dispatch_after. dispatch_time_t dipatchTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay * NSEC_PER_SEC)); dispatch_after(dipatchTime, dispatch_get_main_queue(), ^{ // Stop timer strongSelf.graceTimer = nil; if (strongSelf.fadeOutAnimationDuration > 0) { // Animate appearance [UIView animateWithDuration:strongSelf.fadeOutAnimationDuration delay:0 options:(UIViewAnimationOptions) (UIViewAnimationOptionAllowUserInteraction | UIViewAnimationCurveEaseOut | UIViewAnimationOptionBeginFromCurrentState) animations:^{ animationsBlock(); } completion:^(BOOL finished) { completionBlock(); }]; } else { animationsBlock(); completionBlock(); } }); // Inform iOS to redraw the view hierarchy [strongSelf setNeedsDisplay]; } }]; } #pragma mark - Ring progress animation - (UIView*)indefiniteAnimatedView { // Get the correct spinner for defaultAnimationType if(self.defaultAnimationType == SVProgressHUDAnimationTypeFlat){ // Check if spinner exists and is an object of different class if(_indefiniteAnimatedView && ![_indefiniteAnimatedView isKindOfClass:[SVIndefiniteAnimatedView class]]){ [_indefiniteAnimatedView removeFromSuperview]; _indefiniteAnimatedView = nil; } if(!_indefiniteAnimatedView){ _indefiniteAnimatedView = [[SVIndefiniteAnimatedView alloc] initWithFrame:CGRectZero]; } // Update styling SVIndefiniteAnimatedView *indefiniteAnimatedView = (SVIndefiniteAnimatedView*)_indefiniteAnimatedView; indefiniteAnimatedView.strokeColor = self.foregroundImageColorForStyle; indefiniteAnimatedView.strokeThickness = self.ringThickness; indefiniteAnimatedView.radius = self.statusLabel.text ? self.ringRadius : self.ringNoTextRadius; } else { // Check if spinner exists and is an object of different class if(_indefiniteAnimatedView && ![_indefiniteAnimatedView isKindOfClass:[UIActivityIndicatorView class]]){ [_indefiniteAnimatedView removeFromSuperview]; _indefiniteAnimatedView = nil; } if(!_indefiniteAnimatedView){ _indefiniteAnimatedView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge]; } // Update styling UIActivityIndicatorView *activityIndicatorView = (UIActivityIndicatorView*)_indefiniteAnimatedView; activityIndicatorView.color = self.foregroundImageColorForStyle; } [_indefiniteAnimatedView sizeToFit]; return _indefiniteAnimatedView; } - (SVProgressAnimatedView*)ringView { if(!_ringView) { _ringView = [[SVProgressAnimatedView alloc] initWithFrame:CGRectZero]; } // Update styling _ringView.strokeColor = self.foregroundImageColorForStyle; _ringView.strokeThickness = self.ringThickness; _ringView.radius = self.statusLabel.text ? self.ringRadius : self.ringNoTextRadius; return _ringView; } - (SVProgressAnimatedView*)backgroundRingView { if(!_backgroundRingView) { _backgroundRingView = [[SVProgressAnimatedView alloc] initWithFrame:CGRectZero]; _backgroundRingView.strokeEnd = 1.0f; } // Update styling _backgroundRingView.strokeColor = [self.foregroundImageColorForStyle colorWithAlphaComponent:0.1f]; _backgroundRingView.strokeThickness = self.ringThickness; _backgroundRingView.radius = self.statusLabel.text ? self.ringRadius : self.ringNoTextRadius; return _backgroundRingView; } - (void)cancelRingLayerAnimation { // Animate value update, stop animation [CATransaction begin]; [CATransaction setDisableActions:YES]; [self.hudView.layer removeAllAnimations]; self.ringView.strokeEnd = 0.0f; [CATransaction commit]; // Remove from view [self.ringView removeFromSuperview]; [self.backgroundRingView removeFromSuperview]; } - (void)cancelIndefiniteAnimatedViewAnimation { // Stop animation if([self.indefiniteAnimatedView respondsToSelector:@selector(stopAnimating)]) { [(id)self.indefiniteAnimatedView stopAnimating]; } // Remove from view [self.indefiniteAnimatedView removeFromSuperview]; } #pragma mark - Utilities + (BOOL)isVisible { // Checking one alpha value is sufficient as they are all the same return [self sharedView].backgroundView.alpha > 0.0f; } #pragma mark - Getters + (NSTimeInterval)displayDurationForString:(NSString*)string { CGFloat minimum = MAX((CGFloat)string.length * 0.06 + 0.5, [self sharedView].minimumDismissTimeInterval); return MIN(minimum, [self sharedView].maximumDismissTimeInterval); } - (UIColor*)foregroundColorForStyle { if(self.defaultStyle == SVProgressHUDStyleLight) { return [UIColor blackColor]; } else if(self.defaultStyle == SVProgressHUDStyleDark) { return [UIColor whiteColor]; } else { return self.foregroundColor; } } - (UIColor*)foregroundImageColorForStyle { if (self.foregroundImageColor) { return self.foregroundImageColor; } else { return [self foregroundColorForStyle]; } } - (UIColor*)backgroundColorForStyle { if(self.defaultStyle == SVProgressHUDStyleLight) { return [UIColor whiteColor]; } else if(self.defaultStyle == SVProgressHUDStyleDark) { return [UIColor blackColor]; } else { return self.backgroundColor; } } - (UIWindow *)getCurrentWindow { UIWindow *current; if (@available(iOS 13.0, *)) { NSPredicate *foregroundActivePredict = [NSPredicate predicateWithBlock:^BOOL(id _Nullable evaluatedObject, NSDictionary * _Nullable bindings) { return ([evaluatedObject activationState] == UISceneActivationStateForegroundActive); }]; NSSet *setOfScene = [[[UIApplication sharedApplication] connectedScenes] filteredSetUsingPredicate:foregroundActivePredict]; UIWindowScene *windowScene = (UIWindowScene *)[[setOfScene allObjects] firstObject]; current = [[UIWindow alloc] initWithWindowScene:windowScene]; }else{ current = [[[UIApplication sharedApplication] delegate] window]; } return current; } - (UIControl*)controlView { if(!_controlView) { _controlView = [UIControl new]; _controlView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; _controlView.backgroundColor = [UIColor clearColor]; _controlView.userInteractionEnabled = YES; [_controlView addTarget:self action:@selector(controlViewDidReceiveTouchEvent:forEvent:) forControlEvents:UIControlEventTouchDown]; } // Update frames #if !defined(SV_APP_EXTENSIONS) CGRect windowBounds = [self getCurrentWindow].bounds; _controlView.frame = windowBounds; #else _controlView.frame = [UIScreen mainScreen].bounds; #endif return _controlView; } -(UIView *)backgroundView { if(!_backgroundView){ _backgroundView = [UIView new]; _backgroundView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; } if(!_backgroundView.superview){ [self insertSubview:_backgroundView belowSubview:self.hudView]; } // Update styling if(self.defaultMaskType == SVProgressHUDMaskTypeGradient){ if(!_backgroundRadialGradientLayer){ _backgroundRadialGradientLayer = [SVRadialGradientLayer layer]; } if(!_backgroundRadialGradientLayer.superlayer){ [_backgroundView.layer insertSublayer:_backgroundRadialGradientLayer atIndex:0]; } _backgroundView.backgroundColor = [UIColor clearColor]; } else { if(_backgroundRadialGradientLayer && _backgroundRadialGradientLayer.superlayer){ [_backgroundRadialGradientLayer removeFromSuperlayer]; } if(self.defaultMaskType == SVProgressHUDMaskTypeBlack){ _backgroundView.backgroundColor = [UIColor colorWithWhite:0 alpha:0.4]; } else if(self.defaultMaskType == SVProgressHUDMaskTypeCustom){ _backgroundView.backgroundColor = self.backgroundLayerColor; } else { _backgroundView.backgroundColor = [UIColor clearColor]; } } // Update frame if(_backgroundView){ _backgroundView.frame = self.bounds; } if(_backgroundRadialGradientLayer){ _backgroundRadialGradientLayer.frame = self.bounds; // Calculate the new center of the gradient, it may change if keyboard is visible CGPoint gradientCenter = self.center; gradientCenter.y = (self.bounds.size.height - self.visibleKeyboardHeight)/2; _backgroundRadialGradientLayer.gradientCenter = gradientCenter; [_backgroundRadialGradientLayer setNeedsDisplay]; } return _backgroundView; } - (UIVisualEffectView*)hudView { if(!_hudView) { _hudView = [UIVisualEffectView new]; _hudView.layer.masksToBounds = YES; _hudView.autoresizingMask = UIViewAutoresizingFlexibleBottomMargin | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleLeftMargin; } if(!_hudView.superview) { [self addSubview:_hudView]; } // Update styling _hudView.layer.cornerRadius = self.cornerRadius; return _hudView; } - (UILabel*)statusLabel { if(!_statusLabel) { _statusLabel = [[UILabel alloc] initWithFrame:CGRectZero]; _statusLabel.backgroundColor = [UIColor clearColor]; _statusLabel.adjustsFontSizeToFitWidth = YES; _statusLabel.textAlignment = NSTextAlignmentCenter; _statusLabel.baselineAdjustment = UIBaselineAdjustmentAlignCenters; _statusLabel.numberOfLines = 0; } if(!_statusLabel.superview) { [self.hudView.contentView addSubview:_statusLabel]; } // Update styling _statusLabel.textColor = self.foregroundColorForStyle; _statusLabel.font = self.font; return _statusLabel; } - (UIImageView*)imageView { if(_imageView && !CGSizeEqualToSize(_imageView.bounds.size, _imageViewSize)) { [_imageView removeFromSuperview]; _imageView = nil; } if(!_imageView) { _imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, _imageViewSize.width, _imageViewSize.height)]; } if(!_imageView.superview) { [self.hudView.contentView addSubview:_imageView]; } return _imageView; } #pragma mark - Helper - (CGFloat)visibleKeyboardHeight { #if !defined(SV_APP_EXTENSIONS) UIWindow *keyboardWindow = nil; for (UIWindow *testWindow in UIApplication.sharedApplication.windows) { if(![testWindow.class isEqual:UIWindow.class]) { keyboardWindow = testWindow; break; } } for (__strong UIView *possibleKeyboard in keyboardWindow.subviews) { NSString *viewName = NSStringFromClass(possibleKeyboard.class); if([viewName hasPrefix:@"UI"]){ if([viewName hasSuffix:@"PeripheralHostView"] || [viewName hasSuffix:@"Keyboard"]){ return CGRectGetHeight(possibleKeyboard.bounds); } else if ([viewName hasSuffix:@"InputSetContainerView"]){ for (__strong UIView *possibleKeyboardSubview in possibleKeyboard.subviews) { viewName = NSStringFromClass(possibleKeyboardSubview.class); if([viewName hasPrefix:@"UI"] && [viewName hasSuffix:@"InputSetHostView"]) { CGRect convertedRect = [possibleKeyboard convertRect:possibleKeyboardSubview.frame toView:self]; CGRect intersectedRect = CGRectIntersection(convertedRect, self.bounds); if (!CGRectIsNull(intersectedRect)) { return CGRectGetHeight(intersectedRect); } } } } } } #endif return 0; } - (UIWindow *)frontWindow { #if !defined(SV_APP_EXTENSIONS) NSEnumerator *frontToBackWindows = [UIApplication.sharedApplication.windows reverseObjectEnumerator]; for (UIWindow *window in frontToBackWindows) { BOOL windowOnMainScreen = window.screen == UIScreen.mainScreen; BOOL windowIsVisible = !window.hidden && window.alpha > 0; BOOL windowLevelSupported = (window.windowLevel >= UIWindowLevelNormal && window.windowLevel <= self.maxSupportedWindowLevel); BOOL windowKeyWindow = window.isKeyWindow; if(windowOnMainScreen && windowIsVisible && windowLevelSupported && windowKeyWindow) { return window; } } #endif return nil; } - (void)fadeInEffects { if(self.defaultStyle != SVProgressHUDStyleCustom) { // Add blur effect UIBlurEffectStyle blurEffectStyle = self.defaultStyle == SVProgressHUDStyleDark ? UIBlurEffectStyleDark : UIBlurEffectStyleLight; UIBlurEffect *blurEffect = [UIBlurEffect effectWithStyle:blurEffectStyle]; self.hudView.effect = blurEffect; // We omit UIVibrancy effect and use a suitable background color as an alternative. // This will make everything more readable. See the following for details: // https://www.omnigroup.com/developer/how-to-make-text-in-a-uivisualeffectview-readable-on-any-background self.hudView.backgroundColor = [self.backgroundColorForStyle colorWithAlphaComponent:0.6f]; } else { self.hudView.effect = self.hudViewCustomBlurEffect; self.hudView.backgroundColor = self.backgroundColorForStyle; } // Fade in views self.backgroundView.alpha = 1.0f; self.imageView.alpha = 1.0f; self.statusLabel.alpha = 1.0f; self.indefiniteAnimatedView.alpha = 1.0f; self.ringView.alpha = self.backgroundRingView.alpha = 1.0f; } - (void)fadeOutEffects { if(self.defaultStyle != SVProgressHUDStyleCustom) { // Remove blur effect self.hudView.effect = nil; } // Remove background color self.hudView.backgroundColor = [UIColor clearColor]; // Fade out views self.backgroundView.alpha = 0.0f; self.imageView.alpha = 0.0f; self.statusLabel.alpha = 0.0f; self.indefiniteAnimatedView.alpha = 0.0f; self.ringView.alpha = self.backgroundRingView.alpha = 0.0f; } #if TARGET_OS_IOS && __IPHONE_OS_VERSION_MAX_ALLOWED >= 100000 - (UINotificationFeedbackGenerator *)hapticGenerator NS_AVAILABLE_IOS(10_0) { // Only return if haptics are enabled if(!self.hapticsEnabled) { return nil; } if(!_hapticGenerator) { _hapticGenerator = [[UINotificationFeedbackGenerator alloc] init]; } return _hapticGenerator; } #endif #pragma mark - UIAppearance Setters - (void)setDefaultStyle:(SVProgressHUDStyle)style { if (!_isInitializing) _defaultStyle = style; } - (void)setDefaultMaskType:(SVProgressHUDMaskType)maskType { if (!_isInitializing) _defaultMaskType = maskType; } - (void)setDefaultAnimationType:(SVProgressHUDAnimationType)animationType { if (!_isInitializing) _defaultAnimationType = animationType; } - (void)setContainerView:(UIView *)containerView { if (!_isInitializing) _containerView = containerView; } - (void)setMinimumSize:(CGSize)minimumSize { if (!_isInitializing) _minimumSize = minimumSize; } - (void)setRingThickness:(CGFloat)ringThickness { if (!_isInitializing) _ringThickness = ringThickness; } - (void)setRingRadius:(CGFloat)ringRadius { if (!_isInitializing) _ringRadius = ringRadius; } - (void)setRingNoTextRadius:(CGFloat)ringNoTextRadius { if (!_isInitializing) _ringNoTextRadius = ringNoTextRadius; } - (void)setCornerRadius:(CGFloat)cornerRadius { if (!_isInitializing) _cornerRadius = cornerRadius; } - (void)setFont:(UIFont*)font { if (!_isInitializing) _font = font; } - (void)setForegroundColor:(UIColor*)color { if (!_isInitializing) _foregroundColor = color; } - (void)setForegroundImageColor:(UIColor *)color { if (!_isInitializing) _foregroundImageColor = color; } - (void)setBackgroundColor:(UIColor*)color { if (!_isInitializing) _backgroundColor = color; } - (void)setBackgroundLayerColor:(UIColor*)color { if (!_isInitializing) _backgroundLayerColor = color; } - (void)setShouldTintImages:(BOOL)shouldTintImages { if (!_isInitializing) _shouldTintImages = shouldTintImages; } - (void)setInfoImage:(UIImage*)image { if (!_isInitializing) _infoImage = image; } - (void)setSuccessImage:(UIImage*)image { if (!_isInitializing) _successImage = image; } - (void)setErrorImage:(UIImage*)image { if (!_isInitializing) _errorImage = image; } - (void)setViewForExtension:(UIView*)view { if (!_isInitializing) _viewForExtension = view; } - (void)setOffsetFromCenter:(UIOffset)offset { if (!_isInitializing) _offsetFromCenter = offset; } - (void)setMinimumDismissTimeInterval:(NSTimeInterval)minimumDismissTimeInterval { if (!_isInitializing) _minimumDismissTimeInterval = minimumDismissTimeInterval; } - (void)setFadeInAnimationDuration:(NSTimeInterval)duration { if (!_isInitializing) _fadeInAnimationDuration = duration; } - (void)setFadeOutAnimationDuration:(NSTimeInterval)duration { if (!_isInitializing) _fadeOutAnimationDuration = duration; } - (void)setMaxSupportedWindowLevel:(UIWindowLevel)maxSupportedWindowLevel { if (!_isInitializing) _maxSupportedWindowLevel = maxSupportedWindowLevel; } @end