MBProgressHUD.m 44 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195
  1. //
  2. // MBProgressHUD.m
  3. // Version 1.2.0
  4. // Created by Matej Bukovinski on 2.4.09.
  5. //
  6. #import "MBProgressHUD.h"
  7. #import <tgmath.h>
  8. #define MBMainThreadAssert() NSAssert([NSThread isMainThread], @"MBProgressHUD needs to be accessed on the main thread.");
  9. CGFloat const MBProgressMaxOffset = 1000000.f;
  10. static const CGFloat MBDefaultPadding = 4.f;
  11. static const CGFloat MBDefaultLabelFontSize = 16.f;
  12. static const CGFloat MBDefaultDetailsLabelFontSize = 12.f;
  13. @interface MBProgressHUD ()
  14. @property (nonatomic, assign) BOOL useAnimation;
  15. @property (nonatomic, assign, getter=hasFinished) BOOL finished;
  16. @property (nonatomic, strong) UIView *indicator;
  17. @property (nonatomic, strong) NSDate *showStarted;
  18. @property (nonatomic, strong) NSArray *paddingConstraints;
  19. @property (nonatomic, strong) NSArray *bezelConstraints;
  20. @property (nonatomic, strong) UIView *topSpacer;
  21. @property (nonatomic, strong) UIView *bottomSpacer;
  22. @property (nonatomic, strong) UIMotionEffectGroup *bezelMotionEffects;
  23. @property (nonatomic, weak) NSTimer *graceTimer;
  24. @property (nonatomic, weak) NSTimer *minShowTimer;
  25. @property (nonatomic, weak) NSTimer *hideDelayTimer;
  26. @property (nonatomic, weak) CADisplayLink *progressObjectDisplayLink;
  27. @end
  28. @interface MBProgressHUDRoundedButton : UIButton
  29. @end
  30. @implementation MBProgressHUD
  31. #pragma mark - Class methods
  32. + (instancetype)showHUDAddedTo:(UIView *)view animated:(BOOL)animated {
  33. MBProgressHUD *hud = [[self alloc] initWithView:view];
  34. hud.removeFromSuperViewOnHide = YES;
  35. [view addSubview:hud];
  36. [hud showAnimated:animated];
  37. return hud;
  38. }
  39. + (BOOL)hideHUDForView:(UIView *)view animated:(BOOL)animated {
  40. MBProgressHUD *hud = [self HUDForView:view];
  41. if (hud != nil) {
  42. hud.removeFromSuperViewOnHide = YES;
  43. [hud hideAnimated:animated];
  44. return YES;
  45. }
  46. return NO;
  47. }
  48. + (MBProgressHUD *)HUDForView:(UIView *)view {
  49. NSEnumerator *subviewsEnum = [view.subviews reverseObjectEnumerator];
  50. for (UIView *subview in subviewsEnum) {
  51. if ([subview isKindOfClass:self]) {
  52. MBProgressHUD *hud = (MBProgressHUD *)subview;
  53. if (hud.hasFinished == NO) {
  54. return hud;
  55. }
  56. }
  57. }
  58. return nil;
  59. }
  60. #pragma mark - Lifecycle
  61. - (void)commonInit {
  62. // Set default values for properties
  63. _animationType = MBProgressHUDAnimationFade;
  64. _mode = MBProgressHUDModeIndeterminate;
  65. _margin = 20.0f;
  66. _defaultMotionEffectsEnabled = NO;
  67. if (@available(iOS 13.0, tvOS 13, *)) {
  68. _contentColor = [[UIColor labelColor] colorWithAlphaComponent:0.7f];
  69. } else {
  70. _contentColor = [UIColor colorWithWhite:0.f alpha:0.7f];
  71. }
  72. // Transparent background
  73. self.opaque = NO;
  74. self.backgroundColor = [UIColor clearColor];
  75. // Make it invisible for now
  76. self.alpha = 0.0f;
  77. self.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
  78. self.layer.allowsGroupOpacity = NO;
  79. [self setupViews];
  80. [self updateIndicators];
  81. [self registerForNotifications];
  82. }
  83. - (instancetype)initWithFrame:(CGRect)frame {
  84. if ((self = [super initWithFrame:frame])) {
  85. [self commonInit];
  86. }
  87. return self;
  88. }
  89. - (instancetype)initWithCoder:(NSCoder *)aDecoder {
  90. if ((self = [super initWithCoder:aDecoder])) {
  91. [self commonInit];
  92. }
  93. return self;
  94. }
  95. - (id)initWithView:(UIView *)view {
  96. NSAssert(view, @"View must not be nil.");
  97. return [self initWithFrame:view.bounds];
  98. }
  99. - (void)dealloc {
  100. [self unregisterFromNotifications];
  101. }
  102. #pragma mark - Show & hide
  103. - (void)showAnimated:(BOOL)animated {
  104. MBMainThreadAssert();
  105. [self.minShowTimer invalidate];
  106. self.useAnimation = animated;
  107. self.finished = NO;
  108. // If the grace time is set, postpone the HUD display
  109. if (self.graceTime > 0.0) {
  110. NSTimer *timer = [NSTimer timerWithTimeInterval:self.graceTime target:self selector:@selector(handleGraceTimer:) userInfo:nil repeats:NO];
  111. [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
  112. self.graceTimer = timer;
  113. }
  114. // ... otherwise show the HUD immediately
  115. else {
  116. [self showUsingAnimation:self.useAnimation];
  117. }
  118. }
  119. - (void)hideAnimated:(BOOL)animated {
  120. MBMainThreadAssert();
  121. [self.graceTimer invalidate];
  122. self.useAnimation = animated;
  123. self.finished = YES;
  124. // If the minShow time is set, calculate how long the HUD was shown,
  125. // and postpone the hiding operation if necessary
  126. if (self.minShowTime > 0.0 && self.showStarted) {
  127. NSTimeInterval interv = [[NSDate date] timeIntervalSinceDate:self.showStarted];
  128. if (interv < self.minShowTime) {
  129. NSTimer *timer = [NSTimer timerWithTimeInterval:(self.minShowTime - interv) target:self selector:@selector(handleMinShowTimer:) userInfo:nil repeats:NO];
  130. [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
  131. self.minShowTimer = timer;
  132. return;
  133. }
  134. }
  135. // ... otherwise hide the HUD immediately
  136. [self hideUsingAnimation:self.useAnimation];
  137. }
  138. - (void)hideAnimated:(BOOL)animated afterDelay:(NSTimeInterval)delay {
  139. // Cancel any scheduled hideAnimated:afterDelay: calls
  140. [self.hideDelayTimer invalidate];
  141. NSTimer *timer = [NSTimer timerWithTimeInterval:delay target:self selector:@selector(handleHideTimer:) userInfo:@(animated) repeats:NO];
  142. [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
  143. self.hideDelayTimer = timer;
  144. }
  145. #pragma mark - Timer callbacks
  146. - (void)handleGraceTimer:(NSTimer *)theTimer {
  147. // Show the HUD only if the task is still running
  148. if (!self.hasFinished) {
  149. [self showUsingAnimation:self.useAnimation];
  150. }
  151. }
  152. - (void)handleMinShowTimer:(NSTimer *)theTimer {
  153. [self hideUsingAnimation:self.useAnimation];
  154. }
  155. - (void)handleHideTimer:(NSTimer *)timer {
  156. [self hideAnimated:[timer.userInfo boolValue]];
  157. }
  158. #pragma mark - View Hierrarchy
  159. - (void)didMoveToSuperview {
  160. [self updateForCurrentOrientationAnimated:NO];
  161. }
  162. #pragma mark - Internal show & hide operations
  163. - (void)showUsingAnimation:(BOOL)animated {
  164. // Cancel any previous animations
  165. [self.bezelView.layer removeAllAnimations];
  166. [self.backgroundView.layer removeAllAnimations];
  167. // Cancel any scheduled hideAnimated:afterDelay: calls
  168. [self.hideDelayTimer invalidate];
  169. self.showStarted = [NSDate date];
  170. self.alpha = 1.f;
  171. // Needed in case we hide and re-show with the same NSProgress object attached.
  172. [self setNSProgressDisplayLinkEnabled:YES];
  173. // Set up motion effects only at this point to avoid needlessly
  174. // creating the effect if it was disabled after initialization.
  175. [self updateBezelMotionEffects];
  176. if (animated) {
  177. [self animateIn:YES withType:self.animationType completion:NULL];
  178. } else {
  179. self.bezelView.alpha = 1.f;
  180. self.backgroundView.alpha = 1.f;
  181. }
  182. }
  183. - (void)hideUsingAnimation:(BOOL)animated {
  184. // Cancel any scheduled hideAnimated:afterDelay: calls.
  185. // This needs to happen here instead of in done,
  186. // to avoid races if another hideAnimated:afterDelay:
  187. // call comes in while the HUD is animating out.
  188. [self.hideDelayTimer invalidate];
  189. if (animated && self.showStarted) {
  190. self.showStarted = nil;
  191. [self animateIn:NO withType:self.animationType completion:^(BOOL finished) {
  192. [self done];
  193. }];
  194. } else {
  195. self.showStarted = nil;
  196. self.bezelView.alpha = 0.f;
  197. self.backgroundView.alpha = 1.f;
  198. [self done];
  199. }
  200. }
  201. - (void)animateIn:(BOOL)animatingIn withType:(MBProgressHUDAnimation)type completion:(void(^)(BOOL finished))completion {
  202. // Automatically determine the correct zoom animation type
  203. if (type == MBProgressHUDAnimationZoom) {
  204. type = animatingIn ? MBProgressHUDAnimationZoomIn : MBProgressHUDAnimationZoomOut;
  205. }
  206. CGAffineTransform small = CGAffineTransformMakeScale(0.5f, 0.5f);
  207. CGAffineTransform large = CGAffineTransformMakeScale(1.5f, 1.5f);
  208. // Set starting state
  209. UIView *bezelView = self.bezelView;
  210. if (animatingIn && bezelView.alpha == 0.f && type == MBProgressHUDAnimationZoomIn) {
  211. bezelView.transform = small;
  212. } else if (animatingIn && bezelView.alpha == 0.f && type == MBProgressHUDAnimationZoomOut) {
  213. bezelView.transform = large;
  214. }
  215. // Perform animations
  216. dispatch_block_t animations = ^{
  217. if (animatingIn) {
  218. bezelView.transform = CGAffineTransformIdentity;
  219. } else if (!animatingIn && type == MBProgressHUDAnimationZoomIn) {
  220. bezelView.transform = large;
  221. } else if (!animatingIn && type == MBProgressHUDAnimationZoomOut) {
  222. bezelView.transform = small;
  223. }
  224. CGFloat alpha = animatingIn ? 1.f : 0.f;
  225. bezelView.alpha = alpha;
  226. self.backgroundView.alpha = alpha;
  227. };
  228. [UIView animateWithDuration:0.3 delay:0. usingSpringWithDamping:1.f initialSpringVelocity:0.f options:UIViewAnimationOptionBeginFromCurrentState animations:animations completion:completion];
  229. }
  230. - (void)done {
  231. [self setNSProgressDisplayLinkEnabled:NO];
  232. if (self.hasFinished) {
  233. self.alpha = 0.0f;
  234. if (self.removeFromSuperViewOnHide) {
  235. [self removeFromSuperview];
  236. }
  237. }
  238. MBProgressHUDCompletionBlock completionBlock = self.completionBlock;
  239. if (completionBlock) {
  240. completionBlock();
  241. }
  242. id<MBProgressHUDDelegate> delegate = self.delegate;
  243. if ([delegate respondsToSelector:@selector(hudWasHidden:)]) {
  244. [delegate performSelector:@selector(hudWasHidden:) withObject:self];
  245. }
  246. }
  247. #pragma mark - UI
  248. - (void)setupViews {
  249. UIColor *defaultColor = self.contentColor;
  250. MBBackgroundView *backgroundView = [[MBBackgroundView alloc] initWithFrame:self.bounds];
  251. backgroundView.style = MBProgressHUDBackgroundStyleSolidColor;
  252. backgroundView.backgroundColor = [UIColor clearColor];
  253. backgroundView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
  254. backgroundView.alpha = 0.f;
  255. [self addSubview:backgroundView];
  256. _backgroundView = backgroundView;
  257. MBBackgroundView *bezelView = [MBBackgroundView new];
  258. bezelView.translatesAutoresizingMaskIntoConstraints = NO;
  259. bezelView.layer.cornerRadius = 5.f;
  260. bezelView.alpha = 0.f;
  261. [self addSubview:bezelView];
  262. _bezelView = bezelView;
  263. UILabel *label = [UILabel new];
  264. label.adjustsFontSizeToFitWidth = NO;
  265. label.textAlignment = NSTextAlignmentCenter;
  266. label.textColor = defaultColor;
  267. label.font = [UIFont boldSystemFontOfSize:MBDefaultLabelFontSize];
  268. label.opaque = NO;
  269. label.backgroundColor = [UIColor clearColor];
  270. _label = label;
  271. UILabel *detailsLabel = [UILabel new];
  272. detailsLabel.adjustsFontSizeToFitWidth = NO;
  273. detailsLabel.textAlignment = NSTextAlignmentCenter;
  274. detailsLabel.textColor = defaultColor;
  275. detailsLabel.numberOfLines = 0;
  276. detailsLabel.font = [UIFont boldSystemFontOfSize:MBDefaultDetailsLabelFontSize];
  277. detailsLabel.opaque = NO;
  278. detailsLabel.backgroundColor = [UIColor clearColor];
  279. _detailsLabel = detailsLabel;
  280. UIButton *button = [MBProgressHUDRoundedButton buttonWithType:UIButtonTypeCustom];
  281. button.titleLabel.textAlignment = NSTextAlignmentCenter;
  282. button.titleLabel.font = [UIFont boldSystemFontOfSize:MBDefaultDetailsLabelFontSize];
  283. [button setTitleColor:defaultColor forState:UIControlStateNormal];
  284. _button = button;
  285. for (UIView *view in @[label, detailsLabel, button]) {
  286. view.translatesAutoresizingMaskIntoConstraints = NO;
  287. [view setContentCompressionResistancePriority:998.f forAxis:UILayoutConstraintAxisHorizontal];
  288. [view setContentCompressionResistancePriority:998.f forAxis:UILayoutConstraintAxisVertical];
  289. [bezelView addSubview:view];
  290. }
  291. UIView *topSpacer = [UIView new];
  292. topSpacer.translatesAutoresizingMaskIntoConstraints = NO;
  293. topSpacer.hidden = YES;
  294. [bezelView addSubview:topSpacer];
  295. _topSpacer = topSpacer;
  296. UIView *bottomSpacer = [UIView new];
  297. bottomSpacer.translatesAutoresizingMaskIntoConstraints = NO;
  298. bottomSpacer.hidden = YES;
  299. [bezelView addSubview:bottomSpacer];
  300. _bottomSpacer = bottomSpacer;
  301. }
  302. - (void)updateIndicators {
  303. UIView *indicator = self.indicator;
  304. BOOL isActivityIndicator = [indicator isKindOfClass:[UIActivityIndicatorView class]];
  305. BOOL isRoundIndicator = [indicator isKindOfClass:[MBRoundProgressView class]];
  306. MBProgressHUDMode mode = self.mode;
  307. if (mode == MBProgressHUDModeIndeterminate) {
  308. if (!isActivityIndicator) {
  309. // Update to indeterminate indicator
  310. UIActivityIndicatorView *activityIndicator;
  311. [indicator removeFromSuperview];
  312. #if !TARGET_OS_MACCATALYST
  313. if (@available(iOS 13.0, tvOS 13.0, *)) {
  314. #endif
  315. activityIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleLarge];
  316. activityIndicator.color = [UIColor whiteColor];
  317. #if !TARGET_OS_MACCATALYST
  318. } else {
  319. activityIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
  320. }
  321. #endif
  322. [activityIndicator startAnimating];
  323. indicator = activityIndicator;
  324. [self.bezelView addSubview:indicator];
  325. }
  326. }
  327. else if (mode == MBProgressHUDModeDeterminateHorizontalBar) {
  328. // Update to bar determinate indicator
  329. [indicator removeFromSuperview];
  330. indicator = [[MBBarProgressView alloc] init];
  331. [self.bezelView addSubview:indicator];
  332. }
  333. else if (mode == MBProgressHUDModeDeterminate || mode == MBProgressHUDModeAnnularDeterminate) {
  334. if (!isRoundIndicator) {
  335. // Update to determinante indicator
  336. [indicator removeFromSuperview];
  337. indicator = [[MBRoundProgressView alloc] init];
  338. [self.bezelView addSubview:indicator];
  339. }
  340. if (mode == MBProgressHUDModeAnnularDeterminate) {
  341. [(MBRoundProgressView *)indicator setAnnular:YES];
  342. }
  343. }
  344. else if (mode == MBProgressHUDModeCustomView && self.customView != indicator) {
  345. // Update custom view indicator
  346. [indicator removeFromSuperview];
  347. indicator = self.customView;
  348. [self.bezelView addSubview:indicator];
  349. }
  350. else if (mode == MBProgressHUDModeText) {
  351. [indicator removeFromSuperview];
  352. indicator = nil;
  353. }
  354. indicator.translatesAutoresizingMaskIntoConstraints = NO;
  355. self.indicator = indicator;
  356. if ([indicator respondsToSelector:@selector(setProgress:)]) {
  357. [(id)indicator setValue:@(self.progress) forKey:@"progress"];
  358. }
  359. [indicator setContentCompressionResistancePriority:998.f forAxis:UILayoutConstraintAxisHorizontal];
  360. [indicator setContentCompressionResistancePriority:998.f forAxis:UILayoutConstraintAxisVertical];
  361. [self updateViewsForColor:self.contentColor];
  362. [self setNeedsUpdateConstraints];
  363. }
  364. - (void)updateViewsForColor:(UIColor *)color {
  365. if (!color) return;
  366. self.label.textColor = color;
  367. self.detailsLabel.textColor = color;
  368. [self.button setTitleColor:color forState:UIControlStateNormal];
  369. // UIAppearance settings are prioritized. If they are preset the set color is ignored.
  370. UIView *indicator = self.indicator;
  371. if ([indicator isKindOfClass:[UIActivityIndicatorView class]]) {
  372. UIActivityIndicatorView *appearance = nil;
  373. #if __IPHONE_OS_VERSION_MIN_REQUIRED < 90000
  374. appearance = [UIActivityIndicatorView appearanceWhenContainedIn:[MBProgressHUD class], nil];
  375. #else
  376. // For iOS 9+
  377. appearance = [UIActivityIndicatorView appearanceWhenContainedInInstancesOfClasses:@[[MBProgressHUD class]]];
  378. #endif
  379. if (appearance.color == nil) {
  380. ((UIActivityIndicatorView *)indicator).color = color;
  381. }
  382. } else if ([indicator isKindOfClass:[MBRoundProgressView class]]) {
  383. MBRoundProgressView *appearance = nil;
  384. #if __IPHONE_OS_VERSION_MIN_REQUIRED < 90000
  385. appearance = [MBRoundProgressView appearanceWhenContainedIn:[MBProgressHUD class], nil];
  386. #else
  387. appearance = [MBRoundProgressView appearanceWhenContainedInInstancesOfClasses:@[[MBProgressHUD class]]];
  388. #endif
  389. if (appearance.progressTintColor == nil) {
  390. ((MBRoundProgressView *)indicator).progressTintColor = color;
  391. }
  392. if (appearance.backgroundTintColor == nil) {
  393. ((MBRoundProgressView *)indicator).backgroundTintColor = [color colorWithAlphaComponent:0.1];
  394. }
  395. } else if ([indicator isKindOfClass:[MBBarProgressView class]]) {
  396. MBBarProgressView *appearance = nil;
  397. #if __IPHONE_OS_VERSION_MIN_REQUIRED < 90000
  398. appearance = [MBBarProgressView appearanceWhenContainedIn:[MBProgressHUD class], nil];
  399. #else
  400. appearance = [MBBarProgressView appearanceWhenContainedInInstancesOfClasses:@[[MBProgressHUD class]]];
  401. #endif
  402. if (appearance.progressColor == nil) {
  403. ((MBBarProgressView *)indicator).progressColor = color;
  404. }
  405. if (appearance.lineColor == nil) {
  406. ((MBBarProgressView *)indicator).lineColor = color;
  407. }
  408. } else {
  409. [indicator setTintColor:color];
  410. }
  411. }
  412. - (void)updateBezelMotionEffects {
  413. MBBackgroundView *bezelView = self.bezelView;
  414. UIMotionEffectGroup *bezelMotionEffects = self.bezelMotionEffects;
  415. if (self.defaultMotionEffectsEnabled && !bezelMotionEffects) {
  416. CGFloat effectOffset = 10.f;
  417. UIInterpolatingMotionEffect *effectX = [[UIInterpolatingMotionEffect alloc] initWithKeyPath:@"center.x" type:UIInterpolatingMotionEffectTypeTiltAlongHorizontalAxis];
  418. effectX.maximumRelativeValue = @(effectOffset);
  419. effectX.minimumRelativeValue = @(-effectOffset);
  420. UIInterpolatingMotionEffect *effectY = [[UIInterpolatingMotionEffect alloc] initWithKeyPath:@"center.y" type:UIInterpolatingMotionEffectTypeTiltAlongVerticalAxis];
  421. effectY.maximumRelativeValue = @(effectOffset);
  422. effectY.minimumRelativeValue = @(-effectOffset);
  423. UIMotionEffectGroup *group = [[UIMotionEffectGroup alloc] init];
  424. group.motionEffects = @[effectX, effectY];
  425. self.bezelMotionEffects = group;
  426. [bezelView addMotionEffect:group];
  427. } else if (bezelMotionEffects) {
  428. self.bezelMotionEffects = nil;
  429. [bezelView removeMotionEffect:bezelMotionEffects];
  430. }
  431. }
  432. #pragma mark - Layout
  433. - (void)updateConstraints {
  434. UIView *bezel = self.bezelView;
  435. UIView *topSpacer = self.topSpacer;
  436. UIView *bottomSpacer = self.bottomSpacer;
  437. CGFloat margin = self.margin;
  438. NSMutableArray *bezelConstraints = [NSMutableArray array];
  439. NSDictionary *metrics = @{@"margin": @(margin)};
  440. NSMutableArray *subviews = [NSMutableArray arrayWithObjects:self.topSpacer, self.label, self.detailsLabel, self.button, self.bottomSpacer, nil];
  441. if (self.indicator) [subviews insertObject:self.indicator atIndex:1];
  442. // Remove existing constraints
  443. [self removeConstraints:self.constraints];
  444. [topSpacer removeConstraints:topSpacer.constraints];
  445. [bottomSpacer removeConstraints:bottomSpacer.constraints];
  446. if (self.bezelConstraints) {
  447. [bezel removeConstraints:self.bezelConstraints];
  448. self.bezelConstraints = nil;
  449. }
  450. // Center bezel in container (self), applying the offset if set
  451. CGPoint offset = self.offset;
  452. NSMutableArray *centeringConstraints = [NSMutableArray array];
  453. [centeringConstraints addObject:[NSLayoutConstraint constraintWithItem:bezel attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeCenterX multiplier:1.f constant:offset.x]];
  454. [centeringConstraints addObject:[NSLayoutConstraint constraintWithItem:bezel attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeCenterY multiplier:1.f constant:offset.y]];
  455. [self applyPriority:998.f toConstraints:centeringConstraints];
  456. [self addConstraints:centeringConstraints];
  457. // Ensure minimum side margin is kept
  458. NSMutableArray *sideConstraints = [NSMutableArray array];
  459. [sideConstraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"|-(>=margin)-[bezel]-(>=margin)-|" options:0 metrics:metrics views:NSDictionaryOfVariableBindings(bezel)]];
  460. [sideConstraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-(>=margin)-[bezel]-(>=margin)-|" options:0 metrics:metrics views:NSDictionaryOfVariableBindings(bezel)]];
  461. [self applyPriority:999.f toConstraints:sideConstraints];
  462. [self addConstraints:sideConstraints];
  463. // Minimum bezel size, if set
  464. CGSize minimumSize = self.minSize;
  465. if (!CGSizeEqualToSize(minimumSize, CGSizeZero)) {
  466. NSMutableArray *minSizeConstraints = [NSMutableArray array];
  467. [minSizeConstraints addObject:[NSLayoutConstraint constraintWithItem:bezel attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationGreaterThanOrEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.f constant:minimumSize.width]];
  468. [minSizeConstraints addObject:[NSLayoutConstraint constraintWithItem:bezel attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationGreaterThanOrEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.f constant:minimumSize.height]];
  469. [self applyPriority:997.f toConstraints:minSizeConstraints];
  470. [bezelConstraints addObjectsFromArray:minSizeConstraints];
  471. }
  472. // Square aspect ratio, if set
  473. if (self.square) {
  474. NSLayoutConstraint *square = [NSLayoutConstraint constraintWithItem:bezel attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:bezel attribute:NSLayoutAttributeWidth multiplier:1.f constant:0];
  475. square.priority = 997.f;
  476. [bezelConstraints addObject:square];
  477. }
  478. // Top and bottom spacing
  479. [topSpacer addConstraint:[NSLayoutConstraint constraintWithItem:topSpacer attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationGreaterThanOrEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.f constant:margin]];
  480. [bottomSpacer addConstraint:[NSLayoutConstraint constraintWithItem:bottomSpacer attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationGreaterThanOrEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.f constant:margin]];
  481. // Top and bottom spaces should be equal
  482. [bezelConstraints addObject:[NSLayoutConstraint constraintWithItem:topSpacer attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:bottomSpacer attribute:NSLayoutAttributeHeight multiplier:1.f constant:0.f]];
  483. // Layout subviews in bezel
  484. NSMutableArray *paddingConstraints = [NSMutableArray new];
  485. [subviews enumerateObjectsUsingBlock:^(UIView *view, NSUInteger idx, BOOL *stop) {
  486. // Center in bezel
  487. [bezelConstraints addObject:[NSLayoutConstraint constraintWithItem:view attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:bezel attribute:NSLayoutAttributeCenterX multiplier:1.f constant:0.f]];
  488. // Ensure the minimum edge margin is kept
  489. [bezelConstraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"|-(>=margin)-[view]-(>=margin)-|" options:0 metrics:metrics views:NSDictionaryOfVariableBindings(view)]];
  490. // Element spacing
  491. if (idx == 0) {
  492. // First, ensure spacing to bezel edge
  493. [bezelConstraints addObject:[NSLayoutConstraint constraintWithItem:view attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:bezel attribute:NSLayoutAttributeTop multiplier:1.f constant:0.f]];
  494. } else if (idx == subviews.count - 1) {
  495. // Last, ensure spacing to bezel edge
  496. [bezelConstraints addObject:[NSLayoutConstraint constraintWithItem:view attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:bezel attribute:NSLayoutAttributeBottom multiplier:1.f constant:0.f]];
  497. }
  498. if (idx > 0) {
  499. // Has previous
  500. NSLayoutConstraint *padding = [NSLayoutConstraint constraintWithItem:view attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:subviews[idx - 1] attribute:NSLayoutAttributeBottom multiplier:1.f constant:0.f];
  501. [bezelConstraints addObject:padding];
  502. [paddingConstraints addObject:padding];
  503. }
  504. }];
  505. [bezel addConstraints:bezelConstraints];
  506. self.bezelConstraints = bezelConstraints;
  507. self.paddingConstraints = [paddingConstraints copy];
  508. [self updatePaddingConstraints];
  509. [super updateConstraints];
  510. }
  511. - (void)layoutSubviews {
  512. // There is no need to update constraints if they are going to
  513. // be recreated in [super layoutSubviews] due to needsUpdateConstraints being set.
  514. // This also avoids an issue on iOS 8, where updatePaddingConstraints
  515. // would trigger a zombie object access.
  516. if (!self.needsUpdateConstraints) {
  517. [self updatePaddingConstraints];
  518. }
  519. [super layoutSubviews];
  520. }
  521. - (void)updatePaddingConstraints {
  522. // Set padding dynamically, depending on whether the view is visible or not
  523. __block BOOL hasVisibleAncestors = NO;
  524. [self.paddingConstraints enumerateObjectsUsingBlock:^(NSLayoutConstraint *padding, NSUInteger idx, BOOL *stop) {
  525. UIView *firstView = (UIView *)padding.firstItem;
  526. UIView *secondView = (UIView *)padding.secondItem;
  527. BOOL firstVisible = !firstView.hidden && !CGSizeEqualToSize(firstView.intrinsicContentSize, CGSizeZero);
  528. BOOL secondVisible = !secondView.hidden && !CGSizeEqualToSize(secondView.intrinsicContentSize, CGSizeZero);
  529. // Set if both views are visible or if there's a visible view on top that doesn't have padding
  530. // added relative to the current view yet
  531. padding.constant = (firstVisible && (secondVisible || hasVisibleAncestors)) ? MBDefaultPadding : 0.f;
  532. hasVisibleAncestors |= secondVisible;
  533. }];
  534. }
  535. - (void)applyPriority:(UILayoutPriority)priority toConstraints:(NSArray *)constraints {
  536. for (NSLayoutConstraint *constraint in constraints) {
  537. constraint.priority = priority;
  538. }
  539. }
  540. #pragma mark - Properties
  541. - (void)setMode:(MBProgressHUDMode)mode {
  542. if (mode != _mode) {
  543. _mode = mode;
  544. [self updateIndicators];
  545. }
  546. }
  547. - (void)setCustomView:(UIView *)customView {
  548. if (customView != _customView) {
  549. _customView = customView;
  550. if (self.mode == MBProgressHUDModeCustomView) {
  551. [self updateIndicators];
  552. }
  553. }
  554. }
  555. - (void)setOffset:(CGPoint)offset {
  556. if (!CGPointEqualToPoint(offset, _offset)) {
  557. _offset = offset;
  558. [self setNeedsUpdateConstraints];
  559. }
  560. }
  561. - (void)setMargin:(CGFloat)margin {
  562. if (margin != _margin) {
  563. _margin = margin;
  564. [self setNeedsUpdateConstraints];
  565. }
  566. }
  567. - (void)setMinSize:(CGSize)minSize {
  568. if (!CGSizeEqualToSize(minSize, _minSize)) {
  569. _minSize = minSize;
  570. [self setNeedsUpdateConstraints];
  571. }
  572. }
  573. - (void)setSquare:(BOOL)square {
  574. if (square != _square) {
  575. _square = square;
  576. [self setNeedsUpdateConstraints];
  577. }
  578. }
  579. - (void)setProgressObjectDisplayLink:(CADisplayLink *)progressObjectDisplayLink {
  580. if (progressObjectDisplayLink != _progressObjectDisplayLink) {
  581. [_progressObjectDisplayLink invalidate];
  582. _progressObjectDisplayLink = progressObjectDisplayLink;
  583. [_progressObjectDisplayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
  584. }
  585. }
  586. - (void)setProgressObject:(NSProgress *)progressObject {
  587. if (progressObject != _progressObject) {
  588. _progressObject = progressObject;
  589. [self setNSProgressDisplayLinkEnabled:YES];
  590. }
  591. }
  592. - (void)setProgress:(float)progress {
  593. if (progress != _progress) {
  594. _progress = progress;
  595. UIView *indicator = self.indicator;
  596. if ([indicator respondsToSelector:@selector(setProgress:)]) {
  597. [(id)indicator setValue:@(self.progress) forKey:@"progress"];
  598. }
  599. }
  600. }
  601. - (void)setContentColor:(UIColor *)contentColor {
  602. if (contentColor != _contentColor && ![contentColor isEqual:_contentColor]) {
  603. _contentColor = contentColor;
  604. [self updateViewsForColor:contentColor];
  605. }
  606. }
  607. - (void)setDefaultMotionEffectsEnabled:(BOOL)defaultMotionEffectsEnabled {
  608. if (defaultMotionEffectsEnabled != _defaultMotionEffectsEnabled) {
  609. _defaultMotionEffectsEnabled = defaultMotionEffectsEnabled;
  610. [self updateBezelMotionEffects];
  611. }
  612. }
  613. #pragma mark - NSProgress
  614. - (void)setNSProgressDisplayLinkEnabled:(BOOL)enabled {
  615. // We're using CADisplayLink, because NSProgress can change very quickly and observing it may starve the main thread,
  616. // so we're refreshing the progress only every frame draw
  617. if (enabled && self.progressObject) {
  618. // Only create if not already active.
  619. if (!self.progressObjectDisplayLink) {
  620. self.progressObjectDisplayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(updateProgressFromProgressObject)];
  621. }
  622. } else {
  623. self.progressObjectDisplayLink = nil;
  624. }
  625. }
  626. - (void)updateProgressFromProgressObject {
  627. self.progress = self.progressObject.fractionCompleted;
  628. }
  629. #pragma mark - Notifications
  630. - (void)registerForNotifications {
  631. #if !TARGET_OS_TV && !TARGET_OS_MACCATALYST
  632. NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
  633. [nc addObserver:self selector:@selector(statusBarOrientationDidChange:)
  634. name:UIApplicationDidChangeStatusBarOrientationNotification object:nil];
  635. #endif
  636. }
  637. - (void)unregisterFromNotifications {
  638. #if !TARGET_OS_TV && !TARGET_OS_MACCATALYST
  639. NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
  640. [nc removeObserver:self name:UIApplicationDidChangeStatusBarOrientationNotification object:nil];
  641. #endif
  642. }
  643. #if !TARGET_OS_TV && !TARGET_OS_MACCATALYST
  644. - (void)statusBarOrientationDidChange:(NSNotification *)notification {
  645. UIView *superview = self.superview;
  646. if (!superview) {
  647. return;
  648. } else {
  649. [self updateForCurrentOrientationAnimated:YES];
  650. }
  651. }
  652. #endif
  653. - (void)updateForCurrentOrientationAnimated:(BOOL)animated {
  654. // Stay in sync with the superview in any case
  655. if (self.superview) {
  656. self.frame = self.superview.bounds;
  657. }
  658. // Not needed on iOS 8+, compile out when the deployment target allows,
  659. // to avoid sharedApplication problems on extension targets
  660. #if __IPHONE_OS_VERSION_MIN_REQUIRED < 80000
  661. // Only needed pre iOS 8 when added to a window
  662. BOOL iOS8OrLater = kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iOS_8_0;
  663. if (iOS8OrLater || ![self.superview isKindOfClass:[UIWindow class]]) return;
  664. // Make extension friendly. Will not get called on extensions (iOS 8+) due to the above check.
  665. // This just ensures we don't get a warning about extension-unsafe API.
  666. Class UIApplicationClass = NSClassFromString(@"UIApplication");
  667. if (!UIApplicationClass || ![UIApplicationClass respondsToSelector:@selector(sharedApplication)]) return;
  668. UIApplication *application = [UIApplication performSelector:@selector(sharedApplication)];
  669. UIInterfaceOrientation orientation = application.statusBarOrientation;
  670. CGFloat radians = 0;
  671. if (UIInterfaceOrientationIsLandscape(orientation)) {
  672. radians = orientation == UIInterfaceOrientationLandscapeLeft ? -(CGFloat)M_PI_2 : (CGFloat)M_PI_2;
  673. // Window coordinates differ!
  674. self.bounds = CGRectMake(0, 0, self.bounds.size.height, self.bounds.size.width);
  675. } else {
  676. radians = orientation == UIInterfaceOrientationPortraitUpsideDown ? (CGFloat)M_PI : 0.f;
  677. }
  678. if (animated) {
  679. [UIView animateWithDuration:0.3 animations:^{
  680. self.transform = CGAffineTransformMakeRotation(radians);
  681. }];
  682. } else {
  683. self.transform = CGAffineTransformMakeRotation(radians);
  684. }
  685. #endif
  686. }
  687. @end
  688. @implementation MBRoundProgressView
  689. #pragma mark - Lifecycle
  690. - (id)init {
  691. return [self initWithFrame:CGRectMake(0.f, 0.f, 37.f, 37.f)];
  692. }
  693. - (id)initWithFrame:(CGRect)frame {
  694. self = [super initWithFrame:frame];
  695. if (self) {
  696. self.backgroundColor = [UIColor clearColor];
  697. self.opaque = NO;
  698. _progress = 0.f;
  699. _annular = NO;
  700. _progressTintColor = [[UIColor alloc] initWithWhite:1.f alpha:1.f];
  701. _backgroundTintColor = [[UIColor alloc] initWithWhite:1.f alpha:.1f];
  702. }
  703. return self;
  704. }
  705. #pragma mark - Layout
  706. - (CGSize)intrinsicContentSize {
  707. return CGSizeMake(37.f, 37.f);
  708. }
  709. #pragma mark - Properties
  710. - (void)setProgress:(float)progress {
  711. if (progress != _progress) {
  712. _progress = progress;
  713. [self setNeedsDisplay];
  714. }
  715. }
  716. - (void)setProgressTintColor:(UIColor *)progressTintColor {
  717. NSAssert(progressTintColor, @"The color should not be nil.");
  718. if (progressTintColor != _progressTintColor && ![progressTintColor isEqual:_progressTintColor]) {
  719. _progressTintColor = progressTintColor;
  720. [self setNeedsDisplay];
  721. }
  722. }
  723. - (void)setBackgroundTintColor:(UIColor *)backgroundTintColor {
  724. NSAssert(backgroundTintColor, @"The color should not be nil.");
  725. if (backgroundTintColor != _backgroundTintColor && ![backgroundTintColor isEqual:_backgroundTintColor]) {
  726. _backgroundTintColor = backgroundTintColor;
  727. [self setNeedsDisplay];
  728. }
  729. }
  730. #pragma mark - Drawing
  731. - (void)drawRect:(CGRect)rect {
  732. CGContextRef context = UIGraphicsGetCurrentContext();
  733. if (_annular) {
  734. // Draw background
  735. CGFloat lineWidth = 2.f;
  736. UIBezierPath *processBackgroundPath = [UIBezierPath bezierPath];
  737. processBackgroundPath.lineWidth = lineWidth;
  738. processBackgroundPath.lineCapStyle = kCGLineCapButt;
  739. CGPoint center = CGPointMake(CGRectGetMidX(self.bounds), CGRectGetMidY(self.bounds));
  740. CGFloat radius = (self.bounds.size.width - lineWidth)/2;
  741. CGFloat startAngle = - ((float)M_PI / 2); // 90 degrees
  742. CGFloat endAngle = (2 * (float)M_PI) + startAngle;
  743. [processBackgroundPath addArcWithCenter:center radius:radius startAngle:startAngle endAngle:endAngle clockwise:YES];
  744. [_backgroundTintColor set];
  745. [processBackgroundPath stroke];
  746. // Draw progress
  747. UIBezierPath *processPath = [UIBezierPath bezierPath];
  748. processPath.lineCapStyle = kCGLineCapSquare;
  749. processPath.lineWidth = lineWidth;
  750. endAngle = (self.progress * 2 * (float)M_PI) + startAngle;
  751. [processPath addArcWithCenter:center radius:radius startAngle:startAngle endAngle:endAngle clockwise:YES];
  752. [_progressTintColor set];
  753. [processPath stroke];
  754. } else {
  755. // Draw background
  756. CGFloat lineWidth = 2.f;
  757. CGRect allRect = self.bounds;
  758. CGRect circleRect = CGRectInset(allRect, lineWidth/2.f, lineWidth/2.f);
  759. CGPoint center = CGPointMake(CGRectGetMidX(self.bounds), CGRectGetMidY(self.bounds));
  760. [_progressTintColor setStroke];
  761. [_backgroundTintColor setFill];
  762. CGContextSetLineWidth(context, lineWidth);
  763. CGContextStrokeEllipseInRect(context, circleRect);
  764. // 90 degrees
  765. CGFloat startAngle = - ((float)M_PI / 2.f);
  766. // Draw progress
  767. UIBezierPath *processPath = [UIBezierPath bezierPath];
  768. processPath.lineCapStyle = kCGLineCapButt;
  769. processPath.lineWidth = lineWidth * 2.f;
  770. CGFloat radius = (CGRectGetWidth(self.bounds) / 2.f) - (processPath.lineWidth / 2.f);
  771. CGFloat endAngle = (self.progress * 2.f * (float)M_PI) + startAngle;
  772. [processPath addArcWithCenter:center radius:radius startAngle:startAngle endAngle:endAngle clockwise:YES];
  773. // Ensure that we don't get color overlapping when _progressTintColor alpha < 1.f.
  774. CGContextSetBlendMode(context, kCGBlendModeCopy);
  775. [_progressTintColor set];
  776. [processPath stroke];
  777. }
  778. }
  779. @end
  780. @implementation MBBarProgressView
  781. #pragma mark - Lifecycle
  782. - (id)init {
  783. return [self initWithFrame:CGRectMake(.0f, .0f, 120.0f, 20.0f)];
  784. }
  785. - (id)initWithFrame:(CGRect)frame {
  786. self = [super initWithFrame:frame];
  787. if (self) {
  788. _progress = 0.f;
  789. _lineColor = [UIColor whiteColor];
  790. _progressColor = [UIColor whiteColor];
  791. _progressRemainingColor = [UIColor clearColor];
  792. self.backgroundColor = [UIColor clearColor];
  793. self.opaque = NO;
  794. }
  795. return self;
  796. }
  797. #pragma mark - Layout
  798. - (CGSize)intrinsicContentSize {
  799. return CGSizeMake(120.f, 10.f);
  800. }
  801. #pragma mark - Properties
  802. - (void)setProgress:(float)progress {
  803. if (progress != _progress) {
  804. _progress = progress;
  805. [self setNeedsDisplay];
  806. }
  807. }
  808. - (void)setProgressColor:(UIColor *)progressColor {
  809. NSAssert(progressColor, @"The color should not be nil.");
  810. if (progressColor != _progressColor && ![progressColor isEqual:_progressColor]) {
  811. _progressColor = progressColor;
  812. [self setNeedsDisplay];
  813. }
  814. }
  815. - (void)setProgressRemainingColor:(UIColor *)progressRemainingColor {
  816. NSAssert(progressRemainingColor, @"The color should not be nil.");
  817. if (progressRemainingColor != _progressRemainingColor && ![progressRemainingColor isEqual:_progressRemainingColor]) {
  818. _progressRemainingColor = progressRemainingColor;
  819. [self setNeedsDisplay];
  820. }
  821. }
  822. #pragma mark - Drawing
  823. - (void)drawRect:(CGRect)rect {
  824. CGContextRef context = UIGraphicsGetCurrentContext();
  825. CGContextSetLineWidth(context, 2);
  826. CGContextSetStrokeColorWithColor(context,[_lineColor CGColor]);
  827. CGContextSetFillColorWithColor(context, [_progressRemainingColor CGColor]);
  828. // Draw background and Border
  829. CGFloat radius = (rect.size.height / 2) - 2;
  830. CGContextMoveToPoint(context, 2, rect.size.height/2);
  831. CGContextAddArcToPoint(context, 2, 2, radius + 2, 2, radius);
  832. CGContextAddArcToPoint(context, rect.size.width - 2, 2, rect.size.width - 2, rect.size.height / 2, radius);
  833. CGContextAddArcToPoint(context, rect.size.width - 2, rect.size.height - 2, rect.size.width - radius - 2, rect.size.height - 2, radius);
  834. CGContextAddArcToPoint(context, 2, rect.size.height - 2, 2, rect.size.height/2, radius);
  835. CGContextDrawPath(context, kCGPathFillStroke);
  836. CGContextSetFillColorWithColor(context, [_progressColor CGColor]);
  837. radius = radius - 2;
  838. CGFloat amount = self.progress * rect.size.width;
  839. // Progress in the middle area
  840. if (amount >= radius + 4 && amount <= (rect.size.width - radius - 4)) {
  841. CGContextMoveToPoint(context, 4, rect.size.height/2);
  842. CGContextAddArcToPoint(context, 4, 4, radius + 4, 4, radius);
  843. CGContextAddLineToPoint(context, amount, 4);
  844. CGContextAddLineToPoint(context, amount, radius + 4);
  845. CGContextMoveToPoint(context, 4, rect.size.height/2);
  846. CGContextAddArcToPoint(context, 4, rect.size.height - 4, radius + 4, rect.size.height - 4, radius);
  847. CGContextAddLineToPoint(context, amount, rect.size.height - 4);
  848. CGContextAddLineToPoint(context, amount, radius + 4);
  849. CGContextFillPath(context);
  850. }
  851. // Progress in the right arc
  852. else if (amount > radius + 4) {
  853. CGFloat x = amount - (rect.size.width - radius - 4);
  854. CGContextMoveToPoint(context, 4, rect.size.height/2);
  855. CGContextAddArcToPoint(context, 4, 4, radius + 4, 4, radius);
  856. CGContextAddLineToPoint(context, rect.size.width - radius - 4, 4);
  857. CGFloat angle = -acos(x/radius);
  858. if (isnan(angle)) angle = 0;
  859. CGContextAddArc(context, rect.size.width - radius - 4, rect.size.height/2, radius, M_PI, angle, 0);
  860. CGContextAddLineToPoint(context, amount, rect.size.height/2);
  861. CGContextMoveToPoint(context, 4, rect.size.height/2);
  862. CGContextAddArcToPoint(context, 4, rect.size.height - 4, radius + 4, rect.size.height - 4, radius);
  863. CGContextAddLineToPoint(context, rect.size.width - radius - 4, rect.size.height - 4);
  864. angle = acos(x/radius);
  865. if (isnan(angle)) angle = 0;
  866. CGContextAddArc(context, rect.size.width - radius - 4, rect.size.height/2, radius, -M_PI, angle, 1);
  867. CGContextAddLineToPoint(context, amount, rect.size.height/2);
  868. CGContextFillPath(context);
  869. }
  870. // Progress is in the left arc
  871. else if (amount < radius + 4 && amount > 0) {
  872. CGContextMoveToPoint(context, 4, rect.size.height/2);
  873. CGContextAddArcToPoint(context, 4, 4, radius + 4, 4, radius);
  874. CGContextAddLineToPoint(context, radius + 4, rect.size.height/2);
  875. CGContextMoveToPoint(context, 4, rect.size.height/2);
  876. CGContextAddArcToPoint(context, 4, rect.size.height - 4, radius + 4, rect.size.height - 4, radius);
  877. CGContextAddLineToPoint(context, radius + 4, rect.size.height/2);
  878. CGContextFillPath(context);
  879. }
  880. }
  881. @end
  882. @interface MBBackgroundView ()
  883. @property UIVisualEffectView *effectView;
  884. @end
  885. @implementation MBBackgroundView
  886. #pragma mark - Lifecycle
  887. - (instancetype)initWithFrame:(CGRect)frame {
  888. if ((self = [super initWithFrame:frame])) {
  889. _style = MBProgressHUDBackgroundStyleBlur;
  890. if (@available(iOS 13.0, *)) {
  891. #if TARGET_OS_TV
  892. _blurEffectStyle = UIBlurEffectStyleRegular;
  893. #else
  894. _blurEffectStyle = UIBlurEffectStyleSystemThickMaterial;
  895. #endif
  896. // Leaving the color unassigned yields best results.
  897. } else {
  898. _blurEffectStyle = UIBlurEffectStyleLight;
  899. _color = [UIColor colorWithWhite:0.8f alpha:0.6f];
  900. }
  901. self.clipsToBounds = YES;
  902. [self updateForBackgroundStyle];
  903. }
  904. return self;
  905. }
  906. #pragma mark - Layout
  907. - (CGSize)intrinsicContentSize {
  908. // Smallest size possible. Content pushes against this.
  909. return CGSizeZero;
  910. }
  911. #pragma mark - Appearance
  912. - (void)setStyle:(MBProgressHUDBackgroundStyle)style {
  913. if (_style != style) {
  914. _style = style;
  915. [self updateForBackgroundStyle];
  916. }
  917. }
  918. - (void)setColor:(UIColor *)color {
  919. NSAssert(color, @"The color should not be nil.");
  920. if (color != _color && ![color isEqual:_color]) {
  921. _color = color;
  922. [self updateViewsForColor:color];
  923. }
  924. }
  925. - (void)setBlurEffectStyle:(UIBlurEffectStyle)blurEffectStyle {
  926. if (_blurEffectStyle == blurEffectStyle) {
  927. return;
  928. }
  929. _blurEffectStyle = blurEffectStyle;
  930. [self updateForBackgroundStyle];
  931. }
  932. ///////////////////////////////////////////////////////////////////////////////////////////
  933. #pragma mark - Views
  934. - (void)updateForBackgroundStyle {
  935. [self.effectView removeFromSuperview];
  936. self.effectView = nil;
  937. MBProgressHUDBackgroundStyle style = self.style;
  938. if (style == MBProgressHUDBackgroundStyleBlur) {
  939. UIBlurEffect *effect = [UIBlurEffect effectWithStyle:self.blurEffectStyle];
  940. UIVisualEffectView *effectView = [[UIVisualEffectView alloc] initWithEffect:effect];
  941. [self insertSubview:effectView atIndex:0];
  942. effectView.frame = self.bounds;
  943. effectView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
  944. self.backgroundColor = self.color;
  945. self.layer.allowsGroupOpacity = NO;
  946. self.effectView = effectView;
  947. } else {
  948. self.backgroundColor = self.color;
  949. }
  950. }
  951. - (void)updateViewsForColor:(UIColor *)color {
  952. if (self.style == MBProgressHUDBackgroundStyleBlur) {
  953. self.backgroundColor = self.color;
  954. } else {
  955. self.backgroundColor = self.color;
  956. }
  957. }
  958. @end
  959. @implementation MBProgressHUDRoundedButton
  960. #pragma mark - Lifecycle
  961. - (instancetype)initWithFrame:(CGRect)frame {
  962. self = [super initWithFrame:frame];
  963. if (self) {
  964. CALayer *layer = self.layer;
  965. layer.borderWidth = 1.f;
  966. }
  967. return self;
  968. }
  969. #pragma mark - Layout
  970. - (void)layoutSubviews {
  971. [super layoutSubviews];
  972. // Fully rounded corners
  973. CGFloat height = CGRectGetHeight(self.bounds);
  974. self.layer.cornerRadius = ceil(height / 2.f);
  975. }
  976. - (CGSize)intrinsicContentSize {
  977. // Only show if we have associated control events and a title
  978. if ((self.allControlEvents == 0) || ([self titleForState:UIControlStateNormal].length == 0))
  979. return CGSizeZero;
  980. CGSize size = [super intrinsicContentSize];
  981. // Add some side padding
  982. size.width += 20.f;
  983. return size;
  984. }
  985. #pragma mark - Color
  986. - (void)setTitleColor:(UIColor *)color forState:(UIControlState)state {
  987. [super setTitleColor:color forState:state];
  988. // Update related colors
  989. [self setHighlighted:self.highlighted];
  990. self.layer.borderColor = color.CGColor;
  991. }
  992. - (void)setHighlighted:(BOOL)highlighted {
  993. [super setHighlighted:highlighted];
  994. UIColor *baseColor = [self titleColorForState:UIControlStateSelected];
  995. self.backgroundColor = highlighted ? [baseColor colorWithAlphaComponent:0.1f] : [UIColor clearColor];
  996. }
  997. @end