// // FLEXWindowManagerController.m // FLEX // // Created by Tanner on 2/6/20. // Copyright © 2020 FLEX Team. All rights reserved. // #import "FLEXWindowManagerController.h" #import "FLEXManager+Private.h" #import "FLEXUtility.h" #import "FLEXObjectExplorerFactory.h" #import @interface FLEXWindowManagerController () @property (nonatomic) UIWindow *keyWindow; @property (nonatomic, copy) NSString *keyWindowSubtitle; @property (nonatomic, copy) NSArray *windows; @property (nonatomic, copy) NSArray *windowSubtitles; @property (nonatomic, copy) NSArray *scenes API_AVAILABLE(ios(13)); @property (nonatomic, copy) NSArray *sceneSubtitles; @property (nonatomic, copy) NSArray *sections; @end @implementation FLEXWindowManagerController #pragma mark - Initialization - (id)init { return [self initWithStyle:UITableViewStylePlain]; } - (void)viewDidLoad { [super viewDidLoad]; self.title = @"Windows"; if (@available(iOS 13, *)) { self.title = @"Windows and Scenes"; } [self disableToolbar]; [self reloadData]; } #pragma mark - Private - (void)reloadData { self.keyWindow = UIApplication.sharedApplication.keyWindow; self.windows = UIApplication.sharedApplication.windows; self.keyWindowSubtitle = self.windowSubtitles[[self.windows indexOfObject:self.keyWindow]]; self.windowSubtitles = [self.windows flex_mapped:^id(UIWindow *window, NSUInteger idx) { return [NSString stringWithFormat:@"Level: %@ — Root: %@", @(window.windowLevel), window.rootViewController ]; }]; if (@available(iOS 13, *)) { self.scenes = UIApplication.sharedApplication.connectedScenes.allObjects; self.sceneSubtitles = [self.scenes flex_mapped:^id(UIScene *scene, NSUInteger idx) { return [self sceneDescription:scene]; }]; self.sections = @[@[self.keyWindow], self.windows, self.scenes]; } else { self.sections = @[@[self.keyWindow], self.windows]; } [self.tableView reloadData]; } - (void)dismissAnimated { [self dismissViewControllerAnimated:YES completion:nil]; } - (void)showRevertOrDismissAlert:(void(^)())revertBlock { [self.tableView deselectRowAtIndexPath:self.tableView.indexPathForSelectedRow animated:YES]; [self reloadData]; [self.tableView reloadData]; UIWindow *highestWindow = UIApplication.sharedApplication.keyWindow; UIWindowLevel maxLevel = 0; for (UIWindow *window in UIApplication.sharedApplication.windows) { if (window.windowLevel > maxLevel) { maxLevel = window.windowLevel; highestWindow = window; } } [FLEXAlert makeAlert:^(FLEXAlert *make) { make.title(@"Keep Changes?"); make.message(@"If you do not wish to keep these settings, choose 'Revert Changes' below."); make.button(@"Keep Changes").destructiveStyle(); make.button(@"Keep Changes and Dismiss").destructiveStyle().handler(^(NSArray *strings) { [self dismissAnimated]; }); make.button(@"Revert Changes").cancelStyle().handler(^(NSArray *strings) { revertBlock(); [self reloadData]; [self.tableView reloadData]; }); } showFrom:[FLEXUtility topViewControllerInWindow:highestWindow]]; } - (NSString *)sceneDescription:(UIScene *)scene API_AVAILABLE(ios(13)) { NSString *state = [self stringFromSceneState:scene.activationState]; NSString *title = scene.title.length ? scene.title : nil; NSString *suffix = nil; if ([scene isKindOfClass:[UIWindowScene class]]) { UIWindowScene *windowScene = (id)scene; suffix = FLEXPluralString(windowScene.windows.count, @"windows", @"window"); } NSMutableString *description = state.mutableCopy; if (title) { [description appendFormat:@" — %@", title]; } if (suffix) { [description appendFormat:@" — %@", suffix]; } return description.copy; } - (NSString *)stringFromSceneState:(UISceneActivationState)state API_AVAILABLE(ios(13)) { switch (state) { case UISceneActivationStateUnattached: return @"Unattached"; case UISceneActivationStateForegroundActive: return @"Active"; case UISceneActivationStateForegroundInactive: return @"Inactive"; case UISceneActivationStateBackground: return @"Backgrounded"; } return [NSString stringWithFormat:@"Unknown state: %@", @(state)]; } #pragma mark - Table View Data Source - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { return self.sections.count; } - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return self.sections[section].count; } - (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section { switch (section) { case 0: return @"Key Window"; case 1: return @"Windows"; case 2: return @"Connected Scenes"; } return nil; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:kFLEXDetailCell forIndexPath:indexPath]; #if !TARGET_OS_TV cell.accessoryType = UITableViewCellAccessoryDetailButton; #endif cell.textLabel.lineBreakMode = NSLineBreakByTruncatingTail; UIWindow *window = nil; NSString *subtitle = nil; switch (indexPath.section) { case 0: window = self.keyWindow; subtitle = self.keyWindowSubtitle; break; case 1: window = self.windows[indexPath.row]; subtitle = self.windowSubtitles[indexPath.row]; break; case 2: if (@available(iOS 13, *)) { UIScene *scene = self.scenes[indexPath.row]; cell.textLabel.text = scene.description; cell.detailTextLabel.text = self.sceneSubtitles[indexPath.row]; return cell; } } cell.textLabel.text = window.description; cell.detailTextLabel.text = [NSString stringWithFormat:@"Level: %@ — Root: %@", @((NSInteger)window.windowLevel), window.rootViewController.class ]; return cell; } #pragma mark - Table View Delegate - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { UIWindow *window = nil; NSString *subtitle = nil; FLEXWindow *flex = FLEXManager.sharedManager.explorerWindow; id cancelHandler = ^{ [self.tableView deselectRowAtIndexPath:self.tableView.indexPathForSelectedRow animated:YES]; }; switch (indexPath.section) { case 0: window = self.keyWindow; subtitle = self.keyWindowSubtitle; break; case 1: window = self.windows[indexPath.row]; subtitle = self.windowSubtitles[indexPath.row]; break; case 2: if (@available(iOS 13, *)) { UIScene *scene = self.scenes[indexPath.row]; UIWindowScene *oldScene = flex.windowScene; BOOL isWindowScene = [scene isKindOfClass:[UIWindowScene class]]; BOOL isFLEXScene = isWindowScene ? flex.windowScene == scene : NO; [FLEXAlert makeAlert:^(FLEXAlert *make) { make.title(NSStringFromClass(scene.class)); if (isWindowScene) { if (isFLEXScene) { make.message(@"Already the FLEX window scene"); } make.button(@"Set as FLEX Window Scene") .handler(^(NSArray *strings) { flex.windowScene = (id)scene; [self showRevertOrDismissAlert:^{ flex.windowScene = oldScene; }]; }).enabled(!isFLEXScene); make.button(@"Cancel").cancelStyle(); } else { make.message(@"Not a UIWindowScene"); make.button(@"Dismiss").cancelStyle().handler(cancelHandler); } } showFrom:self]; } } __block UIWindow *targetWindow = nil, *oldKeyWindow = nil; __block UIWindowLevel oldLevel; __block BOOL wasVisible; subtitle = [subtitle stringByAppendingString: @"\n\n1) Adjust the FLEX window level relative to this window,\n" "2) adjust this window's level relative to the FLEX window,\n" "3) set this window's level to a specific value, or\n" "4) make this window the key window if it isn't already." ]; [FLEXAlert makeAlert:^(FLEXAlert *make) { make.title(NSStringFromClass(window.class)).message(subtitle); make.button(@"Adjust FLEX Window Level").handler(^(NSArray *strings) { targetWindow = flex; oldLevel = flex.windowLevel; flex.windowLevel = window.windowLevel + strings.firstObject.integerValue; [self showRevertOrDismissAlert:^{ targetWindow.windowLevel = oldLevel; }]; }); make.button(@"Adjust This Window's Level").handler(^(NSArray *strings) { targetWindow = window; oldLevel = window.windowLevel; window.windowLevel = flex.windowLevel + strings.firstObject.integerValue; [self showRevertOrDismissAlert:^{ targetWindow.windowLevel = oldLevel; }]; }); make.button(@"Set This Window's Level").handler(^(NSArray *strings) { targetWindow = window; oldLevel = window.windowLevel; window.windowLevel = strings.firstObject.integerValue; [self showRevertOrDismissAlert:^{ targetWindow.windowLevel = oldLevel; }]; }); make.button(@"Make Key And Visible").handler(^(NSArray *strings) { oldKeyWindow = UIApplication.sharedApplication.keyWindow; wasVisible = window.hidden; [window makeKeyAndVisible]; [self showRevertOrDismissAlert:^{ window.hidden = wasVisible; [oldKeyWindow makeKeyWindow]; }]; }).enabled(!window.isKeyWindow && !window.hidden); make.button(@"Cancel").cancelStyle().handler(cancelHandler); make.textField(@"+/- window level, i.e. 5 or -10"); } showFrom:self]; } - (void)tableView:(UITableView *)tableView accessoryButtonTappedForRowWithIndexPath:(NSIndexPath *)ip { [self.navigationController pushViewController: [FLEXObjectExplorerFactory explorerViewControllerForObject:self.sections[ip.section][ip.row]] animated:YES]; } @end