FLEXWindowManagerController.m 11 KB

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