Compare commits
75 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ebf5254629 | |||
| e199b529c8 | |||
| 06edea64ae | |||
| 6d3afe36d1 | |||
| b22d2b57a2 | |||
| b453086936 | |||
| 103489c566 | |||
| 3dd27557ea | |||
| a64188dd5e | |||
| 44e428655a | |||
| 96d8b425d5 | |||
| 7df172afac | |||
| 9bb44925c8 | |||
| 320aeb815b | |||
| ab4b678498 | |||
| 52bf2071a5 | |||
| f0bb931a64 | |||
| 30f1fecc54 | |||
| 85a424a824 | |||
| 6405bf40e3 | |||
| b79fd26ca4 | |||
| caadcce7f1 | |||
| c250200d03 | |||
| 51c05087e7 | |||
| 7a2e65f292 | |||
| 0489c09ba3 | |||
| 70038d244d | |||
| a9e0dedd31 | |||
| f6ad51219d | |||
| a42af79040 | |||
| 792634527a | |||
| 5627219c56 | |||
| 4e81d4b476 | |||
| 001d58cd89 | |||
| 03c96d8fdb | |||
| b5423192fb | |||
| 1efb40a07e | |||
| 6c6023dc84 | |||
| a6dc4b010c | |||
| dd87da4134 | |||
| 627ff6cbe2 | |||
| 9cc8435cae | |||
| 3d977450ca | |||
| f7c482ceed | |||
| c38b90ee60 | |||
| 70491431fa | |||
| 6bc055911e | |||
| 9b1e13b963 | |||
| 74a73893d4 | |||
| 4a3ab17851 | |||
| 5b8efe71a7 | |||
| b7f2d9bcbe | |||
| f4efc6dbbf | |||
| efab760253 | |||
| 48826e2160 | |||
| 0b4e231814 | |||
| 6f2d811338 | |||
| 9c9ce5e2e1 | |||
| 08b4559b26 | |||
| 913ad5e2c6 | |||
| 7649dc616c | |||
| efabb29a52 | |||
| e3612e31d7 | |||
| 2575d2eaee | |||
| f041002e73 | |||
| 0da49c1eb6 | |||
| a3a84b0cd7 | |||
| 33be034e2b | |||
| f590263d9f | |||
| a1c378a9d5 | |||
| 617db9c48a | |||
| b05f78c388 | |||
| d9ecb2359b | |||
| 5f9a61c755 | |||
| 3dd178c029 |
@@ -0,0 +1,8 @@
|
||||
language: objective-c
|
||||
xcode_workspace: FLEX.xcworkspace
|
||||
matrix:
|
||||
include:
|
||||
- xcode_scheme: UICatalog
|
||||
xcode_sdk: iphonesimulator
|
||||
- xcode_scheme: FLEX
|
||||
xcode_sdk: iphonesimulator
|
||||
@@ -1,159 +0,0 @@
|
||||
//
|
||||
// FLEXManager.m
|
||||
// Flipboard
|
||||
//
|
||||
// Created by Ryan Olson on 4/4/14.
|
||||
// Copyright (c) 2014 Flipboard. All rights reserved.
|
||||
//
|
||||
|
||||
#import "FLEXManager.h"
|
||||
#import "FLEXExplorerViewController.h"
|
||||
#import "FLEXWindow.h"
|
||||
#import "FLEXGlobalsTableViewControllerEntry.h"
|
||||
#import "FLEXObjectExplorerFactory.h"
|
||||
#import "FLEXObjectExplorerViewController.h"
|
||||
#import "FLEXNetworkObserver.h"
|
||||
#import "FLEXNetworkRecorder.h"
|
||||
|
||||
@interface FLEXManager () <FLEXWindowEventDelegate, FLEXExplorerViewControllerDelegate>
|
||||
|
||||
@property (nonatomic, strong) FLEXWindow *explorerWindow;
|
||||
@property (nonatomic, strong) FLEXExplorerViewController *explorerViewController;
|
||||
|
||||
@property (nonatomic, readonly, strong) NSMutableArray *userGlobalEntries;
|
||||
|
||||
@end
|
||||
|
||||
@implementation FLEXManager
|
||||
|
||||
+ (instancetype)sharedManager
|
||||
{
|
||||
static FLEXManager *sharedManager = nil;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
sharedManager = [[[self class] alloc] init];
|
||||
});
|
||||
return sharedManager;
|
||||
}
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_userGlobalEntries = [[NSMutableArray alloc] init];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (FLEXWindow *)explorerWindow
|
||||
{
|
||||
NSAssert([NSThread isMainThread], @"You must use %@ from the main thread only.", NSStringFromClass([self class]));
|
||||
|
||||
if (!_explorerWindow) {
|
||||
_explorerWindow = [[FLEXWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
|
||||
_explorerWindow.eventDelegate = self;
|
||||
_explorerWindow.rootViewController = self.explorerViewController;
|
||||
}
|
||||
|
||||
return _explorerWindow;
|
||||
}
|
||||
|
||||
- (FLEXExplorerViewController *)explorerViewController
|
||||
{
|
||||
if (!_explorerViewController) {
|
||||
_explorerViewController = [[FLEXExplorerViewController alloc] init];
|
||||
_explorerViewController.delegate = self;
|
||||
}
|
||||
|
||||
return _explorerViewController;
|
||||
}
|
||||
|
||||
- (void)showExplorer
|
||||
{
|
||||
self.explorerWindow.hidden = NO;
|
||||
}
|
||||
|
||||
- (void)hideExplorer
|
||||
{
|
||||
self.explorerWindow.hidden = YES;
|
||||
}
|
||||
|
||||
- (BOOL)isHidden
|
||||
{
|
||||
return self.explorerWindow.isHidden;
|
||||
}
|
||||
|
||||
- (BOOL)isNetworkDebuggingEnabled
|
||||
{
|
||||
return [FLEXNetworkObserver isEnabled];
|
||||
}
|
||||
|
||||
- (void)setNetworkDebuggingEnabled:(BOOL)networkDebuggingEnabled
|
||||
{
|
||||
[FLEXNetworkObserver setEnabled:networkDebuggingEnabled];
|
||||
}
|
||||
|
||||
- (NSUInteger)networkResponseCacheByteLimit
|
||||
{
|
||||
return [[FLEXNetworkRecorder defaultRecorder] responseCacheByteLimit];
|
||||
}
|
||||
|
||||
- (void)setNetworkResponseCacheByteLimit:(NSUInteger)networkResponseCacheByteLimit
|
||||
{
|
||||
[[FLEXNetworkRecorder defaultRecorder] setResponseCacheByteLimit:networkResponseCacheByteLimit];
|
||||
}
|
||||
|
||||
#pragma mark - FLEXWindowEventDelegate
|
||||
|
||||
- (BOOL)shouldHandleTouchAtPoint:(CGPoint)pointInWindow
|
||||
{
|
||||
// Ask the explorer view controller
|
||||
return [self.explorerViewController shouldReceiveTouchAtWindowPoint:pointInWindow];
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - FLEXExplorerViewControllerDelegate
|
||||
|
||||
- (void)explorerViewControllerDidFinish:(FLEXExplorerViewController *)explorerViewController
|
||||
{
|
||||
[self hideExplorer];
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Extensions
|
||||
|
||||
- (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;
|
||||
FLEXGlobalsTableViewControllerEntry *entry = [FLEXGlobalsTableViewControllerEntry 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;
|
||||
FLEXGlobalsTableViewControllerEntry *entry = [FLEXGlobalsTableViewControllerEntry 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];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,36 +0,0 @@
|
||||
//
|
||||
// FLEXWindow.m
|
||||
// Flipboard
|
||||
//
|
||||
// Created by Ryan Olson on 4/13/14.
|
||||
// Copyright (c) 2014 Flipboard. All rights reserved.
|
||||
//
|
||||
|
||||
#import "FLEXWindow.h"
|
||||
|
||||
@implementation FLEXWindow
|
||||
|
||||
- (id)initWithFrame:(CGRect)frame
|
||||
{
|
||||
self = [super initWithFrame:frame];
|
||||
if (self) {
|
||||
self.backgroundColor = [UIColor clearColor];
|
||||
// Some apps have windows at UIWindowLevelStatusBar + n.
|
||||
// If we make the window level too high, we block out UIAlertViews.
|
||||
// There's a balance between staying above the app's windows and staying below alerts.
|
||||
// UIWindowLevelStatusBar + 100 seems to hit that balance.
|
||||
self.windowLevel = UIWindowLevelStatusBar + 100.0;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
|
||||
{
|
||||
BOOL pointInside = NO;
|
||||
if ([self.eventDelegate shouldHandleTouchAtPoint:point]) {
|
||||
pointInside = [super pointInside:point withEvent:event];
|
||||
}
|
||||
return pointInside;
|
||||
}
|
||||
|
||||
@end
|
||||
+12
@@ -15,6 +15,18 @@
|
||||
@property (nonatomic, weak) id <FLEXExplorerViewControllerDelegate> delegate;
|
||||
|
||||
- (BOOL)shouldReceiveTouchAtWindowPoint:(CGPoint)pointInWindowCoordinates;
|
||||
- (BOOL)wantsWindowToBecomeKey;
|
||||
|
||||
// Keyboard shortcut helpers
|
||||
|
||||
- (void)toggleSelectTool;
|
||||
- (void)toggleMoveTool;
|
||||
- (void)toggleViewsTool;
|
||||
- (void)toggleMenuTool;
|
||||
- (void)handleDownArrowKeyPressed;
|
||||
- (void)handleUpArrowKeyPressed;
|
||||
- (void)handleRightArrowKeyPressed;
|
||||
- (void)handleLeftArrowKeyPressed;
|
||||
|
||||
@end
|
||||
|
||||
+131
-92
@@ -14,6 +14,7 @@
|
||||
#import "FLEXGlobalsTableViewController.h"
|
||||
#import "FLEXObjectExplorerViewController.h"
|
||||
#import "FLEXObjectExplorerFactory.h"
|
||||
#import "FLEXNetworkHistoryTableViewController.h"
|
||||
|
||||
typedef NS_ENUM(NSUInteger, FLEXExplorerMode) {
|
||||
FLEXExplorerModeDefault,
|
||||
@@ -120,77 +121,27 @@ typedef NS_ENUM(NSUInteger, FLEXExplorerMode) {
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Status Bar Wrangling for iOS 7
|
||||
|
||||
// Try to get the preferred status bar properties from the app's root view controller (not us).
|
||||
// In general, our window shouldn't be the key window when this view controller is asked about the status bar.
|
||||
// However, we guard against infinite recursion and provide a reasonable default for status bar behavior in case our window is the keyWindow.
|
||||
|
||||
- (UIViewController *)viewControllerForStatusBarAndOrientationProperties
|
||||
{
|
||||
UIViewController *viewControllerToAsk = [[[UIApplication sharedApplication] keyWindow] rootViewController];
|
||||
|
||||
// On iPhone, modal view controllers get asked
|
||||
if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone) {
|
||||
while (viewControllerToAsk.presentedViewController) {
|
||||
viewControllerToAsk = viewControllerToAsk.presentedViewController;
|
||||
}
|
||||
}
|
||||
|
||||
return viewControllerToAsk;
|
||||
}
|
||||
|
||||
- (UIStatusBarStyle)preferredStatusBarStyle
|
||||
{
|
||||
UIViewController *viewControllerToAsk = [self viewControllerForStatusBarAndOrientationProperties];
|
||||
UIStatusBarStyle preferredStyle = UIStatusBarStyleDefault;
|
||||
if (viewControllerToAsk && viewControllerToAsk != self) {
|
||||
// We might need to foward to a child
|
||||
UIViewController *childViewControllerToAsk = [viewControllerToAsk childViewControllerForStatusBarStyle];
|
||||
while (childViewControllerToAsk && childViewControllerToAsk != viewControllerToAsk) {
|
||||
viewControllerToAsk = childViewControllerToAsk;
|
||||
childViewControllerToAsk = [viewControllerToAsk childViewControllerForStatusBarStyle];
|
||||
}
|
||||
|
||||
preferredStyle = [viewControllerToAsk preferredStatusBarStyle];
|
||||
}
|
||||
return preferredStyle;
|
||||
}
|
||||
|
||||
- (UIStatusBarAnimation)preferredStatusBarUpdateAnimation
|
||||
{
|
||||
UIViewController *viewControllerToAsk = [self viewControllerForStatusBarAndOrientationProperties];
|
||||
UIStatusBarAnimation preferredAnimation = UIStatusBarAnimationFade;
|
||||
if (viewControllerToAsk && viewControllerToAsk != self) {
|
||||
preferredAnimation = [viewControllerToAsk preferredStatusBarUpdateAnimation];
|
||||
}
|
||||
return preferredAnimation;
|
||||
}
|
||||
|
||||
- (BOOL)prefersStatusBarHidden
|
||||
{
|
||||
UIViewController *viewControllerToAsk = [self viewControllerForStatusBarAndOrientationProperties];
|
||||
BOOL prefersHidden = NO;
|
||||
if (viewControllerToAsk && viewControllerToAsk != self) {
|
||||
// Again, we might need to forward to a child
|
||||
UIViewController *childViewControllerToAsk = [viewControllerToAsk childViewControllerForStatusBarHidden];
|
||||
while (childViewControllerToAsk && childViewControllerToAsk != viewControllerToAsk) {
|
||||
viewControllerToAsk = childViewControllerToAsk;
|
||||
childViewControllerToAsk = [viewControllerToAsk childViewControllerForStatusBarHidden];
|
||||
}
|
||||
|
||||
prefersHidden = [viewControllerToAsk prefersStatusBarHidden];
|
||||
}
|
||||
return prefersHidden;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Rotation
|
||||
|
||||
- (NSUInteger)supportedInterfaceOrientations
|
||||
- (UIViewController *)viewControllerForRotationAndOrientation
|
||||
{
|
||||
UIViewController *viewControllerToAsk = [self viewControllerForStatusBarAndOrientationProperties];
|
||||
NSUInteger supportedOrientations = [FLEXUtility infoPlistSupportedInterfaceOrientationsMask];
|
||||
UIWindow *window = self.previousKeyWindow ?: [[UIApplication sharedApplication] keyWindow];
|
||||
UIViewController *viewController = window.rootViewController;
|
||||
NSString *viewControllerSelectorString = [@[@"_vie", @"wContro", @"llerFor", @"Supported", @"Interface", @"Orientations"] componentsJoinedByString:@""];
|
||||
SEL viewControllerSelector = NSSelectorFromString(viewControllerSelectorString);
|
||||
if ([viewController respondsToSelector:viewControllerSelector]) {
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
|
||||
viewController = [viewController performSelector:viewControllerSelector];
|
||||
#pragma clang diagnostic pop
|
||||
}
|
||||
return viewController;
|
||||
}
|
||||
|
||||
- (UIInterfaceOrientationMask)supportedInterfaceOrientations
|
||||
{
|
||||
UIViewController *viewControllerToAsk = [self viewControllerForRotationAndOrientation];
|
||||
UIInterfaceOrientationMask supportedOrientations = [FLEXUtility infoPlistSupportedInterfaceOrientationsMask];
|
||||
if (viewControllerToAsk && viewControllerToAsk != self) {
|
||||
supportedOrientations = [viewControllerToAsk supportedInterfaceOrientations];
|
||||
}
|
||||
@@ -206,7 +157,7 @@ typedef NS_ENUM(NSUInteger, FLEXExplorerMode) {
|
||||
|
||||
- (BOOL)shouldAutorotate
|
||||
{
|
||||
UIViewController *viewControllerToAsk = [self viewControllerForStatusBarAndOrientationProperties];
|
||||
UIViewController *viewControllerToAsk = [self viewControllerForRotationAndOrientation];
|
||||
BOOL shouldAutorotate = YES;
|
||||
if (viewControllerToAsk && viewControllerToAsk != self) {
|
||||
shouldAutorotate = [viewControllerToAsk shouldAutorotate];
|
||||
@@ -417,21 +368,12 @@ typedef NS_ENUM(NSUInteger, FLEXExplorerMode) {
|
||||
|
||||
- (void)selectButtonTapped:(FLEXToolbarItem *)sender
|
||||
{
|
||||
if (self.currentMode == FLEXExplorerModeSelect) {
|
||||
self.currentMode = FLEXExplorerModeDefault;
|
||||
} else {
|
||||
self.currentMode = FLEXExplorerModeSelect;
|
||||
}
|
||||
[self toggleSelectTool];
|
||||
}
|
||||
|
||||
- (void)hierarchyButtonTapped:(FLEXToolbarItem *)sender
|
||||
{
|
||||
NSArray *allViews = [self allViewsInHierarchy];
|
||||
NSDictionary *depthsForViews = [self hierarchyDepthsForViews:allViews];
|
||||
FLEXHierarchyTableViewController *hierarchyTVC = [[FLEXHierarchyTableViewController alloc] initWithViews:allViews viewsAtTap:self.viewsAtTapPoint selectedView:self.selectedView depths:depthsForViews];
|
||||
hierarchyTVC.delegate = self;
|
||||
UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:hierarchyTVC];
|
||||
[self makeKeyAndPresentViewController:navigationController animated:YES completion:nil];
|
||||
[self toggleViewsTool];
|
||||
}
|
||||
|
||||
- (NSArray *)allViewsInHierarchy
|
||||
@@ -477,20 +419,12 @@ typedef NS_ENUM(NSUInteger, FLEXExplorerMode) {
|
||||
|
||||
- (void)moveButtonTapped:(FLEXToolbarItem *)sender
|
||||
{
|
||||
if (self.currentMode == FLEXExplorerModeMove) {
|
||||
self.currentMode = FLEXExplorerModeDefault;
|
||||
} else {
|
||||
self.currentMode = FLEXExplorerModeMove;
|
||||
}
|
||||
[self toggleMoveTool];
|
||||
}
|
||||
|
||||
- (void)globalsButtonTapped:(FLEXToolbarItem *)sender
|
||||
{
|
||||
FLEXGlobalsTableViewController *globalsViewController = [[FLEXGlobalsTableViewController alloc] init];
|
||||
globalsViewController.delegate = self;
|
||||
[FLEXGlobalsTableViewController setApplicationWindow:[[UIApplication sharedApplication] keyWindow]];
|
||||
UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:globalsViewController];
|
||||
[self makeKeyAndPresentViewController:navigationController animated:YES completion:nil];
|
||||
[self toggleMenuTool];
|
||||
}
|
||||
|
||||
- (void)closeButtonTapped:(FLEXToolbarItem *)sender
|
||||
@@ -860,9 +794,10 @@ typedef NS_ENUM(NSUInteger, FLEXExplorerMode) {
|
||||
|
||||
- (void)resignKeyAndDismissViewControllerAnimated:(BOOL)animated completion:(void (^)(void))completion
|
||||
{
|
||||
[self.previousKeyWindow makeKeyWindow];
|
||||
|
||||
UIWindow *previousKeyWindow = self.previousKeyWindow;
|
||||
self.previousKeyWindow = nil;
|
||||
[previousKeyWindow makeKeyWindow];
|
||||
[[previousKeyWindow rootViewController] setNeedsStatusBarAppearanceUpdate];
|
||||
|
||||
// Restore the status bar window's normal window level.
|
||||
// We want it above FLEX while a modal is presented for scroll to top, but below FLEX otherwise for exploration.
|
||||
@@ -874,4 +809,108 @@ typedef NS_ENUM(NSUInteger, FLEXExplorerMode) {
|
||||
[self dismissViewControllerAnimated:animated completion:completion];
|
||||
}
|
||||
|
||||
- (BOOL)wantsWindowToBecomeKey
|
||||
{
|
||||
return self.previousKeyWindow != nil;
|
||||
}
|
||||
|
||||
#pragma mark - Keyboard Shortcut Helpers
|
||||
|
||||
- (void)toggleSelectTool
|
||||
{
|
||||
if (self.currentMode == FLEXExplorerModeSelect) {
|
||||
self.currentMode = FLEXExplorerModeDefault;
|
||||
} else {
|
||||
self.currentMode = FLEXExplorerModeSelect;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)toggleMoveTool
|
||||
{
|
||||
if (self.currentMode == FLEXExplorerModeMove) {
|
||||
self.currentMode = FLEXExplorerModeDefault;
|
||||
} else {
|
||||
self.currentMode = FLEXExplorerModeMove;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)toggleViewsTool
|
||||
{
|
||||
BOOL viewsModalShown = [[self presentedViewController] isKindOfClass:[UINavigationController class]];
|
||||
viewsModalShown = viewsModalShown && [[[(UINavigationController *)[self presentedViewController] viewControllers] firstObject] isKindOfClass:[FLEXHierarchyTableViewController class]];
|
||||
if (viewsModalShown) {
|
||||
[self resignKeyAndDismissViewControllerAnimated:YES completion:nil];
|
||||
} else {
|
||||
[self resignKeyAndDismissViewControllerAnimated:NO completion:nil];
|
||||
NSArray *allViews = [self allViewsInHierarchy];
|
||||
NSDictionary *depthsForViews = [self hierarchyDepthsForViews:allViews];
|
||||
FLEXHierarchyTableViewController *hierarchyTVC = [[FLEXHierarchyTableViewController alloc] initWithViews:allViews viewsAtTap:self.viewsAtTapPoint selectedView:self.selectedView depths:depthsForViews];
|
||||
hierarchyTVC.delegate = self;
|
||||
UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:hierarchyTVC];
|
||||
[self makeKeyAndPresentViewController:navigationController animated:YES completion:nil];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)toggleMenuTool
|
||||
{
|
||||
BOOL menuModalShown = [[self presentedViewController] isKindOfClass:[UINavigationController class]];
|
||||
menuModalShown = menuModalShown && [[[(UINavigationController *)[self presentedViewController] viewControllers] firstObject] isKindOfClass:[FLEXGlobalsTableViewController class]];
|
||||
if (menuModalShown) {
|
||||
[self resignKeyAndDismissViewControllerAnimated:YES completion:nil];
|
||||
} else {
|
||||
[self resignKeyAndDismissViewControllerAnimated:NO completion:nil];
|
||||
FLEXGlobalsTableViewController *globalsViewController = [[FLEXGlobalsTableViewController alloc] init];
|
||||
globalsViewController.delegate = self;
|
||||
[FLEXGlobalsTableViewController setApplicationWindow:[[UIApplication sharedApplication] keyWindow]];
|
||||
UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:globalsViewController];
|
||||
[self makeKeyAndPresentViewController:navigationController animated:YES completion:nil];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)handleDownArrowKeyPressed
|
||||
{
|
||||
if (self.currentMode == FLEXExplorerModeMove) {
|
||||
CGRect frame = self.selectedView.frame;
|
||||
frame.origin.y += 1.0 / [[UIScreen mainScreen] scale];
|
||||
self.selectedView.frame = frame;
|
||||
} else if (self.currentMode == FLEXExplorerModeSelect && [self.viewsAtTapPoint count] > 0) {
|
||||
NSInteger selectedViewIndex = [self.viewsAtTapPoint indexOfObject:self.selectedView];
|
||||
if (selectedViewIndex > 0) {
|
||||
self.selectedView = [self.viewsAtTapPoint objectAtIndex:selectedViewIndex - 1];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)handleUpArrowKeyPressed
|
||||
{
|
||||
if (self.currentMode == FLEXExplorerModeMove) {
|
||||
CGRect frame = self.selectedView.frame;
|
||||
frame.origin.y -= 1.0 / [[UIScreen mainScreen] scale];
|
||||
self.selectedView.frame = frame;
|
||||
} else if (self.currentMode == FLEXExplorerModeSelect && [self.viewsAtTapPoint count] > 0) {
|
||||
NSInteger selectedViewIndex = [self.viewsAtTapPoint indexOfObject:self.selectedView];
|
||||
if (selectedViewIndex < [self.viewsAtTapPoint count] - 1) {
|
||||
self.selectedView = [self.viewsAtTapPoint objectAtIndex:selectedViewIndex + 1];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)handleRightArrowKeyPressed
|
||||
{
|
||||
if (self.currentMode == FLEXExplorerModeMove) {
|
||||
CGRect frame = self.selectedView.frame;
|
||||
frame.origin.x += 1.0 / [[UIScreen mainScreen] scale];
|
||||
self.selectedView.frame = frame;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)handleLeftArrowKeyPressed
|
||||
{
|
||||
if (self.currentMode == FLEXExplorerModeMove) {
|
||||
CGRect frame = self.selectedView.frame;
|
||||
frame.origin.x -= 1.0 / [[UIScreen mainScreen] scale];
|
||||
self.selectedView.frame = frame;
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -7,6 +7,7 @@
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@interface FLEXManager : NSObject
|
||||
|
||||
@@ -17,15 +18,34 @@
|
||||
- (void)showExplorer;
|
||||
- (void)hideExplorer;
|
||||
|
||||
#pragma mark - Network Debugging
|
||||
|
||||
/// If this property is set to YES, FLEX will swizzle NSURLConnection*Delegate and NSURLSession*Delegate methods
|
||||
/// on classes that conform to the protocols. This allows you to view network activity history from the main FLEX menu.
|
||||
/// Full responses are kept temporarily in a size limited cache and may be pruged under memory pressure.
|
||||
@property (nonatomic, assign, getter=isNetworkDebuggingEnabled) BOOL networkDebuggingEnabled;
|
||||
|
||||
/// Defaults to 50 MB if never set. Values set here are presisted across launches of the app.
|
||||
/// Defaults to 25 MB if never set. Values set here are presisted across launches of the app.
|
||||
/// The response cache uses an NSCache, so it may purge prior to hitting the limit when the app is under memory pressure.
|
||||
@property (nonatomic, assign) NSUInteger networkResponseCacheByteLimit;
|
||||
|
||||
#pragma mark - Keyboard Shortcuts
|
||||
|
||||
/// Simulator keyboard shortcuts are enabled by default.
|
||||
/// The shortcuts will not fire when there is an active text field, text view, or other responder accepting key input.
|
||||
/// You can disable keyboard shortcuts if you have existing keyboard shortcuts that conflict with FLEX, or if you like doing things the hard way ;)
|
||||
/// Keyboard shortcuts are always disabled (and support is compiled out) in non-simulator builds
|
||||
@property (nonatomic, assign) BOOL simulatorShortcutsEnabled;
|
||||
|
||||
/// Adds an action to run when the specified key & modifier combination is pressed
|
||||
/// @param key A single character string matching a key on the keyboard
|
||||
/// @param modifiers Modifier keys such as shift, command, or alt/option
|
||||
/// @param action The block to run on the main thread when the key & modifier combination is recognized.
|
||||
/// @param description Shown the the keyboard shortcut help menu, which is accessed via the '?' key.
|
||||
/// @note The action block will be retained for the duration of the application. You may want to use weak references.
|
||||
/// @note FLEX registers several default keyboard shortcuts. Use the '?' key to see a list of shortcuts.
|
||||
- (void)registerSimulatorShortcutWithKey:(NSString *)key modifiers:(UIKeyModifierFlags)modifiers action:(dispatch_block_t)action description:(NSString *)description;
|
||||
|
||||
#pragma mark - Extensions
|
||||
|
||||
/// Adds an entry at the bottom of the list of Global State items. Call this method before this view controller is displayed.
|
||||
@@ -0,0 +1,356 @@
|
||||
//
|
||||
// FLEXManager.m
|
||||
// Flipboard
|
||||
//
|
||||
// Created by Ryan Olson on 4/4/14.
|
||||
// Copyright (c) 2014 Flipboard. All rights reserved.
|
||||
//
|
||||
|
||||
#import "FLEXManager.h"
|
||||
#import "FLEXExplorerViewController.h"
|
||||
#import "FLEXWindow.h"
|
||||
#import "FLEXGlobalsTableViewControllerEntry.h"
|
||||
#import "FLEXObjectExplorerFactory.h"
|
||||
#import "FLEXObjectExplorerViewController.h"
|
||||
#import "FLEXNetworkObserver.h"
|
||||
#import "FLEXNetworkRecorder.h"
|
||||
#import "FLEXKeyboardShortcutManager.h"
|
||||
#import "FLEXFileBrowserTableViewController.h"
|
||||
#import "FLEXNetworkHistoryTableViewController.h"
|
||||
#import "FLEXKeyboardHelpViewController.h"
|
||||
|
||||
@interface FLEXManager () <FLEXWindowEventDelegate, FLEXExplorerViewControllerDelegate>
|
||||
|
||||
@property (nonatomic, strong) FLEXWindow *explorerWindow;
|
||||
@property (nonatomic, strong) FLEXExplorerViewController *explorerViewController;
|
||||
|
||||
@property (nonatomic, readonly, strong) NSMutableArray *userGlobalEntries;
|
||||
|
||||
@end
|
||||
|
||||
@implementation FLEXManager
|
||||
|
||||
+ (instancetype)sharedManager
|
||||
{
|
||||
static FLEXManager *sharedManager = nil;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
sharedManager = [[[self class] alloc] init];
|
||||
});
|
||||
return sharedManager;
|
||||
}
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_userGlobalEntries = [[NSMutableArray alloc] init];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (FLEXWindow *)explorerWindow
|
||||
{
|
||||
NSAssert([NSThread isMainThread], @"You must use %@ from the main thread only.", NSStringFromClass([self class]));
|
||||
|
||||
if (!_explorerWindow) {
|
||||
_explorerWindow = [[FLEXWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
|
||||
_explorerWindow.eventDelegate = self;
|
||||
_explorerWindow.rootViewController = self.explorerViewController;
|
||||
}
|
||||
|
||||
return _explorerWindow;
|
||||
}
|
||||
|
||||
- (FLEXExplorerViewController *)explorerViewController
|
||||
{
|
||||
if (!_explorerViewController) {
|
||||
_explorerViewController = [[FLEXExplorerViewController alloc] init];
|
||||
_explorerViewController.delegate = self;
|
||||
}
|
||||
|
||||
return _explorerViewController;
|
||||
}
|
||||
|
||||
- (void)showExplorer
|
||||
{
|
||||
self.explorerWindow.hidden = NO;
|
||||
}
|
||||
|
||||
- (void)hideExplorer
|
||||
{
|
||||
self.explorerWindow.hidden = YES;
|
||||
}
|
||||
|
||||
- (BOOL)isHidden
|
||||
{
|
||||
return self.explorerWindow.isHidden;
|
||||
}
|
||||
|
||||
- (BOOL)isNetworkDebuggingEnabled
|
||||
{
|
||||
return [FLEXNetworkObserver isEnabled];
|
||||
}
|
||||
|
||||
- (void)setNetworkDebuggingEnabled:(BOOL)networkDebuggingEnabled
|
||||
{
|
||||
[FLEXNetworkObserver setEnabled:networkDebuggingEnabled];
|
||||
}
|
||||
|
||||
- (NSUInteger)networkResponseCacheByteLimit
|
||||
{
|
||||
return [[FLEXNetworkRecorder defaultRecorder] responseCacheByteLimit];
|
||||
}
|
||||
|
||||
- (void)setNetworkResponseCacheByteLimit:(NSUInteger)networkResponseCacheByteLimit
|
||||
{
|
||||
[[FLEXNetworkRecorder defaultRecorder] setResponseCacheByteLimit:networkResponseCacheByteLimit];
|
||||
}
|
||||
|
||||
#pragma mark - FLEXWindowEventDelegate
|
||||
|
||||
- (BOOL)shouldHandleTouchAtPoint:(CGPoint)pointInWindow
|
||||
{
|
||||
// Ask the explorer view controller
|
||||
return [self.explorerViewController shouldReceiveTouchAtWindowPoint:pointInWindow];
|
||||
}
|
||||
|
||||
- (BOOL)canBecomeKeyWindow
|
||||
{
|
||||
// Only when the explorer view controller wants it because it needs to accept key input & affect the status bar.
|
||||
return [self.explorerViewController wantsWindowToBecomeKey];
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - FLEXExplorerViewControllerDelegate
|
||||
|
||||
- (void)explorerViewControllerDidFinish:(FLEXExplorerViewController *)explorerViewController
|
||||
{
|
||||
[self hideExplorer];
|
||||
}
|
||||
|
||||
#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];
|
||||
#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
|
||||
}
|
||||
|
||||
- (void)registerDefaultSimulatorShortcuts
|
||||
{
|
||||
[self registerSimulatorShortcutWithKey:@"f" modifiers:0 action:^{
|
||||
if ([self isHidden]) {
|
||||
[self showExplorer];
|
||||
} else {
|
||||
[self hideExplorer];
|
||||
}
|
||||
} description:@"Toggle FLEX toolbar"];
|
||||
|
||||
[self registerSimulatorShortcutWithKey:@"g" modifiers:0 action:^{
|
||||
[self showExplorerIfNeeded];
|
||||
[self.explorerViewController toggleMenuTool];
|
||||
} description:@"Toggle FLEX globlas menu"];
|
||||
|
||||
[self registerSimulatorShortcutWithKey:@"v" modifiers:0 action:^{
|
||||
[self showExplorerIfNeeded];
|
||||
[self.explorerViewController toggleViewsTool];
|
||||
} description:@"Toggle view hierarchy menu"];
|
||||
|
||||
[self registerSimulatorShortcutWithKey:@"s" modifiers:0 action:^{
|
||||
[self showExplorerIfNeeded];
|
||||
[self.explorerViewController toggleSelectTool];
|
||||
} description:@"Toggle select tool"];
|
||||
|
||||
[self registerSimulatorShortcutWithKey:@"m" modifiers:0 action:^{
|
||||
[self showExplorerIfNeeded];
|
||||
[self.explorerViewController toggleMoveTool];
|
||||
} description:@"Toggle move tool"];
|
||||
|
||||
[self registerSimulatorShortcutWithKey:@"n" modifiers:0 action:^{
|
||||
[self toggleTopViewControllerOfClass:[FLEXNetworkHistoryTableViewController class]];
|
||||
} description:@"Toggle network history view"];
|
||||
|
||||
[self registerSimulatorShortcutWithKey:UIKeyInputDownArrow modifiers:0 action:^{
|
||||
if ([self isHidden]) {
|
||||
[self tryScrollDown];
|
||||
} else {
|
||||
[self.explorerViewController handleDownArrowKeyPressed];
|
||||
}
|
||||
} description:@"Cycle view selection\n\t\tMove view down\n\t\tScroll down"];
|
||||
|
||||
[self registerSimulatorShortcutWithKey:UIKeyInputUpArrow modifiers:0 action:^{
|
||||
if ([self isHidden]) {
|
||||
[self tryScrollUp];
|
||||
} else {
|
||||
[self.explorerViewController handleUpArrowKeyPressed];
|
||||
}
|
||||
} description:@"Cycle view selection\n\t\tMove view up\n\t\tScroll up"];
|
||||
|
||||
[self registerSimulatorShortcutWithKey:UIKeyInputRightArrow modifiers:0 action:^{
|
||||
if (![self isHidden]) {
|
||||
[self.explorerViewController handleRightArrowKeyPressed];
|
||||
}
|
||||
} description:@"Move selected view right"];
|
||||
|
||||
[self registerSimulatorShortcutWithKey:UIKeyInputLeftArrow modifiers:0 action:^{
|
||||
if ([self isHidden]) {
|
||||
[self tryGoBack];
|
||||
} else {
|
||||
[self.explorerViewController handleLeftArrowKeyPressed];
|
||||
}
|
||||
} description:@"Move selected view left"];
|
||||
|
||||
[self registerSimulatorShortcutWithKey:@"?" modifiers:0 action:^{
|
||||
[self toggleTopViewControllerOfClass:[FLEXKeyboardHelpViewController class]];
|
||||
} description:@"Toggle (this) help menu"];
|
||||
|
||||
[self registerSimulatorShortcutWithKey:UIKeyInputEscape modifiers:0 action:^{
|
||||
[[[self topViewController] presentingViewController] dismissViewControllerAnimated:YES completion:nil];
|
||||
} description:@"End editing text\n\t\tDismiss top view controller"];
|
||||
|
||||
[self registerSimulatorShortcutWithKey:@"o" modifiers:UIKeyModifierCommand|UIKeyModifierShift action:^{
|
||||
[self toggleTopViewControllerOfClass:[FLEXFileBrowserTableViewController class]];
|
||||
} description:@"Toggle file browser menu"];
|
||||
}
|
||||
|
||||
+ (void)load
|
||||
{
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[[[self class] sharedManager] registerDefaultSimulatorShortcuts];
|
||||
});
|
||||
}
|
||||
|
||||
#pragma mark - Extensions
|
||||
|
||||
- (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;
|
||||
FLEXGlobalsTableViewControllerEntry *entry = [FLEXGlobalsTableViewControllerEntry 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;
|
||||
FLEXGlobalsTableViewControllerEntry *entry = [FLEXGlobalsTableViewControllerEntry 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)tryScrollDown
|
||||
{
|
||||
UIScrollView *firstScrollView = [self firstScrollView];
|
||||
CGPoint contentOffset = [firstScrollView contentOffset];
|
||||
CGFloat distance = floor(firstScrollView.frame.size.height / 2.0);
|
||||
CGFloat maxContentOffsetY = firstScrollView.contentSize.height + firstScrollView.contentInset.bottom - firstScrollView.frame.size.height;
|
||||
distance = MIN(maxContentOffsetY - firstScrollView.contentOffset.y, distance);
|
||||
contentOffset.y += distance;
|
||||
[firstScrollView setContentOffset:contentOffset animated:YES];
|
||||
}
|
||||
|
||||
- (void)tryScrollUp
|
||||
{
|
||||
UIScrollView *firstScrollView = [self firstScrollView];
|
||||
CGPoint contentOffset = [firstScrollView contentOffset];
|
||||
CGFloat distance = floor(firstScrollView.frame.size.height / 2.0);
|
||||
CGFloat minContentOffsetY = -firstScrollView.contentInset.top;
|
||||
distance = MIN(firstScrollView.contentOffset.y - minContentOffsetY, distance);
|
||||
contentOffset.y -= distance;
|
||||
[firstScrollView setContentOffset:contentOffset animated:YES];
|
||||
}
|
||||
|
||||
- (UIScrollView *)firstScrollView
|
||||
{
|
||||
NSMutableArray *views = [[[[UIApplication sharedApplication] keyWindow] 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
|
||||
{
|
||||
UIViewController *topViewController = [[[UIApplication sharedApplication] keyWindow] rootViewController];
|
||||
while ([topViewController presentedViewController]) {
|
||||
topViewController = [topViewController presentedViewController];
|
||||
}
|
||||
return topViewController;
|
||||
}
|
||||
|
||||
- (void)toggleTopViewControllerOfClass:(Class)class
|
||||
{
|
||||
UIViewController *topViewController = [self topViewController];
|
||||
if ([topViewController isKindOfClass:[UINavigationController class]] && [[[(UINavigationController *)topViewController viewControllers] firstObject] isKindOfClass:[class class]]) {
|
||||
[[topViewController presentingViewController] dismissViewControllerAnimated:YES completion:nil];
|
||||
} else {
|
||||
id viewController = [[class alloc] init];
|
||||
UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:viewController];
|
||||
[topViewController presentViewController:navigationController animated:YES completion:nil];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)showExplorerIfNeeded
|
||||
{
|
||||
if ([self isHidden]) {
|
||||
[self showExplorer];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -19,5 +19,6 @@
|
||||
@protocol FLEXWindowEventDelegate <NSObject>
|
||||
|
||||
- (BOOL)shouldHandleTouchAtPoint:(CGPoint)pointInWindow;
|
||||
- (BOOL)canBecomeKeyWindow;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,67 @@
|
||||
//
|
||||
// FLEXWindow.m
|
||||
// Flipboard
|
||||
//
|
||||
// Created by Ryan Olson on 4/13/14.
|
||||
// Copyright (c) 2014 Flipboard. All rights reserved.
|
||||
//
|
||||
|
||||
#import "FLEXWindow.h"
|
||||
#import <objc/runtime.h>
|
||||
|
||||
@implementation FLEXWindow
|
||||
|
||||
- (id)initWithFrame:(CGRect)frame
|
||||
{
|
||||
self = [super initWithFrame:frame];
|
||||
if (self) {
|
||||
self.backgroundColor = [UIColor clearColor];
|
||||
// Some apps have windows at UIWindowLevelStatusBar + n.
|
||||
// If we make the window level too high, we block out UIAlertViews.
|
||||
// There's a balance between staying above the app's windows and staying below alerts.
|
||||
// UIWindowLevelStatusBar + 100 seems to hit that balance.
|
||||
self.windowLevel = UIWindowLevelStatusBar + 100.0;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
|
||||
{
|
||||
BOOL pointInside = NO;
|
||||
if ([self.eventDelegate shouldHandleTouchAtPoint:point]) {
|
||||
pointInside = [super pointInside:point withEvent:event];
|
||||
}
|
||||
return pointInside;
|
||||
}
|
||||
|
||||
- (BOOL)shouldAffectStatusBarAppearance
|
||||
{
|
||||
return [self isKeyWindow];
|
||||
}
|
||||
|
||||
- (BOOL)canBecomeKeyWindow
|
||||
{
|
||||
return [self.eventDelegate canBecomeKeyWindow];
|
||||
}
|
||||
|
||||
+ (void)initialize
|
||||
{
|
||||
// This adds a method (superclass override) at runtime which gives us the status bar behavior we want.
|
||||
// The FLEX window is intended to be an overlay that generally doesn't affect the app underneath.
|
||||
// Most of the time, we want the app's main window(s) to be in control of status bar behavior.
|
||||
// Done at runtime with an obfuscated selector because it is private API. But you shoudn't ship this to the App Store anyways...
|
||||
NSString *canAffectSelectorString = [@[@"_can", @"Affect", @"Status", @"Bar", @"Appearance"] componentsJoinedByString:@""];
|
||||
SEL canAffectSelector = NSSelectorFromString(canAffectSelectorString);
|
||||
Method shouldAffectMethod = class_getInstanceMethod(self, @selector(shouldAffectStatusBarAppearance));
|
||||
IMP canAffectImplementation = method_getImplementation(shouldAffectMethod);
|
||||
class_addMethod(self, canAffectSelector, canAffectImplementation, method_getTypeEncoding(shouldAffectMethod));
|
||||
|
||||
// One more...
|
||||
NSString *canBecomeKeySelectorString = [NSString stringWithFormat:@"_%@", NSStringFromSelector(@selector(canBecomeKeyWindow))];
|
||||
SEL canBecomeKeySelector = NSSelectorFromString(canBecomeKeySelectorString);
|
||||
Method canBecomeKeyMethod = class_getInstanceMethod(self, @selector(canBecomeKeyWindow));
|
||||
IMP canBecomeKeyImplementation = method_getImplementation(canBecomeKeyMethod);
|
||||
class_addMethod(self, canBecomeKeySelector, canBecomeKeyImplementation, method_getTypeEncoding(canBecomeKeyMethod));
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,17 @@
|
||||
//
|
||||
// FLEX.h
|
||||
// FLEX
|
||||
//
|
||||
// Created by Eric Horacek on 7/18/15.
|
||||
// Copyright (c) 2015 Flipboard. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
//! Project version number for FLEX.
|
||||
FOUNDATION_EXPORT double FLEXVersionNumber;
|
||||
|
||||
//! Project version string for FLEX.
|
||||
FOUNDATION_EXPORT const unsigned char FLEXVersionString[];
|
||||
|
||||
#import <FLEX/FLEXManager.h>
|
||||
+5
@@ -7,11 +7,14 @@
|
||||
//
|
||||
|
||||
#import "FLEXFileBrowserFileOperationController.h"
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@interface FLEXFileBrowserFileDeleteOperationController () <UIAlertViewDelegate>
|
||||
|
||||
@property (nonatomic, copy, readonly) NSString *path;
|
||||
|
||||
- (instancetype)initWithPath:(NSString *)path NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
@end
|
||||
|
||||
@implementation FLEXFileBrowserFileDeleteOperationController
|
||||
@@ -73,6 +76,8 @@
|
||||
|
||||
@property (nonatomic, copy, readonly) NSString *path;
|
||||
|
||||
- (instancetype)initWithPath:(NSString *)path NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
@end
|
||||
|
||||
@implementation FLEXFileBrowserFileRenameOperationController
|
||||
+4
-4
@@ -316,7 +316,7 @@
|
||||
NSString *subpath = [self.childPaths objectAtIndex:indexPath.row];
|
||||
fullPath = [self.path stringByAppendingPathComponent:subpath];
|
||||
} else {
|
||||
indexPath = [self.searchDisplayController.searchResultsTableView indexPathForCell:sender];
|
||||
indexPath = [self.searchController.searchResultsTableView indexPathForCell:sender];
|
||||
fullPath = [self.searchPaths objectAtIndex:indexPath.row];
|
||||
}
|
||||
|
||||
@@ -334,7 +334,7 @@
|
||||
NSString *subpath = [self.childPaths objectAtIndex:indexPath.row];
|
||||
fullPath = [self.path stringByAppendingPathComponent:subpath];
|
||||
} else {
|
||||
indexPath = [self.searchDisplayController.searchResultsTableView indexPathForCell:sender];
|
||||
indexPath = [self.searchController.searchResultsTableView indexPathForCell:sender];
|
||||
fullPath = [self.searchPaths objectAtIndex:indexPath.row];
|
||||
}
|
||||
|
||||
@@ -345,9 +345,9 @@
|
||||
|
||||
- (void)reloadDisplayedPaths
|
||||
{
|
||||
if (self.searchDisplayController.isActive) {
|
||||
if (self.searchController.isActive) {
|
||||
[self reloadSearchPaths];
|
||||
[self.searchDisplayController.searchResultsTableView reloadData];
|
||||
[self.searchController.searchResultsTableView reloadData];
|
||||
} else {
|
||||
[self reloadChildPaths];
|
||||
[self.tableView reloadData];
|
||||
@@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>en</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>$(PRODUCT_NAME)</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>FMWK</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||
<key>NSPrincipalClass</key>
|
||||
<string></string>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -62,7 +62,10 @@
|
||||
|
||||
UISearchBar *searchBar = [[UISearchBar alloc] init];
|
||||
[searchBar sizeToFit];
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
||||
self.searchController = [[UISearchDisplayController alloc] initWithSearchBar:searchBar contentsController:self];
|
||||
#pragma clang diagnostic pop
|
||||
self.searchController.delegate = self;
|
||||
self.searchController.searchResultsDataSource = self;
|
||||
self.searchController.searchResultsDelegate = self;
|
||||
@@ -229,10 +232,10 @@
|
||||
for (UITableView *tableView in tableViews) {
|
||||
for (FLEXNetworkTransactionTableViewCell *cell in [tableView visibleCells]) {
|
||||
if ([cell.transaction isEqual:transaction]) {
|
||||
NSIndexPath *indexPath = [tableView indexPathForCell:cell];
|
||||
if (indexPath) {
|
||||
[tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone];
|
||||
}
|
||||
// Using -[UITableView reloadRowsAtIndexPaths:withRowAnimation:] is overkill here and kicks off a lot of
|
||||
// work that can make the table view somewhat unresponseive when lots of updates are streaming in.
|
||||
// We just need to tell the cell that it needs to re-layout.
|
||||
[cell setNeedsLayout];
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -309,6 +312,27 @@
|
||||
[self.navigationController pushViewController:detailViewController animated:YES];
|
||||
}
|
||||
|
||||
#pragma mark - Menu Actions
|
||||
|
||||
- (BOOL)tableView:(UITableView *)tableView shouldShowMenuForRowAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)tableView:(UITableView *)tableView canPerformAction:(SEL)action forRowAtIndexPath:(NSIndexPath *)indexPath withSender:(id)sender
|
||||
{
|
||||
return action == @selector(copy:);
|
||||
}
|
||||
|
||||
- (void)tableView:(UITableView *)tableView performAction:(SEL)action forRowAtIndexPath:(NSIndexPath *)indexPath withSender:(id)sender
|
||||
{
|
||||
if (action == @selector(copy:)) {
|
||||
FLEXNetworkTransaction *transaction = [self transactionAtIndexPath:indexPath inTableView:tableView];
|
||||
NSString *requestURLString = transaction.request.URL.absoluteString ?: @"";
|
||||
[[UIPasteboard generalPasteboard] setString:requestURLString];
|
||||
}
|
||||
}
|
||||
|
||||
- (FLEXNetworkTransaction *)transactionAtIndexPath:(NSIndexPath *)indexPath inTableView:(UITableView *)tableView
|
||||
{
|
||||
FLEXNetworkTransaction *transaction = nil;
|
||||
@@ -337,9 +361,9 @@
|
||||
return [[transaction.request.URL absoluteString] rangeOfString:searchString options:NSCaseInsensitiveSearch].length > 0;
|
||||
}]];
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
if ([self.searchDisplayController.searchBar.text isEqual:searchString]) {
|
||||
if ([self.searchController.searchBar.text isEqual:searchString]) {
|
||||
self.filteredNetworkTransactions = filteredNetworkTransactions;
|
||||
[self.searchDisplayController.searchResultsTableView reloadData];
|
||||
[self.searchController.searchResultsTableView reloadData];
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -103,6 +103,8 @@ NSString *const kFLEXNetworkRecorderResponseCacheLimitDefaultsKey = @"com.flex.r
|
||||
|
||||
- (void)recordRequestWillBeSentWithRequestID:(NSString *)requestID request:(NSURLRequest *)request redirectResponse:(NSURLResponse *)redirectResponse
|
||||
{
|
||||
NSDate *startDate = [NSDate date];
|
||||
|
||||
if (redirectResponse) {
|
||||
[self recordResponseReceivedWithRequestID:requestID response:redirectResponse];
|
||||
[self recordLoadingFinishedWithRequestID:requestID responseBody:nil];
|
||||
@@ -112,7 +114,7 @@ NSString *const kFLEXNetworkRecorderResponseCacheLimitDefaultsKey = @"com.flex.r
|
||||
FLEXNetworkTransaction *transaction = [[FLEXNetworkTransaction alloc] init];
|
||||
transaction.requestID = requestID;
|
||||
transaction.request = request;
|
||||
transaction.startTime = [NSDate date];
|
||||
transaction.startTime = startDate;
|
||||
|
||||
[self.orderedTransactions insertObject:transaction atIndex:0];
|
||||
[self.networkTransactionsForRequestIdentifiers setObject:transaction forKey:requestID];
|
||||
@@ -124,6 +126,8 @@ NSString *const kFLEXNetworkRecorderResponseCacheLimitDefaultsKey = @"com.flex.r
|
||||
|
||||
- (void)recordResponseReceivedWithRequestID:(NSString *)requestID response:(NSURLResponse *)response
|
||||
{
|
||||
NSDate *responseDate = [NSDate date];
|
||||
|
||||
dispatch_async(self.queue, ^{
|
||||
FLEXNetworkTransaction *transaction = [self.networkTransactionsForRequestIdentifiers objectForKey:requestID];
|
||||
if (!transaction) {
|
||||
@@ -131,7 +135,7 @@ NSString *const kFLEXNetworkRecorderResponseCacheLimitDefaultsKey = @"com.flex.r
|
||||
}
|
||||
transaction.response = response;
|
||||
transaction.transactionState = FLEXNetworkTransactionStateReceivingData;
|
||||
transaction.latency = -[transaction.startTime timeIntervalSinceNow];
|
||||
transaction.latency = -[transaction.startTime timeIntervalSinceDate:responseDate];
|
||||
|
||||
[self postUpdateNotificationForTransaction:transaction];
|
||||
});
|
||||
@@ -152,13 +156,15 @@ NSString *const kFLEXNetworkRecorderResponseCacheLimitDefaultsKey = @"com.flex.r
|
||||
|
||||
- (void)recordLoadingFinishedWithRequestID:(NSString *)requestID responseBody:(NSData *)responseBody
|
||||
{
|
||||
NSDate *finishedDate = [NSDate date];
|
||||
|
||||
dispatch_async(self.queue, ^{
|
||||
FLEXNetworkTransaction *transaction = [self.networkTransactionsForRequestIdentifiers objectForKey:requestID];
|
||||
if (!transaction) {
|
||||
return;
|
||||
}
|
||||
transaction.transactionState = FLEXNetworkTransactionStateFinished;
|
||||
transaction.duration = -[transaction.startTime timeIntervalSinceNow];
|
||||
transaction.duration = -[transaction.startTime timeIntervalSinceDate:finishedDate];
|
||||
|
||||
BOOL shouldCache = [responseBody length] > 0;
|
||||
if (!self.shouldCacheMediaResponses) {
|
||||
@@ -173,7 +179,7 @@ NSString *const kFLEXNetworkRecorderResponseCacheLimitDefaultsKey = @"com.flex.r
|
||||
}
|
||||
|
||||
NSString *mimeType = transaction.response.MIMEType;
|
||||
if ([mimeType hasPrefix:@"image/"]) {
|
||||
if ([mimeType hasPrefix:@"image/"] && [responseBody length] > 0) {
|
||||
// Thumbnail image previews on a separate background queue
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||
NSInteger maxPixelDimension = [[UIScreen mainScreen] scale] * 32.0;
|
||||
|
||||
@@ -39,9 +39,6 @@
|
||||
UITableViewCell *networkDebuggingCell = [self switchCellWithTitle:@"Network Debugging" toggleAction:@selector(networkDebuggingToggled:) isOn:[FLEXNetworkObserver isEnabled]];
|
||||
[mutableCells addObject:networkDebuggingCell];
|
||||
|
||||
UITableViewCell *enableOnLaunchCell = [self switchCellWithTitle:@"Enable on Launch" toggleAction:@selector(enableOnLaunchToggled:) isOn:[FLEXNetworkObserver shouldEnableOnLaunch]];
|
||||
[mutableCells addObject:enableOnLaunchCell];
|
||||
|
||||
UITableViewCell *cacheMediaResponsesCell = [self switchCellWithTitle:@"Cache Media Responses" toggleAction:@selector(cacheMediaResponsesToggled:) isOn:NO];
|
||||
[mutableCells addObject:cacheMediaResponsesCell];
|
||||
|
||||
@@ -64,11 +61,6 @@
|
||||
[FLEXNetworkObserver setEnabled:sender.isOn];
|
||||
}
|
||||
|
||||
- (void)enableOnLaunchToggled:(UISwitch *)sender
|
||||
{
|
||||
[FLEXNetworkObserver setShouldEnableOnLaunch:sender.isOn];
|
||||
}
|
||||
|
||||
- (void)cacheMediaResponsesToggled:(UISwitch *)sender
|
||||
{
|
||||
[[FLEXNetworkRecorder defaultRecorder] setShouldCacheMediaResponses:sender.isOn];
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "UIKit/UIKit.h"
|
||||
|
||||
typedef NS_ENUM(NSInteger, FLEXNetworkTransactionState) {
|
||||
FLEXNetworkTransactionStateUnstarted,
|
||||
|
||||
@@ -53,6 +53,7 @@ typedef UIViewController *(^FLEXNetworkDetailRowSelectionFuture)(void);
|
||||
self = [super initWithStyle:UITableViewStyleGrouped];
|
||||
if (self) {
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleTransactionUpdatedNotification:) name:kFLEXNetworkRecorderTransactionUpdatedNotification object:nil];
|
||||
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"Copy" style:UIBarButtonItemStylePlain target:self action:@selector(copyButtonPressed:)];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
@@ -117,6 +118,30 @@ typedef UIViewController *(^FLEXNetworkDetailRowSelectionFuture)(void);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)copyButtonPressed:(id)sender
|
||||
{
|
||||
NSMutableString *requestDetailString = [NSMutableString string];
|
||||
|
||||
for (FLEXNetworkDetailSection *section in self.sections) {
|
||||
if ([section.rows count] > 0) {
|
||||
if ([section.title length] > 0) {
|
||||
[requestDetailString appendString:section.title];
|
||||
[requestDetailString appendString:@"\n\n"];
|
||||
}
|
||||
for (FLEXNetworkDetailRow *row in section.rows) {
|
||||
NSString *rowDescription = [[[self class] attributedTextForRow:row] string];
|
||||
if ([rowDescription length] > 0) {
|
||||
[requestDetailString appendString:rowDescription];
|
||||
[requestDetailString appendString:@"\n"];
|
||||
}
|
||||
}
|
||||
[requestDetailString appendString:@"\n\n"];
|
||||
}
|
||||
}
|
||||
|
||||
[[UIPasteboard generalPasteboard] setString:requestDetailString];
|
||||
}
|
||||
|
||||
#pragma mark - Table view data source
|
||||
|
||||
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
|
||||
@@ -179,6 +204,26 @@ typedef UIViewController *(^FLEXNetworkDetailRowSelectionFuture)(void);
|
||||
return [sectionModel.rows objectAtIndex:indexPath.row];
|
||||
}
|
||||
|
||||
#pragma mark - Cell Copying
|
||||
|
||||
- (BOOL)tableView:(UITableView *)tableView shouldShowMenuForRowAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)tableView:(UITableView *)tableView canPerformAction:(SEL)action forRowAtIndexPath:(NSIndexPath *)indexPath withSender:(id)sender
|
||||
{
|
||||
return action == @selector(copy:);
|
||||
}
|
||||
|
||||
- (void)tableView:(UITableView *)tableView performAction:(SEL)action forRowAtIndexPath:(NSIndexPath *)indexPath withSender:(id)sender
|
||||
{
|
||||
if (action == @selector(copy:)) {
|
||||
FLEXNetworkDetailRow *row = [self rowModelAtIndexPath:indexPath];
|
||||
[[UIPasteboard generalPasteboard] setString:row.detailText];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - View Configuration
|
||||
|
||||
+ (NSAttributedString *)attributedTextForRow:(FLEXNetworkDetailRow *)row
|
||||
@@ -400,7 +445,7 @@ typedef UIViewController *(^FLEXNetworkDetailRowSelectionFuture)(void);
|
||||
{
|
||||
// FIXME (RKO): Don't rely on UTF8 string encoding
|
||||
UIViewController *detailViewController = nil;
|
||||
if ([mimeType isEqual:@"application/json"] || [mimeType isEqual:@"application/javascript"] || [mimeType isEqual:@"text/javascript"]) {
|
||||
if ([FLEXUtility isValidJSONData:data]) {
|
||||
NSString *prettyJSON = [FLEXUtility prettyJSONStringFromData:data];
|
||||
if ([prettyJSON length] > 0) {
|
||||
detailViewController = [[FLEXWebViewController alloc] initWithText:prettyJSON];
|
||||
|
||||
@@ -12,6 +12,8 @@
|
||||
// which Square, Inc. licenses this file to you.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
extern NSString *const kFLEXNetworkObserverEnabledStateChangedNotification;
|
||||
|
||||
/// This class swizzles NSURLConnection and NSURLSession delegate methods to observe events in the URL loading system.
|
||||
@@ -20,12 +22,8 @@ extern NSString *const kFLEXNetworkObserverEnabledStateChangedNotification;
|
||||
|
||||
/// Swizzling occurs when the observer is enabled for the first time.
|
||||
/// This reduces the impact of FLEX if network debugging is not desired.
|
||||
/// NOTE: this setting persists between launches of the app.
|
||||
+ (void)setEnabled:(BOOL)enabled;
|
||||
+ (BOOL)isEnabled;
|
||||
|
||||
/// The enable on launch setting is persisted accross launches of the app.
|
||||
/// If YES, the observer will automatically enable itself early in the application lifecycle.
|
||||
+ (void)setShouldEnableOnLaunch:(BOOL)shouldEnableOnLaunch;
|
||||
+ (BOOL)shouldEnableOnLaunch;
|
||||
|
||||
@end
|
||||
|
||||
@@ -14,13 +14,14 @@
|
||||
|
||||
#import "FLEXNetworkObserver.h"
|
||||
#import "FLEXNetworkRecorder.h"
|
||||
#import "FLEXUtility.h"
|
||||
|
||||
#import <objc/runtime.h>
|
||||
#import <objc/message.h>
|
||||
#import <dispatch/queue.h>
|
||||
|
||||
NSString *const kFLEXNetworkObserverEnabledStateChangedNotification = @"kFLEXNetworkObserverEnabledStateChangedNotification";
|
||||
static NSString *const kFLEXNetworkObserverEnableOnLaunchDefaultsKey = @"com.flex.FLEXNetworkObserver.enableOnLaunch";
|
||||
static NSString *const kFLEXNetworkObserverEnabledDefaultsKey = @"com.flex.FLEXNetworkObserver.enableOnLaunch";
|
||||
|
||||
typedef void (^NSURLSessionAsyncCompletion)(id fileURLOrData, NSURLResponse *response, NSError *error);
|
||||
|
||||
@@ -67,7 +68,6 @@ didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask delegate:(id <NSU
|
||||
|
||||
@interface FLEXNetworkObserver ()
|
||||
|
||||
@property (nonatomic, assign, getter=isEnabled) BOOL enabled;
|
||||
@property (nonatomic, strong) NSMutableDictionary *requestStatesForRequestIDs;
|
||||
@property (nonatomic, strong) dispatch_queue_t queue;
|
||||
|
||||
@@ -79,43 +79,32 @@ didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask delegate:(id <NSU
|
||||
|
||||
+ (void)setEnabled:(BOOL)enabled
|
||||
{
|
||||
BOOL previouslyEnabled = [self isEnabled];
|
||||
|
||||
[[NSUserDefaults standardUserDefaults] setBool:enabled forKey:kFLEXNetworkObserverEnabledDefaultsKey];
|
||||
|
||||
if (enabled) {
|
||||
// Inject if needed. This injection is protected with a dispatch_once, so we're ok calling it multiple times.
|
||||
// By doing the injection lazily, we keep the impact of the tool lower when this feature isn't enabled.
|
||||
[self injectIntoAllNSURLConnectionDelegateClasses];
|
||||
}
|
||||
[[self sharedObserver] setEnabled:enabled];
|
||||
}
|
||||
|
||||
+ (BOOL)isEnabled
|
||||
{
|
||||
return [[self sharedObserver] isEnabled];
|
||||
}
|
||||
|
||||
- (void)setEnabled:(BOOL)enabled
|
||||
{
|
||||
if (_enabled != enabled) {
|
||||
_enabled = enabled;
|
||||
|
||||
if (previouslyEnabled != enabled) {
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:kFLEXNetworkObserverEnabledStateChangedNotification object:self];
|
||||
}
|
||||
}
|
||||
|
||||
+ (void)setShouldEnableOnLaunch:(BOOL)shouldEnableOnLaunch
|
||||
+ (BOOL)isEnabled
|
||||
{
|
||||
[[NSUserDefaults standardUserDefaults] setObject:@YES forKey:kFLEXNetworkObserverEnableOnLaunchDefaultsKey];
|
||||
}
|
||||
|
||||
+ (BOOL)shouldEnableOnLaunch
|
||||
{
|
||||
return [[[NSUserDefaults standardUserDefaults] objectForKey:kFLEXNetworkObserverEnableOnLaunchDefaultsKey] boolValue];
|
||||
return [[[NSUserDefaults standardUserDefaults] objectForKey:kFLEXNetworkObserverEnabledDefaultsKey] boolValue];
|
||||
}
|
||||
|
||||
+ (void)load
|
||||
{
|
||||
// We don't want to do the swizzling from +load because not all the classes may be loaded at this point.
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
if ([self shouldEnableOnLaunch]) {
|
||||
[self setEnabled:YES];
|
||||
if ([self isEnabled]) {
|
||||
[self injectIntoAllNSURLConnectionDelegateClasses];
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -139,16 +128,19 @@ didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask delegate:(id <NSU
|
||||
|
||||
#pragma mark Delegate Injection Convenience Methods
|
||||
|
||||
+ (SEL)swizzledSelectorForSelector:(SEL)selector
|
||||
{
|
||||
return NSSelectorFromString([NSString stringWithFormat:@"_flex_swizzle_%x_%@", arc4random(), NSStringFromSelector(selector)]);
|
||||
}
|
||||
|
||||
/// All swizzled delegate methods should make use of this guard.
|
||||
/// This will prevent duplicated sniffing when the original implementation calls up to a superclass implementation which we've also swizzled.
|
||||
/// The superclass implementation (and implementations in classes above that) will be executed without inteference if called from the original implementation.
|
||||
+ (void)sniffWithoutDuplicationForObject:(NSObject *)object selector:(SEL)selector sniffingBlock:(void (^)(void))sniffingBlock originalImplementationBlock:(void (^)(void))originalImplementationBlock
|
||||
{
|
||||
// If we don't have an object to detect nested calls on, just run the original implmentation and bail.
|
||||
// This case can happen if someone besides the URL loading system calls the delegate methods directly.
|
||||
// See https://github.com/Flipboard/FLEX/issues/61 for an example.
|
||||
if (!object) {
|
||||
originalImplementationBlock();
|
||||
return;
|
||||
}
|
||||
|
||||
const void *key = selector;
|
||||
|
||||
// Don't run the sniffing block if we're inside a nested call
|
||||
@@ -162,66 +154,6 @@ didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask delegate:(id <NSU
|
||||
objc_setAssociatedObject(object, key, nil, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
|
||||
}
|
||||
|
||||
+ (BOOL)instanceRespondsButDoesNotImplementSelector:(SEL)selector class:(Class)cls
|
||||
{
|
||||
if ([cls instancesRespondToSelector:selector]) {
|
||||
unsigned int numMethods = 0;
|
||||
Method *methods = class_copyMethodList(cls, &numMethods);
|
||||
|
||||
BOOL implementsSelector = NO;
|
||||
for (int index = 0; index < numMethods; index++) {
|
||||
SEL methodSelector = method_getName(methods[index]);
|
||||
if (selector == methodSelector) {
|
||||
implementsSelector = YES;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
free(methods);
|
||||
|
||||
if (!implementsSelector) {
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
+ (void)replaceImplementationOfKnownSelector:(SEL)originalSelector onClass:(Class)class withBlock:(id)block swizzledSelector:(SEL)swizzledSelector
|
||||
{
|
||||
// This method is only intended for swizzling methods that are know to exist on the class.
|
||||
// Bail if that isn't the case.
|
||||
Method originalMethod = class_getInstanceMethod(class, originalSelector);
|
||||
if (!originalMethod) {
|
||||
return;
|
||||
}
|
||||
|
||||
IMP implementation = imp_implementationWithBlock(block);
|
||||
class_addMethod(class, swizzledSelector, implementation, method_getTypeEncoding(originalMethod));
|
||||
Method newMethod = class_getInstanceMethod(class, swizzledSelector);
|
||||
method_exchangeImplementations(originalMethod, newMethod);
|
||||
}
|
||||
|
||||
+ (void)replaceImplementationOfSelector:(SEL)selector withSelector:(SEL)swizzledSelector forClass:(Class)cls withMethodDescription:(struct objc_method_description)methodDescription implementationBlock:(id)implementationBlock undefinedBlock:(id)undefinedBlock
|
||||
{
|
||||
if ([self instanceRespondsButDoesNotImplementSelector:selector class:cls]) {
|
||||
return;
|
||||
}
|
||||
|
||||
IMP implementation = imp_implementationWithBlock((id)([cls instancesRespondToSelector:selector] ? implementationBlock : undefinedBlock));
|
||||
|
||||
Method oldMethod = class_getInstanceMethod(cls, selector);
|
||||
if (oldMethod) {
|
||||
class_addMethod(cls, swizzledSelector, implementation, methodDescription.types);
|
||||
|
||||
Method newMethod = class_getInstanceMethod(cls, swizzledSelector);
|
||||
|
||||
method_exchangeImplementations(oldMethod, newMethod);
|
||||
} else {
|
||||
class_addMethod(cls, selector, implementation, methodDescription.types);
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Delegate Injection
|
||||
|
||||
+ (void)injectIntoAllNSURLConnectionDelegateClasses
|
||||
@@ -325,7 +257,7 @@ didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask delegate:(id <NSU
|
||||
dispatch_once(&onceToken, ^{
|
||||
Class class = [NSURLConnection class];
|
||||
SEL selector = @selector(cancel);
|
||||
SEL swizzledSelector = [self swizzledSelectorForSelector:selector];
|
||||
SEL swizzledSelector = [FLEXUtility swizzledSelectorForSelector:selector];
|
||||
Method originalCancel = class_getInstanceMethod(class, selector);
|
||||
|
||||
void (^swizzleBlock)(NSURLConnection *) = ^(NSURLConnection *slf) {
|
||||
@@ -344,14 +276,19 @@ didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask delegate:(id <NSU
|
||||
{
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
Class class = [NSURLSessionTask class];
|
||||
SEL selector = @selector(resume);
|
||||
SEL swizzledSelector = [self swizzledSelectorForSelector:selector];
|
||||
|
||||
if ([self instanceRespondsButDoesNotImplementSelector:selector class:class]) {
|
||||
// Dummy NSURLSessionTask to get the actual class, needed for iOS 7 (__NSCFURLSessionTask)
|
||||
class = [[[NSURLSession sharedSession] dataTaskWithURL:[NSURL URLWithString:@"about:blank"]] superclass];
|
||||
// In iOS 7 resume lives in __NSCFLocalSessionTask
|
||||
// In iOS 8 resume lives in NSURLSessionTask
|
||||
// In iOS 9 resume lives in __NSCFURLSessionTask
|
||||
Class class = Nil;
|
||||
if (![[NSProcessInfo processInfo] respondsToSelector:@selector(operatingSystemVersion)]) {
|
||||
class = NSClassFromString([@[@"__", @"NSC", @"FLocalS", @"ession", @"Task"] componentsJoinedByString:@""]);
|
||||
} else if ([[NSProcessInfo processInfo] operatingSystemVersion].majorVersion < 9) {
|
||||
class = [NSURLSessionTask class];
|
||||
} else {
|
||||
class = NSClassFromString([@[@"__", @"NSC", @"FURLS", @"ession", @"Task"] componentsJoinedByString:@""]);
|
||||
}
|
||||
SEL selector = @selector(resume);
|
||||
SEL swizzledSelector = [FLEXUtility swizzledSelectorForSelector:selector];
|
||||
|
||||
Method originalResume = class_getInstanceMethod(class, selector);
|
||||
|
||||
@@ -373,7 +310,7 @@ didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask delegate:(id <NSU
|
||||
dispatch_once(&onceToken, ^{
|
||||
Class class = objc_getMetaClass(class_getName([NSURLConnection class]));
|
||||
SEL selector = @selector(sendAsynchronousRequest:queue:completionHandler:);
|
||||
SEL swizzledSelector = [self swizzledSelectorForSelector:selector];
|
||||
SEL swizzledSelector = [FLEXUtility swizzledSelectorForSelector:selector];
|
||||
|
||||
typedef void (^NSURLConnectionAsyncCompletion)(NSURLResponse* response, NSData* data, NSError* connectionError);
|
||||
|
||||
@@ -403,7 +340,7 @@ didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask delegate:(id <NSU
|
||||
}
|
||||
};
|
||||
|
||||
[self replaceImplementationOfKnownSelector:selector onClass:class withBlock:asyncSwizzleBlock swizzledSelector:swizzledSelector];
|
||||
[FLEXUtility replaceImplementationOfKnownSelector:selector onClass:class withBlock:asyncSwizzleBlock swizzledSelector:swizzledSelector];
|
||||
});
|
||||
}
|
||||
|
||||
@@ -413,7 +350,7 @@ didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask delegate:(id <NSU
|
||||
dispatch_once(&onceToken, ^{
|
||||
Class class = objc_getMetaClass(class_getName([NSURLConnection class]));
|
||||
SEL selector = @selector(sendSynchronousRequest:returningResponse:error:);
|
||||
SEL swizzledSelector = [self swizzledSelectorForSelector:selector];
|
||||
SEL swizzledSelector = [FLEXUtility swizzledSelectorForSelector:selector];
|
||||
|
||||
NSData *(^syncSwizzleBlock)(Class, NSURLRequest *, NSURLResponse **, NSError **) = ^NSData *(Class slf, NSURLRequest *request, NSURLResponse **response, NSError **error) {
|
||||
NSData *data = nil;
|
||||
@@ -445,7 +382,7 @@ didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask delegate:(id <NSU
|
||||
return data;
|
||||
};
|
||||
|
||||
[self replaceImplementationOfKnownSelector:selector onClass:class withBlock:syncSwizzleBlock swizzledSelector:swizzledSelector];
|
||||
[FLEXUtility replaceImplementationOfKnownSelector:selector onClass:class withBlock:syncSwizzleBlock swizzledSelector:swizzledSelector];
|
||||
});
|
||||
}
|
||||
|
||||
@@ -457,7 +394,6 @@ didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask delegate:(id <NSU
|
||||
|
||||
// The method signatures here are close enough that we can use the same logic to inject into all of them.
|
||||
const SEL selectors[] = {
|
||||
@selector(dataTaskWithHTTPGetRequest:completionHandler:),
|
||||
@selector(dataTaskWithRequest:completionHandler:),
|
||||
@selector(dataTaskWithURL:completionHandler:),
|
||||
@selector(downloadTaskWithRequest:completionHandler:),
|
||||
@@ -469,9 +405,9 @@ didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask delegate:(id <NSU
|
||||
|
||||
for (int selectorIndex = 0; selectorIndex < numSelectors; selectorIndex++) {
|
||||
SEL selector = selectors[selectorIndex];
|
||||
SEL swizzledSelector = [self swizzledSelectorForSelector:selector];
|
||||
SEL swizzledSelector = [FLEXUtility swizzledSelectorForSelector:selector];
|
||||
|
||||
if ([self instanceRespondsButDoesNotImplementSelector:selector class:class]) {
|
||||
if ([FLEXUtility instanceRespondsButDoesNotImplementSelector:selector class:class]) {
|
||||
// iOS 7 does not implement these methods on NSURLSession. We actually want to
|
||||
// swizzle __NSCFURLSession, which we can get from the class of the shared session
|
||||
class = [[NSURLSession sharedSession] class];
|
||||
@@ -479,7 +415,10 @@ didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask delegate:(id <NSU
|
||||
|
||||
NSURLSessionTask *(^asyncDataOrDownloadSwizzleBlock)(Class, id, NSURLSessionAsyncCompletion) = ^NSURLSessionTask *(Class slf, id argument, NSURLSessionAsyncCompletion completion) {
|
||||
NSURLSessionTask *task = nil;
|
||||
if ([FLEXNetworkObserver isEnabled]) {
|
||||
// If completion block was not provided sender expect to receive delegated methods or does not
|
||||
// interested in callback at all. In this case we should just call original method implementation
|
||||
// with nil completion block.
|
||||
if ([FLEXNetworkObserver isEnabled] && completion) {
|
||||
NSString *requestID = [self nextRequestID];
|
||||
NSString *mechanism = [self mechansimFromClassMethod:selector onClass:class];
|
||||
NSURLSessionAsyncCompletion completionWrapper = [self asyncCompletionWrapperForRequestID:requestID mechanism:mechanism completion:completion];
|
||||
@@ -491,7 +430,7 @@ didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask delegate:(id <NSU
|
||||
return task;
|
||||
};
|
||||
|
||||
[self replaceImplementationOfKnownSelector:selector onClass:class withBlock:asyncDataOrDownloadSwizzleBlock swizzledSelector:swizzledSelector];
|
||||
[FLEXUtility replaceImplementationOfKnownSelector:selector onClass:class withBlock:asyncDataOrDownloadSwizzleBlock swizzledSelector:swizzledSelector];
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -513,9 +452,9 @@ didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask delegate:(id <NSU
|
||||
|
||||
for (int selectorIndex = 0; selectorIndex < numSelectors; selectorIndex++) {
|
||||
SEL selector = selectors[selectorIndex];
|
||||
SEL swizzledSelector = [self swizzledSelectorForSelector:selector];
|
||||
SEL swizzledSelector = [FLEXUtility swizzledSelectorForSelector:selector];
|
||||
|
||||
if ([self instanceRespondsButDoesNotImplementSelector:selector class:class]) {
|
||||
if ([FLEXUtility instanceRespondsButDoesNotImplementSelector:selector class:class]) {
|
||||
// iOS 7 does not implement these methods on NSURLSession. We actually want to
|
||||
// swizzle __NSCFURLSession, which we can get from the class of the shared session
|
||||
class = [[NSURLSession sharedSession] class];
|
||||
@@ -535,7 +474,7 @@ didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask delegate:(id <NSU
|
||||
return task;
|
||||
};
|
||||
|
||||
[self replaceImplementationOfKnownSelector:selector onClass:class withBlock:asyncUploadTaskSwizzleBlock swizzledSelector:swizzledSelector];
|
||||
[FLEXUtility replaceImplementationOfKnownSelector:selector onClass:class withBlock:asyncUploadTaskSwizzleBlock swizzledSelector:swizzledSelector];
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -565,7 +504,7 @@ didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask delegate:(id <NSU
|
||||
|
||||
// Call through to the original completion handler
|
||||
if (completion) {
|
||||
completion(data, response, error);
|
||||
completion(fileURLOrData, response, error);
|
||||
}
|
||||
};
|
||||
return completionWrapper;
|
||||
@@ -574,7 +513,7 @@ didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask delegate:(id <NSU
|
||||
+ (void)injectWillSendRequestIntoDelegateClass:(Class)cls
|
||||
{
|
||||
SEL selector = @selector(connection:willSendRequest:redirectResponse:);
|
||||
SEL swizzledSelector = [self swizzledSelectorForSelector:selector];
|
||||
SEL swizzledSelector = [FLEXUtility swizzledSelectorForSelector:selector];
|
||||
|
||||
Protocol *protocol = @protocol(NSURLConnectionDataDelegate);
|
||||
if (!protocol) {
|
||||
@@ -600,13 +539,13 @@ didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask delegate:(id <NSU
|
||||
return returnValue;
|
||||
};
|
||||
|
||||
[self replaceImplementationOfSelector:selector withSelector:swizzledSelector forClass:cls withMethodDescription:methodDescription implementationBlock:implementationBlock undefinedBlock:undefinedBlock];
|
||||
[FLEXUtility replaceImplementationOfSelector:selector withSelector:swizzledSelector forClass:cls withMethodDescription:methodDescription implementationBlock:implementationBlock undefinedBlock:undefinedBlock];
|
||||
}
|
||||
|
||||
+ (void)injectDidReceiveResponseIntoDelegateClass:(Class)cls
|
||||
{
|
||||
SEL selector = @selector(connection:didReceiveResponse:);
|
||||
SEL swizzledSelector = [self swizzledSelectorForSelector:selector];
|
||||
SEL swizzledSelector = [FLEXUtility swizzledSelectorForSelector:selector];
|
||||
|
||||
Protocol *protocol = @protocol(NSURLConnectionDataDelegate);
|
||||
if (!protocol) {
|
||||
@@ -629,13 +568,13 @@ didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask delegate:(id <NSU
|
||||
}];
|
||||
};
|
||||
|
||||
[self replaceImplementationOfSelector:selector withSelector:swizzledSelector forClass:cls withMethodDescription:methodDescription implementationBlock:implementationBlock undefinedBlock:undefinedBlock];
|
||||
[FLEXUtility replaceImplementationOfSelector:selector withSelector:swizzledSelector forClass:cls withMethodDescription:methodDescription implementationBlock:implementationBlock undefinedBlock:undefinedBlock];
|
||||
}
|
||||
|
||||
+ (void)injectDidReceiveDataIntoDelegateClass:(Class)cls
|
||||
{
|
||||
SEL selector = @selector(connection:didReceiveData:);
|
||||
SEL swizzledSelector = [self swizzledSelectorForSelector:selector];
|
||||
SEL swizzledSelector = [FLEXUtility swizzledSelectorForSelector:selector];
|
||||
|
||||
Protocol *protocol = @protocol(NSURLConnectionDataDelegate);
|
||||
if (!protocol) {
|
||||
@@ -658,13 +597,13 @@ didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask delegate:(id <NSU
|
||||
}];
|
||||
};
|
||||
|
||||
[self replaceImplementationOfSelector:selector withSelector:swizzledSelector forClass:cls withMethodDescription:methodDescription implementationBlock:implementationBlock undefinedBlock:undefinedBlock];
|
||||
[FLEXUtility replaceImplementationOfSelector:selector withSelector:swizzledSelector forClass:cls withMethodDescription:methodDescription implementationBlock:implementationBlock undefinedBlock:undefinedBlock];
|
||||
}
|
||||
|
||||
+ (void)injectDidFinishLoadingIntoDelegateClass:(Class)cls
|
||||
{
|
||||
SEL selector = @selector(connectionDidFinishLoading:);
|
||||
SEL swizzledSelector = [self swizzledSelectorForSelector:selector];
|
||||
SEL swizzledSelector = [FLEXUtility swizzledSelectorForSelector:selector];
|
||||
|
||||
Protocol *protocol = @protocol(NSURLConnectionDataDelegate);
|
||||
if (!protocol) {
|
||||
@@ -687,13 +626,13 @@ didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask delegate:(id <NSU
|
||||
}];
|
||||
};
|
||||
|
||||
[self replaceImplementationOfSelector:selector withSelector:swizzledSelector forClass:cls withMethodDescription:methodDescription implementationBlock:implementationBlock undefinedBlock:undefinedBlock];
|
||||
[FLEXUtility replaceImplementationOfSelector:selector withSelector:swizzledSelector forClass:cls withMethodDescription:methodDescription implementationBlock:implementationBlock undefinedBlock:undefinedBlock];
|
||||
}
|
||||
|
||||
+ (void)injectDidFailWithErrorIntoDelegateClass:(Class)cls
|
||||
{
|
||||
SEL selector = @selector(connection:didFailWithError:);
|
||||
SEL swizzledSelector = [self swizzledSelectorForSelector:selector];
|
||||
SEL swizzledSelector = [FLEXUtility swizzledSelectorForSelector:selector];
|
||||
|
||||
Protocol *protocol = @protocol(NSURLConnectionDelegate);
|
||||
struct objc_method_description methodDescription = protocol_getMethodDescription(protocol, selector, NO, YES);
|
||||
@@ -712,13 +651,13 @@ didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask delegate:(id <NSU
|
||||
}];
|
||||
};
|
||||
|
||||
[self replaceImplementationOfSelector:selector withSelector:swizzledSelector forClass:cls withMethodDescription:methodDescription implementationBlock:implementationBlock undefinedBlock:undefinedBlock];
|
||||
[FLEXUtility replaceImplementationOfSelector:selector withSelector:swizzledSelector forClass:cls withMethodDescription:methodDescription implementationBlock:implementationBlock undefinedBlock:undefinedBlock];
|
||||
}
|
||||
|
||||
+ (void)injectTaskWillPerformHTTPRedirectionIntoDelegateClass:(Class)cls
|
||||
{
|
||||
SEL selector = @selector(URLSession:task:willPerformHTTPRedirection:newRequest:completionHandler:);
|
||||
SEL swizzledSelector = [self swizzledSelectorForSelector:selector];
|
||||
SEL swizzledSelector = [FLEXUtility swizzledSelectorForSelector:selector];
|
||||
|
||||
Protocol *protocol = @protocol(NSURLSessionTaskDelegate);
|
||||
|
||||
@@ -738,14 +677,14 @@ didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask delegate:(id <NSU
|
||||
}];
|
||||
};
|
||||
|
||||
[self replaceImplementationOfSelector:selector withSelector:swizzledSelector forClass:cls withMethodDescription:methodDescription implementationBlock:implementationBlock undefinedBlock:undefinedBlock];
|
||||
[FLEXUtility replaceImplementationOfSelector:selector withSelector:swizzledSelector forClass:cls withMethodDescription:methodDescription implementationBlock:implementationBlock undefinedBlock:undefinedBlock];
|
||||
|
||||
}
|
||||
|
||||
+ (void)injectTaskDidReceiveDataIntoDelegateClass:(Class)cls
|
||||
{
|
||||
SEL selector = @selector(URLSession:dataTask:didReceiveData:);
|
||||
SEL swizzledSelector = [self swizzledSelectorForSelector:selector];
|
||||
SEL swizzledSelector = [FLEXUtility swizzledSelectorForSelector:selector];
|
||||
|
||||
Protocol *protocol = @protocol(NSURLSessionDataDelegate);
|
||||
|
||||
@@ -765,14 +704,14 @@ didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask delegate:(id <NSU
|
||||
}];
|
||||
};
|
||||
|
||||
[self replaceImplementationOfSelector:selector withSelector:swizzledSelector forClass:cls withMethodDescription:methodDescription implementationBlock:implementationBlock undefinedBlock:undefinedBlock];
|
||||
[FLEXUtility replaceImplementationOfSelector:selector withSelector:swizzledSelector forClass:cls withMethodDescription:methodDescription implementationBlock:implementationBlock undefinedBlock:undefinedBlock];
|
||||
|
||||
}
|
||||
|
||||
+ (void)injectDataTaskDidBecomeDownloadTaskIntoDelegateClass:(Class)cls
|
||||
{
|
||||
SEL selector = @selector(URLSession:dataTask:didBecomeDownloadTask:);
|
||||
SEL swizzledSelector = [self swizzledSelectorForSelector:selector];
|
||||
SEL swizzledSelector = [FLEXUtility swizzledSelectorForSelector:selector];
|
||||
|
||||
Protocol *protocol = @protocol(NSURLSessionDataDelegate);
|
||||
|
||||
@@ -792,13 +731,13 @@ didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask delegate:(id <NSU
|
||||
}];
|
||||
};
|
||||
|
||||
[self replaceImplementationOfSelector:selector withSelector:swizzledSelector forClass:cls withMethodDescription:methodDescription implementationBlock:implementationBlock undefinedBlock:undefinedBlock];
|
||||
[FLEXUtility replaceImplementationOfSelector:selector withSelector:swizzledSelector forClass:cls withMethodDescription:methodDescription implementationBlock:implementationBlock undefinedBlock:undefinedBlock];
|
||||
}
|
||||
|
||||
+ (void)injectTaskDidReceiveResponseIntoDelegateClass:(Class)cls
|
||||
{
|
||||
SEL selector = @selector(URLSession:dataTask:didReceiveResponse:completionHandler:);
|
||||
SEL swizzledSelector = [self swizzledSelectorForSelector:selector];
|
||||
SEL swizzledSelector = [FLEXUtility swizzledSelectorForSelector:selector];
|
||||
|
||||
Protocol *protocol = @protocol(NSURLSessionDataDelegate);
|
||||
|
||||
@@ -818,14 +757,14 @@ didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask delegate:(id <NSU
|
||||
}];
|
||||
};
|
||||
|
||||
[self replaceImplementationOfSelector:selector withSelector:swizzledSelector forClass:cls withMethodDescription:methodDescription implementationBlock:implementationBlock undefinedBlock:undefinedBlock];
|
||||
[FLEXUtility replaceImplementationOfSelector:selector withSelector:swizzledSelector forClass:cls withMethodDescription:methodDescription implementationBlock:implementationBlock undefinedBlock:undefinedBlock];
|
||||
|
||||
}
|
||||
|
||||
+ (void)injectTaskDidCompleteWithErrorIntoDelegateClass:(Class)cls
|
||||
{
|
||||
SEL selector = @selector(URLSession:task:didCompleteWithError:);
|
||||
SEL swizzledSelector = [self swizzledSelectorForSelector:selector];
|
||||
SEL swizzledSelector = [FLEXUtility swizzledSelectorForSelector:selector];
|
||||
|
||||
Protocol *protocol = @protocol(NSURLSessionTaskDelegate);
|
||||
struct objc_method_description methodDescription = protocol_getMethodDescription(protocol, selector, NO, YES);
|
||||
@@ -844,21 +783,19 @@ didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask delegate:(id <NSU
|
||||
}];
|
||||
};
|
||||
|
||||
[self replaceImplementationOfSelector:selector withSelector:swizzledSelector forClass:cls withMethodDescription:methodDescription implementationBlock:implementationBlock undefinedBlock:undefinedBlock];
|
||||
[FLEXUtility replaceImplementationOfSelector:selector withSelector:swizzledSelector forClass:cls withMethodDescription:methodDescription implementationBlock:implementationBlock undefinedBlock:undefinedBlock];
|
||||
}
|
||||
|
||||
// Used for overriding AFNetworking behavior
|
||||
+ (void)injectRespondsToSelectorIntoDelegateClass:(Class)cls
|
||||
{
|
||||
SEL selector = @selector(respondsToSelector:);
|
||||
SEL swizzledSelector = [self swizzledSelectorForSelector:selector];
|
||||
SEL swizzledSelector = [FLEXUtility swizzledSelectorForSelector:selector];
|
||||
|
||||
//Protocol *protocol = @protocol(NSURLSessionTaskDelegate);
|
||||
Method method = class_getInstanceMethod(cls, selector);
|
||||
struct objc_method_description methodDescription = *method_getDescription(method);
|
||||
|
||||
typedef void (^NSURLSessionTaskDidCompleteWithErrorBlock)(id slf, SEL sel);
|
||||
|
||||
BOOL (^undefinedBlock)(id <NSURLSessionTaskDelegate>, SEL) = ^(id slf, SEL sel) {
|
||||
return YES;
|
||||
};
|
||||
@@ -870,14 +807,14 @@ didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask delegate:(id <NSU
|
||||
return ((BOOL(*)(id, SEL, SEL))objc_msgSend)(slf, swizzledSelector, sel);
|
||||
};
|
||||
|
||||
[self replaceImplementationOfSelector:selector withSelector:swizzledSelector forClass:cls withMethodDescription:methodDescription implementationBlock:implementationBlock undefinedBlock:undefinedBlock];
|
||||
[FLEXUtility replaceImplementationOfSelector:selector withSelector:swizzledSelector forClass:cls withMethodDescription:methodDescription implementationBlock:implementationBlock undefinedBlock:undefinedBlock];
|
||||
}
|
||||
|
||||
|
||||
+ (void)injectDownloadTaskDidFinishDownloadingIntoDelegateClass:(Class)cls
|
||||
{
|
||||
SEL selector = @selector(URLSession:downloadTask:didFinishDownloadingToURL:);
|
||||
SEL swizzledSelector = [self swizzledSelectorForSelector:selector];
|
||||
SEL swizzledSelector = [FLEXUtility swizzledSelectorForSelector:selector];
|
||||
|
||||
Protocol *protocol = @protocol(NSURLSessionDownloadDelegate);
|
||||
struct objc_method_description methodDescription = protocol_getMethodDescription(protocol, selector, NO, YES);
|
||||
@@ -897,13 +834,13 @@ didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask delegate:(id <NSU
|
||||
}];
|
||||
};
|
||||
|
||||
[self replaceImplementationOfSelector:selector withSelector:swizzledSelector forClass:cls withMethodDescription:methodDescription implementationBlock:implementationBlock undefinedBlock:undefinedBlock];
|
||||
[FLEXUtility replaceImplementationOfSelector:selector withSelector:swizzledSelector forClass:cls withMethodDescription:methodDescription implementationBlock:implementationBlock undefinedBlock:undefinedBlock];
|
||||
}
|
||||
|
||||
+ (void)injectDownloadTaskDidWriteDataIntoDelegateClass:(Class)cls
|
||||
{
|
||||
SEL selector = @selector(URLSession:downloadTask:didWriteData:totalBytesWritten:totalBytesExpectedToWrite:);
|
||||
SEL swizzledSelector = [self swizzledSelectorForSelector:selector];
|
||||
SEL swizzledSelector = [FLEXUtility swizzledSelectorForSelector:selector];
|
||||
|
||||
Protocol *protocol = @protocol(NSURLSessionDownloadDelegate);
|
||||
struct objc_method_description methodDescription = protocol_getMethodDescription(protocol, selector, NO, YES);
|
||||
@@ -922,7 +859,7 @@ didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask delegate:(id <NSU
|
||||
}];
|
||||
};
|
||||
|
||||
[self replaceImplementationOfSelector:selector withSelector:swizzledSelector forClass:cls withMethodDescription:methodDescription implementationBlock:implementationBlock undefinedBlock:undefinedBlock];
|
||||
[FLEXUtility replaceImplementationOfSelector:selector withSelector:swizzledSelector forClass:cls withMethodDescription:methodDescription implementationBlock:implementationBlock undefinedBlock:undefinedBlock];
|
||||
|
||||
}
|
||||
|
||||
@@ -959,7 +896,7 @@ static char const * const kFLEXRequestIDKey = "kFLEXRequestIDKey";
|
||||
|
||||
- (void)performBlock:(dispatch_block_t)block
|
||||
{
|
||||
if (self.isEnabled) {
|
||||
if ([[self class] isEnabled]) {
|
||||
dispatch_async(_queue, block);
|
||||
}
|
||||
}
|
||||
@@ -1143,7 +1080,8 @@ static char const * const kFLEXRequestIDKey = "kFLEXRequestIDKey";
|
||||
FLEXInternalRequestState *requestState = [self requestStateForRequestID:requestID];
|
||||
|
||||
if (!requestState.dataAccumulator) {
|
||||
requestState.dataAccumulator = [[NSMutableData alloc] initWithCapacity:(NSUInteger)totalBytesExpectedToWrite];
|
||||
NSUInteger unsignedBytesExpectedToWrite = totalBytesExpectedToWrite > 0 ? (NSUInteger)totalBytesExpectedToWrite : 0;
|
||||
requestState.dataAccumulator = [[NSMutableData alloc] initWithCapacity:unsignedBytesExpectedToWrite];
|
||||
[[FLEXNetworkRecorder defaultRecorder] recordResponseReceivedWithRequestID:requestID response:downloadTask.response];
|
||||
|
||||
NSString *requestMechanism = [NSString stringWithFormat:@"NSURLSessionDownloadTask (delegate: %@)", [delegate class]];
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
//
|
||||
// FLEXKeyboardHelpViewController.h
|
||||
// UICatalog
|
||||
//
|
||||
// Created by Ryan Olson on 9/19/15.
|
||||
// Copyright © 2015 f. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@interface FLEXKeyboardHelpViewController : UIViewController
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,44 @@
|
||||
//
|
||||
// FLEXKeyboardHelpViewController.m
|
||||
// UICatalog
|
||||
//
|
||||
// Created by Ryan Olson on 9/19/15.
|
||||
// Copyright © 2015 f. All rights reserved.
|
||||
//
|
||||
|
||||
#import "FLEXKeyboardHelpViewController.h"
|
||||
#import "FLEXKeyboardShortcutManager.h"
|
||||
|
||||
@interface FLEXKeyboardHelpViewController ()
|
||||
|
||||
@property (nonatomic, strong) UITextView *textView;
|
||||
|
||||
@end
|
||||
|
||||
@implementation FLEXKeyboardHelpViewController
|
||||
|
||||
- (void)viewDidLoad
|
||||
{
|
||||
[super viewDidLoad];
|
||||
|
||||
self.textView = [[UITextView alloc] initWithFrame:self.view.bounds];
|
||||
self.textView.autoresizingMask = UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight;
|
||||
[self.view addSubview:self.textView];
|
||||
#if TARGET_OS_SIMULATOR
|
||||
self.textView.text = [[FLEXKeyboardShortcutManager sharedManager] keyboardShortcutsDescription];
|
||||
#endif
|
||||
self.textView.backgroundColor = [UIColor blackColor];
|
||||
self.textView.textColor = [UIColor whiteColor];
|
||||
self.textView.font = [UIFont boldSystemFontOfSize:14.0];
|
||||
self.navigationController.navigationBar.barStyle = UIBarStyleBlackOpaque;
|
||||
|
||||
self.title = @"Simulator Shortcuts";
|
||||
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone target:self action:@selector(donePressed:)];
|
||||
}
|
||||
|
||||
- (void)donePressed:(id)sender
|
||||
{
|
||||
[self.presentingViewController dismissViewControllerAnimated:YES completion:nil];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,24 @@
|
||||
//
|
||||
// FLEXKeyboardShortcutManager.h
|
||||
// FLEX
|
||||
//
|
||||
// Created by Ryan Olson on 9/19/15.
|
||||
// Copyright © 2015 Flipboard. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
#if TARGET_OS_SIMULATOR
|
||||
|
||||
@interface FLEXKeyboardShortcutManager : NSObject
|
||||
|
||||
+ (instancetype)sharedManager;
|
||||
|
||||
- (void)registerSimulatorShortcutWithKey:(NSString *)key modifiers:(UIKeyModifierFlags)modifiers action:(dispatch_block_t)action description:(NSString *)description;
|
||||
- (NSString *)keyboardShortcutsDescription;
|
||||
|
||||
@property (nonatomic, assign, getter=isEnabled) BOOL enabled;
|
||||
|
||||
@end
|
||||
|
||||
#endif
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user