123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273 |
- //
- // FLEXManager+Extensibility.m
- // FLEX
- //
- // Created by Tanner on 2/2/20.
- // Copyright © 2020 FLEX Team. All rights reserved.
- //
- #import "FLEXManager+Extensibility.h"
- #import "FLEXManager+Private.h"
- #import "FLEXNavigationController.h"
- #import "FLEXGlobalsEntry.h"
- #import "FLEXObjectExplorerFactory.h"
- #import "FLEXKeyboardShortcutManager.h"
- #import "FLEXExplorerViewController.h"
- #import "FLEXNetworkMITMViewController.h"
- #import "FLEXKeyboardHelpViewController.h"
- #import "FLEXFileBrowserController.h"
- #import "FLEXUtility.h"
- @interface FLEXManager (ExtensibilityPrivate)
- @property (nonatomic, readonly) UIViewController *topViewController;
- @end
- @implementation FLEXManager (Extensibility)
- #pragma mark - Globals Screen Entries
- - (void)registerGlobalEntryWithName:(NSString *)entryName objectFutureBlock:(id (^)(void))objectFutureBlock {
- NSParameterAssert(entryName);
- NSParameterAssert(objectFutureBlock);
- NSAssert(NSThread.isMainThread, @"This method must be called from the main thread.");
- entryName = entryName.copy;
- FLEXGlobalsEntry *entry = [FLEXGlobalsEntry entryWithNameFuture:^NSString *{
- return entryName;
- } viewControllerFuture:^UIViewController *{
- return [FLEXObjectExplorerFactory explorerViewControllerForObject:objectFutureBlock()];
- }];
- [self.userGlobalEntries addObject:entry];
- }
- - (void)registerGlobalEntryWithName:(NSString *)entryName viewControllerFutureBlock:(UIViewController * (^)(void))viewControllerFutureBlock {
- NSParameterAssert(entryName);
- NSParameterAssert(viewControllerFutureBlock);
- NSAssert(NSThread.isMainThread, @"This method must be called from the main thread.");
- entryName = entryName.copy;
- FLEXGlobalsEntry *entry = [FLEXGlobalsEntry entryWithNameFuture:^NSString *{
- return entryName;
- } viewControllerFuture:^UIViewController *{
- UIViewController *viewController = viewControllerFutureBlock();
- NSCAssert(viewController, @"'%@' entry returned nil viewController. viewControllerFutureBlock should never return nil.", entryName);
- return viewController;
- }];
- [self.userGlobalEntries addObject:entry];
- }
- - (void)clearGlobalEntries
- {
- [self.userGlobalEntries removeAllObjects];
- }
- #pragma mark - Simulator Shortcuts
- - (void)registerSimulatorShortcutWithKey:(NSString *)key modifiers:(UIKeyModifierFlags)modifiers action:(dispatch_block_t)action description:(NSString *)description {
- #if TARGET_OS_SIMULATOR
- [FLEXKeyboardShortcutManager.sharedManager registerSimulatorShortcutWithKey:key modifiers:modifiers action:action description:description allowOverride:YES];
- #endif
- }
- - (void)setSimulatorShortcutsEnabled:(BOOL)simulatorShortcutsEnabled {
- #if TARGET_OS_SIMULATOR
- [FLEXKeyboardShortcutManager.sharedManager setEnabled:simulatorShortcutsEnabled];
- #endif
- }
- - (BOOL)simulatorShortcutsEnabled {
- #if TARGET_OS_SIMULATOR
- return FLEXKeyboardShortcutManager.sharedManager.isEnabled;
- #else
- return NO;
- #endif
- }
- #pragma mark - Shortcuts Defaults
- - (void)registerDefaultSimulatorShortcutWithKey:(NSString *)key modifiers:(UIKeyModifierFlags)modifiers action:(dispatch_block_t)action description:(NSString *)description {
- #if TARGET_OS_SIMULATOR
- // Don't allow override to avoid changing keys registered by the app
- [FLEXKeyboardShortcutManager.sharedManager registerSimulatorShortcutWithKey:key modifiers:modifiers action:action description:description allowOverride:NO];
- #endif
- }
- - (void)registerDefaultSimulatorShortcuts {
- [self registerDefaultSimulatorShortcutWithKey:@"f" modifiers:0 action:^{
- [self toggleExplorer];
- } description:@"Toggle FLEX toolbar"];
- [self registerDefaultSimulatorShortcutWithKey:@"g" modifiers:0 action:^{
- [self showExplorerIfNeeded];
- [self.explorerViewController toggleMenuTool];
- } description:@"Toggle FLEX globals menu"];
- [self registerDefaultSimulatorShortcutWithKey:@"v" modifiers:0 action:^{
- [self showExplorerIfNeeded];
- [self.explorerViewController toggleViewsTool];
- } description:@"Toggle view hierarchy menu"];
- [self registerDefaultSimulatorShortcutWithKey:@"s" modifiers:0 action:^{
- [self showExplorerIfNeeded];
- [self.explorerViewController toggleSelectTool];
- } description:@"Toggle select tool"];
- [self registerDefaultSimulatorShortcutWithKey:@"m" modifiers:0 action:^{
- [self showExplorerIfNeeded];
- [self.explorerViewController toggleMoveTool];
- } description:@"Toggle move tool"];
- [self registerDefaultSimulatorShortcutWithKey:@"n" modifiers:0 action:^{
- [self toggleTopViewControllerOfClass:[FLEXNetworkMITMViewController class]];
- } description:@"Toggle network history view"];
- // 't' is for testing: quickly present an object explorer for debugging
- [self registerDefaultSimulatorShortcutWithKey:@"t" modifiers:0 action:^{
- [self showExplorerIfNeeded];
- [self.explorerViewController toggleToolWithViewControllerProvider:^UINavigationController *{
- return [FLEXNavigationController withRootViewController:[FLEXObjectExplorerFactory
- explorerViewControllerForObject:NSBundle.mainBundle
- ]];
- } completion:nil];
- } description:@"Present an object explorer for debugging"];
- [self registerDefaultSimulatorShortcutWithKey:UIKeyInputDownArrow modifiers:0 action:^{
- if (self.isHidden || ![self.explorerViewController handleDownArrowKeyPressed]) {
- [self tryScrollDown];
- }
- } description:@"Cycle view selection\n\t\tMove view down\n\t\tScroll down"];
- [self registerDefaultSimulatorShortcutWithKey:UIKeyInputUpArrow modifiers:0 action:^{
- if (self.isHidden || ![self.explorerViewController handleUpArrowKeyPressed]) {
- [self tryScrollUp];
- }
- } description:@"Cycle view selection\n\t\tMove view up\n\t\tScroll up"];
- [self registerDefaultSimulatorShortcutWithKey:UIKeyInputRightArrow modifiers:0 action:^{
- if (!self.isHidden) {
- [self.explorerViewController handleRightArrowKeyPressed];
- }
- } description:@"Move selected view right"];
- [self registerDefaultSimulatorShortcutWithKey:UIKeyInputLeftArrow modifiers:0 action:^{
- if (self.isHidden) {
- [self tryGoBack];
- } else {
- [self.explorerViewController handleLeftArrowKeyPressed];
- }
- } description:@"Move selected view left"];
- [self registerDefaultSimulatorShortcutWithKey:@"?" modifiers:0 action:^{
- [self toggleTopViewControllerOfClass:[FLEXKeyboardHelpViewController class]];
- } description:@"Toggle (this) help menu"];
- [self registerDefaultSimulatorShortcutWithKey:UIKeyInputEscape modifiers:0 action:^{
- [[self.topViewController presentingViewController] dismissViewControllerAnimated:YES completion:nil];
- } description:@"End editing text\n\t\tDismiss top view controller"];
- [self registerDefaultSimulatorShortcutWithKey:@"o" modifiers:UIKeyModifierCommand|UIKeyModifierShift action:^{
- [self toggleTopViewControllerOfClass:[FLEXFileBrowserController class]];
- } description:@"Toggle file browser menu"];
- }
- + (void)load {
- dispatch_async(dispatch_get_main_queue(), ^{
- [self.sharedManager registerDefaultSimulatorShortcuts];
- });
- }
- #pragma mark - Private
- - (UIEdgeInsets)contentInsetsOfScrollView:(UIScrollView *)scrollView {
- if (@available(iOS 11, *)) {
- return scrollView.adjustedContentInset;
- }
- return scrollView.contentInset;
- }
- - (void)tryScrollDown {
- UIScrollView *scrollview = [self firstScrollView];
- UIEdgeInsets insets = [self contentInsetsOfScrollView:scrollview];
- CGPoint contentOffset = scrollview.contentOffset;
- CGFloat maxYOffset = scrollview.contentSize.height - scrollview.bounds.size.height + insets.bottom;
- contentOffset.y = MIN(contentOffset.y + 200, maxYOffset);
- [scrollview setContentOffset:contentOffset animated:YES];
- }
- - (void)tryScrollUp {
- UIScrollView *scrollview = [self firstScrollView];
- UIEdgeInsets insets = [self contentInsetsOfScrollView:scrollview];
- CGPoint contentOffset = scrollview.contentOffset;
- contentOffset.y = MAX(contentOffset.y - 200, -insets.top);
- [scrollview setContentOffset:contentOffset animated:YES];
- }
- - (UIScrollView *)firstScrollView {
- NSMutableArray<UIView *> *views = FLEXUtility.appKeyWindow.subviews.mutableCopy;
- UIScrollView *scrollView = nil;
- while (views.count > 0) {
- UIView *view = views.firstObject;
- [views removeObjectAtIndex:0];
- if ([view isKindOfClass:[UIScrollView class]]) {
- scrollView = (UIScrollView *)view;
- break;
- } else {
- [views addObjectsFromArray:view.subviews];
- }
- }
- return scrollView;
- }
- - (void)tryGoBack {
- UINavigationController *navigationController = nil;
- UIViewController *topViewController = self.topViewController;
- if ([topViewController isKindOfClass:[UINavigationController class]]) {
- navigationController = (UINavigationController *)topViewController;
- } else {
- navigationController = topViewController.navigationController;
- }
- [navigationController popViewControllerAnimated:YES];
- }
- - (UIViewController *)topViewController {
- return [FLEXUtility topViewControllerInWindow:UIApplication.sharedApplication.keyWindow];
- }
- - (void)toggleTopViewControllerOfClass:(Class)class {
- UINavigationController *topViewController = (id)self.topViewController;
- if ([topViewController isKindOfClass:[FLEXNavigationController class]]) {
- if ([topViewController.topViewController isKindOfClass:[class class]]) {
- if (topViewController.viewControllers.count == 1) {
- // Dismiss since we are already presenting it
- [topViewController.presentingViewController dismissViewControllerAnimated:YES completion:nil];
- } else {
- // Pop since we are viewing it but it's not the only thing on the stack
- [topViewController popViewControllerAnimated:YES];
- }
- } else {
- // Push it on the existing navigation stack
- [topViewController pushViewController:[class new] animated:YES];
- }
- } else {
- // Present it in an entirely new navigation controller
- [self.explorerViewController presentViewController:
- [FLEXNavigationController withRootViewController:[class new]]
- animated:YES completion:nil];
- }
- }
- - (void)showExplorerIfNeeded {
- if (self.isHidden) {
- [self showExplorer];
- }
- }
- @end
|