UIViewController+Alerts.m 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346
  1. /*
  2. * Copyright 2017 Google
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. #import "UIViewController+Alerts.h"
  17. #import <objc/runtime.h>
  18. /*! @var kPleaseWaitAssociatedObjectKey
  19. @brief Key used to identify the "please wait" spinner associated object.
  20. */
  21. static NSString *const kPleaseWaitAssociatedObjectKey =
  22. @"_UIViewControllerAlertCategory_PleaseWaitScreenAssociatedObject";
  23. /*! @var kUseStatusBarSpinnerAssociatedObjectKey
  24. @brief The address of this constant is the key used to identify the "use status bar spinner"
  25. associated object.
  26. */
  27. static const void *const kUseStatusBarSpinnerAssociatedObjectKey;
  28. /*! @var kOK
  29. @brief Text for an 'OK' button.
  30. */
  31. static NSString *const kOK = @"OK";
  32. /*! @var kCancel
  33. @brief Text for an 'Cancel' button.
  34. */
  35. static NSString *const kCancel = @"Cancel";
  36. /*! @class SimpleTextPromptDelegate
  37. @brief A @c UIAlertViewDelegate which allows @c UIAlertView to be used with blocks more easily.
  38. */
  39. @interface SimpleTextPromptDelegate : NSObject <UIAlertViewDelegate>
  40. /*! @fn init
  41. @brief Please use initWithCompletionHandler.
  42. */
  43. - (nullable instancetype)init NS_UNAVAILABLE;
  44. /*! @fn initWithCompletionHandler:
  45. @brief Designated initializer.
  46. @param completionHandler The block to call when the alert view is dismissed.
  47. */
  48. - (nullable instancetype)initWithCompletionHandler:(AlertPromptCompletionBlock)completionHandler
  49. NS_DESIGNATED_INITIALIZER;
  50. @end
  51. @implementation UIViewController (Alerts)
  52. - (void)setUseStatusBarSpinner:(BOOL)useStatusBarSpinner {
  53. objc_setAssociatedObject(self,
  54. &kUseStatusBarSpinnerAssociatedObjectKey,
  55. useStatusBarSpinner ? @(YES) : nil,
  56. OBJC_ASSOCIATION_RETAIN_NONATOMIC);
  57. }
  58. - (BOOL)useStatusBarSpinner {
  59. return objc_getAssociatedObject(self, &kUseStatusBarSpinnerAssociatedObjectKey) ? YES : NO;
  60. }
  61. /*! @fn supportsAlertController
  62. @brief Determines if the current platform supports @c UIAlertController.
  63. @return YES if the current platform supports @c UIAlertController.
  64. */
  65. - (BOOL)supportsAlertController {
  66. return NSClassFromString(@"UIAlertController") != nil;
  67. }
  68. - (void)showMessagePrompt:(NSString *)message {
  69. [self showMessagePromptWithTitle:nil message:message showCancelButton:NO completion:nil];
  70. }
  71. - (void)showMessagePromptWithTitle:(nullable NSString *)title
  72. message:(NSString *)message
  73. showCancelButton:(BOOL)showCancelButton
  74. completion:(nullable AlertPromptCompletionBlock)completion {
  75. if (message) {
  76. [UIPasteboard generalPasteboard].string = message;
  77. }
  78. if ([self supportsAlertController]) {
  79. UIAlertController *alert =
  80. [UIAlertController alertControllerWithTitle:title
  81. message:message
  82. preferredStyle:UIAlertControllerStyleAlert];
  83. UIAlertAction *okAction =
  84. [UIAlertAction actionWithTitle:kOK
  85. style:UIAlertActionStyleDefault
  86. handler:^(UIAlertAction * _Nonnull action) {
  87. if (completion) {
  88. completion(YES, nil);
  89. }
  90. }];
  91. [alert addAction:okAction];
  92. if (showCancelButton) {
  93. UIAlertAction *cancelAction =
  94. [UIAlertAction actionWithTitle:kCancel
  95. style:UIAlertActionStyleCancel
  96. handler:^(UIAlertAction * _Nonnull action) {
  97. completion(NO, nil);
  98. }];
  99. [alert addAction:cancelAction];
  100. }
  101. [self presentViewController:alert animated:YES completion:nil];
  102. } else {
  103. UIAlertView *alert =
  104. [[UIAlertView alloc] initWithTitle:title
  105. message:message
  106. delegate:nil
  107. cancelButtonTitle:nil
  108. otherButtonTitles:kOK, nil];
  109. [alert show];
  110. }
  111. }
  112. - (void)showTextInputPromptWithMessage:(NSString *)message
  113. completionBlock:(AlertPromptCompletionBlock)completion {
  114. [self showTextInputPromptWithMessage:message
  115. keyboardType:UIKeyboardTypeDefault
  116. completionBlock:completion];
  117. }
  118. - (void)showTextInputPromptWithMessage:(NSString *)message
  119. keyboardType:(UIKeyboardType)keyboardType
  120. completionBlock:(nonnull AlertPromptCompletionBlock)completion {
  121. if ([self supportsAlertController]) {
  122. UIAlertController *prompt =
  123. [UIAlertController alertControllerWithTitle:nil
  124. message:message
  125. preferredStyle:UIAlertControllerStyleAlert];
  126. __weak UIAlertController *weakPrompt = prompt;
  127. UIAlertAction *cancelAction =
  128. [UIAlertAction actionWithTitle:kCancel
  129. style:UIAlertActionStyleCancel
  130. handler:^(UIAlertAction * _Nonnull action) {
  131. completion(NO, nil);
  132. }];
  133. UIAlertAction *okAction = [UIAlertAction actionWithTitle:kOK
  134. style:UIAlertActionStyleDefault
  135. handler:^(UIAlertAction * _Nonnull action) {
  136. UIAlertController *strongPrompt = weakPrompt;
  137. completion(YES, strongPrompt.textFields[0].text);
  138. }];
  139. [prompt addTextFieldWithConfigurationHandler:^(UITextField *_Nonnull textField) {
  140. textField.keyboardType = keyboardType;
  141. }];
  142. [prompt addAction:cancelAction];
  143. [prompt addAction:okAction];
  144. [[AppDelegate sharedAppDelegate].topViewController presentViewController:prompt animated:YES completion:nil];
  145. } else {
  146. SimpleTextPromptDelegate *prompt =
  147. [[SimpleTextPromptDelegate alloc] initWithCompletionHandler:completion];
  148. UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:nil
  149. message:message
  150. delegate:prompt
  151. cancelButtonTitle:@"Cancel"
  152. otherButtonTitles:@"Ok", nil];
  153. alertView.alertViewStyle = UIAlertViewStylePlainTextInput;
  154. [alertView show];
  155. }
  156. }
  157. - (void)showSpinner:(nullable void(^)(void))completion {
  158. if (self.useStatusBarSpinner) {
  159. [UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
  160. completion();
  161. return;
  162. }
  163. if ([self supportsAlertController]) {
  164. [self showModernSpinner:completion];
  165. } else {
  166. [self showIOS7Spinner:completion];
  167. }
  168. }
  169. - (void)showModernSpinner:(nullable void (^)(void))completion {
  170. UIAlertController *pleaseWaitAlert =
  171. objc_getAssociatedObject(self,
  172. (__bridge const void *)kPleaseWaitAssociatedObjectKey);
  173. if (pleaseWaitAlert) {
  174. if (completion) {
  175. completion();
  176. }
  177. return;
  178. }
  179. pleaseWaitAlert = [UIAlertController alertControllerWithTitle:nil
  180. message:@"Please Wait...\n\n\n\n"
  181. preferredStyle:UIAlertControllerStyleAlert];
  182. UIActivityIndicatorView *spinner =
  183. [[UIActivityIndicatorView alloc]
  184. initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
  185. spinner.color = [UIColor blackColor];
  186. spinner.center = CGPointMake(pleaseWaitAlert.view.bounds.size.width / 2,
  187. pleaseWaitAlert.view.bounds.size.height / 2);
  188. spinner.autoresizingMask = UIViewAutoresizingFlexibleBottomMargin |
  189. UIViewAutoresizingFlexibleTopMargin |
  190. UIViewAutoresizingFlexibleLeftMargin |
  191. UIViewAutoresizingFlexibleRightMargin;
  192. [spinner startAnimating];
  193. [pleaseWaitAlert.view addSubview:spinner];
  194. objc_setAssociatedObject(self,
  195. (__bridge const void *)(kPleaseWaitAssociatedObjectKey),
  196. pleaseWaitAlert,
  197. OBJC_ASSOCIATION_RETAIN_NONATOMIC);
  198. [self presentViewController:pleaseWaitAlert animated:YES completion:completion];
  199. }
  200. - (void)showIOS7Spinner:(nullable void (^)(void))completion {
  201. UIWindow *pleaseWaitWindow =
  202. objc_getAssociatedObject(self,
  203. (__bridge const void *)kPleaseWaitAssociatedObjectKey);
  204. if (pleaseWaitWindow) {
  205. if (completion) {
  206. completion();
  207. }
  208. return;
  209. }
  210. pleaseWaitWindow = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
  211. pleaseWaitWindow.backgroundColor = [UIColor clearColor];
  212. pleaseWaitWindow.windowLevel = UIWindowLevelStatusBar - 1;
  213. UIView *pleaseWaitView = [[UIView alloc] initWithFrame:pleaseWaitWindow.bounds];
  214. pleaseWaitView.autoresizingMask = UIViewAutoresizingFlexibleWidth |
  215. UIViewAutoresizingFlexibleHeight;
  216. pleaseWaitView.backgroundColor = [UIColor colorWithWhite:0 alpha:0.5];
  217. UIActivityIndicatorView *spinner =
  218. [[UIActivityIndicatorView alloc]
  219. initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite];
  220. spinner.center = pleaseWaitView.center;
  221. [pleaseWaitView addSubview:spinner];
  222. [spinner startAnimating];
  223. pleaseWaitView.layer.opacity = 0.0;
  224. [self.view addSubview:pleaseWaitView];
  225. [pleaseWaitWindow addSubview:pleaseWaitView];
  226. [pleaseWaitWindow makeKeyAndVisible];
  227. objc_setAssociatedObject(self,
  228. (__bridge const void *)(kPleaseWaitAssociatedObjectKey),
  229. pleaseWaitWindow,
  230. OBJC_ASSOCIATION_RETAIN_NONATOMIC);
  231. [UIView animateWithDuration:0.5f animations:^{
  232. pleaseWaitView.layer.opacity = 1.0f;
  233. } completion:^(BOOL finished) {
  234. if (completion) {
  235. completion();
  236. }
  237. }];
  238. }
  239. - (void)hideSpinner:(nullable void(^)(void))completion {
  240. if (self.useStatusBarSpinner) {
  241. [UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
  242. completion();
  243. return;
  244. }
  245. if ([self supportsAlertController]) {
  246. [self hideModernSpinner:completion];
  247. } else {
  248. [self hideIOS7Spinner:completion];
  249. }
  250. }
  251. - (void)hideModernSpinner:(nullable void(^)(void))completion {
  252. UIAlertController *pleaseWaitAlert =
  253. objc_getAssociatedObject(self,
  254. (__bridge const void *)kPleaseWaitAssociatedObjectKey);
  255. [pleaseWaitAlert dismissViewControllerAnimated:YES completion:completion];
  256. objc_setAssociatedObject(self,
  257. (__bridge const void *)(kPleaseWaitAssociatedObjectKey),
  258. nil,
  259. OBJC_ASSOCIATION_RETAIN_NONATOMIC);
  260. }
  261. - (void)hideIOS7Spinner:(nullable void(^)(void))completion {
  262. UIWindow *pleaseWaitWindow =
  263. objc_getAssociatedObject(self,
  264. (__bridge const void *)kPleaseWaitAssociatedObjectKey);
  265. UIView *pleaseWaitView;
  266. pleaseWaitView = pleaseWaitWindow.subviews.firstObject;
  267. [UIView animateWithDuration:0.5f animations:^{
  268. pleaseWaitView.layer.opacity = 0.0f;
  269. } completion:^(BOOL finished) {
  270. [pleaseWaitWindow resignKeyWindow];
  271. objc_setAssociatedObject(self,
  272. (__bridge const void *)(kPleaseWaitAssociatedObjectKey),
  273. nil,
  274. OBJC_ASSOCIATION_RETAIN_NONATOMIC);
  275. if (completion) {
  276. completion();
  277. }
  278. }];
  279. }
  280. @end
  281. @implementation SimpleTextPromptDelegate {
  282. AlertPromptCompletionBlock _completionHandler;
  283. SimpleTextPromptDelegate *_retainedSelf;
  284. }
  285. - (instancetype)initWithCompletionHandler:(AlertPromptCompletionBlock)completionHandler {
  286. self = [super init];
  287. if (self) {
  288. _completionHandler = completionHandler;
  289. _retainedSelf = self;
  290. }
  291. return self;
  292. }
  293. - (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex {
  294. if (buttonIndex == alertView.firstOtherButtonIndex) {
  295. _completionHandler(YES, [alertView textFieldAtIndex:0].text);
  296. } else {
  297. _completionHandler(NO, nil);
  298. }
  299. _completionHandler = nil;
  300. _retainedSelf = nil;
  301. }
  302. @end