FLEXAlert.m 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234
  1. //
  2. // FLEXAlert.m
  3. // FLEX
  4. //
  5. // Created by Tanner Bennett on 8/20/19.
  6. // Copyright © 2020 FLEX Team. All rights reserved.
  7. //
  8. #import "FLEXAlert.h"
  9. @interface FLEXAlert ()
  10. @property (nonatomic, readonly) UIAlertController *_controller;
  11. @property (nonatomic, readonly) NSMutableArray<FLEXAlertAction *> *_actions;
  12. @end
  13. #define FLEXAlertActionMutationAssertion() \
  14. NSAssert(!self._action, @"Cannot mutate action after retreiving underlying UIAlertAction");
  15. @interface FLEXAlertAction ()
  16. @property (nonatomic) UIAlertController *_controller;
  17. @property (nonatomic) NSString *_title;
  18. @property (nonatomic) UIAlertActionStyle _style;
  19. @property (nonatomic) BOOL _disable;
  20. @property (nonatomic) void(^_handler)(UIAlertAction *action);
  21. @property (nonatomic) UIAlertAction *_action;
  22. @end
  23. @implementation FLEXAlert
  24. + (void)showAlert:(NSString *)title message:(NSString *)message from:(UIViewController *)viewController {
  25. [self makeAlert:^(FLEXAlert *make) {
  26. make.title(title).message(message).button(@"Dismiss").cancelStyle();
  27. } showFrom:viewController];
  28. }
  29. #pragma mark Initialization
  30. - (instancetype)initWithController:(UIAlertController *)controller {
  31. self = [super init];
  32. if (self) {
  33. __controller = controller;
  34. __actions = [NSMutableArray new];
  35. }
  36. return self;
  37. }
  38. + (UIAlertController *)make:(FLEXAlertBuilder)block withStyle:(UIAlertControllerStyle)style {
  39. // Create alert builder
  40. FLEXAlert *alert = [[self alloc] initWithController:
  41. [UIAlertController alertControllerWithTitle:nil message:nil preferredStyle:style]
  42. ];
  43. // Configure alert
  44. block(alert);
  45. // Add actions
  46. for (FLEXAlertAction *builder in alert._actions) {
  47. [alert._controller addAction:builder.action];
  48. }
  49. return alert._controller;
  50. }
  51. + (void)make:(FLEXAlertBuilder)block
  52. withStyle:(UIAlertControllerStyle)style
  53. showFrom:(UIViewController *)viewController
  54. source:(id)viewOrBarItem {
  55. UIAlertController *alert = [self make:block withStyle:style];
  56. if ([viewOrBarItem isKindOfClass:[UIBarButtonItem class]]) {
  57. alert.popoverPresentationController.barButtonItem = viewOrBarItem;
  58. } else if ([viewOrBarItem isKindOfClass:[UIView class]]) {
  59. alert.popoverPresentationController.sourceView = viewOrBarItem;
  60. alert.popoverPresentationController.sourceRect = [viewOrBarItem bounds];
  61. } else if (viewOrBarItem) {
  62. NSParameterAssert(
  63. [viewOrBarItem isKindOfClass:[UIBarButtonItem class]] ||
  64. [viewOrBarItem isKindOfClass:[UIView class]] ||
  65. !viewOrBarItem
  66. );
  67. }
  68. [viewController presentViewController:alert animated:YES completion:nil];
  69. }
  70. + (void)makeAlert:(FLEXAlertBuilder)block showFrom:(UIViewController *)controller {
  71. [self make:block withStyle:UIAlertControllerStyleAlert showFrom:controller source:nil];
  72. }
  73. + (void)makeSheet:(FLEXAlertBuilder)block showFrom:(UIViewController *)controller {
  74. [self make:block withStyle:UIAlertControllerStyleActionSheet showFrom:controller source:nil];
  75. }
  76. /// Construct and display an action sheet-style alert
  77. + (void)makeSheet:(FLEXAlertBuilder)block
  78. showFrom:(UIViewController *)controller
  79. source:(id)viewOrBarItem {
  80. [self make:block
  81. withStyle:UIAlertControllerStyleActionSheet
  82. showFrom:controller
  83. source:viewOrBarItem];
  84. }
  85. + (UIAlertController *)makeAlert:(FLEXAlertBuilder)block {
  86. return [self make:block withStyle:UIAlertControllerStyleAlert];
  87. }
  88. + (UIAlertController *)makeSheet:(FLEXAlertBuilder)block {
  89. return [self make:block withStyle:UIAlertControllerStyleActionSheet];
  90. }
  91. #pragma mark Configuration
  92. - (FLEXAlertStringProperty)title {
  93. return ^FLEXAlert *(NSString *title) {
  94. if (self._controller.title) {
  95. self._controller.title = [self._controller.title stringByAppendingString:title ?: @""];
  96. } else {
  97. self._controller.title = title;
  98. }
  99. return self;
  100. };
  101. }
  102. - (FLEXAlertStringProperty)message {
  103. return ^FLEXAlert *(NSString *message) {
  104. if (self._controller.message) {
  105. self._controller.message = [self._controller.message stringByAppendingString:message ?: @""];
  106. } else {
  107. self._controller.message = message;
  108. }
  109. return self;
  110. };
  111. }
  112. - (FLEXAlertAddAction)button {
  113. return ^FLEXAlertAction *(NSString *title) {
  114. FLEXAlertAction *action = FLEXAlertAction.new.title(title);
  115. action._controller = self._controller;
  116. [self._actions addObject:action];
  117. return action;
  118. };
  119. }
  120. - (FLEXAlertStringArg)textField {
  121. return ^FLEXAlert *(NSString *placeholder) {
  122. [self._controller addTextFieldWithConfigurationHandler:^(UITextField *textField) {
  123. textField.placeholder = placeholder;
  124. }];
  125. return self;
  126. };
  127. }
  128. - (FLEXAlertTextField)configuredTextField {
  129. return ^FLEXAlert *(void(^configurationHandler)(UITextField *)) {
  130. [self._controller addTextFieldWithConfigurationHandler:configurationHandler];
  131. return self;
  132. };
  133. }
  134. @end
  135. @implementation FLEXAlertAction
  136. - (FLEXAlertActionStringProperty)title {
  137. return ^FLEXAlertAction *(NSString *title) {
  138. FLEXAlertActionMutationAssertion();
  139. if (self._title) {
  140. self._title = [self._title stringByAppendingString:title ?: @""];
  141. } else {
  142. self._title = title;
  143. }
  144. return self;
  145. };
  146. }
  147. - (FLEXAlertActionProperty)destructiveStyle {
  148. return ^FLEXAlertAction *() {
  149. FLEXAlertActionMutationAssertion();
  150. self._style = UIAlertActionStyleDestructive;
  151. return self;
  152. };
  153. }
  154. - (FLEXAlertActionProperty)cancelStyle {
  155. return ^FLEXAlertAction *() {
  156. FLEXAlertActionMutationAssertion();
  157. self._style = UIAlertActionStyleCancel;
  158. return self;
  159. };
  160. }
  161. - (FLEXAlertActionBOOLProperty)enabled {
  162. return ^FLEXAlertAction *(BOOL enabled) {
  163. FLEXAlertActionMutationAssertion();
  164. self._disable = !enabled;
  165. return self;
  166. };
  167. }
  168. - (FLEXAlertActionHandler)handler {
  169. return ^FLEXAlertAction *(void(^handler)(NSArray<NSString *> *)) {
  170. FLEXAlertActionMutationAssertion();
  171. // Get weak reference to the alert to avoid block <--> alert retain cycle
  172. __weak __typeof(self._controller) weakController = self._controller;
  173. self._handler = ^(UIAlertAction *action) {
  174. // Strongify that reference and pass the text field strings to the handler
  175. __strong __typeof(weakController) controller = weakController;
  176. NSArray *strings = [controller.textFields valueForKeyPath:@"text"];
  177. handler(strings);
  178. };
  179. return self;
  180. };
  181. }
  182. - (UIAlertAction *)action {
  183. if (self._action) {
  184. return self._action;
  185. }
  186. self._action = [UIAlertAction
  187. actionWithTitle:self._title
  188. style:self._style
  189. handler:self._handler
  190. ];
  191. self._action.enabled = !self._disable;
  192. return self._action;
  193. }
  194. @end