3fd8e7c77d
This allows `FLEX` to be linked statically to a binary that has `-Wstrict-prototypes` enabled (for example in CocoaPods without `use_frameworks!`. Changes: - Apply `void` to all the empty function/block declarations that don't take arguments. - Apply `SEL`/`UIImage *` to the others that actually take arguments. - Remove `CLANG_WARN_STRICT_PROTOTYPES = NO` since the default is enabled.
303 lines
11 KiB
Objective-C
303 lines
11 KiB
Objective-C
//
|
|
// 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"
|
|
|
|
@interface FLEXWindowManagerController ()
|
|
@property (nonatomic) UIWindow *keyWindow;
|
|
@property (nonatomic, copy) NSString *keyWindowSubtitle;
|
|
@property (nonatomic, copy) NSArray<UIWindow *> *windows;
|
|
@property (nonatomic, copy) NSArray<NSString *> *windowSubtitles;
|
|
@property (nonatomic, copy) NSArray<UIScene *> *scenes API_AVAILABLE(ios(13));
|
|
@property (nonatomic, copy) NSArray<NSString *> *sceneSubtitles;
|
|
@property (nonatomic, copy) NSArray<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(^)(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<NSString *> *strings) {
|
|
[self dismissAnimated];
|
|
});
|
|
make.button(@"Revert Changes").cancelStyle().handler(^(NSArray<NSString *> *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];
|
|
cell.accessoryType = UITableViewCellAccessoryDetailButton;
|
|
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<NSString *> *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<NSString *> *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<NSString *> *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<NSString *> *strings) {
|
|
targetWindow = window; oldLevel = window.windowLevel;
|
|
window.windowLevel = strings.firstObject.integerValue;
|
|
|
|
[self showRevertOrDismissAlert:^{ targetWindow.windowLevel = oldLevel; }];
|
|
});
|
|
make.button(@"Make Key And Visible").handler(^(NSArray<NSString *> *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
|