diff --git a/.gitignore b/.gitignore index fd1dc16..3ea9f80 100644 --- a/.gitignore +++ b/.gitignore @@ -67,3 +67,7 @@ fastlane/test_output iOSInjectionProject/ /.DS_Store +/media/.DS_Store +/PsychicStapler.xcodeproj/xcuserdata/lolgrep.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist +/PsychicStapler.xcodeproj/project.xcworkspace/xcuserdata/lolgrep.xcuserdatad/UserInterfaceState.xcuserstate +.DS_Store diff --git a/PsychicStapler.xcodeproj/project.pbxproj b/PsychicStapler.xcodeproj/project.pbxproj index ffd6389..efd875c 100644 --- a/PsychicStapler.xcodeproj/project.pbxproj +++ b/PsychicStapler.xcodeproj/project.pbxproj @@ -45,6 +45,13 @@ 3FCA5CF524D762950021FEDB /* ProcessDetailsViewController.mm in Sources */ = {isa = PBXBuildFile; fileRef = 3FCA5CF424D762950021FEDB /* ProcessDetailsViewController.mm */; }; 3FE942D524C7B78E008FCB38 /* TaskHelpers.mm in Sources */ = {isa = PBXBuildFile; fileRef = 3FE942D324C7B78E008FCB38 /* TaskHelpers.mm */; }; 3FE942F724CE771A008FCB38 /* libPsychicStapler.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 3F17A5FA24C3F485000227B5 /* libPsychicStapler.a */; }; + 3FF4B82525011E950047642E /* SVRadialGradientLayer.m in Sources */ = {isa = PBXBuildFile; fileRef = 3FF4B81B25011E950047642E /* SVRadialGradientLayer.m */; }; + 3FF4B82625011E950047642E /* SVProgressAnimatedView.m in Sources */ = {isa = PBXBuildFile; fileRef = 3FF4B81E25011E950047642E /* SVProgressAnimatedView.m */; }; + 3FF4B82725011E950047642E /* SVProgressHUD.m in Sources */ = {isa = PBXBuildFile; fileRef = 3FF4B82125011E950047642E /* SVProgressHUD.m */; }; + 3FF4B82825011E950047642E /* SVIndefiniteAnimatedView.m in Sources */ = {isa = PBXBuildFile; fileRef = 3FF4B82225011E950047642E /* SVIndefiniteAnimatedView.m */; }; + 3FF4B82925011E950047642E /* SVProgressHUD.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 3FF4B82325011E950047642E /* SVProgressHUD.bundle */; }; + 3FF4B82C25012BFF0047642E /* AirdropOnlyActivity.m in Sources */ = {isa = PBXBuildFile; fileRef = 3FF4B82B25012BFF0047642E /* AirdropOnlyActivity.m */; }; + 3FF4B830250131DD0047642E /* ProcessModule.m in Sources */ = {isa = PBXBuildFile; fileRef = 3FF4B82F250131DD0047642E /* ProcessModule.m */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -169,6 +176,20 @@ 3FE942F424CE7492008FCB38 /* evil_ent.xml */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = evil_ent.xml; sourceTree = ""; }; 3FE942FA24CE818C008FCB38 /* commons.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = commons.h; sourceTree = ""; }; 3FE942FD24CF7371008FCB38 /* CoreServices.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CoreServices.h; sourceTree = ""; }; + 3FF4B81B25011E950047642E /* SVRadialGradientLayer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SVRadialGradientLayer.m; sourceTree = ""; }; + 3FF4B81C25011E950047642E /* SVIndefiniteAnimatedView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SVIndefiniteAnimatedView.h; sourceTree = ""; }; + 3FF4B81D25011E950047642E /* SVProgressHUD.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SVProgressHUD.h; sourceTree = ""; }; + 3FF4B81E25011E950047642E /* SVProgressAnimatedView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SVProgressAnimatedView.m; sourceTree = ""; }; + 3FF4B81F25011E950047642E /* SVProgressHUD-Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "SVProgressHUD-Prefix.pch"; sourceTree = ""; }; + 3FF4B82025011E950047642E /* SVRadialGradientLayer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SVRadialGradientLayer.h; sourceTree = ""; }; + 3FF4B82125011E950047642E /* SVProgressHUD.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SVProgressHUD.m; sourceTree = ""; }; + 3FF4B82225011E950047642E /* SVIndefiniteAnimatedView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SVIndefiniteAnimatedView.m; sourceTree = ""; }; + 3FF4B82325011E950047642E /* SVProgressHUD.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = SVProgressHUD.bundle; sourceTree = ""; }; + 3FF4B82425011E950047642E /* SVProgressAnimatedView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SVProgressAnimatedView.h; sourceTree = ""; }; + 3FF4B82A25012BFF0047642E /* AirdropOnlyActivity.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AirdropOnlyActivity.h; sourceTree = ""; }; + 3FF4B82B25012BFF0047642E /* AirdropOnlyActivity.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AirdropOnlyActivity.m; sourceTree = ""; }; + 3FF4B82E250131DD0047642E /* ProcessModule.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ProcessModule.h; sourceTree = ""; }; + 3FF4B82F250131DD0047642E /* ProcessModule.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ProcessModule.m; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -341,6 +362,8 @@ 3F7513D824C713A600DC892B /* yacd */ = { isa = PBXGroup; children = ( + 3FF4B82D250131C50047642E /* Models */, + 3FF4B81A25011E950047642E /* SVProgressHUD */, 3F6EA03E24F4005D001AD9C8 /* SSZipArchive */, 3F36E35D24E032060002EE58 /* ViewControllers */, 3FCD7B3E24E030FE00E7414A /* Helpers */, @@ -352,6 +375,8 @@ 3F7513E724C713A700DC892B /* LaunchScreen.storyboard */, 3F7513EA24C713A700DC892B /* Info.plist */, 3F7513EB24C713A700DC892B /* main.m */, + 3FF4B82A25012BFF0047642E /* AirdropOnlyActivity.h */, + 3FF4B82B25012BFF0047642E /* AirdropOnlyActivity.m */, ); path = yacd; sourceTree = ""; @@ -367,6 +392,32 @@ path = Helpers; sourceTree = ""; }; + 3FF4B81A25011E950047642E /* SVProgressHUD */ = { + isa = PBXGroup; + children = ( + 3FF4B81B25011E950047642E /* SVRadialGradientLayer.m */, + 3FF4B81C25011E950047642E /* SVIndefiniteAnimatedView.h */, + 3FF4B81D25011E950047642E /* SVProgressHUD.h */, + 3FF4B81E25011E950047642E /* SVProgressAnimatedView.m */, + 3FF4B81F25011E950047642E /* SVProgressHUD-Prefix.pch */, + 3FF4B82025011E950047642E /* SVRadialGradientLayer.h */, + 3FF4B82125011E950047642E /* SVProgressHUD.m */, + 3FF4B82225011E950047642E /* SVIndefiniteAnimatedView.m */, + 3FF4B82325011E950047642E /* SVProgressHUD.bundle */, + 3FF4B82425011E950047642E /* SVProgressAnimatedView.h */, + ); + path = SVProgressHUD; + sourceTree = SOURCE_ROOT; + }; + 3FF4B82D250131C50047642E /* Models */ = { + isa = PBXGroup; + children = ( + 3FF4B82E250131DD0047642E /* ProcessModule.h */, + 3FF4B82F250131DD0047642E /* ProcessModule.m */, + ); + path = Models; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -470,6 +521,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + 3FF4B82925011E950047642E /* SVProgressHUD.bundle in Resources */, 3F7513E924C713A700DC892B /* LaunchScreen.storyboard in Resources */, 3F7513E624C713A700DC892B /* Assets.xcassets in Resources */, 3F6EA06724F4005D001AD9C8 /* LICENSE in Resources */, @@ -526,13 +578,17 @@ buildActionMask = 2147483647; files = ( 3F7513E124C713A700DC892B /* ViewController.mm in Sources */, + 3FF4B82625011E950047642E /* SVProgressAnimatedView.m in Sources */, 3F6EA07324F4005D001AD9C8 /* SSZipArchive.m in Sources */, 3FCA5CF524D762950021FEDB /* ProcessDetailsViewController.mm in Sources */, 3F6EA06524F4005D001AD9C8 /* mz_strm_zlib.c in Sources */, 3F6EA06824F4005D001AD9C8 /* mz_zip_rw.c in Sources */, 3F6EA06624F4005D001AD9C8 /* mz_zip.c in Sources */, 3F7513DB24C713A700DC892B /* AppDelegate.m in Sources */, + 3FF4B82C25012BFF0047642E /* AirdropOnlyActivity.m in Sources */, 3F6EA06A24F4005D001AD9C8 /* mz_strm_mem.c in Sources */, + 3FF4B82725011E950047642E /* SVProgressHUD.m in Sources */, + 3FF4B830250131DD0047642E /* ProcessModule.m in Sources */, 3F6EA06F24F4005D001AD9C8 /* mz_os_posix.c in Sources */, 3F6EA07124F4005D001AD9C8 /* mz_strm_split.c in Sources */, 3F6EA06B24F4005D001AD9C8 /* mz_crypt.c in Sources */, @@ -543,7 +599,9 @@ 3F4072FF24FC9CA90016831B /* NSError+ErrorHelper.m in Sources */, 3F6EA06424F4005D001AD9C8 /* mz_strm_pkcrypt.c in Sources */, 3F4072FB24FB580D0016831B /* ApplicationTableViewCell.m in Sources */, + 3FF4B82825011E950047642E /* SVIndefiniteAnimatedView.m in Sources */, 3F6EA06E24F4005D001AD9C8 /* mz_os.c in Sources */, + 3FF4B82525011E950047642E /* SVRadialGradientLayer.m in Sources */, 3F6EA06924F4005D001AD9C8 /* mz_crypt_apple.c in Sources */, 3FBDE5D824DD293900D53CB8 /* UIViewController+DisplayError.m in Sources */, 3F6EA07024F4005D001AD9C8 /* mz_strm_buf.c in Sources */, diff --git a/PsychicStapler.xcodeproj/project.xcworkspace/xcuserdata/lolgrep.xcuserdatad/UserInterfaceState.xcuserstate b/PsychicStapler.xcodeproj/project.xcworkspace/xcuserdata/lolgrep.xcuserdatad/UserInterfaceState.xcuserstate deleted file mode 100644 index 1b09700..0000000 Binary files a/PsychicStapler.xcodeproj/project.xcworkspace/xcuserdata/lolgrep.xcuserdatad/UserInterfaceState.xcuserstate and /dev/null differ diff --git a/PsychicStapler.xcodeproj/xcuserdata/lolgrep.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist b/PsychicStapler.xcodeproj/xcuserdata/lolgrep.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist deleted file mode 100644 index b34097c..0000000 --- a/PsychicStapler.xcodeproj/xcuserdata/lolgrep.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist +++ /dev/null @@ -1,251 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/PsychicStapler/CoreServices.h b/PsychicStapler/CoreServices.h index fe727f4..ee5b380 100644 --- a/PsychicStapler/CoreServices.h +++ b/PsychicStapler/CoreServices.h @@ -26,7 +26,14 @@ -(NSArray >*)allApplications; @end -//__attribute__((weak)) -//CGImageRef LICreateIconFromCachedBitmap(NSData* data); +#ifdef __cplusplus +extern "C" { +#endif +__attribute__((weak)) + CGImageRef LICreateIconFromCachedBitmap(NSData* data); + +#ifdef __cplusplus +} +#endif diff --git a/SVProgressHUD/SVIndefiniteAnimatedView.h b/SVProgressHUD/SVIndefiniteAnimatedView.h new file mode 100644 index 0000000..5e300ca --- /dev/null +++ b/SVProgressHUD/SVIndefiniteAnimatedView.h @@ -0,0 +1,17 @@ +// +// SVIndefiniteAnimatedView.h +// SVProgressHUD, https://github.com/SVProgressHUD/SVProgressHUD +// +// Copyright (c) 2014-2019 Guillaume Campagna. All rights reserved. +// + +#import + +@interface SVIndefiniteAnimatedView : UIView + +@property (nonatomic, assign) CGFloat strokeThickness; +@property (nonatomic, assign) CGFloat radius; +@property (nonatomic, strong) UIColor *strokeColor; + +@end + diff --git a/SVProgressHUD/SVIndefiniteAnimatedView.m b/SVProgressHUD/SVIndefiniteAnimatedView.m new file mode 100644 index 0000000..9f7412a --- /dev/null +++ b/SVProgressHUD/SVIndefiniteAnimatedView.m @@ -0,0 +1,146 @@ +// +// SVIndefiniteAnimatedView.m +// SVProgressHUD, https://github.com/SVProgressHUD/SVProgressHUD +// +// Copyright (c) 2014-2019 Guillaume Campagna. All rights reserved. +// + +#import "SVIndefiniteAnimatedView.h" +#import "SVProgressHUD.h" + +@interface SVIndefiniteAnimatedView () + +@property (nonatomic, strong) CAShapeLayer *indefiniteAnimatedLayer; + +@end + +@implementation SVIndefiniteAnimatedView + +- (void)willMoveToSuperview:(UIView*)newSuperview { + if (newSuperview) { + [self layoutAnimatedLayer]; + } else { + [_indefiniteAnimatedLayer removeFromSuperlayer]; + _indefiniteAnimatedLayer = nil; + } +} + +- (void)layoutSubviews { + [super layoutSubviews]; + + [self layoutAnimatedLayer]; +} + +- (void)layoutAnimatedLayer { + CALayer *layer = self.indefiniteAnimatedLayer; + + if (!layer.superlayer) { + [self.layer addSublayer:layer]; + } + + CGFloat widthDiff = CGRectGetWidth(self.bounds) - CGRectGetWidth(layer.bounds); + CGFloat heightDiff = CGRectGetHeight(self.bounds) - CGRectGetHeight(layer.bounds); + layer.position = CGPointMake(CGRectGetWidth(self.bounds) - CGRectGetWidth(layer.bounds) / 2 - widthDiff / 2, CGRectGetHeight(self.bounds) - CGRectGetHeight(layer.bounds) / 2 - heightDiff / 2); +} + +- (CAShapeLayer*)indefiniteAnimatedLayer { + if(!_indefiniteAnimatedLayer) { + CGPoint arcCenter = CGPointMake(self.radius+self.strokeThickness/2+5, self.radius+self.strokeThickness/2+5); + UIBezierPath* smoothedPath = [UIBezierPath bezierPathWithArcCenter:arcCenter radius:self.radius startAngle:(CGFloat) (M_PI*3/2) endAngle:(CGFloat) (M_PI/2+M_PI*5) clockwise:YES]; + + _indefiniteAnimatedLayer = [CAShapeLayer layer]; + _indefiniteAnimatedLayer.contentsScale = [[UIScreen mainScreen] scale]; + _indefiniteAnimatedLayer.frame = CGRectMake(0.0f, 0.0f, arcCenter.x*2, arcCenter.y*2); + _indefiniteAnimatedLayer.fillColor = [UIColor clearColor].CGColor; + _indefiniteAnimatedLayer.strokeColor = self.strokeColor.CGColor; + _indefiniteAnimatedLayer.lineWidth = self.strokeThickness; + _indefiniteAnimatedLayer.lineCap = kCALineCapRound; + _indefiniteAnimatedLayer.lineJoin = kCALineJoinBevel; + _indefiniteAnimatedLayer.path = smoothedPath.CGPath; + + CALayer *maskLayer = [CALayer layer]; + + NSBundle *bundle = [NSBundle bundleForClass:[SVProgressHUD class]]; + NSURL *url = [bundle URLForResource:@"SVProgressHUD" withExtension:@"bundle"]; + NSBundle *imageBundle = [NSBundle bundleWithURL:url]; + + NSString *path = [imageBundle pathForResource:@"angle-mask" ofType:@"png"]; + + maskLayer.contents = (__bridge id)[[UIImage imageWithContentsOfFile:path] CGImage]; + maskLayer.frame = _indefiniteAnimatedLayer.bounds; + _indefiniteAnimatedLayer.mask = maskLayer; + + NSTimeInterval animationDuration = 1; + CAMediaTimingFunction *linearCurve = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear]; + + CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"transform.rotation"]; + animation.fromValue = (id) 0; + animation.toValue = @(M_PI*2); + animation.duration = animationDuration; + animation.timingFunction = linearCurve; + animation.removedOnCompletion = NO; + animation.repeatCount = INFINITY; + animation.fillMode = kCAFillModeForwards; + animation.autoreverses = NO; + [_indefiniteAnimatedLayer.mask addAnimation:animation forKey:@"rotate"]; + + CAAnimationGroup *animationGroup = [CAAnimationGroup animation]; + animationGroup.duration = animationDuration; + animationGroup.repeatCount = INFINITY; + animationGroup.removedOnCompletion = NO; + animationGroup.timingFunction = linearCurve; + + CABasicAnimation *strokeStartAnimation = [CABasicAnimation animationWithKeyPath:@"strokeStart"]; + strokeStartAnimation.fromValue = @0.015; + strokeStartAnimation.toValue = @0.515; + + CABasicAnimation *strokeEndAnimation = [CABasicAnimation animationWithKeyPath:@"strokeEnd"]; + strokeEndAnimation.fromValue = @0.485; + strokeEndAnimation.toValue = @0.985; + + animationGroup.animations = @[strokeStartAnimation, strokeEndAnimation]; + [_indefiniteAnimatedLayer addAnimation:animationGroup forKey:@"progress"]; + + } + return _indefiniteAnimatedLayer; +} + +- (void)setFrame:(CGRect)frame { + if(!CGRectEqualToRect(frame, super.frame)) { + [super setFrame:frame]; + + if(self.superview) { + [self layoutAnimatedLayer]; + } + } + +} + +- (void)setRadius:(CGFloat)radius { + if(radius != _radius) { + _radius = radius; + + [_indefiniteAnimatedLayer removeFromSuperlayer]; + _indefiniteAnimatedLayer = nil; + + if(self.superview) { + [self layoutAnimatedLayer]; + } + } +} + +- (void)setStrokeColor:(UIColor*)strokeColor { + _strokeColor = strokeColor; + _indefiniteAnimatedLayer.strokeColor = strokeColor.CGColor; +} + +- (void)setStrokeThickness:(CGFloat)strokeThickness { + _strokeThickness = strokeThickness; + _indefiniteAnimatedLayer.lineWidth = _strokeThickness; +} + +- (CGSize)sizeThatFits:(CGSize)size { + return CGSizeMake((self.radius+self.strokeThickness/2+5)*2, (self.radius+self.strokeThickness/2+5)*2); +} + +@end diff --git a/SVProgressHUD/SVProgressAnimatedView.h b/SVProgressHUD/SVProgressAnimatedView.h new file mode 100644 index 0000000..c730075 --- /dev/null +++ b/SVProgressHUD/SVProgressAnimatedView.h @@ -0,0 +1,17 @@ +// +// SVProgressAnimatedView.h +// SVProgressHUD, https://github.com/SVProgressHUD/SVProgressHUD +// +// Copyright (c) 2017-2019 Tobias Tiemerding. All rights reserved. +// + +#import + +@interface SVProgressAnimatedView : UIView + +@property (nonatomic, assign) CGFloat radius; +@property (nonatomic, assign) CGFloat strokeThickness; +@property (nonatomic, strong) UIColor *strokeColor; +@property (nonatomic, assign) CGFloat strokeEnd; + +@end diff --git a/SVProgressHUD/SVProgressAnimatedView.m b/SVProgressHUD/SVProgressAnimatedView.m new file mode 100644 index 0000000..30587e6 --- /dev/null +++ b/SVProgressHUD/SVProgressAnimatedView.m @@ -0,0 +1,96 @@ +// +// SVProgressAnimatedView.m +// SVProgressHUD, https://github.com/SVProgressHUD/SVProgressHUD +// +// Copyright (c) 2017-2019 Tobias Tiemerding. All rights reserved. +// + +#import "SVProgressAnimatedView.h" + +@interface SVProgressAnimatedView () + +@property (nonatomic, strong) CAShapeLayer *ringAnimatedLayer; + +@end + +@implementation SVProgressAnimatedView + +- (void)willMoveToSuperview:(UIView*)newSuperview { + if (newSuperview) { + [self layoutAnimatedLayer]; + } else { + [_ringAnimatedLayer removeFromSuperlayer]; + _ringAnimatedLayer = nil; + } +} + +- (void)layoutAnimatedLayer { + CALayer *layer = self.ringAnimatedLayer; + [self.layer addSublayer:layer]; + + CGFloat widthDiff = CGRectGetWidth(self.bounds) - CGRectGetWidth(layer.bounds); + CGFloat heightDiff = CGRectGetHeight(self.bounds) - CGRectGetHeight(layer.bounds); + layer.position = CGPointMake(CGRectGetWidth(self.bounds) - CGRectGetWidth(layer.bounds) / 2 - widthDiff / 2, CGRectGetHeight(self.bounds) - CGRectGetHeight(layer.bounds) / 2 - heightDiff / 2); +} + +- (CAShapeLayer*)ringAnimatedLayer { + if(!_ringAnimatedLayer) { + CGPoint arcCenter = CGPointMake(self.radius+self.strokeThickness/2+5, self.radius+self.strokeThickness/2+5); + UIBezierPath* smoothedPath = [UIBezierPath bezierPathWithArcCenter:arcCenter radius:self.radius startAngle:(CGFloat)-M_PI_2 endAngle:(CGFloat) (M_PI + M_PI_2) clockwise:YES]; + + _ringAnimatedLayer = [CAShapeLayer layer]; + _ringAnimatedLayer.contentsScale = [[UIScreen mainScreen] scale]; + _ringAnimatedLayer.frame = CGRectMake(0.0f, 0.0f, arcCenter.x*2, arcCenter.y*2); + _ringAnimatedLayer.fillColor = [UIColor clearColor].CGColor; + _ringAnimatedLayer.strokeColor = self.strokeColor.CGColor; + _ringAnimatedLayer.lineWidth = self.strokeThickness; + _ringAnimatedLayer.lineCap = kCALineCapRound; + _ringAnimatedLayer.lineJoin = kCALineJoinBevel; + _ringAnimatedLayer.path = smoothedPath.CGPath; + } + return _ringAnimatedLayer; +} + +- (void)setFrame:(CGRect)frame { + if(!CGRectEqualToRect(frame, super.frame)) { + [super setFrame:frame]; + + if(self.superview) { + [self layoutAnimatedLayer]; + } + } +} + +- (void)setRadius:(CGFloat)radius { + if(radius != _radius) { + _radius = radius; + + [_ringAnimatedLayer removeFromSuperlayer]; + _ringAnimatedLayer = nil; + + if(self.superview) { + [self layoutAnimatedLayer]; + } + } +} + +- (void)setStrokeColor:(UIColor*)strokeColor { + _strokeColor = strokeColor; + _ringAnimatedLayer.strokeColor = strokeColor.CGColor; +} + +- (void)setStrokeThickness:(CGFloat)strokeThickness { + _strokeThickness = strokeThickness; + _ringAnimatedLayer.lineWidth = _strokeThickness; +} + +- (void)setStrokeEnd:(CGFloat)strokeEnd { + _strokeEnd = strokeEnd; + _ringAnimatedLayer.strokeEnd = _strokeEnd; +} + +- (CGSize)sizeThatFits:(CGSize)size { + return CGSizeMake((self.radius+self.strokeThickness/2+5)*2, (self.radius+self.strokeThickness/2+5)*2); +} + +@end diff --git a/SVProgressHUD/SVProgressHUD-Prefix.pch b/SVProgressHUD/SVProgressHUD-Prefix.pch new file mode 100644 index 0000000..2fff997 --- /dev/null +++ b/SVProgressHUD/SVProgressHUD-Prefix.pch @@ -0,0 +1,7 @@ +// +// Prefix header for all source files of the 'SVProgressHUD' target in the 'SVProgressHUD' project +// + +#ifdef __OBJC__ + #import +#endif diff --git a/SVProgressHUD/SVProgressHUD.bundle/angle-mask.png b/SVProgressHUD/SVProgressHUD.bundle/angle-mask.png new file mode 100644 index 0000000..0150a03 Binary files /dev/null and b/SVProgressHUD/SVProgressHUD.bundle/angle-mask.png differ diff --git a/SVProgressHUD/SVProgressHUD.bundle/angle-mask@2x.png b/SVProgressHUD/SVProgressHUD.bundle/angle-mask@2x.png new file mode 100644 index 0000000..9a302b6 Binary files /dev/null and b/SVProgressHUD/SVProgressHUD.bundle/angle-mask@2x.png differ diff --git a/SVProgressHUD/SVProgressHUD.bundle/angle-mask@3x.png b/SVProgressHUD/SVProgressHUD.bundle/angle-mask@3x.png new file mode 100644 index 0000000..d07f3ce Binary files /dev/null and b/SVProgressHUD/SVProgressHUD.bundle/angle-mask@3x.png differ diff --git a/SVProgressHUD/SVProgressHUD.bundle/error.png b/SVProgressHUD/SVProgressHUD.bundle/error.png new file mode 100644 index 0000000..a57c8e4 Binary files /dev/null and b/SVProgressHUD/SVProgressHUD.bundle/error.png differ diff --git a/SVProgressHUD/SVProgressHUD.bundle/error@2x.png b/SVProgressHUD/SVProgressHUD.bundle/error@2x.png new file mode 100644 index 0000000..aaf6798 Binary files /dev/null and b/SVProgressHUD/SVProgressHUD.bundle/error@2x.png differ diff --git a/SVProgressHUD/SVProgressHUD.bundle/error@3x.png b/SVProgressHUD/SVProgressHUD.bundle/error@3x.png new file mode 100644 index 0000000..c92518f Binary files /dev/null and b/SVProgressHUD/SVProgressHUD.bundle/error@3x.png differ diff --git a/SVProgressHUD/SVProgressHUD.bundle/info.png b/SVProgressHUD/SVProgressHUD.bundle/info.png new file mode 100644 index 0000000..a3a1f75 Binary files /dev/null and b/SVProgressHUD/SVProgressHUD.bundle/info.png differ diff --git a/SVProgressHUD/SVProgressHUD.bundle/info@2x.png b/SVProgressHUD/SVProgressHUD.bundle/info@2x.png new file mode 100644 index 0000000..1b333e7 Binary files /dev/null and b/SVProgressHUD/SVProgressHUD.bundle/info@2x.png differ diff --git a/SVProgressHUD/SVProgressHUD.bundle/info@3x.png b/SVProgressHUD/SVProgressHUD.bundle/info@3x.png new file mode 100644 index 0000000..d56aa0c Binary files /dev/null and b/SVProgressHUD/SVProgressHUD.bundle/info@3x.png differ diff --git a/SVProgressHUD/SVProgressHUD.bundle/success.png b/SVProgressHUD/SVProgressHUD.bundle/success.png new file mode 100644 index 0000000..44769d0 Binary files /dev/null and b/SVProgressHUD/SVProgressHUD.bundle/success.png differ diff --git a/SVProgressHUD/SVProgressHUD.bundle/success@2x.png b/SVProgressHUD/SVProgressHUD.bundle/success@2x.png new file mode 100644 index 0000000..a9d1653 Binary files /dev/null and b/SVProgressHUD/SVProgressHUD.bundle/success@2x.png differ diff --git a/SVProgressHUD/SVProgressHUD.bundle/success@3x.png b/SVProgressHUD/SVProgressHUD.bundle/success@3x.png new file mode 100644 index 0000000..42bad9b Binary files /dev/null and b/SVProgressHUD/SVProgressHUD.bundle/success@3x.png differ diff --git a/SVProgressHUD/SVProgressHUD.h b/SVProgressHUD/SVProgressHUD.h new file mode 100644 index 0000000..1372cf0 --- /dev/null +++ b/SVProgressHUD/SVProgressHUD.h @@ -0,0 +1,152 @@ +// +// SVProgressHUD.h +// SVProgressHUD, https://github.com/SVProgressHUD/SVProgressHUD +// +// Copyright (c) 2011-2019 Sam Vermette and contributors. All rights reserved. +// + +#import +#import + +extern NSString * _Nonnull const SVProgressHUDDidReceiveTouchEventNotification; +extern NSString * _Nonnull const SVProgressHUDDidTouchDownInsideNotification; +extern NSString * _Nonnull const SVProgressHUDWillDisappearNotification; +extern NSString * _Nonnull const SVProgressHUDDidDisappearNotification; +extern NSString * _Nonnull const SVProgressHUDWillAppearNotification; +extern NSString * _Nonnull const SVProgressHUDDidAppearNotification; + +extern NSString * _Nonnull const SVProgressHUDStatusUserInfoKey; + +typedef NS_ENUM(NSInteger, SVProgressHUDStyle) { + SVProgressHUDStyleLight NS_SWIFT_NAME(light), // default style, white HUD with black text, HUD background will be blurred + SVProgressHUDStyleDark NS_SWIFT_NAME(dark), // black HUD and white text, HUD background will be blurred + SVProgressHUDStyleCustom NS_SWIFT_NAME(custom) // uses the fore- and background color properties +}; + +typedef NS_ENUM(NSUInteger, SVProgressHUDMaskType) { + SVProgressHUDMaskTypeNone NS_SWIFT_NAME(none) = 1, // default mask type, allow user interactions while HUD is displayed + SVProgressHUDMaskTypeClear NS_SWIFT_NAME(clear), // don't allow user interactions with background objects + SVProgressHUDMaskTypeBlack NS_SWIFT_NAME(black), // don't allow user interactions with background objects and dim the UI in the back of the HUD (as seen in iOS 7 and above) + SVProgressHUDMaskTypeGradient NS_SWIFT_NAME(gradient), // don't allow user interactions with background objects and dim the UI with a a-la UIAlertView background gradient (as seen in iOS 6) + SVProgressHUDMaskTypeCustom NS_SWIFT_NAME(custom) // don't allow user interactions with background objects and dim the UI in the back of the HUD with a custom color +}; + +typedef NS_ENUM(NSUInteger, SVProgressHUDAnimationType) { + SVProgressHUDAnimationTypeFlat NS_SWIFT_NAME(flat), // default animation type, custom flat animation (indefinite animated ring) + SVProgressHUDAnimationTypeNative NS_SWIFT_NAME(native) // iOS native UIActivityIndicatorView +}; + +typedef void (^SVProgressHUDShowCompletion)(void); +typedef void (^SVProgressHUDDismissCompletion)(void); + +@interface SVProgressHUD : UIView + +#pragma mark - Customization + +@property (assign, nonatomic) SVProgressHUDStyle defaultStyle UI_APPEARANCE_SELECTOR; // default is SVProgressHUDStyleLight +@property (assign, nonatomic) SVProgressHUDMaskType defaultMaskType UI_APPEARANCE_SELECTOR; // default is SVProgressHUDMaskTypeNone +@property (assign, nonatomic) SVProgressHUDAnimationType defaultAnimationType UI_APPEARANCE_SELECTOR; // default is SVProgressHUDAnimationTypeFlat +@property (strong, nonatomic, nullable) UIView *containerView; // if nil then use default window level +@property (assign, nonatomic) CGSize minimumSize UI_APPEARANCE_SELECTOR; // default is CGSizeZero, can be used to avoid resizing for a larger message +@property (assign, nonatomic) CGFloat ringThickness UI_APPEARANCE_SELECTOR; // default is 2 pt +@property (assign, nonatomic) CGFloat ringRadius UI_APPEARANCE_SELECTOR; // default is 18 pt +@property (assign, nonatomic) CGFloat ringNoTextRadius UI_APPEARANCE_SELECTOR; // default is 24 pt +@property (assign, nonatomic) CGFloat cornerRadius UI_APPEARANCE_SELECTOR; // default is 14 pt +@property (strong, nonatomic, nonnull) UIFont *font UI_APPEARANCE_SELECTOR; // default is [UIFont preferredFontForTextStyle:UIFontTextStyleSubheadline] +@property (strong, nonatomic, nonnull) UIColor *backgroundColor UI_APPEARANCE_SELECTOR; // default is [UIColor whiteColor] +@property (strong, nonatomic, nonnull) UIColor *foregroundColor UI_APPEARANCE_SELECTOR; // default is [UIColor blackColor] +@property (strong, nonatomic, nullable) UIColor *foregroundImageColor UI_APPEARANCE_SELECTOR; // default is the same as foregroundColor +@property (strong, nonatomic, nonnull) UIColor *backgroundLayerColor UI_APPEARANCE_SELECTOR; // default is [UIColor colorWithWhite:0 alpha:0.4] +@property (assign, nonatomic) CGSize imageViewSize UI_APPEARANCE_SELECTOR; // default is 28x28 pt +@property (assign, nonatomic) BOOL shouldTintImages UI_APPEARANCE_SELECTOR; // default is YES +@property (strong, nonatomic, nonnull) UIImage *infoImage UI_APPEARANCE_SELECTOR; // default is the bundled info image provided by Freepik +@property (strong, nonatomic, nonnull) UIImage *successImage UI_APPEARANCE_SELECTOR; // default is the bundled success image provided by Freepik +@property (strong, nonatomic, nonnull) UIImage *errorImage UI_APPEARANCE_SELECTOR; // default is the bundled error image provided by Freepik +@property (strong, nonatomic, nonnull) UIView *viewForExtension UI_APPEARANCE_SELECTOR; // default is nil, only used if #define SV_APP_EXTENSIONS is set +@property (assign, nonatomic) NSTimeInterval graceTimeInterval; // default is 0 seconds +@property (assign, nonatomic) NSTimeInterval minimumDismissTimeInterval; // default is 5.0 seconds +@property (assign, nonatomic) NSTimeInterval maximumDismissTimeInterval; // default is CGFLOAT_MAX + +@property (assign, nonatomic) UIOffset offsetFromCenter UI_APPEARANCE_SELECTOR; // default is 0, 0 + +@property (assign, nonatomic) NSTimeInterval fadeInAnimationDuration UI_APPEARANCE_SELECTOR; // default is 0.15 +@property (assign, nonatomic) NSTimeInterval fadeOutAnimationDuration UI_APPEARANCE_SELECTOR; // default is 0.15 + +@property (assign, nonatomic) UIWindowLevel maxSupportedWindowLevel; // default is UIWindowLevelNormal + +@property (assign, nonatomic) BOOL hapticsEnabled; // default is NO +@property (assign, nonatomic) BOOL motionEffectEnabled; // default is YES + ++ (void)setDefaultStyle:(SVProgressHUDStyle)style; // default is SVProgressHUDStyleLight ++ (void)setDefaultMaskType:(SVProgressHUDMaskType)maskType; // default is SVProgressHUDMaskTypeNone ++ (void)setDefaultAnimationType:(SVProgressHUDAnimationType)type; // default is SVProgressHUDAnimationTypeFlat ++ (void)setContainerView:(nullable UIView*)containerView; // default is window level ++ (void)setMinimumSize:(CGSize)minimumSize; // default is CGSizeZero, can be used to avoid resizing for a larger message ++ (void)setRingThickness:(CGFloat)ringThickness; // default is 2 pt ++ (void)setRingRadius:(CGFloat)radius; // default is 18 pt ++ (void)setRingNoTextRadius:(CGFloat)radius; // default is 24 pt ++ (void)setCornerRadius:(CGFloat)cornerRadius; // default is 14 pt ++ (void)setBorderColor:(nonnull UIColor*)color; // default is nil ++ (void)setBorderWidth:(CGFloat)width; // default is 0 ++ (void)setFont:(nonnull UIFont*)font; // default is [UIFont preferredFontForTextStyle:UIFontTextStyleSubheadline] ++ (void)setForegroundColor:(nonnull UIColor*)color; // default is [UIColor blackColor], only used for SVProgressHUDStyleCustom ++ (void)setForegroundImageColor:(nullable UIColor*)color; // default is nil == foregroundColor, only used for SVProgressHUDStyleCustom ++ (void)setBackgroundColor:(nonnull UIColor*)color; // default is [UIColor whiteColor], only used for SVProgressHUDStyleCustom ++ (void)setHudViewCustomBlurEffect:(nullable UIBlurEffect*)blurEffect; // default is nil, only used for SVProgressHUDStyleCustom, can be combined with backgroundColor ++ (void)setBackgroundLayerColor:(nonnull UIColor*)color; // default is [UIColor colorWithWhite:0 alpha:0.5], only used for SVProgressHUDMaskTypeCustom ++ (void)setImageViewSize:(CGSize)size; // default is 28x28 pt ++ (void)setShouldTintImages:(BOOL)shouldTintImages; // default is YES ++ (void)setInfoImage:(nonnull UIImage*)image; // default is the bundled info image provided by Freepik ++ (void)setSuccessImage:(nonnull UIImage*)image; // default is the bundled success image provided by Freepik ++ (void)setErrorImage:(nonnull UIImage*)image; // default is the bundled error image provided by Freepik ++ (void)setViewForExtension:(nonnull UIView*)view; // default is nil, only used if #define SV_APP_EXTENSIONS is set ++ (void)setGraceTimeInterval:(NSTimeInterval)interval; // default is 0 seconds ++ (void)setMinimumDismissTimeInterval:(NSTimeInterval)interval; // default is 5.0 seconds ++ (void)setMaximumDismissTimeInterval:(NSTimeInterval)interval; // default is infinite ++ (void)setFadeInAnimationDuration:(NSTimeInterval)duration; // default is 0.15 seconds ++ (void)setFadeOutAnimationDuration:(NSTimeInterval)duration; // default is 0.15 seconds ++ (void)setMaxSupportedWindowLevel:(UIWindowLevel)windowLevel; // default is UIWindowLevelNormal ++ (void)setHapticsEnabled:(BOOL)hapticsEnabled; // default is NO ++ (void)setMotionEffectEnabled:(BOOL)motionEffectEnabled; // default is YES + +#pragma mark - Show Methods + ++ (void)show; ++ (void)showWithMaskType:(SVProgressHUDMaskType)maskType __attribute__((deprecated("Use show and setDefaultMaskType: instead."))); ++ (void)showWithStatus:(nullable NSString*)status; ++ (void)showWithStatus:(nullable NSString*)status maskType:(SVProgressHUDMaskType)maskType __attribute__((deprecated("Use showWithStatus: and setDefaultMaskType: instead."))); + ++ (void)showProgress:(float)progress; ++ (void)showProgress:(float)progress maskType:(SVProgressHUDMaskType)maskType __attribute__((deprecated("Use showProgress: and setDefaultMaskType: instead."))); ++ (void)showProgress:(float)progress status:(nullable NSString*)status; ++ (void)showProgress:(float)progress status:(nullable NSString*)status maskType:(SVProgressHUDMaskType)maskType __attribute__((deprecated("Use showProgress:status: and setDefaultMaskType: instead."))); + ++ (void)setStatus:(nullable NSString*)status; // change the HUD loading status while it's showing + +// stops the activity indicator, shows a glyph + status, and dismisses the HUD a little bit later ++ (void)showInfoWithStatus:(nullable NSString*)status; ++ (void)showInfoWithStatus:(nullable NSString*)status maskType:(SVProgressHUDMaskType)maskType __attribute__((deprecated("Use showInfoWithStatus: and setDefaultMaskType: instead."))); ++ (void)showSuccessWithStatus:(nullable NSString*)status; ++ (void)showSuccessWithStatus:(nullable NSString*)status maskType:(SVProgressHUDMaskType)maskType __attribute__((deprecated("Use showSuccessWithStatus: and setDefaultMaskType: instead."))); ++ (void)showErrorWithStatus:(nullable NSString*)status; ++ (void)showErrorWithStatus:(nullable NSString*)status maskType:(SVProgressHUDMaskType)maskType __attribute__((deprecated("Use showErrorWithStatus: and setDefaultMaskType: instead."))); + +// shows a image + status, use white PNGs with the imageViewSize (default is 28x28 pt) ++ (void)showImage:(nonnull UIImage*)image status:(nullable NSString*)status; ++ (void)showImage:(nonnull UIImage*)image status:(nullable NSString*)status maskType:(SVProgressHUDMaskType)maskType __attribute__((deprecated("Use showImage:status: and setDefaultMaskType: instead."))); + ++ (void)setOffsetFromCenter:(UIOffset)offset; ++ (void)resetOffsetFromCenter; + ++ (void)popActivity; // decrease activity count, if activity count == 0 the HUD is dismissed ++ (void)dismiss; ++ (void)dismissWithCompletion:(nullable SVProgressHUDDismissCompletion)completion; ++ (void)dismissWithDelay:(NSTimeInterval)delay; ++ (void)dismissWithDelay:(NSTimeInterval)delay completion:(nullable SVProgressHUDDismissCompletion)completion; + ++ (BOOL)isVisible; + ++ (NSTimeInterval)displayDurationForString:(nullable NSString*)string; + +@end + diff --git a/SVProgressHUD/SVProgressHUD.m b/SVProgressHUD/SVProgressHUD.m new file mode 100644 index 0000000..3b72759 --- /dev/null +++ b/SVProgressHUD/SVProgressHUD.m @@ -0,0 +1,1546 @@ +// +// 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) + 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 = [[[UIApplication sharedApplication] delegate] window].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 = [[UIApplication sharedApplication] keyWindow].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; + } +} + +- (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 = [[[UIApplication sharedApplication] delegate] window].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 diff --git a/SVProgressHUD/SVRadialGradientLayer.h b/SVProgressHUD/SVRadialGradientLayer.h new file mode 100644 index 0000000..b63aeba --- /dev/null +++ b/SVProgressHUD/SVRadialGradientLayer.h @@ -0,0 +1,14 @@ +// +// SVRadialGradientLayer.h +// SVProgressHUD, https://github.com/SVProgressHUD/SVProgressHUD +// +// Copyright (c) 2014-2019 Tobias Tiemerding. All rights reserved. +// + +#import + +@interface SVRadialGradientLayer : CALayer + +@property (nonatomic) CGPoint gradientCenter; + +@end diff --git a/SVProgressHUD/SVRadialGradientLayer.m b/SVProgressHUD/SVRadialGradientLayer.m new file mode 100644 index 0000000..1c5d181 --- /dev/null +++ b/SVProgressHUD/SVRadialGradientLayer.m @@ -0,0 +1,25 @@ +// +// SVRadialGradientLayer.m +// SVProgressHUD, https://github.com/SVProgressHUD/SVProgressHUD +// +// Copyright (c) 2014-2019 Tobias Tiemerding. All rights reserved. +// + +#import "SVRadialGradientLayer.h" + +@implementation SVRadialGradientLayer + +- (void)drawInContext:(CGContextRef)context { + size_t locationsCount = 2; + CGFloat locations[2] = {0.0f, 1.0f}; + CGFloat colors[8] = {0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.75f}; + CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); + CGGradientRef gradient = CGGradientCreateWithColorComponents(colorSpace, colors, locations, locationsCount); + CGColorSpaceRelease(colorSpace); + + float radius = MIN(self.bounds.size.width , self.bounds.size.height); + CGContextDrawRadialGradient (context, gradient, self.gradientCenter, 0, self.gradientCenter, radius, kCGGradientDrawsAfterEndLocation); + CGGradientRelease(gradient); +} + +@end diff --git a/yacd/AirdropOnlyActivity.h b/yacd/AirdropOnlyActivity.h new file mode 100644 index 0000000..57e9cc6 --- /dev/null +++ b/yacd/AirdropOnlyActivity.h @@ -0,0 +1,17 @@ +// +// AirdropOnlyActivity.h +// yacd +// +// Created by Derek Selander on 9/3/20. +// Copyright © 2020 Selander. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface AirdropOnlyActivity : UIActivity +- (instancetype)initWithImage:(UIImage *)image appName:(NSString*)appName; +@end + +NS_ASSUME_NONNULL_END diff --git a/yacd/AirdropOnlyActivity.m b/yacd/AirdropOnlyActivity.m new file mode 100644 index 0000000..f5669c6 --- /dev/null +++ b/yacd/AirdropOnlyActivity.m @@ -0,0 +1,38 @@ +// +// AirdropOnlyActivity.m +// yacd +// +// Created by Derek Selander on 9/3/20. +// Copyright © 2020 Selander. All rights reserved. +// + +#import "AirdropOnlyActivity.h" + +@interface AirdropOnlyActivity () +@property (nonatomic, copy) NSString *appName; +@property (nonatomic, strong) UIImage *image; +@end + +@implementation AirdropOnlyActivity + +- (instancetype)initWithImage:(UIImage *)image appName:(NSString*)appName +{ + self = [super init]; + if (self) { + _image = image; + _appName = appName; + } + return self; +} + +- (UIActivityType)activityType { + return UIActivityTypeAirDrop; +} + +- (UIActivityCategory)activityCategory { + return UIActivityCategoryAction; +} + + + +@end diff --git a/yacd/AppDelegate.m b/yacd/AppDelegate.m index ef7f512..b355ec7 100644 --- a/yacd/AppDelegate.m +++ b/yacd/AppDelegate.m @@ -7,13 +7,24 @@ // #import "AppDelegate.h" +#import "SVProgressHUD.h" @interface AppDelegate () @end +void SSZipArchiveProgressCallback(float progress) { + [SVProgressHUD showProgress:progress status:@"Zipping Contents"]; +} + +void SSZipArchiveCompletionCallback(BOOL success) { + [SVProgressHUD dismiss]; +} + @implementation AppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { - // Override point for customization after application launch. + + [SVProgressHUD setDefaultMaskType:SVProgressHUDMaskTypeGradient]; + [SVProgressHUD setMaximumDismissTimeInterval:1.5]; return YES; } diff --git a/yacd/Models/ProcessModule.h b/yacd/Models/ProcessModule.h new file mode 100644 index 0000000..aa1fa61 --- /dev/null +++ b/yacd/Models/ProcessModule.h @@ -0,0 +1,21 @@ +// +// ProcessModule.h +// yacd +// +// Created by Derek Selander on 9/3/20. +// Copyright © 2020 Selander. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface ProcessModule : NSObject +@property (nonatomic, strong, readonly) NSString *path; +@property (nonatomic, strong, readonly) NSNumber *address; +@property (nonatomic, readonly, assign) BOOL isSharedCache; + +- (instancetype)initWithPath:(const char *)path startAddress:(uintptr_t)address task:(mach_port_t)task; +@end + +NS_ASSUME_NONNULL_END diff --git a/yacd/Models/ProcessModule.m b/yacd/Models/ProcessModule.m new file mode 100644 index 0000000..c0130e7 --- /dev/null +++ b/yacd/Models/ProcessModule.m @@ -0,0 +1,29 @@ +// +// ProcessModule.m +// yacd +// +// Created by Derek Selander on 9/3/20. +// Copyright © 2020 Selander. All rights reserved. +// + +#import "ProcessModule.h" +#import +#import +@implementation ProcessModule + +- (instancetype)initWithPath:(const char *)path startAddress:(uintptr_t)address task:(mach_port_t)task { + if (self = [super init]) { + _path = [NSString stringWithUTF8String:path]; + _address = @(address); + + mach_vm_size_t size = sizeof(struct mach_header_64); + struct mach_header_64 header = {}; + if (mach_vm_read_overwrite(task, address, sizeof(struct mach_header_64), (mach_vm_address_t)&header, &size)) { + return self; + } + _isSharedCache = (header.flags & MH_DYLIB_IN_CACHE) ? YES : NO; + + } + return self; +} +@end diff --git a/yacd/SSZipArchive/SSZipArchive.m b/yacd/SSZipArchive/SSZipArchive.m index 8a77c9b..9e38bbd 100644 --- a/yacd/SSZipArchive/SSZipArchive.m +++ b/yacd/SSZipArchive/SSZipArchive.m @@ -14,6 +14,8 @@ NSString *const SSZipArchiveErrorDomain = @"SSZipArchiveErrorDomain"; +extern void SSZipArchiveProgressCallback(float progress); +extern void SSZipArchiveCompletionCallback(BOOL success); #define CHUNK 16384 int _zipOpenEntry(zipFile entry, NSString *name, const zip_fileinfo *zipfi, int level, NSString *password, BOOL aes); @@ -815,7 +817,18 @@ + (BOOL)createZipFileAtPath:(NSString *)path allObjects = @[@""]; total = 1; } - for (__strong NSString *fileName in allObjects) { + float mostRecentProgressUpdate = 0.0; + for (int i = 0; i < total; i++) { + + float prog = (float)i/(float)total; + if (prog - mostRecentProgressUpdate > 0.15) { + mostRecentProgressUpdate = prog; + dispatch_async(dispatch_get_main_queue(), ^{ + SSZipArchiveProgressCallback(prog); + }); + } + + NSString *fileName = allObjects[i]; NSString *fullFilePath = [directoryPath stringByAppendingPathComponent:fileName]; if ([fullFilePath isEqualToString:path]) { NSLog(@"[SSZipArchive] the archive path and the file path: %@ are the same, which is forbidden.", fullFilePath); @@ -845,6 +858,9 @@ + (BOOL)createZipFileAtPath:(NSString *)path } success &= [zipArchive close]; } + dispatch_async(dispatch_get_main_queue(), ^{ + SSZipArchiveCompletionCallback(success); + }); return success; } diff --git a/yacd/ViewControllers/ApplicationTableViewCell.m b/yacd/ViewControllers/ApplicationTableViewCell.m index 9444188..1652c05 100644 --- a/yacd/ViewControllers/ApplicationTableViewCell.m +++ b/yacd/ViewControllers/ApplicationTableViewCell.m @@ -8,15 +8,14 @@ #import "ApplicationTableViewCell.h" -__attribute__((weak)) -extern CGImageRef LICreateIconFromCachedBitmap(NSData* data); @implementation ApplicationTableViewCell - (void)setupCellWithApplicationProxy:(id ) applicationProxy pidInfo:(PIDInfo*)pidInfo { - NSData *data = [applicationProxy primaryIconDataForVariant:0x22]; + NSData *data = [applicationProxy primaryIconDataForVariant:0x20]; CGImageRef imageRef = LICreateIconFromCachedBitmap(data); - UIImage *image = [[UIImage alloc] initWithCGImage:imageRef scale:2.0 orientation:UIImageOrientationUp]; + CGFloat scale = [UIScreen mainScreen].scale; + UIImage *image = [[UIImage alloc] initWithCGImage:imageRef scale:scale orientation:UIImageOrientationUp]; self.appIconImageView.image = image; self.bundleIdentifierLabel.text = [applicationProxy bundleIdentifier]; diff --git a/yacd/ViewControllers/ProcessDetailsViewController.h b/yacd/ViewControllers/ProcessDetailsViewController.h index 26817e0..05252cd 100644 --- a/yacd/ViewControllers/ProcessDetailsViewController.h +++ b/yacd/ViewControllers/ProcessDetailsViewController.h @@ -8,6 +8,12 @@ #import #import + +#import "UIViewController+DisplayError.h" +#import "SSZipArchive.h" +#import "NSError+ErrorHelper.h" +#import "SVProgressHUD.h" +#import "AirdropOnlyActivity.h" #import "PIDInfo.h" NS_ASSUME_NONNULL_BEGIN diff --git a/yacd/ViewControllers/ProcessDetailsViewController.mm b/yacd/ViewControllers/ProcessDetailsViewController.mm index 7e00295..6723683 100644 --- a/yacd/ViewControllers/ProcessDetailsViewController.mm +++ b/yacd/ViewControllers/ProcessDetailsViewController.mm @@ -7,19 +7,20 @@ // #import "ProcessDetailsViewController.h" -#import -#import "UIViewController+DisplayError.h" -#import "SSZipArchive.h" -#import "NSError+ErrorHelper.h" +#import "ProcessModule.h" #define YACD_DIR @"yacd" #define ZIP_PAYLOAD @"payload.zip" +__attribute__((weak)) +extern CGImageRef LICreateIconFromCachedBitmap(NSData* data); + @interface ProcessDetailsViewController () @property (nonatomic, weak) IBOutlet UITableView *tableView; @property (nonatomic, strong) NSString *appPath; -@property (nonatomic, strong) NSArray * dataSource; -@property (nonatomic, assign) mach_port_name_t externalPort; +@property (nonatomic, strong) NSArray * dataSource; +@property (nonatomic, strong) NSArray * sharedDataSource; +@property (nonatomic, assign) mach_port_t externalPort; @end extern "C" { @@ -33,127 +34,163 @@ @implementation ProcessDetailsViewController - (void)viewDidLoad { [super viewDidLoad]; - NSError *err = nil; -// mach_port_name_t externalPort = MACH_PORT_NULL; self.appPath = [self.pidInfo.path stringByDeletingLastPathComponent]; if (!self.appPath) { - err = [NSError errorWithDescription:@"Malformed process"]; - [self displayModalErrorWithMessage:@"Error" andError:err]; + [SVProgressHUD showErrorWithStatus:@"Application must be launched first"]; return; } if (task_for_pid(mach_task_self(), self.pidInfo.pid.intValue, &_externalPort)) { - err = [NSError errorWithDescription:@"Unable to get task_for_pid"]; - [self displayModalErrorWithMessage:@"Error" andError:err]; + [SVProgressHUD showErrorWithStatus:@"Unable to get task for PID"]; return; } NSMutableArray *data = [NSMutableArray new]; - ::enumerate_encrypted_modules(_externalPort, ^(char *path, mach_vm_address_t module_start, uint32_t cryptoff, uint32_t cryptsize) { - - [data addObject:@{ @"module" : [NSString stringWithUTF8String:path], @"addr" : @(module_start)}]; + NSMutableArray *sharedData = [NSMutableArray new]; + BOOL success = enumerate_loaded_modules(_externalPort, ^(char *path, mach_vm_address_t module_start) { + ProcessModule *processModule = [[ProcessModule alloc] initWithPath:path startAddress:module_start task:self.externalPort]; + + + if (processModule.isSharedCache) { + [sharedData addObject:processModule]; + } else { + [data addObject:processModule]; + } }); + + if (!success) { + [SVProgressHUD showErrorWithStatus:@"task_info error (System App)?"]; + } self.dataSource = [data copy]; + self.sharedDataSource = [sharedData copy]; } +- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section { + return section == 0 ? @"NON-Shared DYLD" : @"Shared DYLD"; +} - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { - return self.dataSource.count; + return section == 0 ? self.dataSource.count : self.sharedDataSource.count; } +- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { + return 2; +} - (nonnull UITableViewCell *)tableView:(nonnull UITableView *)tableView cellForRowAtIndexPath:(nonnull NSIndexPath *)indexPath { - NSDictionary *dict = self.dataSource[indexPath.row]; + + ProcessModule *pm = indexPath.section == 0 ? self.dataSource[indexPath.row] : self.sharedDataSource[indexPath.row]; UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell"]; cell.textLabel.minimumScaleFactor = 0.5; - cell.textLabel.text = [dict[@"module"] lastPathComponent]; - NSNumber *addr = dict[@"addr"]; + + cell.textLabel.text = [pm.path lastPathComponent]; + NSNumber *addr = pm.address; cell.detailTextLabel.text = [NSString stringWithFormat:@"0x%012lx", [addr unsignedLongValue]]; return cell; } - (IBAction)decryptApplicationTapped:(id)sender { - NSError *err = nil; mach_port_name_t externalPort = MACH_PORT_NULL; + ((UIButton*)sender).enabled = NO; if (task_for_pid(mach_task_self(), self.pidInfo.pid.intValue, &externalPort)) { - err = [NSError errorWithDescription:@"Unable to get task_for_pid"]; - [self displayModalErrorWithMessage:@"Error" andError:err]; + [SVProgressHUD showErrorWithStatus:@"Unable to get task for PID"]; return; } // Create the path to/from - NSFileManager *manager = [NSFileManager defaultManager]; - NSString* tmpDir = NSTemporaryDirectory(); - NSString *appName = [self.appPath lastPathComponent]; - NSString *copyToDir = [tmpDir stringByAppendingPathComponent:YACD_DIR]; - NSString *fullPathToDir = [copyToDir stringByAppendingPathComponent:appName]; - NSString *zippedPath = [tmpDir stringByAppendingPathComponent:ZIP_PAYLOAD]; - - [manager removeItemAtPath:fullPathToDir error:&err]; - if (err) { - NSLog(@"%@", err); - err = nil; - } + [SVProgressHUD showInfoWithStatus:@"Copying .app contents"]; - [manager createDirectoryAtPath:copyToDir withIntermediateDirectories:YES attributes:nil error:&err]; - if (err) { - NSLog(@"%@", err); - err = nil; - } - - if (![manager copyItemAtPath:self.appPath toPath:fullPathToDir error:&err]) { + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{ + + NSError *err = nil; + NSFileManager *manager = [NSFileManager defaultManager]; + NSString* tmpDir = NSTemporaryDirectory(); + NSString *appName = [self.appPath lastPathComponent]; + NSString *copyToDir = [tmpDir stringByAppendingPathComponent:YACD_DIR]; + NSString *fullPathToDir = [copyToDir stringByAppendingPathComponent:appName]; + NSString *zippedPath = [tmpDir stringByAppendingPathComponent:ZIP_PAYLOAD]; + + [manager removeItemAtPath:fullPathToDir error:&err]; if (err) { NSLog(@"%@", err); + err = nil; } - } - - // Replica at fullPathToDir, cross reference memory in process and grab decrypted parts - - ::enumerate_encrypted_modules(externalPort, ^(char *path, mach_vm_address_t module_start, uint32_t cryptoff, uint32_t cryptsize) { + [manager createDirectoryAtPath:copyToDir withIntermediateDirectories:YES attributes:nil error:&err]; + if (err) { + NSLog(@"%@", err); + err = nil; + } - NSString *fullPath = [NSString stringWithUTF8String:path]; - NSRange range = [fullPath rangeOfString:self.appPath]; - NSString *relativePath = [fullPath substringFromIndex:range.length]; - NSString *realizedPath = [fullPathToDir stringByAppendingPathComponent:relativePath]; - - // Grab the decrypted payload - void* payload = calloc(cryptsize, sizeof(char)); - mach_vm_size_t outsize = cryptsize; - kern_return_t kr = mach_vm_read_overwrite(self->_externalPort, module_start + cryptoff + sizeof(struct mach_header_64), cryptsize, (mach_vm_address_t)payload, &outsize); + if (![manager copyItemAtPath:self.appPath toPath:fullPathToDir error:&err]) { + if (err) { + NSLog(@"%@", err); + } + } - if (kr != KERN_SUCCESS || outsize != cryptsize) { - NSError *err = [NSError errorWithDescription:@"Error Patching"]; - [self displayModalErrorWithMessage:@"Error" andError:err]; + // Replica at fullPathToDir, cross reference memory in process and grab decrypted parts + [SVProgressHUD showInfoWithStatus:@"Patching encrypted modules"]; + ::enumerate_encrypted_modules(externalPort, ^(char *path, mach_vm_address_t module_start, uint32_t cryptoff, uint32_t cryptsize) { + + + NSString *fullPath = [NSString stringWithUTF8String:path]; + NSRange range = [fullPath rangeOfString:self.appPath]; + NSString *relativePath = [fullPath substringFromIndex:range.length]; + NSString *realizedPath = [fullPathToDir stringByAppendingPathComponent:relativePath]; + + // Grab the decrypted payload + void* payload = calloc(cryptsize, sizeof(char)); + mach_vm_size_t outsize = cryptsize; + kern_return_t kr = mach_vm_read_overwrite(self->_externalPort, module_start + cryptoff + sizeof(struct mach_header_64), cryptsize, (mach_vm_address_t)payload, &outsize); + + if (kr != KERN_SUCCESS || outsize != cryptsize) { + [SVProgressHUD showErrorWithStatus:@"Error reading encrypted module"]; + free(payload); + return; + } + + // Patch file from decrypted mem + if (!patch_encrypted_module_with_decrypted_memory((mach_vm_address_t)payload, cryptsize, cryptoff, realizedPath.UTF8String)) { + [SVProgressHUD showErrorWithStatus:@"Error replacing memory"]; + } free(payload); - return; - } + }); - // Patch file from decrypted mem - if (!patch_encrypted_module_with_decrypted_memory((mach_vm_address_t)payload, cryptsize, cryptoff, realizedPath.UTF8String)) { - NSError *err = [NSError errorWithDescription:@"Error Replacing Decrypted Memory"]; - [self displayModalErrorWithMessage:@"Error" andError:err]; - } - free(payload); + [self zipAndShare:zippedPath withContents:fullPathToDir]; + dispatch_async(dispatch_get_main_queue(), ^{ + ((UIButton*)sender).enabled = YES; + }); }); - - [self zipAndShare:zippedPath withContents:fullPathToDir]; } -- (void)zipAndShare:(NSString *)zippedPath withContents:(NSString *)contents { - if ([SSZipArchive createZipFileAtPath:zippedPath withContentsOfDirectory:contents keepParentDirectory:YES]) { - NSURL *url = [NSURL fileURLWithPath:zippedPath]; - UIActivityViewController *activityViewController = [[UIActivityViewController alloc] initWithActivityItems:@[url] applicationActivities:nil]; - [self presentViewController:activityViewController animated:YES completion:nil]; - } else { - NSError *err = [NSError errorWithDescription:@"Zipping problem"]; - [self displayModalErrorWithMessage:@"Error" andError:err]; - } +- (void)zipAndShare:(NSString *)zippedPath withContents:(NSString *)contents { + [SVProgressHUD showInfoWithStatus:@"Zipping contents"]; + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{ + BOOL success = [SSZipArchive createZipFileAtPath:zippedPath withContentsOfDirectory:contents keepParentDirectory:YES]; + dispatch_async(dispatch_get_main_queue(), ^{ + if (success) { + NSURL *url = [NSURL fileURLWithPath:zippedPath]; + + NSData *data = [self.application primaryIconDataForVariant:0x20]; + CGImageRef imageRef = LICreateIconFromCachedBitmap(data); + CGFloat scale = [UIScreen mainScreen].scale; + UIImage *image = [[UIImage alloc] initWithCGImage:imageRef scale:scale orientation:UIImageOrientationUp]; + + UIActivity *airdropActivity = [[AirdropOnlyActivity alloc] initWithImage:image appName:self.application.bundleIdentifier]; + UIActivityViewController *activityViewController = [[UIActivityViewController alloc] initWithActivityItems:@[url] applicationActivities:@[airdropActivity]]; + + [self presentViewController:activityViewController animated:YES completion:nil]; + } else { + [SVProgressHUD showErrorWithStatus:@"Zipping problem"]; + } + + }); + }); } @end diff --git a/yacd/ViewControllers/ViewController.mm b/yacd/ViewControllers/ViewController.mm index 11a4db9..e324123 100644 --- a/yacd/ViewControllers/ViewController.mm +++ b/yacd/ViewControllers/ViewController.mm @@ -54,6 +54,7 @@ - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { NSIndexPath* indexPath = [self.tableView indexPathForSelectedRow]; ProcessDetailsViewController* vc = (ProcessDetailsViewController*)[segue destinationViewController]; auto application = self.filteredInstalledApplications[indexPath.row]; + vc.application = application; auto pidInfo = self.processDictionary[application.canonicalExecutablePath]; vc.pidInfo = pidInfo; }