FLEXWindowManagerController.m 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304
  1. //
  2. // FLEXWindowManagerController.m
  3. // FLEX
  4. //
  5. // Created by Tanner on 2/6/20.
  6. // Copyright © 2020 Flipboard. All rights reserved.
  7. //
  8. #import "FLEXWindowManagerController.h"
  9. #import "FLEXManager+Private.h"
  10. #import "FLEXUtility.h"
  11. @interface FLEXWindowManagerController ()
  12. @property (nonatomic) UIWindow *keyWindow;
  13. @property (nonatomic, copy) NSString *keyWindowSubtitle;
  14. @property (nonatomic, copy) NSArray<UIWindow *> *windows;
  15. @property (nonatomic, copy) NSArray<NSString *> *windowSubtitles;
  16. @property (nonatomic, copy) NSArray<UIScene *> *scenes API_AVAILABLE(ios(13));
  17. @property (nonatomic, copy) NSArray<NSString *> *sceneSubtitles;
  18. @end
  19. @implementation FLEXWindowManagerController
  20. #pragma mark - Initialization
  21. - (id)init {
  22. return [self initWithStyle:UITableViewStylePlain];
  23. }
  24. - (void)viewDidLoad {
  25. [super viewDidLoad];
  26. self.title = @"Windows";
  27. if (@available(iOS 13, *)) {
  28. self.title = @"Windows and Scenes";
  29. }
  30. [self disableToolbar];
  31. self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc]
  32. initWithBarButtonSystemItem:UIBarButtonSystemItemDone target:self action:@selector(dismissAnimated)
  33. ];
  34. [self reloadData];
  35. }
  36. #pragma mark - Private
  37. - (void)reloadData {
  38. self.keyWindow = UIApplication.sharedApplication.keyWindow;
  39. self.windows = UIApplication.sharedApplication.windows;
  40. self.windowSubtitles = [self.windows flex_mapped:^id(UIWindow *window, NSUInteger idx) {
  41. return [NSString stringWithFormat:@"Level: %@ — Root: %@",
  42. @(window.windowLevel), window.rootViewController
  43. ];
  44. }];
  45. self.keyWindowSubtitle = self.windowSubtitles[[self.windows indexOfObject:self.keyWindow]];
  46. if (@available(iOS 13, *)) {
  47. self.scenes = UIApplication.sharedApplication.connectedScenes.allObjects;
  48. self.sceneSubtitles = [self.scenes flex_mapped:^id(UIScene *scene, NSUInteger idx) {
  49. return [self sceneDescription:scene];
  50. }];
  51. }
  52. }
  53. - (void)dismissAnimated {
  54. [self dismissViewControllerAnimated:YES completion:nil];
  55. }
  56. - (void)showRevertOrDismissAlert:(void(^)())revertBlock {
  57. [self.tableView deselectRowAtIndexPath:self.tableView.indexPathForSelectedRow animated:YES];
  58. [self reloadData];
  59. [self.tableView reloadData];
  60. UIWindow *highestWindow = UIApplication.sharedApplication.keyWindow;
  61. UIWindowLevel maxLevel = 0;
  62. for (UIWindow *window in UIApplication.sharedApplication.windows) {
  63. if (window.windowLevel > maxLevel) {
  64. maxLevel = window.windowLevel;
  65. highestWindow = window;
  66. }
  67. }
  68. [FLEXAlert makeAlert:^(FLEXAlert *make) {
  69. make.title(@"Keep Changes?");
  70. make.message(@"If you do not wish to keep these settings, choose 'Revert Changes' below.");
  71. make.button(@"Keep Changes").destructiveStyle();
  72. make.button(@"Keep Changes and Dismiss").destructiveStyle().handler(^(NSArray<NSString *> *strings) {
  73. [self dismissAnimated];
  74. });
  75. make.button(@"Revert Changes").cancelStyle().handler(^(NSArray<NSString *> *strings) {
  76. revertBlock();
  77. [self reloadData];
  78. [self.tableView reloadData];
  79. });
  80. } showFrom:[FLEXUtility topViewControllerInWindow:highestWindow]];
  81. }
  82. - (NSString *)sceneDescription:(UIScene *)scene API_AVAILABLE(ios(13)) {
  83. NSString *state = [self stringFromSceneState:scene.activationState];
  84. NSString *title = scene.title.length ? scene.title : nil;
  85. NSString *suffix = nil;
  86. if ([scene isKindOfClass:[UIWindowScene class]]) {
  87. UIWindowScene *windowScene = (id)scene;
  88. suffix = FLEXPluralString(windowScene.windows.count, @"windows", @"window");
  89. }
  90. NSMutableString *description = state.mutableCopy;
  91. if (title) {
  92. [description appendFormat:@" — %@", title];
  93. }
  94. if (suffix) {
  95. [description appendFormat:@" — %@", suffix];
  96. }
  97. return description.copy;
  98. }
  99. - (NSString *)stringFromSceneState:(UISceneActivationState)state API_AVAILABLE(ios(13)) {
  100. switch (state) {
  101. case UISceneActivationStateUnattached:
  102. return @"Unattached";
  103. case UISceneActivationStateForegroundActive:
  104. return @"Active";
  105. case UISceneActivationStateForegroundInactive:
  106. return @"Inactive";
  107. case UISceneActivationStateBackground:
  108. return @"Backgrounded";
  109. }
  110. return [NSString stringWithFormat:@"Unknown state: %@", @(state)];
  111. }
  112. #pragma mark - Table View Data Source
  113. - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
  114. if (@available(iOS 13, *)) {
  115. return 3;
  116. }
  117. return 2;
  118. }
  119. - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
  120. switch (section) {
  121. case 0:
  122. return 1;
  123. case 1:
  124. return self.windows.count;
  125. case 2:
  126. if (@available(iOS 13, *)) {
  127. return self.scenes.count;
  128. }
  129. }
  130. @throw NSInternalInconsistencyException;
  131. return 0;
  132. }
  133. - (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
  134. switch (section) {
  135. case 0: return @"Key Window";
  136. case 1: return @"Windows";
  137. case 2: return @"Connected Scenes";
  138. }
  139. return nil;
  140. }
  141. - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
  142. UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:kFLEXDetailCell forIndexPath:indexPath];
  143. UIWindow *window = nil;
  144. NSString *subtitle = nil;
  145. switch (indexPath.section) {
  146. case 0:
  147. window = self.keyWindow;
  148. subtitle = self.keyWindowSubtitle;
  149. break;
  150. case 1:
  151. window = self.windows[indexPath.row];
  152. subtitle = self.windowSubtitles[indexPath.row];
  153. break;
  154. case 2:
  155. if (@available(iOS 13, *)) {
  156. UIScene *scene = self.scenes[indexPath.row];
  157. cell.textLabel.text = scene.description;
  158. cell.detailTextLabel.text = self.sceneSubtitles[indexPath.row];
  159. return cell;
  160. }
  161. }
  162. cell.textLabel.text = window.description;
  163. cell.detailTextLabel.text = [NSString
  164. stringWithFormat:@"Level: %@ — Root: %@", @(window.windowLevel), window.rootViewController
  165. ];
  166. return cell;
  167. }
  168. #pragma mark - Table View Delegate
  169. - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
  170. UIWindow *window = nil;
  171. NSString *subtitle = nil;
  172. FLEXWindow *flex = FLEXManager.sharedManager.explorerWindow;
  173. id cancelHandler = ^{
  174. [self.tableView deselectRowAtIndexPath:self.tableView.indexPathForSelectedRow animated:YES];
  175. };
  176. switch (indexPath.section) {
  177. case 0:
  178. window = self.keyWindow;
  179. subtitle = self.keyWindowSubtitle;
  180. break;
  181. case 1:
  182. window = self.windows[indexPath.row];
  183. subtitle = self.windowSubtitles[indexPath.row];
  184. break;
  185. case 2:
  186. if (@available(iOS 13, *)) {
  187. UIScene *scene = self.scenes[indexPath.row];
  188. UIWindowScene *oldScene = flex.windowScene;
  189. BOOL isWindowScene = [scene isKindOfClass:[UIWindowScene class]];
  190. BOOL isFLEXScene = isWindowScene ? flex.windowScene == scene : NO;
  191. [FLEXAlert makeAlert:^(FLEXAlert *make) {
  192. make.title(NSStringFromClass(scene.class));
  193. if (isWindowScene) {
  194. if (isFLEXScene) {
  195. make.message(@"Already the FLEX window scene");
  196. }
  197. make.button(@"Set as FLEX Window Scene")
  198. .handler(^(NSArray<NSString *> *strings) {
  199. flex.windowScene = (id)scene;
  200. [self showRevertOrDismissAlert:^{
  201. flex.windowScene = oldScene;
  202. }];
  203. }).enabled(!isFLEXScene);
  204. make.button(@"Cancel").cancelStyle();
  205. } else {
  206. make.message(@"Not a UIWindowScene");
  207. make.button(@"Dismiss").cancelStyle().handler(cancelHandler);
  208. }
  209. } showFrom:self];
  210. }
  211. }
  212. __block UIWindow *targetWindow = nil, *oldKeyWindow = nil;
  213. __block UIWindowLevel oldLevel;
  214. __block BOOL wasVisible;
  215. subtitle = [subtitle stringByAppendingString:
  216. @"\n\n1) Adjust the FLEX window level relative to this window,\n"
  217. "2) adjust this window's level relative to the FLEX window,\n"
  218. "3) set this window's level to a specific value, or\n"
  219. "4) make this window the key window if it isn't already."
  220. ];
  221. [FLEXAlert makeAlert:^(FLEXAlert *make) {
  222. make.title(NSStringFromClass(window.class)).message(subtitle);
  223. make.button(@"Adjust FLEX Window Level").handler(^(NSArray<NSString *> *strings) {
  224. targetWindow = flex; oldLevel = flex.windowLevel;
  225. flex.windowLevel = window.windowLevel + strings.firstObject.integerValue;
  226. [self showRevertOrDismissAlert:^{ targetWindow.windowLevel = oldLevel; }];
  227. });
  228. make.button(@"Adjust This Window's Level").handler(^(NSArray<NSString *> *strings) {
  229. targetWindow = window; oldLevel = window.windowLevel;
  230. window.windowLevel = flex.windowLevel + strings.firstObject.integerValue;
  231. [self showRevertOrDismissAlert:^{ targetWindow.windowLevel = oldLevel; }];
  232. });
  233. make.button(@"Set This Window's Level").handler(^(NSArray<NSString *> *strings) {
  234. targetWindow = window; oldLevel = window.windowLevel;
  235. window.windowLevel = strings.firstObject.integerValue;
  236. [self showRevertOrDismissAlert:^{ targetWindow.windowLevel = oldLevel; }];
  237. });
  238. make.button(@"Make Key And Visible").handler(^(NSArray<NSString *> *strings) {
  239. oldKeyWindow = UIApplication.sharedApplication.keyWindow;
  240. wasVisible = window.hidden;
  241. [window makeKeyAndVisible];
  242. [self showRevertOrDismissAlert:^{
  243. window.hidden = wasVisible;
  244. [oldKeyWindow makeKeyWindow];
  245. }];
  246. }).enabled(!window.isKeyWindow && !window.hidden);
  247. make.button(@"Cancel").cancelStyle().handler(cancelHandler);
  248. make.textField(@"+/- window level, i.e. 5 or -10");
  249. } showFrom:self];
  250. }
  251. @end