mirror of
https://gitee.com/shuto/cordova-imagePicker.git
synced 2026-05-23 00:05:03 +08:00
838 lines
28 KiB
Objective-C
Executable File
838 lines
28 KiB
Objective-C
Executable File
//
|
|
// 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 <QuartzCore/QuartzCore.h>
|
|
#import <CoreGraphics/CoreGraphics.h>
|
|
#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<MRStopableView>)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<MRStopableView>)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
|