Compare commits
193 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6c9d48ac97 | |||
| 5a0fbf401a | |||
| 170cc5f9ad | |||
| b6e0ccbcdb | |||
| 79a704ab1a | |||
| 44e9d55fb8 | |||
| ab9515caaf | |||
| 0dd0fc9418 | |||
| 24d5f3e9b2 | |||
| 693f57eef7 | |||
| 7c17ce0787 | |||
| 400a3ccd1c | |||
| a8cdac1872 | |||
| dedac1f98d | |||
| efa317f0d1 | |||
| 9b55bb10de | |||
| 122fb41fa8 | |||
| d6b5e8c77d | |||
| a6ad98dd53 | |||
| cc35f2086a | |||
| 7038aae6db | |||
| f5433153d0 | |||
| 8b7c59d949 | |||
| faef524b6c | |||
| a2bdc03684 | |||
| bd5f9740b7 | |||
| 505bb2ca41 | |||
| 009711ab3f | |||
| 7ad7653cdf | |||
| e5f51e4dfa | |||
| 92029d2b43 | |||
| 7da059791e | |||
| af57527961 | |||
| 9a8f45663e | |||
| 8528c8a1f6 | |||
| d682fd0ace | |||
| 31af87a81e | |||
| 386d6ae06a | |||
| df79ae7971 | |||
| d30c642707 | |||
| f463e2b43e | |||
| ed49a4fc89 | |||
| b897250fde | |||
| 06709a5afe | |||
| 6eee9e6080 | |||
| c50e6e51c5 | |||
| d1e9248695 | |||
| a535f10d0c | |||
| 009aa5e3f9 | |||
| 675f03fc71 | |||
| 99eccdf4c3 | |||
| 29afa5e80f | |||
| bf26bc6539 | |||
| f7b40646e2 | |||
| 84c1fb159b | |||
| 62ef95ff93 | |||
| 731b729db7 | |||
| a1c464d1a7 | |||
| eb2ecbf9b3 | |||
| 16fab66f7b | |||
| b3e70ac491 | |||
| d5177bb049 | |||
| bb0faeb3cf | |||
| 761feef3c0 | |||
| 352bae03ea | |||
| b0085cae7d | |||
| b2f93f1752 | |||
| 354510f2c4 | |||
| b8c6175193 | |||
| d409b110f5 | |||
| 5c73220158 | |||
| 397721e7ea | |||
| 5714275bcd | |||
| 833c584e41 | |||
| 49b24487c5 | |||
| 6d4eb01a07 | |||
| a752203ff9 | |||
| c69427613d | |||
| 5d75a83568 | |||
| 7642a0632d | |||
| 841054a713 | |||
| 49f368fd63 | |||
| 1dc99250c8 | |||
| 00edccf326 | |||
| 7f3af90645 | |||
| 1a030f06cd | |||
| 0477858bed | |||
| 000e061d00 | |||
| 224978b31b | |||
| 7fd133f13b | |||
| e40054ba1a | |||
| 1761734447 | |||
| f23ee3cd95 | |||
| e455ac0c7d | |||
| 94f68c6dfe | |||
| a22f022014 | |||
| 22e7edb698 | |||
| 52fcda53c5 | |||
| be6c5d0e43 | |||
| 6ed0037f50 | |||
| 81e3a5ff47 | |||
| 2a6e28c9d0 | |||
| 9e928b0b09 | |||
| d5deaad628 | |||
| 232ae8a6fd | |||
| c766e5d94a | |||
| 5f27a2304b | |||
| 0cb0f44f18 | |||
| d1c1aa0a26 | |||
| 832957f621 | |||
| 24985ac984 | |||
| 0b652c2f2a | |||
| 98d83bb438 | |||
| e13717b056 | |||
| 552e687b9c | |||
| fb29421644 | |||
| f5d930bd58 | |||
| 26af4ef476 | |||
| ac8940da26 | |||
| f597152a62 | |||
| 58f94f108c | |||
| a5e0bbd50e | |||
| ac273fbfc9 | |||
| 548fd03bd5 | |||
| b564c25d2a | |||
| bd821dc553 | |||
| e46a33417b | |||
| a3419a841f | |||
| cafc1ba0bd | |||
| 0112c097d9 | |||
| 507d03fd90 | |||
| b6453ac360 | |||
| b693ceb20e | |||
| 31e81a616d | |||
| 928f60b56f | |||
| 12a1900d75 | |||
| c72b6f7e5b | |||
| 3cf9f72dcb | |||
| 9b1318e975 | |||
| d3d0f04c23 | |||
| b606d04944 | |||
| 7afd50d241 | |||
| 94f06d5ff8 | |||
| 85424fd15e | |||
| cff391f78c | |||
| 3e420bb747 | |||
| 8aece0a266 | |||
| 81b27b6918 | |||
| 727943c4b3 | |||
| 9f2c032157 | |||
| d6a5b1af8d | |||
| dda9dd5beb | |||
| 888887f09a | |||
| b70a1a2f48 | |||
| 54730c368c | |||
| 21672e6f8d | |||
| 4ffc992872 | |||
| 8eea2ec652 | |||
| 3df01ee7bb | |||
| d0ad6e4319 | |||
| 37aec6dacc | |||
| cdc5aae4b7 | |||
| fd2b89fd24 | |||
| f1683e54c3 | |||
| c66dd2e7d3 | |||
| 5a5b921bbf | |||
| 30cc65bd9d | |||
| 29a45aa02d | |||
| 08b25ea8d3 | |||
| 7ffcb83563 | |||
| b0b64c1ba9 | |||
| bc5dfa02ec | |||
| c69f5c220a | |||
| 7f28d430d0 | |||
| 30dc024903 | |||
| 6403053989 | |||
| ba352c15e8 | |||
| e9e084e6f1 | |||
| 26e92c2bd6 | |||
| 41d761f822 | |||
| 832a03bf27 | |||
| ebf5254629 | |||
| e199b529c8 | |||
| 06edea64ae | |||
| 6d3afe36d1 | |||
| b22d2b57a2 | |||
| b453086936 | |||
| 103489c566 | |||
| 3dd27557ea | |||
| a64188dd5e | |||
| 44e428655a | |||
| 96d8b425d5 | |||
| 7df172afac |
+6
-2
@@ -1,8 +1,12 @@
|
||||
language: objective-c
|
||||
xcode_workspace: FLEX.xcworkspace
|
||||
xcode_sdk: iphonesimulator
|
||||
before_install:
|
||||
- gem install xcpretty
|
||||
matrix:
|
||||
include:
|
||||
- xcode_scheme: UICatalog
|
||||
xcode_sdk: iphonesimulator
|
||||
- xcode_scheme: FLEX
|
||||
xcode_sdk: iphonesimulator
|
||||
script:
|
||||
- set -o pipefail
|
||||
- xcodebuild -workspace $TRAVIS_XCODE_WORKSPACE -scheme $TRAVIS_XCODE_SCHEME -sdk $TRAVIS_XCODE_SDK build | xcpretty
|
||||
@@ -0,0 +1,3 @@
|
||||
# Contributing to FLEX #
|
||||
|
||||
We welcome contributions! Please open a pull request with your changes.
|
||||
@@ -228,7 +228,7 @@
|
||||
CGFloat hexLabelOriginY = CGRectGetMaxY(self.colorPreviewBox.frame) - self.colorPreviewBox.layer.borderWidth - self.hexLabel.frame.size.height;
|
||||
self.hexLabel.frame = CGRectMake(hexLabelOriginX, hexLabelOriginY, self.hexLabel.frame.size.width, self.hexLabel.frame.size.height);
|
||||
|
||||
NSArray *colorComponentInputViews = @[self.alphaInput, self.redInput, self.greenInput, self.blueInput];
|
||||
NSArray<FLEXColorComponentInputView *> *colorComponentInputViews = @[self.alphaInput, self.redInput, self.greenInput, self.blueInput];
|
||||
for (FLEXColorComponentInputView *inputView in colorComponentInputViews) {
|
||||
CGSize fitSize = [inputView sizeThatFits:constrainSize];
|
||||
inputView.frame = CGRectMake(0, runningOriginY, fitSize.width, fitSize.height);
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
@interface FLEXArgumentInputFontsPickerView ()
|
||||
|
||||
@property (nonatomic, strong) NSMutableArray *availableFonts;
|
||||
@property (nonatomic, strong) NSMutableArray<NSString *> *availableFonts;
|
||||
|
||||
@end
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
if ([self.availableFonts indexOfObject:inputValue] == NSNotFound) {
|
||||
[self.availableFonts insertObject:inputValue atIndex:0];
|
||||
}
|
||||
[(UIPickerView*)self.inputTextView.inputView selectRow:[self.availableFonts indexOfObject:inputValue] inComponent:0 animated:NO];
|
||||
[(UIPickerView *)self.inputTextView.inputView selectRow:[self.availableFonts indexOfObject:inputValue] inComponent:0 animated:NO];
|
||||
}
|
||||
|
||||
- (id)inputValue
|
||||
@@ -56,7 +56,7 @@
|
||||
|
||||
- (void)createAvailableFonts
|
||||
{
|
||||
NSMutableArray *unsortedFontsArray = [NSMutableArray array];
|
||||
NSMutableArray<NSString *> *unsortedFontsArray = [NSMutableArray array];
|
||||
for (NSString *eachFontFamily in [UIFont familyNames]) {
|
||||
for (NSString *eachFontName in [UIFont fontNamesForFamilyName:eachFontFamily]) {
|
||||
[unsortedFontsArray addObject:eachFontName];
|
||||
@@ -90,7 +90,7 @@
|
||||
fontLabel = (UILabel*)view;
|
||||
}
|
||||
UIFont *font = [UIFont fontWithName:self.availableFonts[row] size:15.0];
|
||||
NSDictionary *attributesDictionary = [NSDictionary dictionaryWithObject:font forKey:NSFontAttributeName];
|
||||
NSDictionary<NSString *, id> *attributesDictionary = [NSDictionary<NSString *, id> dictionaryWithObject:font forKey:NSFontAttributeName];
|
||||
NSAttributedString *attributesString = [[NSAttributedString alloc] initWithString:self.availableFonts[row] attributes:attributesDictionary];
|
||||
fontLabel.attributedText = attributesString;
|
||||
[fontLabel sizeToFit];
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
|
||||
+ (BOOL)supportsObjCType:(const char *)type withCurrentValue:(id)value
|
||||
{
|
||||
static NSArray *primitiveTypes = nil;
|
||||
static NSArray<NSString *> *primitiveTypes = nil;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
primitiveTypes = @[@(@encode(char)),
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
|
||||
@interface FLEXArgumentInputStructView ()
|
||||
|
||||
@property (nonatomic, strong) NSArray *argumentInputViews;
|
||||
@property (nonatomic, strong) NSArray<FLEXArgumentInputView *> *argumentInputViews;
|
||||
|
||||
@end
|
||||
|
||||
@@ -22,8 +22,8 @@
|
||||
{
|
||||
self = [super initWithArgumentTypeEncoding:typeEncoding];
|
||||
if (self) {
|
||||
NSMutableArray *inputViews = [NSMutableArray array];
|
||||
NSArray *customTitles = [[self class] customFieldTitlesForTypeEncoding:typeEncoding];
|
||||
NSMutableArray<FLEXArgumentInputView *> *inputViews = [NSMutableArray array];
|
||||
NSArray<NSString *> *customTitles = [[self class] customFieldTitlesForTypeEncoding:typeEncoding];
|
||||
[FLEXRuntimeUtility enumerateTypesInStructEncoding:typeEncoding usingBlock:^(NSString *structName, const char *fieldTypeEncoding, NSString *prettyTypeEncoding, NSUInteger fieldIndex, NSUInteger fieldOffset) {
|
||||
|
||||
FLEXArgumentInputView *inputView = [FLEXArgumentInputViewFactory argumentInputViewForTypeEncoding:fieldTypeEncoding];
|
||||
@@ -31,7 +31,7 @@
|
||||
inputView.targetSize = FLEXArgumentInputViewSizeSmall;
|
||||
|
||||
if (fieldIndex < [customTitles count]) {
|
||||
inputView.title = [customTitles objectAtIndex:fieldIndex];
|
||||
inputView.title = customTitles[fieldIndex];
|
||||
} else {
|
||||
inputView.title = [NSString stringWithFormat:@"%@ field %lu (%@)", structName, (unsigned long)fieldIndex, prettyTypeEncoding];
|
||||
}
|
||||
@@ -72,7 +72,7 @@
|
||||
[FLEXRuntimeUtility enumerateTypesInStructEncoding:structTypeEncoding usingBlock:^(NSString *structName, const char *fieldTypeEncoding, NSString *prettyTypeEncoding, NSUInteger fieldIndex, NSUInteger fieldOffset) {
|
||||
|
||||
void *fieldPointer = unboxedValue + fieldOffset;
|
||||
FLEXArgumentInputView *inputView = [self.argumentInputViews objectAtIndex:fieldIndex];
|
||||
FLEXArgumentInputView *inputView = self.argumentInputViews[fieldIndex];
|
||||
|
||||
if (fieldTypeEncoding[0] == @encode(id)[0] || fieldTypeEncoding[0] == @encode(Class)[0]) {
|
||||
inputView.inputValue = (__bridge id)fieldPointer;
|
||||
@@ -102,7 +102,7 @@
|
||||
[FLEXRuntimeUtility enumerateTypesInStructEncoding:structTypeEncoding usingBlock:^(NSString *structName, const char *fieldTypeEncoding, NSString *prettyTypeEncoding, NSUInteger fieldIndex, NSUInteger fieldOffset) {
|
||||
|
||||
void *fieldPointer = unboxedStruct + fieldOffset;
|
||||
FLEXArgumentInputView *inputView = [self.argumentInputViews objectAtIndex:fieldIndex];
|
||||
FLEXArgumentInputView *inputView = self.argumentInputViews[fieldIndex];
|
||||
|
||||
if (fieldTypeEncoding[0] == @encode(id)[0] || fieldTypeEncoding[0] == @encode(Class)[0]) {
|
||||
// Object fields
|
||||
@@ -179,9 +179,9 @@
|
||||
return type && type[0] == '{';
|
||||
}
|
||||
|
||||
+ (NSArray *)customFieldTitlesForTypeEncoding:(const char *)typeEncoding
|
||||
+ (NSArray<NSString *> *)customFieldTitlesForTypeEncoding:(const char *)typeEncoding
|
||||
{
|
||||
NSArray *customTitles = nil;
|
||||
NSArray<NSString *> *customTitles = nil;
|
||||
if (strcmp(typeEncoding, @encode(CGRect)) == 0) {
|
||||
customTitles = @[@"CGPoint origin", @"CGSize size"];
|
||||
} else if (strcmp(typeEncoding, @encode(CGPoint)) == 0) {
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
{
|
||||
self = [super initWithFrame:CGRectZero];
|
||||
if (self) {
|
||||
self.typeEncoding = @(typeEncoding);
|
||||
self.typeEncoding = typeEncoding != NULL ? @(typeEncoding) : nil;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
@@ -8,11 +8,13 @@
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@class FLEXArgumentInputView;
|
||||
|
||||
@interface FLEXFieldEditorView : UIView
|
||||
|
||||
@property (nonatomic, copy) NSString *targetDescription;
|
||||
@property (nonatomic, copy) NSString *fieldDescription;
|
||||
|
||||
@property (nonatomic, strong) NSArray *argumentInputViews;
|
||||
@property (nonatomic, strong) NSArray<FLEXArgumentInputView *> *argumentInputViews;
|
||||
|
||||
@end
|
||||
|
||||
@@ -103,7 +103,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setArgumentInputViews:(NSArray *)argumentInputViews
|
||||
- (void)setArgumentInputViews:(NSArray<FLEXArgumentInputView *> *)argumentInputViews
|
||||
{
|
||||
if (![_argumentInputViews isEqual:argumentInputViews]) {
|
||||
|
||||
|
||||
@@ -22,7 +22,11 @@
|
||||
@property (nonatomic, strong, readonly) id target;
|
||||
@property (nonatomic, strong, readonly) FLEXFieldEditorView *fieldEditorView;
|
||||
@property (nonatomic, strong, readonly) UIBarButtonItem *setterButton;
|
||||
|
||||
- (void)actionButtonPressed:(id)sender;
|
||||
- (NSString *)titleForActionButton;
|
||||
/// Pushes an explorer view controller for the given object
|
||||
/// or pops the current view controller.
|
||||
- (void)exploreObjectOrPopViewController:(id)objectOrNil;
|
||||
|
||||
@end
|
||||
|
||||
@@ -10,8 +10,10 @@
|
||||
#import "FLEXFieldEditorView.h"
|
||||
#import "FLEXRuntimeUtility.h"
|
||||
#import "FLEXUtility.h"
|
||||
#import "FLEXObjectExplorerFactory.h"
|
||||
#import "FLEXArgumentInputView.h"
|
||||
#import "FLEXArgumentInputViewFactory.h"
|
||||
#import "FLEXObjectExplorerViewController.h"
|
||||
|
||||
@interface FLEXFieldEditorViewController () <UIScrollViewDelegate>
|
||||
|
||||
@@ -114,4 +116,16 @@
|
||||
return @"Set";
|
||||
}
|
||||
|
||||
- (void)exploreObjectOrPopViewController:(id)objectOrNil {
|
||||
if (objectOrNil) {
|
||||
// For non-nil (or void) return types, push an explorer view controller to display the object
|
||||
FLEXObjectExplorerViewController *explorerViewController = [FLEXObjectExplorerFactory explorerViewControllerForObject:objectOrNil];
|
||||
[self.navigationController pushViewController:explorerViewController animated:YES];
|
||||
} else {
|
||||
// If we didn't get a returned object but the method call succeeded,
|
||||
// pop this view controller off the stack to indicate that the call went through.
|
||||
[self.navigationController popViewControllerAnimated:YES];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -6,10 +6,10 @@
|
||||
// Copyright (c) 2014 Flipboard. All rights reserved.
|
||||
//
|
||||
|
||||
#import "FLEXFieldEditorViewController.h"
|
||||
#import "FLEXMutableFieldEditorViewController.h"
|
||||
#import <objc/runtime.h>
|
||||
|
||||
@interface FLEXIvarEditorViewController : FLEXFieldEditorViewController
|
||||
@interface FLEXIvarEditorViewController : FLEXMutableFieldEditorViewController
|
||||
|
||||
- (id)initWithTarget:(id)target ivar:(Ivar)ivar;
|
||||
|
||||
|
||||
@@ -55,6 +55,17 @@
|
||||
|
||||
[FLEXRuntimeUtility setValue:self.firstInputView.inputValue forIvar:self.ivar onObject:self.target];
|
||||
self.firstInputView.inputValue = [FLEXRuntimeUtility valueForIvar:self.ivar onObject:self.target];
|
||||
|
||||
// Pop view controller for consistency;
|
||||
// property setters and method calls also pop on success.
|
||||
[self.navigationController popViewControllerAnimated:YES];
|
||||
}
|
||||
|
||||
- (void)getterButtonPressed:(id)sender
|
||||
{
|
||||
[super getterButtonPressed:sender];
|
||||
id returnedObject = [FLEXRuntimeUtility valueForIvar:self.ivar onObject:self.target];
|
||||
[self exploreObjectOrPopViewController:returnedObject];
|
||||
}
|
||||
|
||||
- (void)argumentInputViewValueDidChange:(FLEXArgumentInputView *)argumentInputView
|
||||
|
||||
@@ -9,14 +9,13 @@
|
||||
#import "FLEXMethodCallingViewController.h"
|
||||
#import "FLEXRuntimeUtility.h"
|
||||
#import "FLEXFieldEditorView.h"
|
||||
#import "FLEXObjectExplorerFactory.h"
|
||||
#import "FLEXObjectExplorerViewController.h"
|
||||
#import "FLEXArgumentInputView.h"
|
||||
#import "FLEXArgumentInputViewFactory.h"
|
||||
|
||||
@interface FLEXMethodCallingViewController ()
|
||||
|
||||
@property (nonatomic, assign) Method method;
|
||||
@property (nonatomic, assign) FLEXTypeEncoding *returnType;
|
||||
|
||||
@end
|
||||
|
||||
@@ -27,7 +26,8 @@
|
||||
self = [super initWithTarget:target];
|
||||
if (self) {
|
||||
self.method = method;
|
||||
self.title = [self isClassMethod] ? @"Class Method" : @"Method";
|
||||
self.returnType = [FLEXRuntimeUtility returnTypeForMethod:method];
|
||||
self.title = [self isClassMethod] ? @"Class Method" : @"Method";;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
@@ -36,10 +36,14 @@
|
||||
{
|
||||
[super viewDidLoad];
|
||||
|
||||
self.fieldEditorView.fieldDescription = [FLEXRuntimeUtility prettyNameForMethod:self.method isClassMethod:[self isClassMethod]];
|
||||
NSString *returnType = @((const char *)self.returnType);
|
||||
NSString *methodDescription = [FLEXRuntimeUtility prettyNameForMethod:self.method isClassMethod:[self isClassMethod]];
|
||||
NSString *format = @"Signature:\n%@\n\nReturn Type:\n%@";
|
||||
NSString *info = [NSString stringWithFormat:format, methodDescription, returnType];
|
||||
self.fieldEditorView.fieldDescription = info;
|
||||
|
||||
NSArray *methodComponents = [FLEXRuntimeUtility prettyArgumentComponentsForMethod:self.method];
|
||||
NSMutableArray *argumentInputViews = [NSMutableArray array];
|
||||
NSArray<NSString *> *methodComponents = [FLEXRuntimeUtility prettyArgumentComponentsForMethod:self.method];
|
||||
NSMutableArray<FLEXArgumentInputView *> *argumentInputViews = [NSMutableArray array];
|
||||
unsigned int argumentIndex = kFLEXNumberOfImplicitArgs;
|
||||
for (NSString *methodComponent in methodComponents) {
|
||||
char *argumentTypeEncoding = method_copyArgumentType(self.method, argumentIndex);
|
||||
@@ -54,6 +58,12 @@
|
||||
self.fieldEditorView.argumentInputViews = argumentInputViews;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
free(self.returnType);
|
||||
self.returnType = NULL;
|
||||
}
|
||||
|
||||
- (BOOL)isClassMethod
|
||||
{
|
||||
return self.target && self.target == [self.target class];
|
||||
@@ -86,14 +96,8 @@
|
||||
NSString *message = [error localizedDescription];
|
||||
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:title message:message delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil];
|
||||
[alert show];
|
||||
} else if (returnedObject) {
|
||||
// For non-nil (or void) return types, push an explorer view controller to display the returned object
|
||||
FLEXObjectExplorerViewController *explorerViewController = [FLEXObjectExplorerFactory explorerViewControllerForObject:returnedObject];
|
||||
[self.navigationController pushViewController:explorerViewController animated:YES];
|
||||
} else {
|
||||
// If we didn't get a returned object but the method call succeeded,
|
||||
// pop this view controller off the stack to indicate that the call went through.
|
||||
[self.navigationController popViewControllerAnimated:YES];
|
||||
[self exploreObjectOrPopViewController:returnedObject];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
//
|
||||
// FLEXMutableFieldEditorViewController.h
|
||||
// FLEX
|
||||
//
|
||||
// Created by Tanner on 11/22/18.
|
||||
// Copyright © 2018 Flipboard. All rights reserved.
|
||||
//
|
||||
|
||||
#import "FLEXFieldEditorViewController.h"
|
||||
|
||||
@interface FLEXMutableFieldEditorViewController : FLEXFieldEditorViewController
|
||||
|
||||
@property (nonatomic, strong, readonly) UIBarButtonItem *getterButton;
|
||||
|
||||
- (void)getterButtonPressed:(id)sender;
|
||||
- (NSString *)titleForGetterButton;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,36 @@
|
||||
//
|
||||
// FLEXMutableFieldEditorViewController.m
|
||||
// FLEX
|
||||
//
|
||||
// Created by Tanner on 11/22/18.
|
||||
// Copyright © 2018 Flipboard. All rights reserved.
|
||||
//
|
||||
|
||||
#import "FLEXMutableFieldEditorViewController.h"
|
||||
#import "FLEXFieldEditorView.h"
|
||||
|
||||
@interface FLEXMutableFieldEditorViewController ()
|
||||
|
||||
@property (nonatomic, strong, readwrite) UIBarButtonItem *getterButton;
|
||||
|
||||
@end
|
||||
|
||||
@implementation FLEXMutableFieldEditorViewController
|
||||
|
||||
- (void)viewDidLoad {
|
||||
[super viewDidLoad];
|
||||
|
||||
self.getterButton = [[UIBarButtonItem alloc] initWithTitle:[self titleForGetterButton] style:UIBarButtonItemStyleDone target:self action:@selector(getterButtonPressed:)];
|
||||
self.navigationItem.rightBarButtonItems = @[self.setterButton, self.getterButton];
|
||||
}
|
||||
|
||||
- (void)getterButtonPressed:(id)sender {
|
||||
// Subclasses can override
|
||||
[self.fieldEditorView endEditing:YES];
|
||||
}
|
||||
|
||||
- (NSString *)titleForGetterButton {
|
||||
return @"Get";
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -6,10 +6,10 @@
|
||||
// Copyright (c) 2014 Flipboard. All rights reserved.
|
||||
//
|
||||
|
||||
#import "FLEXFieldEditorViewController.h"
|
||||
#import "FLEXMutableFieldEditorViewController.h"
|
||||
#import <objc/runtime.h>
|
||||
|
||||
@interface FLEXPropertyEditorViewController : FLEXFieldEditorViewController
|
||||
@interface FLEXPropertyEditorViewController : FLEXMutableFieldEditorViewController
|
||||
|
||||
- (id)initWithTarget:(id)target property:(objc_property_t)property;
|
||||
|
||||
|
||||
@@ -76,6 +76,13 @@
|
||||
}
|
||||
}
|
||||
|
||||
- (void)getterButtonPressed:(id)sender
|
||||
{
|
||||
[super getterButtonPressed:sender];
|
||||
id returnedObject = [FLEXRuntimeUtility valueForProperty:self.property onObject:self.target];
|
||||
[self exploreObjectOrPopViewController:returnedObject];
|
||||
}
|
||||
|
||||
- (void)argumentInputViewValueDidChange:(FLEXArgumentInputView *)argumentInputView
|
||||
{
|
||||
if ([argumentInputView isKindOfClass:[FLEXArgumentInputSwitchView class]]) {
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
//
|
||||
// FLEXExplorerViewController.h
|
||||
// Flipboard
|
||||
//
|
||||
// Created by Ryan Olson on 4/4/14.
|
||||
// Copyright (c) 2014 Flipboard. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@protocol FLEXExplorerViewControllerDelegate;
|
||||
|
||||
@interface FLEXExplorerViewController : UIViewController
|
||||
|
||||
@property (nonatomic, weak) id <FLEXExplorerViewControllerDelegate> delegate;
|
||||
|
||||
- (BOOL)shouldReceiveTouchAtWindowPoint:(CGPoint)pointInWindowCoordinates;
|
||||
- (BOOL)wantsWindowToBecomeKey;
|
||||
|
||||
/// @brief Used to present (or dismiss) a modal view controller ("tool"), typically triggered by pressing a button in the toolbar.
|
||||
///
|
||||
/// If a tool is already presented, this method simply dismisses it and calls the completion block.
|
||||
/// If no tool is presented, @code future() @endcode is presented and the completion block is called.
|
||||
- (void)toggleToolWithViewControllerProvider:(UIViewController *(^)(void))future completion:(void(^)(void))completion;
|
||||
|
||||
// Keyboard shortcut helpers
|
||||
|
||||
- (void)toggleSelectTool;
|
||||
- (void)toggleMoveTool;
|
||||
- (void)toggleViewsTool;
|
||||
- (void)toggleMenuTool;
|
||||
- (void)handleDownArrowKeyPressed;
|
||||
- (void)handleUpArrowKeyPressed;
|
||||
- (void)handleRightArrowKeyPressed;
|
||||
- (void)handleLeftArrowKeyPressed;
|
||||
|
||||
@end
|
||||
|
||||
@protocol FLEXExplorerViewControllerDelegate <NSObject>
|
||||
|
||||
- (void)explorerViewControllerDidFinish:(FLEXExplorerViewController *)explorerViewController;
|
||||
|
||||
@end
|
||||
+212
-104
@@ -14,6 +14,9 @@
|
||||
#import "FLEXGlobalsTableViewController.h"
|
||||
#import "FLEXObjectExplorerViewController.h"
|
||||
#import "FLEXObjectExplorerFactory.h"
|
||||
#import "FLEXNetworkHistoryTableViewController.h"
|
||||
|
||||
static NSString *const kFLEXToolbarTopMarginDefaultsKey = @"com.flex.FLEXToolbar.topMargin";
|
||||
|
||||
typedef NS_ENUM(NSUInteger, FLEXExplorerMode) {
|
||||
FLEXExplorerModeDefault,
|
||||
@@ -42,10 +45,10 @@ typedef NS_ENUM(NSUInteger, FLEXExplorerMode) {
|
||||
|
||||
/// Borders of all the visible views in the hierarchy at the selection point.
|
||||
/// The keys are NSValues with the correponding view (nonretained).
|
||||
@property (nonatomic, strong) NSDictionary *outlineViewsForVisibleViews;
|
||||
@property (nonatomic, strong) NSDictionary<NSValue *, UIView *> *outlineViewsForVisibleViews;
|
||||
|
||||
/// The actual views at the selection point with the deepest view last.
|
||||
@property (nonatomic, strong) NSArray *viewsAtTapPoint;
|
||||
@property (nonatomic, strong) NSArray<UIView *> *viewsAtTapPoint;
|
||||
|
||||
/// The view that we're currently highlighting with an overlay and displaying details for.
|
||||
@property (nonatomic, strong) UIView *selectedView;
|
||||
@@ -65,7 +68,7 @@ typedef NS_ENUM(NSUInteger, FLEXExplorerMode) {
|
||||
@property (nonatomic, assign) UIStatusBarStyle previousStatusBarStyle;
|
||||
|
||||
/// All views that we're KVOing. Used to help us clean up properly.
|
||||
@property (nonatomic, strong) NSMutableSet *observedViews;
|
||||
@property (nonatomic, strong) NSMutableSet<UIView *> *observedViews;
|
||||
|
||||
@end
|
||||
|
||||
@@ -93,10 +96,14 @@ typedef NS_ENUM(NSUInteger, FLEXExplorerMode) {
|
||||
|
||||
// Toolbar
|
||||
self.explorerToolbar = [[FLEXExplorerToolbar alloc] init];
|
||||
CGSize toolbarSize = [self.explorerToolbar sizeThatFits:self.view.bounds.size];
|
||||
|
||||
// Start the toolbar off below any bars that may be at the top of the view.
|
||||
CGFloat toolbarOriginY = 100.0;
|
||||
self.explorerToolbar.frame = CGRectMake(0.0, toolbarOriginY, toolbarSize.width, toolbarSize.height);
|
||||
id toolbarOriginYDefault = [[NSUserDefaults standardUserDefaults] objectForKey:kFLEXToolbarTopMarginDefaultsKey];
|
||||
CGFloat toolbarOriginY = toolbarOriginYDefault ? [toolbarOriginYDefault doubleValue] : 100;
|
||||
|
||||
CGRect safeArea = [self viewSafeArea];
|
||||
CGSize toolbarSize = [self.explorerToolbar sizeThatFits:CGSizeMake(CGRectGetWidth(self.view.bounds), CGRectGetHeight(safeArea))];
|
||||
[self updateToolbarPositionWithUnconstrainedFrame:CGRectMake(CGRectGetMinX(safeArea), toolbarOriginY, toolbarSize.width, toolbarSize.height)];
|
||||
self.explorerToolbar.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleBottomMargin | UIViewAutoresizingFlexibleTopMargin;
|
||||
[self.view addSubview:self.explorerToolbar];
|
||||
[self setupToolbarActions];
|
||||
@@ -164,32 +171,30 @@ typedef NS_ENUM(NSUInteger, FLEXExplorerMode) {
|
||||
return shouldAutorotate;
|
||||
}
|
||||
|
||||
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
|
||||
- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator
|
||||
{
|
||||
for (UIView *outlineView in [self.outlineViewsForVisibleViews allValues]) {
|
||||
outlineView.hidden = YES;
|
||||
}
|
||||
self.selectedViewOverlay.hidden = YES;
|
||||
}
|
||||
[coordinator animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> context) {
|
||||
for (UIView *outlineView in [self.outlineViewsForVisibleViews allValues]) {
|
||||
outlineView.hidden = YES;
|
||||
}
|
||||
self.selectedViewOverlay.hidden = YES;
|
||||
} completion:^(id<UIViewControllerTransitionCoordinatorContext> context) {
|
||||
for (UIView *view in self.viewsAtTapPoint) {
|
||||
NSValue *key = [NSValue valueWithNonretainedObject:view];
|
||||
UIView *outlineView = self.outlineViewsForVisibleViews[key];
|
||||
outlineView.frame = [self frameInLocalCoordinatesForView:view];
|
||||
if (self.currentMode == FLEXExplorerModeSelect) {
|
||||
outlineView.hidden = NO;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation
|
||||
{
|
||||
for (UIView *view in self.viewsAtTapPoint) {
|
||||
NSValue *key = [NSValue valueWithNonretainedObject:view];
|
||||
UIView *outlineView = self.outlineViewsForVisibleViews[key];
|
||||
outlineView.frame = [self frameInLocalCoordinatesForView:view];
|
||||
if (self.currentMode == FLEXExplorerModeSelect) {
|
||||
outlineView.hidden = NO;
|
||||
}
|
||||
}
|
||||
|
||||
if (self.selectedView) {
|
||||
self.selectedViewOverlay.frame = [self frameInLocalCoordinatesForView:self.selectedView];
|
||||
self.selectedViewOverlay.hidden = NO;
|
||||
}
|
||||
if (self.selectedView) {
|
||||
self.selectedViewOverlay.frame = [self frameInLocalCoordinatesForView:self.selectedView];
|
||||
self.selectedViewOverlay.hidden = NO;
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Setter Overrides
|
||||
|
||||
- (void)setSelectedView:(UIView *)selectedView
|
||||
@@ -205,7 +210,7 @@ typedef NS_ENUM(NSUInteger, FLEXExplorerMode) {
|
||||
|
||||
// Update the toolbar and selected overlay
|
||||
self.explorerToolbar.selectedViewDescription = [FLEXUtility descriptionForView:selectedView includingFrame:YES];
|
||||
self.explorerToolbar.selectedViewOverlayColor = [FLEXUtility consistentRandomColorForObject:selectedView];;
|
||||
self.explorerToolbar.selectedViewOverlayColor = [FLEXUtility consistentRandomColorForObject:selectedView];
|
||||
|
||||
if (selectedView) {
|
||||
if (!self.selectedViewOverlay) {
|
||||
@@ -231,7 +236,7 @@ typedef NS_ENUM(NSUInteger, FLEXExplorerMode) {
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setViewsAtTapPoint:(NSArray *)viewsAtTapPoint
|
||||
- (void)setViewsAtTapPoint:(NSArray<UIView *> *)viewsAtTapPoint
|
||||
{
|
||||
if (![_viewsAtTapPoint isEqual:viewsAtTapPoint]) {
|
||||
for (UIView *view in _viewsAtTapPoint) {
|
||||
@@ -261,7 +266,7 @@ typedef NS_ENUM(NSUInteger, FLEXExplorerMode) {
|
||||
|
||||
case FLEXExplorerModeSelect:
|
||||
// Make sure the outline views are unhidden in case we came from the move mode.
|
||||
for (id key in self.outlineViewsForVisibleViews) {
|
||||
for (NSValue *key in self.outlineViewsForVisibleViews) {
|
||||
UIView *outlineView = self.outlineViewsForVisibleViews[key];
|
||||
outlineView.hidden = NO;
|
||||
}
|
||||
@@ -269,7 +274,7 @@ typedef NS_ENUM(NSUInteger, FLEXExplorerMode) {
|
||||
|
||||
case FLEXExplorerModeMove:
|
||||
// Hide all the outline views to focus on the selected view, which is the only one that will move.
|
||||
for (id key in self.outlineViewsForVisibleViews) {
|
||||
for (NSValue *key in self.outlineViewsForVisibleViews) {
|
||||
UIView *outlineView = self.outlineViewsForVisibleViews[key];
|
||||
outlineView.hidden = YES;
|
||||
}
|
||||
@@ -310,9 +315,9 @@ typedef NS_ENUM(NSUInteger, FLEXExplorerMode) {
|
||||
[self.observedViews removeObject:view];
|
||||
}
|
||||
|
||||
+ (NSArray *)viewKeyPathsToTrack
|
||||
+ (NSArray<NSString *> *)viewKeyPathsToTrack
|
||||
{
|
||||
static NSArray *trackedViewKeyPaths = nil;
|
||||
static NSArray<NSString *> *trackedViewKeyPaths = nil;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
NSString *frameKeyPath = NSStringFromSelector(@selector(frame));
|
||||
@@ -321,7 +326,7 @@ typedef NS_ENUM(NSUInteger, FLEXExplorerMode) {
|
||||
return trackedViewKeyPaths;
|
||||
}
|
||||
|
||||
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
|
||||
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *, id> *)change context:(void *)context
|
||||
{
|
||||
[self updateOverlayAndDescriptionForObjectIfNeeded:object];
|
||||
}
|
||||
@@ -330,9 +335,9 @@ typedef NS_ENUM(NSUInteger, FLEXExplorerMode) {
|
||||
{
|
||||
NSUInteger indexOfView = [self.viewsAtTapPoint indexOfObject:object];
|
||||
if (indexOfView != NSNotFound) {
|
||||
UIView *view = [self.viewsAtTapPoint objectAtIndex:indexOfView];
|
||||
UIView *view = self.viewsAtTapPoint[indexOfView];
|
||||
NSValue *key = [NSValue valueWithNonretainedObject:view];
|
||||
UIView *outline = [self.outlineViewsForVisibleViews objectForKey:key];
|
||||
UIView *outline = self.outlineViewsForVisibleViews[key];
|
||||
if (outline) {
|
||||
outline.frame = [self frameInLocalCoordinatesForView:view];
|
||||
}
|
||||
@@ -367,27 +372,18 @@ 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
|
||||
- (NSArray<UIView *> *)allViewsInHierarchy
|
||||
{
|
||||
NSMutableArray *allViews = [NSMutableArray array];
|
||||
NSArray *windows = [self allWindows];
|
||||
NSMutableArray<UIView *> *allViews = [NSMutableArray array];
|
||||
NSArray<UIWindow *> *windows = [FLEXUtility allWindows];
|
||||
for (UIWindow *window in windows) {
|
||||
if (window != self.view.window) {
|
||||
[allViews addObject:window];
|
||||
@@ -397,28 +393,6 @@ typedef NS_ENUM(NSUInteger, FLEXExplorerMode) {
|
||||
return allViews;
|
||||
}
|
||||
|
||||
- (NSArray *)allWindows
|
||||
{
|
||||
BOOL includeInternalWindows = YES;
|
||||
BOOL onlyVisibleWindows = NO;
|
||||
|
||||
NSArray *allWindowsComponents = @[@"al", @"lWindo", @"wsIncl", @"udingInt", @"ernalWin", @"dows:o", @"nlyVisi", @"bleWin", @"dows:"];
|
||||
SEL allWindowsSelector = NSSelectorFromString([allWindowsComponents componentsJoinedByString:@""]);
|
||||
|
||||
NSMethodSignature *methodSignature = [[UIWindow class] methodSignatureForSelector:allWindowsSelector];
|
||||
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSignature];
|
||||
|
||||
invocation.target = [UIWindow class];
|
||||
invocation.selector = allWindowsSelector;
|
||||
[invocation setArgument:&includeInternalWindows atIndex:2];
|
||||
[invocation setArgument:&onlyVisibleWindows atIndex:3];
|
||||
[invocation invoke];
|
||||
|
||||
__unsafe_unretained NSArray *windows = nil;
|
||||
[invocation getReturnValue:&windows];
|
||||
return windows;
|
||||
}
|
||||
|
||||
- (UIWindow *)statusWindow
|
||||
{
|
||||
NSString *statusBarString = [NSString stringWithFormat:@"%@arWindow", @"_statusB"];
|
||||
@@ -427,20 +401,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
|
||||
@@ -500,14 +466,24 @@ typedef NS_ENUM(NSUInteger, FLEXExplorerMode) {
|
||||
CGRect newToolbarFrame = self.toolbarFrameBeforeDragging;
|
||||
newToolbarFrame.origin.y += translation.y;
|
||||
|
||||
CGFloat maxY = CGRectGetMaxY(self.view.bounds) - newToolbarFrame.size.height;
|
||||
if (newToolbarFrame.origin.y < 0.0) {
|
||||
newToolbarFrame.origin.y = 0.0;
|
||||
} else if (newToolbarFrame.origin.y > maxY) {
|
||||
newToolbarFrame.origin.y = maxY;
|
||||
[self updateToolbarPositionWithUnconstrainedFrame:newToolbarFrame];
|
||||
}
|
||||
|
||||
- (void)updateToolbarPositionWithUnconstrainedFrame:(CGRect)unconstrainedFrame
|
||||
{
|
||||
CGRect safeArea = [self viewSafeArea];
|
||||
// We only constrain the Y-axis because We want the toolbar to handle the X-axis safeArea layout by itself
|
||||
CGFloat minY = CGRectGetMinY(safeArea);
|
||||
CGFloat maxY = CGRectGetMaxY(safeArea) - unconstrainedFrame.size.height;
|
||||
if (unconstrainedFrame.origin.y < minY) {
|
||||
unconstrainedFrame.origin.y = minY;
|
||||
} else if (unconstrainedFrame.origin.y > maxY) {
|
||||
unconstrainedFrame.origin.y = maxY;
|
||||
}
|
||||
|
||||
self.explorerToolbar.frame = newToolbarFrame;
|
||||
|
||||
self.explorerToolbar.frame = unconstrainedFrame;
|
||||
|
||||
[[NSUserDefaults standardUserDefaults] setDouble:unconstrainedFrame.origin.y forKey:kFLEXToolbarTopMarginDefaultsKey];
|
||||
}
|
||||
|
||||
- (void)handleToolbarHintTapGesture:(UITapGestureRecognizer *)tapGR
|
||||
@@ -564,8 +540,8 @@ typedef NS_ENUM(NSUInteger, FLEXExplorerMode) {
|
||||
|
||||
// For outlined views and the selected view, only use visible views.
|
||||
// Outlining hidden views adds clutter and makes the selection behavior confusing.
|
||||
NSArray *visibleViewsAtTapPoint = [self viewsAtPoint:selectionPointInWindow skipHiddenViews:YES];
|
||||
NSMutableDictionary *newOutlineViewsForVisibleViews = [NSMutableDictionary dictionary];
|
||||
NSArray<UIView *> *visibleViewsAtTapPoint = [self viewsAtPoint:selectionPointInWindow skipHiddenViews:YES];
|
||||
NSMutableDictionary<NSValue *, UIView *> *newOutlineViewsForVisibleViews = [NSMutableDictionary dictionary];
|
||||
for (UIView *view in visibleViewsAtTapPoint) {
|
||||
UIView *outlineView = [self outlineViewForView:view];
|
||||
[self.view addSubview:outlineView];
|
||||
@@ -593,17 +569,17 @@ typedef NS_ENUM(NSUInteger, FLEXExplorerMode) {
|
||||
|
||||
- (void)removeAndClearOutlineViews
|
||||
{
|
||||
for (id key in self.outlineViewsForVisibleViews) {
|
||||
for (NSValue *key in self.outlineViewsForVisibleViews) {
|
||||
UIView *outlineView = self.outlineViewsForVisibleViews[key];
|
||||
[outlineView removeFromSuperview];
|
||||
}
|
||||
self.outlineViewsForVisibleViews = nil;
|
||||
}
|
||||
|
||||
- (NSArray *)viewsAtPoint:(CGPoint)tapPointInWindow skipHiddenViews:(BOOL)skipHidden
|
||||
- (NSArray<UIView *> *)viewsAtPoint:(CGPoint)tapPointInWindow skipHiddenViews:(BOOL)skipHidden
|
||||
{
|
||||
NSMutableArray *views = [NSMutableArray array];
|
||||
for (UIWindow *window in [self allWindows]) {
|
||||
NSMutableArray<UIView *> *views = [NSMutableArray array];
|
||||
for (UIWindow *window in [FLEXUtility allWindows]) {
|
||||
// Don't include the explorer's own window or subviews.
|
||||
if (window != self.view.window && [window pointInside:tapPointInWindow withEvent:nil]) {
|
||||
[views addObject:window];
|
||||
@@ -618,7 +594,7 @@ typedef NS_ENUM(NSUInteger, FLEXExplorerMode) {
|
||||
// Select in the window that would handle the touch, but don't just use the result of hitTest:withEvent: so we can still select views with interaction disabled.
|
||||
// Default to the the application's key window if none of the windows want the touch.
|
||||
UIWindow *windowForSelection = [[UIApplication sharedApplication] keyWindow];
|
||||
for (UIWindow *window in [[self allWindows] reverseObjectEnumerator]) {
|
||||
for (UIWindow *window in [[FLEXUtility allWindows] reverseObjectEnumerator]) {
|
||||
// Ignore the explorer's own window.
|
||||
if (window != self.view.window) {
|
||||
if ([window hitTest:tapPointInWindow withEvent:nil]) {
|
||||
@@ -632,9 +608,9 @@ typedef NS_ENUM(NSUInteger, FLEXExplorerMode) {
|
||||
return [[self recursiveSubviewsAtPoint:tapPointInWindow inView:windowForSelection skipHiddenViews:YES] lastObject];
|
||||
}
|
||||
|
||||
- (NSArray *)recursiveSubviewsAtPoint:(CGPoint)pointInView inView:(UIView *)view skipHiddenViews:(BOOL)skipHidden
|
||||
- (NSArray<UIView *> *)recursiveSubviewsAtPoint:(CGPoint)pointInView inView:(UIView *)view skipHiddenViews:(BOOL)skipHidden
|
||||
{
|
||||
NSMutableArray *subviewsAtPoint = [NSMutableArray array];
|
||||
NSMutableArray<UIView *> *subviewsAtPoint = [NSMutableArray array];
|
||||
for (UIView *subview in view.subviews) {
|
||||
BOOL isHidden = subview.hidden || subview.alpha < 0.01;
|
||||
if (skipHidden && isHidden) {
|
||||
@@ -656,9 +632,9 @@ typedef NS_ENUM(NSUInteger, FLEXExplorerMode) {
|
||||
return subviewsAtPoint;
|
||||
}
|
||||
|
||||
- (NSArray *)allRecursiveSubviewsInView:(UIView *)view
|
||||
- (NSArray<UIView *> *)allRecursiveSubviewsInView:(UIView *)view
|
||||
{
|
||||
NSMutableArray *subviews = [NSMutableArray array];
|
||||
NSMutableArray<UIView *> *subviews = [NSMutableArray array];
|
||||
for (UIView *subview in view.subviews) {
|
||||
[subviews addObject:subview];
|
||||
[subviews addObjectsFromArray:[self allRecursiveSubviewsInView:subview]];
|
||||
@@ -666,9 +642,9 @@ typedef NS_ENUM(NSUInteger, FLEXExplorerMode) {
|
||||
return subviews;
|
||||
}
|
||||
|
||||
- (NSDictionary *)hierarchyDepthsForViews:(NSArray *)views
|
||||
- (NSDictionary<NSValue *, NSNumber *> *)hierarchyDepthsForViews:(NSArray<UIView *> *)views
|
||||
{
|
||||
NSMutableDictionary *hierarchyDepths = [NSMutableDictionary dictionary];
|
||||
NSMutableDictionary<NSValue *, NSNumber *> *hierarchyDepths = [NSMutableDictionary dictionary];
|
||||
for (UIView *view in views) {
|
||||
NSInteger depth = 0;
|
||||
UIView *tryView = view;
|
||||
@@ -712,6 +688,33 @@ typedef NS_ENUM(NSUInteger, FLEXExplorerMode) {
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Safe Area Handling
|
||||
|
||||
- (CGRect)viewSafeArea
|
||||
{
|
||||
CGRect safeArea = self.view.bounds;
|
||||
#if FLEX_AT_LEAST_IOS11_SDK
|
||||
if (@available(iOS 11, *)) {
|
||||
safeArea = UIEdgeInsetsInsetRect(self.view.bounds, self.view.safeAreaInsets);
|
||||
}
|
||||
#endif
|
||||
return safeArea;
|
||||
}
|
||||
|
||||
#if FLEX_AT_LEAST_IOS11_SDK
|
||||
- (void)viewSafeAreaInsetsDidChange
|
||||
{
|
||||
if (@available(iOS 11, *)) {
|
||||
[super viewSafeAreaInsetsDidChange];
|
||||
}
|
||||
|
||||
CGRect safeArea = [self viewSafeArea];
|
||||
CGSize toolbarSize = [self.explorerToolbar sizeThatFits:CGSizeMake(CGRectGetWidth(self.view.bounds), CGRectGetHeight(safeArea))];
|
||||
[self updateToolbarPositionWithUnconstrainedFrame:CGRectMake(CGRectGetMinX(self.explorerToolbar.frame), CGRectGetMinY(self.explorerToolbar.frame), toolbarSize.width, toolbarSize.height)];
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
#pragma mark - Touch Handling
|
||||
|
||||
- (BOOL)shouldReceiveTouchAtWindowPoint:(CGPoint)pointInWindowCoordinates
|
||||
@@ -750,7 +753,7 @@ typedef NS_ENUM(NSUInteger, FLEXExplorerMode) {
|
||||
{
|
||||
// Note that we need to wait until the view controller is dismissed to calculated the frame of the outline view.
|
||||
// Otherwise the coordinate conversion doesn't give the correct result.
|
||||
[self resignKeyAndDismissViewControllerAnimated:YES completion:^{
|
||||
[self toggleViewsToolWithCompletion:^{
|
||||
// If the selected view is outside of the tap point array (selected from "Full Hierarchy"),
|
||||
// then clear out the tap point array and remove all the outline views.
|
||||
if (![self.viewsAtTapPoint containsObject:selectedView]) {
|
||||
@@ -830,4 +833,109 @@ typedef NS_ENUM(NSUInteger, FLEXExplorerMode) {
|
||||
return self.previousKeyWindow != nil;
|
||||
}
|
||||
|
||||
- (void)toggleToolWithViewControllerProvider:(UIViewController *(^)(void))future completion:(void(^)(void))completion
|
||||
{
|
||||
if (self.presentedViewController) {
|
||||
[self resignKeyAndDismissViewControllerAnimated:YES completion:completion];
|
||||
} else {
|
||||
[self makeKeyAndPresentViewController:future() animated:YES completion:completion];
|
||||
}
|
||||
}
|
||||
|
||||
#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
|
||||
{
|
||||
[self toggleViewsToolWithCompletion:nil];
|
||||
}
|
||||
|
||||
- (void)toggleViewsToolWithCompletion:(void(^)(void))completion
|
||||
{
|
||||
[self toggleToolWithViewControllerProvider:^UIViewController *{
|
||||
NSArray<UIView *> *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;
|
||||
return [[UINavigationController alloc] initWithRootViewController:hierarchyTVC];
|
||||
} completion:^{
|
||||
if (completion) {
|
||||
completion();
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)toggleMenuTool
|
||||
{
|
||||
[self toggleToolWithViewControllerProvider:^UIViewController *{
|
||||
FLEXGlobalsTableViewController *globalsViewController = [[FLEXGlobalsTableViewController alloc] init];
|
||||
globalsViewController.delegate = self;
|
||||
[FLEXGlobalsTableViewController setApplicationWindow:[[UIApplication sharedApplication] keyWindow]];
|
||||
return [[UINavigationController alloc] initWithRootViewController:globalsViewController];
|
||||
} 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
|
||||
@@ -1,26 +0,0 @@
|
||||
//
|
||||
// FLEXExplorerViewController.h
|
||||
// Flipboard
|
||||
//
|
||||
// Created by Ryan Olson on 4/4/14.
|
||||
// Copyright (c) 2014 Flipboard. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@protocol FLEXExplorerViewControllerDelegate;
|
||||
|
||||
@interface FLEXExplorerViewController : UIViewController
|
||||
|
||||
@property (nonatomic, weak) id <FLEXExplorerViewControllerDelegate> delegate;
|
||||
|
||||
- (BOOL)shouldReceiveTouchAtWindowPoint:(CGPoint)pointInWindowCoordinates;
|
||||
- (BOOL)wantsWindowToBecomeKey;
|
||||
|
||||
@end
|
||||
|
||||
@protocol FLEXExplorerViewControllerDelegate <NSObject>
|
||||
|
||||
- (void)explorerViewControllerDidFinish:(FLEXExplorerViewController *)explorerViewController;
|
||||
|
||||
@end
|
||||
@@ -1,50 +0,0 @@
|
||||
//
|
||||
// FLEXManager.h
|
||||
// Flipboard
|
||||
//
|
||||
// Created by Ryan Olson on 4/4/14.
|
||||
// Copyright (c) 2014 Flipboard. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@interface FLEXManager : NSObject
|
||||
|
||||
+ (instancetype)sharedManager;
|
||||
|
||||
@property (nonatomic, readonly) BOOL isHidden;
|
||||
|
||||
- (void)showExplorer;
|
||||
- (void)hideExplorer;
|
||||
|
||||
/// 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.
|
||||
/// 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 - Extensions
|
||||
|
||||
/// Adds an entry at the bottom of the list of Global State items. Call this method before this view controller is displayed.
|
||||
/// @param entryName The string to be displayed in the cell.
|
||||
/// @param objectFutureBlock When you tap on the row, information about the object returned by this block will be displayed.
|
||||
/// Passing a block that returns an object allows you to display information about an object whose actual pointer may change at runtime (e.g. +currentUser)
|
||||
/// @note This method must be called from the main thread.
|
||||
/// The objectFutureBlock will be invoked from the main thread and may return nil.
|
||||
/// @note The passed block will be copied and retain for the duration of the application, you may want to use __weak references.
|
||||
- (void)registerGlobalEntryWithName:(NSString *)entryName objectFutureBlock:(id (^)(void))objectFutureBlock;
|
||||
|
||||
/// Adds an entry at the bottom of the list of Global State items. Call this method before this view controller is displayed.
|
||||
/// @param entryName The string to be displayed in the cell.
|
||||
/// @param viewControllerFutureBlock When you tap on the row, view controller returned by this block will be pushed on the navigation controller stack.
|
||||
/// @note This method must be called from the main thread.
|
||||
/// The viewControllerFutureBlock will be invoked from the main thread and may not return nil.
|
||||
/// @note The passed block will be copied and retain for the duration of the application, you may want to use __weak references.
|
||||
- (void)registerGlobalEntryWithName:(NSString *)entryName
|
||||
viewControllerFutureBlock:(UIViewController * (^)(void))viewControllerFutureBlock;
|
||||
|
||||
@end
|
||||
@@ -1,165 +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];
|
||||
}
|
||||
|
||||
- (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 - 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
|
||||
@@ -6,12 +6,4 @@
|
||||
// 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>
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
//
|
||||
// FLEXManager.h
|
||||
// Flipboard
|
||||
//
|
||||
// Created by Ryan Olson on 4/4/14.
|
||||
// Copyright (c) 2014 Flipboard. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
typedef UIViewController *(^FLEXCustomContentViewerFuture)(NSData *data);
|
||||
|
||||
@interface FLEXManager : NSObject
|
||||
|
||||
+ (instancetype)sharedManager;
|
||||
|
||||
@property (nonatomic, readonly) BOOL isHidden;
|
||||
|
||||
- (void)showExplorer;
|
||||
- (void)hideExplorer;
|
||||
- (void)toggleExplorer;
|
||||
|
||||
#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 pruned under memory pressure.
|
||||
@property (nonatomic, assign, getter=isNetworkDebuggingEnabled) BOOL networkDebuggingEnabled;
|
||||
|
||||
/// 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;
|
||||
|
||||
/// Requests whose host ends with one of the blacklisted entries in this array will be not be recorded (eg. google.com).
|
||||
/// Wildcard or subdomain entries are not required (eg. google.com will match any subdomain under google.com).
|
||||
/// Useful to remove requests that are typically noisy, such as analytics requests that you aren't interested in tracking.
|
||||
@property (nonatomic, copy) NSArray<NSString *> *networkRequestHostBlacklist;
|
||||
|
||||
|
||||
#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
|
||||
|
||||
/// Default database password is @c nil by default.
|
||||
/// Set this to the password you want the databases to open with.
|
||||
@property (copy, nonatomic) NSString *defaultSqliteDatabasePassword;
|
||||
|
||||
/// Adds an entry at the bottom of the list of Global State items. Call this method before this view controller is displayed.
|
||||
/// @param entryName The string to be displayed in the cell.
|
||||
/// @param objectFutureBlock When you tap on the row, information about the object returned by this block will be displayed.
|
||||
/// Passing a block that returns an object allows you to display information about an object whose actual pointer may change at runtime (e.g. +currentUser)
|
||||
/// @note This method must be called from the main thread.
|
||||
/// The objectFutureBlock will be invoked from the main thread and may return nil.
|
||||
/// @note The passed block will be copied and retain for the duration of the application, you may want to use __weak references.
|
||||
- (void)registerGlobalEntryWithName:(NSString *)entryName objectFutureBlock:(id (^)(void))objectFutureBlock;
|
||||
|
||||
/// Adds an entry at the bottom of the list of Global State items. Call this method before this view controller is displayed.
|
||||
/// @param entryName The string to be displayed in the cell.
|
||||
/// @param viewControllerFutureBlock When you tap on the row, view controller returned by this block will be pushed on the navigation controller stack.
|
||||
/// @note This method must be called from the main thread.
|
||||
/// The viewControllerFutureBlock will be invoked from the main thread and may not return nil.
|
||||
/// @note The passed block will be copied and retain for the duration of the application, you may want to use __weak references.
|
||||
- (void)registerGlobalEntryWithName:(NSString *)entryName
|
||||
viewControllerFutureBlock:(UIViewController * (^)(void))viewControllerFutureBlock;
|
||||
|
||||
/// Sets custom viewer for specific content type.
|
||||
/// @param contentType Mime type like application/json
|
||||
/// @param viewControllerFutureBlock Viewer (view controller) creation block
|
||||
/// @note This method must be called from the main thread.
|
||||
/// The viewControllerFutureBlock will be invoked from the main thread and may not return nil.
|
||||
/// @note The passed block will be copied and retain for the duration of the application, you may want to use __weak references.
|
||||
- (void)setCustomViewerForContentType:(NSString *)contentType
|
||||
viewControllerFutureBlock:(FLEXCustomContentViewerFuture)viewControllerFutureBlock;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,26 @@
|
||||
//
|
||||
// PTDatabaseManager.h
|
||||
// Derived from:
|
||||
//
|
||||
// FMDatabase.h
|
||||
// FMDB( https://github.com/ccgus/fmdb )
|
||||
//
|
||||
// Created by Peng Tao on 15/11/23.
|
||||
//
|
||||
// Licensed to Flying Meat Inc. under one or more contributor license agreements.
|
||||
// See the LICENSE file distributed with this work for the terms under
|
||||
// which Flying Meat Inc. licenses this file to you.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@protocol FLEXDatabaseManager <NSObject>
|
||||
|
||||
@required
|
||||
- (instancetype)initWithPath:(NSString*)path;
|
||||
|
||||
- (BOOL)open;
|
||||
- (NSArray<NSDictionary<NSString *, id> *> *)queryAllTables;
|
||||
- (NSArray<NSString *> *)queryAllColumnsWithTableName:(NSString *)tableName;
|
||||
- (NSArray<NSDictionary<NSString *, id> *> *)queryAllDataWithTableName:(NSString *)tableName;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,48 @@
|
||||
//
|
||||
// PTMultiColumnTableView.h
|
||||
// PTMultiColumnTableViewDemo
|
||||
//
|
||||
// Created by Peng Tao on 15/11/16.
|
||||
// Copyright © 2015年 Peng Tao. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import "FLEXTableColumnHeader.h"
|
||||
|
||||
@class FLEXMultiColumnTableView;
|
||||
|
||||
@protocol FLEXMultiColumnTableViewDelegate <NSObject>
|
||||
|
||||
@required
|
||||
- (void)multiColumnTableView:(FLEXMultiColumnTableView *)tableView didTapLabelWithText:(NSString *)text;
|
||||
- (void)multiColumnTableView:(FLEXMultiColumnTableView *)tableView didTapHeaderWithText:(NSString *)text sortType:(FLEXTableColumnHeaderSortType)sortType;
|
||||
|
||||
@end
|
||||
|
||||
@protocol FLEXMultiColumnTableViewDataSource <NSObject>
|
||||
|
||||
@required
|
||||
|
||||
- (NSInteger)numberOfColumnsInTableView:(FLEXMultiColumnTableView *)tableView;
|
||||
- (NSInteger)numberOfRowsInTableView:(FLEXMultiColumnTableView *)tableView;
|
||||
- (NSString *)columnNameInColumn:(NSInteger)column;
|
||||
- (NSString *)rowNameInRow:(NSInteger)row;
|
||||
- (NSString *)contentAtColumn:(NSInteger)column row:(NSInteger)row;
|
||||
- (NSArray *)contentAtRow:(NSInteger)row;
|
||||
|
||||
- (CGFloat)multiColumnTableView:(FLEXMultiColumnTableView *)tableView widthForContentCellInColumn:(NSInteger)column;
|
||||
- (CGFloat)multiColumnTableView:(FLEXMultiColumnTableView *)tableView heightForContentCellInRow:(NSInteger)row;
|
||||
- (CGFloat)heightForTopHeaderInTableView:(FLEXMultiColumnTableView *)tableView;
|
||||
- (CGFloat)widthForLeftHeaderInTableView:(FLEXMultiColumnTableView *)tableView;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@interface FLEXMultiColumnTableView : UIView
|
||||
|
||||
@property (nonatomic, weak) id<FLEXMultiColumnTableViewDataSource>dataSource;
|
||||
@property (nonatomic, weak) id<FLEXMultiColumnTableViewDelegate>delegate;
|
||||
|
||||
- (void)reloadData;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,341 @@
|
||||
//
|
||||
// PTMultiColumnTableView.m
|
||||
// PTMultiColumnTableViewDemo
|
||||
//
|
||||
// Created by Peng Tao on 15/11/16.
|
||||
// Copyright © 2015年 Peng Tao. All rights reserved.
|
||||
//
|
||||
|
||||
#import "FLEXMultiColumnTableView.h"
|
||||
#import "FLEXTableContentCell.h"
|
||||
#import "FLEXTableLeftCell.h"
|
||||
|
||||
@interface FLEXMultiColumnTableView ()
|
||||
<UITableViewDataSource, UITableViewDelegate,UIScrollViewDelegate, FLEXTableContentCellDelegate>
|
||||
|
||||
@property (nonatomic, strong) UIScrollView *contentScrollView;
|
||||
@property (nonatomic, strong) UIScrollView *headerScrollView;
|
||||
@property (nonatomic, strong) UITableView *leftTableView;
|
||||
@property (nonatomic, strong) UITableView *contentTableView;
|
||||
@property (nonatomic, strong) UIView *leftHeader;
|
||||
|
||||
@property (nonatomic, strong) NSDictionary<NSString *, NSNumber *> *sortStatusDict;
|
||||
@property (nonatomic, strong) NSArray *rowData;
|
||||
@end
|
||||
|
||||
static const CGFloat kColumnMargin = 1;
|
||||
|
||||
@implementation FLEXMultiColumnTableView
|
||||
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame
|
||||
{
|
||||
self = [super initWithFrame:frame];
|
||||
if (self) {
|
||||
[self loadUI];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)didMoveToSuperview
|
||||
{
|
||||
[super didMoveToSuperview];
|
||||
[self reloadData];
|
||||
}
|
||||
|
||||
- (void)layoutSubviews
|
||||
{
|
||||
[super layoutSubviews];
|
||||
|
||||
CGFloat width = self.frame.size.width;
|
||||
CGFloat height = self.frame.size.height;
|
||||
CGFloat topheaderHeight = [self topHeaderHeight];
|
||||
CGFloat leftHeaderWidth = [self leftHeaderWidth];
|
||||
|
||||
CGFloat contentWidth = 0.0;
|
||||
NSInteger rowsCount = [self numberOfColumns];
|
||||
for (int i = 0; i < rowsCount; i++) {
|
||||
contentWidth += [self contentWidthForColumn:i];
|
||||
}
|
||||
|
||||
self.leftTableView.frame = CGRectMake(0, topheaderHeight, leftHeaderWidth, height - topheaderHeight);
|
||||
self.headerScrollView.frame = CGRectMake(leftHeaderWidth, 0, width - leftHeaderWidth, topheaderHeight);
|
||||
self.headerScrollView.contentSize = CGSizeMake( self.contentTableView.frame.size.width, self.headerScrollView.frame.size.height);
|
||||
self.contentTableView.frame = CGRectMake(0, 0, contentWidth + [self numberOfColumns] * [self columnMargin] , height - topheaderHeight);
|
||||
self.contentScrollView.frame = CGRectMake(leftHeaderWidth, topheaderHeight, width - leftHeaderWidth, height - topheaderHeight);
|
||||
self.contentScrollView.contentSize = self.contentTableView.frame.size;
|
||||
self.leftHeader.frame = CGRectMake(0, 0, [self leftHeaderWidth], [self topHeaderHeight]);
|
||||
}
|
||||
|
||||
|
||||
- (void)loadUI
|
||||
{
|
||||
[self loadHeaderScrollView];
|
||||
[self loadContentScrollView];
|
||||
[self loadLeftView];
|
||||
}
|
||||
|
||||
- (void)reloadData
|
||||
{
|
||||
[self loadLeftViewData];
|
||||
[self loadContentData];
|
||||
[self loadHeaderData];
|
||||
}
|
||||
|
||||
#pragma mark - UI
|
||||
|
||||
- (void)loadHeaderScrollView
|
||||
{
|
||||
UIScrollView *headerScrollView = [[UIScrollView alloc] init];
|
||||
headerScrollView.delegate = self;
|
||||
self.headerScrollView = headerScrollView;
|
||||
self.headerScrollView.backgroundColor = [UIColor colorWithWhite:0.803 alpha:0.850];
|
||||
|
||||
[self addSubview:headerScrollView];
|
||||
}
|
||||
|
||||
- (void)loadContentScrollView
|
||||
{
|
||||
|
||||
UIScrollView *scrollView = [[UIScrollView alloc] init];
|
||||
scrollView.bounces = NO;
|
||||
scrollView.delegate = self;
|
||||
|
||||
UITableView *tableView = [[UITableView alloc] init];
|
||||
tableView.delegate = self;
|
||||
tableView.dataSource = self;
|
||||
tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
|
||||
|
||||
[self addSubview:scrollView];
|
||||
[scrollView addSubview:tableView];
|
||||
|
||||
self.contentScrollView = scrollView;
|
||||
self.contentTableView = tableView;
|
||||
|
||||
}
|
||||
|
||||
- (void)loadLeftView
|
||||
{
|
||||
UITableView *leftTableView = [[UITableView alloc] init];
|
||||
leftTableView.delegate = self;
|
||||
leftTableView.dataSource = self;
|
||||
leftTableView.separatorStyle = UITableViewCellSeparatorStyleNone;
|
||||
self.leftTableView = leftTableView;
|
||||
[self addSubview:leftTableView];
|
||||
|
||||
UIView *leftHeader = [[UIView alloc] init];
|
||||
leftHeader.backgroundColor = [UIColor colorWithWhite:0.950 alpha:0.668];
|
||||
self.leftHeader = leftHeader;
|
||||
[self addSubview:leftHeader];
|
||||
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Data
|
||||
|
||||
- (void)loadHeaderData
|
||||
{
|
||||
NSArray<UIView *> *subviews = self.headerScrollView.subviews;
|
||||
|
||||
for (UIView *subview in subviews) {
|
||||
[subview removeFromSuperview];
|
||||
}
|
||||
CGFloat x = 0.0;
|
||||
CGFloat w = 0.0;
|
||||
for (int i = 0; i < [self numberOfColumns] ; i++) {
|
||||
w = [self contentWidthForColumn:i] + [self columnMargin];
|
||||
|
||||
FLEXTableColumnHeader *cell = [[FLEXTableColumnHeader alloc] initWithFrame:CGRectMake(x, 0, w, [self topHeaderHeight] - 1)];
|
||||
cell.label.text = [self columnTitleForColumn:i];
|
||||
[self.headerScrollView addSubview:cell];
|
||||
|
||||
FLEXTableColumnHeaderSortType type = [self.sortStatusDict[[self columnTitleForColumn:i]] integerValue];
|
||||
[cell changeSortStatusWithType:type];
|
||||
|
||||
UITapGestureRecognizer *gesture = [[UITapGestureRecognizer alloc] initWithTarget:self
|
||||
action:@selector(contentHeaderTap:)];
|
||||
[cell addGestureRecognizer:gesture];
|
||||
cell.userInteractionEnabled = YES;
|
||||
|
||||
x = x + w;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)contentHeaderTap:(UIGestureRecognizer *)gesture
|
||||
{
|
||||
FLEXTableColumnHeader *header = (FLEXTableColumnHeader *)gesture.view;
|
||||
NSString *string = header.label.text;
|
||||
FLEXTableColumnHeaderSortType currentType = [self.sortStatusDict[string] integerValue];
|
||||
FLEXTableColumnHeaderSortType newType ;
|
||||
|
||||
switch (currentType) {
|
||||
case FLEXTableColumnHeaderSortTypeNone:
|
||||
newType = FLEXTableColumnHeaderSortTypeAsc;
|
||||
break;
|
||||
case FLEXTableColumnHeaderSortTypeAsc:
|
||||
newType = FLEXTableColumnHeaderSortTypeDesc;
|
||||
break;
|
||||
case FLEXTableColumnHeaderSortTypeDesc:
|
||||
newType = FLEXTableColumnHeaderSortTypeAsc;
|
||||
break;
|
||||
}
|
||||
|
||||
self.sortStatusDict = @{header.label.text : @(newType)};
|
||||
[header changeSortStatusWithType:newType];
|
||||
[self.delegate multiColumnTableView:self didTapHeaderWithText:string sortType:newType];
|
||||
|
||||
}
|
||||
|
||||
- (void)loadContentData
|
||||
{
|
||||
[self.contentTableView reloadData];
|
||||
}
|
||||
|
||||
- (void)loadLeftViewData
|
||||
{
|
||||
[self.leftTableView reloadData];
|
||||
}
|
||||
|
||||
- (UITableViewCell *)tableView:(UITableView *)tableView
|
||||
cellForRowAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
UIColor *backgroundColor = [UIColor whiteColor];
|
||||
if (indexPath.row % 2 != 0) {
|
||||
backgroundColor = [UIColor colorWithWhite:0.950 alpha:0.750];
|
||||
}
|
||||
|
||||
if (tableView != self.leftTableView) {
|
||||
self.rowData = [self.dataSource contentAtRow:indexPath.row];
|
||||
FLEXTableContentCell *cell = [FLEXTableContentCell cellWithTableView:tableView
|
||||
columnNumber:[self numberOfColumns]];
|
||||
cell.contentView.backgroundColor = backgroundColor;
|
||||
cell.delegate = self;
|
||||
|
||||
for (int i = 0 ; i < cell.labels.count; i++) {
|
||||
|
||||
UILabel *label = cell.labels[i];
|
||||
label.textColor = [UIColor blackColor];
|
||||
|
||||
NSString *content = [NSString stringWithFormat:@"%@",self.rowData[i]];
|
||||
if ([content isEqualToString:@"<null>"]) {
|
||||
label.textColor = [UIColor lightGrayColor];
|
||||
content = @"NULL";
|
||||
}
|
||||
label.text = content;
|
||||
label.backgroundColor = backgroundColor;
|
||||
}
|
||||
return cell;
|
||||
}
|
||||
else {
|
||||
FLEXTableLeftCell *cell = [FLEXTableLeftCell cellWithTableView:tableView];
|
||||
cell.contentView.backgroundColor = backgroundColor;
|
||||
cell.titlelabel.text = [self rowTitleForRow:indexPath.row];
|
||||
return cell;
|
||||
}
|
||||
}
|
||||
|
||||
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
|
||||
{
|
||||
return [self.dataSource numberOfRowsInTableView:self];
|
||||
}
|
||||
|
||||
|
||||
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
return [self.dataSource multiColumnTableView:self heightForContentCellInRow:indexPath.row];
|
||||
}
|
||||
|
||||
|
||||
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
|
||||
{
|
||||
if (scrollView == self.contentScrollView) {
|
||||
self.headerScrollView.contentOffset = scrollView.contentOffset;
|
||||
}
|
||||
else if (scrollView == self.headerScrollView) {
|
||||
self.contentScrollView.contentOffset = scrollView.contentOffset;
|
||||
}
|
||||
else if (scrollView == self.leftTableView) {
|
||||
self.contentTableView.contentOffset = scrollView.contentOffset;
|
||||
}
|
||||
else if (scrollView == self.contentTableView) {
|
||||
self.leftTableView.contentOffset = scrollView.contentOffset;
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark UITableView Delegate
|
||||
|
||||
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
if (tableView == self.leftTableView) {
|
||||
[self.contentTableView selectRowAtIndexPath:indexPath
|
||||
animated:NO
|
||||
scrollPosition:UITableViewScrollPositionNone];
|
||||
}
|
||||
else if (tableView == self.contentTableView) {
|
||||
[self.leftTableView selectRowAtIndexPath:indexPath
|
||||
animated:NO
|
||||
scrollPosition:UITableViewScrollPositionNone];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark DataSource Accessor
|
||||
|
||||
- (NSInteger)numberOfrows
|
||||
{
|
||||
return [self.dataSource numberOfRowsInTableView:self];
|
||||
}
|
||||
|
||||
- (NSInteger)numberOfColumns
|
||||
{
|
||||
return [self.dataSource numberOfColumnsInTableView:self];
|
||||
}
|
||||
|
||||
- (NSString *)columnTitleForColumn:(NSInteger)column
|
||||
{
|
||||
return [self.dataSource columnNameInColumn:column];
|
||||
}
|
||||
|
||||
- (NSString *)rowTitleForRow:(NSInteger)row
|
||||
{
|
||||
return [self.dataSource rowNameInRow:row];
|
||||
}
|
||||
|
||||
- (NSString *)contentAtColumn:(NSInteger)column row:(NSInteger)row;
|
||||
{
|
||||
return [self.dataSource contentAtColumn:column row:row];
|
||||
}
|
||||
|
||||
- (CGFloat)contentWidthForColumn:(NSInteger)column
|
||||
{
|
||||
return [self.dataSource multiColumnTableView:self widthForContentCellInColumn:column];
|
||||
}
|
||||
|
||||
- (CGFloat)contentHeightForRow:(NSInteger)row
|
||||
{
|
||||
return [self.dataSource multiColumnTableView:self heightForContentCellInRow:row];
|
||||
}
|
||||
|
||||
- (CGFloat)topHeaderHeight
|
||||
{
|
||||
return [self.dataSource heightForTopHeaderInTableView:self];
|
||||
}
|
||||
|
||||
- (CGFloat)leftHeaderWidth
|
||||
{
|
||||
return [self.dataSource widthForLeftHeaderInTableView:self];
|
||||
}
|
||||
|
||||
- (CGFloat)columnMargin
|
||||
{
|
||||
return kColumnMargin;
|
||||
}
|
||||
|
||||
|
||||
- (void)tableContentCell:(FLEXTableContentCell *)tableView labelDidTapWithText:(NSString *)text
|
||||
{
|
||||
[self.delegate multiColumnTableView:self didTapLabelWithText:text];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,14 @@
|
||||
//
|
||||
// FLEXRealmDatabaseManager.h
|
||||
// FLEX
|
||||
//
|
||||
// Created by Tim Oliver on 28/01/2016.
|
||||
// Copyright © 2016 Realm. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "FLEXDatabaseManager.h"
|
||||
|
||||
@interface FLEXRealmDatabaseManager : NSObject <FLEXDatabaseManager>
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,114 @@
|
||||
//
|
||||
// FLEXRealmDatabaseManager.m
|
||||
// FLEX
|
||||
//
|
||||
// Created by Tim Oliver on 28/01/2016.
|
||||
// Copyright © 2016 Realm. All rights reserved.
|
||||
//
|
||||
|
||||
#import "FLEXRealmDatabaseManager.h"
|
||||
|
||||
#if __has_include(<Realm/Realm.h>)
|
||||
#import <Realm/Realm.h>
|
||||
#import <Realm/RLMRealm_Dynamic.h>
|
||||
#else
|
||||
#import "FLEXRealmDefines.h"
|
||||
#endif
|
||||
|
||||
@interface FLEXRealmDatabaseManager ()
|
||||
|
||||
@property (nonatomic, copy) NSString *path;
|
||||
@property (nonatomic, strong) RLMRealm * realm;
|
||||
|
||||
@end
|
||||
|
||||
//#endif
|
||||
|
||||
@implementation FLEXRealmDatabaseManager
|
||||
|
||||
- (instancetype)initWithPath:(NSString*)aPath
|
||||
{
|
||||
Class realmClass = NSClassFromString(@"RLMRealm");
|
||||
if (realmClass == nil) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
self = [super init];
|
||||
|
||||
if (self) {
|
||||
_path = aPath;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (BOOL)open
|
||||
{
|
||||
Class realmClass = NSClassFromString(@"RLMRealm");
|
||||
Class configurationClass = NSClassFromString(@"RLMRealmConfiguration");
|
||||
|
||||
if (realmClass == nil || configurationClass == nil) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
NSError *error = nil;
|
||||
id configuration = [[configurationClass alloc] init];
|
||||
[(RLMRealmConfiguration *)configuration setFileURL:[NSURL fileURLWithPath:self.path]];
|
||||
self.realm = [realmClass realmWithConfiguration:configuration error:&error];
|
||||
return (error == nil);
|
||||
}
|
||||
|
||||
- (NSArray<NSDictionary<NSString *, id> *> *)queryAllTables
|
||||
{
|
||||
NSMutableArray<NSDictionary<NSString *, id> *> *allTables = [NSMutableArray array];
|
||||
RLMSchema *schema = [self.realm schema];
|
||||
|
||||
for (RLMObjectSchema *objectSchema in schema.objectSchema) {
|
||||
if (objectSchema.className == nil) {
|
||||
continue;
|
||||
}
|
||||
|
||||
NSDictionary<NSString *, id> *dictionary = @{@"name":objectSchema.className};
|
||||
[allTables addObject:dictionary];
|
||||
}
|
||||
|
||||
return allTables;
|
||||
}
|
||||
|
||||
- (NSArray<NSString *> *)queryAllColumnsWithTableName:(NSString *)tableName
|
||||
{
|
||||
RLMObjectSchema *objectSchema = [[self.realm schema] schemaForClassName:tableName];
|
||||
if (objectSchema == nil) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
NSMutableArray<NSString *> *columnNames = [NSMutableArray array];
|
||||
for (RLMProperty *property in objectSchema.properties) {
|
||||
[columnNames addObject:property.name];
|
||||
}
|
||||
|
||||
return columnNames;
|
||||
}
|
||||
|
||||
- (NSArray<NSDictionary<NSString *, id> *> *)queryAllDataWithTableName:(NSString *)tableName
|
||||
{
|
||||
RLMObjectSchema *objectSchema = [[self.realm schema] schemaForClassName:tableName];
|
||||
RLMResults *results = [self.realm allObjects:tableName];
|
||||
if (results.count == 0 || objectSchema == nil) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
NSMutableArray<NSDictionary<NSString *, id> *> *allDataEntries = [NSMutableArray array];
|
||||
for (RLMObject *result in results) {
|
||||
NSMutableDictionary<NSString *, id> *entry = [NSMutableDictionary dictionary];
|
||||
for (RLMProperty *property in objectSchema.properties) {
|
||||
id value = [result valueForKey:property.name];
|
||||
entry[property.name] = (value) ? (value) : [NSNull null];
|
||||
}
|
||||
|
||||
[allDataEntries addObject:entry];
|
||||
}
|
||||
|
||||
return allDataEntries;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,46 @@
|
||||
//
|
||||
// Realm.h
|
||||
// FLEX
|
||||
//
|
||||
// Created by Tim Oliver on 16/02/2016.
|
||||
// Copyright © 2016 Realm. All rights reserved.
|
||||
//
|
||||
|
||||
#if __has_include(<Realm/Realm.h>)
|
||||
#else
|
||||
|
||||
@class RLMObject, RLMResults, RLMRealm, RLMRealmConfiguration, RLMSchema, RLMObjectSchema, RLMProperty;
|
||||
|
||||
@interface RLMRealmConfiguration : NSObject
|
||||
@property (nonatomic, copy) NSURL *fileURL;
|
||||
@end
|
||||
|
||||
@interface RLMRealm : NSObject
|
||||
@property (nonatomic, readonly) RLMSchema *schema;
|
||||
+ (RLMRealm *)realmWithConfiguration:(RLMRealmConfiguration *)configuration error:(NSError **)error;
|
||||
- (RLMResults *)allObjects:(NSString *)className;
|
||||
@end
|
||||
|
||||
@interface RLMSchema : NSObject
|
||||
@property (nonatomic, readonly) NSArray<RLMObjectSchema *> *objectSchema;
|
||||
- (RLMObjectSchema *)schemaForClassName:(NSString *)className;
|
||||
@end
|
||||
|
||||
@interface RLMObjectSchema : NSObject
|
||||
@property (nonatomic, readonly) NSString *className;
|
||||
@property (nonatomic, readonly) NSArray<RLMProperty *> *properties;
|
||||
@end
|
||||
|
||||
@interface RLMProperty : NSString
|
||||
@property (nonatomic, readonly) NSString *name;
|
||||
@end
|
||||
|
||||
@interface RLMResults : NSObject <NSFastEnumeration>
|
||||
@property (nonatomic, readonly) NSInteger count;
|
||||
@end
|
||||
|
||||
@interface RLMObject : NSObject
|
||||
|
||||
@end
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,19 @@
|
||||
//
|
||||
// PTDatabaseManager.h
|
||||
// Derived from:
|
||||
//
|
||||
// FMDatabase.h
|
||||
// FMDB( https://github.com/ccgus/fmdb )
|
||||
//
|
||||
// Created by Peng Tao on 15/11/23.
|
||||
//
|
||||
// Licensed to Flying Meat Inc. under one or more contributor license agreements.
|
||||
// See the LICENSE file distributed with this work for the terms under
|
||||
// which Flying Meat Inc. licenses this file to you.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "FLEXDatabaseManager.h"
|
||||
|
||||
@interface FLEXSQLiteDatabaseManager : NSObject <FLEXDatabaseManager>
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,203 @@
|
||||
//
|
||||
// PTDatabaseManager.m
|
||||
// PTDatabaseReader
|
||||
//
|
||||
// Created by Peng Tao on 15/11/23.
|
||||
// Copyright © 2015年 Peng Tao. All rights reserved.
|
||||
//
|
||||
|
||||
#import "FLEXSQLiteDatabaseManager.h"
|
||||
#import "FLEXManager.h"
|
||||
#import <sqlite3.h>
|
||||
|
||||
|
||||
static NSString *const QUERY_TABLENAMES_SQL = @"SELECT name FROM sqlite_master WHERE type='table' ORDER BY name";
|
||||
|
||||
@implementation FLEXSQLiteDatabaseManager
|
||||
{
|
||||
sqlite3* _db;
|
||||
NSString* _databasePath;
|
||||
}
|
||||
|
||||
- (instancetype)initWithPath:(NSString*)aPath
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
if (self) {
|
||||
_databasePath = [aPath copy];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (BOOL)open {
|
||||
if (_db) {
|
||||
return YES;
|
||||
}
|
||||
int err = sqlite3_open([_databasePath UTF8String], &_db);
|
||||
|
||||
#if SQLITE_HAS_CODEC
|
||||
NSString *defaultSqliteDatabasePassword = [FLEXManager sharedManager].defaultSqliteDatabasePassword;
|
||||
|
||||
if (defaultSqliteDatabasePassword) {
|
||||
const char *key = defaultSqliteDatabasePassword.UTF8String;
|
||||
|
||||
sqlite3_key(_db, key, (int)strlen(key));
|
||||
}
|
||||
#endif
|
||||
|
||||
if(err != SQLITE_OK) {
|
||||
NSLog(@"error opening!: %d", err);
|
||||
return NO;
|
||||
}
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)close {
|
||||
if (!_db) {
|
||||
return YES;
|
||||
}
|
||||
|
||||
int rc;
|
||||
BOOL retry;
|
||||
BOOL triedFinalizingOpenStatements = NO;
|
||||
|
||||
do {
|
||||
retry = NO;
|
||||
rc = sqlite3_close(_db);
|
||||
if (SQLITE_BUSY == rc || SQLITE_LOCKED == rc) {
|
||||
if (!triedFinalizingOpenStatements) {
|
||||
triedFinalizingOpenStatements = YES;
|
||||
sqlite3_stmt *pStmt;
|
||||
while ((pStmt = sqlite3_next_stmt(_db, nil)) !=0) {
|
||||
NSLog(@"Closing leaked statement");
|
||||
sqlite3_finalize(pStmt);
|
||||
retry = YES;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (SQLITE_OK != rc) {
|
||||
NSLog(@"error closing!: %d", rc);
|
||||
}
|
||||
}
|
||||
while (retry);
|
||||
|
||||
_db = nil;
|
||||
return YES;
|
||||
}
|
||||
|
||||
|
||||
- (NSArray<NSDictionary<NSString *, id> *> *)queryAllTables
|
||||
{
|
||||
return [self executeQuery:QUERY_TABLENAMES_SQL];
|
||||
}
|
||||
|
||||
- (NSArray<NSString *> *)queryAllColumnsWithTableName:(NSString *)tableName
|
||||
{
|
||||
NSString *sql = [NSString stringWithFormat:@"PRAGMA table_info('%@')",tableName];
|
||||
NSArray<NSDictionary<NSString *, id> *> *resultArray = [self executeQuery:sql];
|
||||
NSMutableArray<NSString *> *array = [NSMutableArray array];
|
||||
for (NSDictionary<NSString *, id> *dict in resultArray) {
|
||||
NSString *columnName = (NSString *)dict[@"name"] ?: @"";
|
||||
[array addObject:columnName];
|
||||
}
|
||||
return array;
|
||||
}
|
||||
|
||||
- (NSArray<NSDictionary<NSString *, id> *> *)queryAllDataWithTableName:(NSString *)tableName
|
||||
{
|
||||
NSString *sql = [NSString stringWithFormat:@"SELECT * FROM %@",tableName];
|
||||
return [self executeQuery:sql];
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark - Private
|
||||
|
||||
- (NSArray<NSDictionary<NSString *, id> *> *)executeQuery:(NSString *)sql
|
||||
{
|
||||
[self open];
|
||||
NSMutableArray<NSDictionary<NSString *, id> *> *resultArray = [NSMutableArray array];
|
||||
sqlite3_stmt *pstmt;
|
||||
if (sqlite3_prepare_v2(_db, [sql UTF8String], -1, &pstmt, 0) == SQLITE_OK) {
|
||||
while (sqlite3_step(pstmt) == SQLITE_ROW) {
|
||||
NSUInteger num_cols = (NSUInteger)sqlite3_data_count(pstmt);
|
||||
if (num_cols > 0) {
|
||||
NSMutableDictionary<NSString *, id> *dict = [NSMutableDictionary dictionaryWithCapacity:num_cols];
|
||||
|
||||
int columnCount = sqlite3_column_count(pstmt);
|
||||
|
||||
int columnIdx = 0;
|
||||
for (columnIdx = 0; columnIdx < columnCount; columnIdx++) {
|
||||
|
||||
NSString *columnName = [NSString stringWithUTF8String:sqlite3_column_name(pstmt, columnIdx)];
|
||||
id objectValue = [self objectForColumnIndex:columnIdx stmt:pstmt];
|
||||
[dict setObject:objectValue forKey:columnName];
|
||||
}
|
||||
[resultArray addObject:dict];
|
||||
}
|
||||
}
|
||||
}
|
||||
[self close];
|
||||
return resultArray;
|
||||
}
|
||||
|
||||
|
||||
- (id)objectForColumnIndex:(int)columnIdx stmt:(sqlite3_stmt*)stmt {
|
||||
int columnType = sqlite3_column_type(stmt, columnIdx);
|
||||
|
||||
id returnValue = nil;
|
||||
|
||||
if (columnType == SQLITE_INTEGER) {
|
||||
returnValue = [NSNumber numberWithLongLong:sqlite3_column_int64(stmt, columnIdx)];
|
||||
}
|
||||
else if (columnType == SQLITE_FLOAT) {
|
||||
returnValue = [NSNumber numberWithDouble:sqlite3_column_double(stmt, columnIdx)];
|
||||
}
|
||||
else if (columnType == SQLITE_BLOB) {
|
||||
returnValue = [self dataForColumnIndex:columnIdx stmt:stmt];
|
||||
}
|
||||
else {
|
||||
//default to a string for everything else
|
||||
returnValue = [self stringForColumnIndex:columnIdx stmt:stmt];
|
||||
}
|
||||
|
||||
if (returnValue == nil) {
|
||||
returnValue = [NSNull null];
|
||||
}
|
||||
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
- (NSString *)stringForColumnIndex:(int)columnIdx stmt:(sqlite3_stmt *)stmt {
|
||||
|
||||
if (sqlite3_column_type(stmt, columnIdx) == SQLITE_NULL || (columnIdx < 0)) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
const char *c = (const char *)sqlite3_column_text(stmt, columnIdx);
|
||||
|
||||
if (!c) {
|
||||
// null row.
|
||||
return nil;
|
||||
}
|
||||
|
||||
return [NSString stringWithUTF8String:c];
|
||||
}
|
||||
|
||||
- (NSData *)dataForColumnIndex:(int)columnIdx stmt:(sqlite3_stmt *)stmt{
|
||||
|
||||
if (sqlite3_column_type(stmt, columnIdx) == SQLITE_NULL || (columnIdx < 0)) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
const char *dataBuffer = sqlite3_column_blob(stmt, columnIdx);
|
||||
int dataSize = sqlite3_column_bytes(stmt, columnIdx);
|
||||
|
||||
if (dataBuffer == NULL) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
return [NSData dataWithBytes:(const void *)dataBuffer length:(NSUInteger)dataSize];
|
||||
}
|
||||
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,24 @@
|
||||
//
|
||||
// FLEXTableContentHeaderCell.h
|
||||
// UICatalog
|
||||
//
|
||||
// Created by Peng Tao on 15/11/26.
|
||||
// Copyright © 2015年 f. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
typedef NS_ENUM(NSUInteger, FLEXTableColumnHeaderSortType) {
|
||||
FLEXTableColumnHeaderSortTypeNone = 0,
|
||||
FLEXTableColumnHeaderSortTypeAsc,
|
||||
FLEXTableColumnHeaderSortTypeDesc,
|
||||
};
|
||||
|
||||
@interface FLEXTableColumnHeader : UIView
|
||||
|
||||
@property (nonatomic, strong) UILabel *label;
|
||||
|
||||
- (void)changeSortStatusWithType:(FLEXTableColumnHeaderSortType)type;
|
||||
|
||||
@end
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
//
|
||||
// FLEXTableContentHeaderCell.m
|
||||
// UICatalog
|
||||
//
|
||||
// Created by Peng Tao on 15/11/26.
|
||||
// Copyright © 2015年 f. All rights reserved.
|
||||
//
|
||||
|
||||
#import "FLEXTableColumnHeader.h"
|
||||
|
||||
@implementation FLEXTableColumnHeader
|
||||
{
|
||||
UILabel *_arrowLabel;
|
||||
}
|
||||
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame
|
||||
{
|
||||
self = [super initWithFrame:frame];
|
||||
if (self) {
|
||||
self.backgroundColor = [UIColor whiteColor];
|
||||
|
||||
UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(5, 0, frame.size.width - 25, frame.size.height)];
|
||||
label.font = [UIFont systemFontOfSize:13.0];
|
||||
[self addSubview:label];
|
||||
self.label = label;
|
||||
|
||||
|
||||
_arrowLabel = [[UILabel alloc] initWithFrame:CGRectMake(frame.size.width - 20, 0, 20, frame.size.height)];
|
||||
_arrowLabel.font = [UIFont systemFontOfSize:13.0];
|
||||
[self addSubview:_arrowLabel];
|
||||
|
||||
UIView *line = [[UIView alloc] initWithFrame:CGRectMake(frame.size.width - 1, 2, 1, frame.size.height - 4)];
|
||||
line.backgroundColor = [UIColor colorWithWhite:0.803 alpha:0.850];
|
||||
[self addSubview:line];
|
||||
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)changeSortStatusWithType:(FLEXTableColumnHeaderSortType)type
|
||||
{
|
||||
switch (type) {
|
||||
case FLEXTableColumnHeaderSortTypeNone:
|
||||
_arrowLabel.text = @"";
|
||||
break;
|
||||
case FLEXTableColumnHeaderSortTypeAsc:
|
||||
_arrowLabel.text = @"⬆️";
|
||||
break;
|
||||
case FLEXTableColumnHeaderSortTypeDesc:
|
||||
_arrowLabel.text = @"⬇️";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,27 @@
|
||||
//
|
||||
// FLEXTableContentCell.h
|
||||
// UICatalog
|
||||
//
|
||||
// Created by Peng Tao on 15/11/24.
|
||||
// Copyright © 2015年 f. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@class FLEXTableContentCell;
|
||||
@protocol FLEXTableContentCellDelegate <NSObject>
|
||||
|
||||
@optional
|
||||
- (void)tableContentCell:(FLEXTableContentCell *)tableView labelDidTapWithText:(NSString *)text;
|
||||
|
||||
@end
|
||||
|
||||
@interface FLEXTableContentCell : UITableViewCell
|
||||
|
||||
@property (nonatomic, strong) NSArray<UILabel *> *labels;
|
||||
|
||||
@property (nonatomic, weak) id<FLEXTableContentCellDelegate> delegate;
|
||||
|
||||
+ (instancetype)cellWithTableView:(UITableView *)tableView columnNumber:(NSInteger)number;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,66 @@
|
||||
//
|
||||
// FLEXTableContentCell.m
|
||||
// UICatalog
|
||||
//
|
||||
// Created by Peng Tao on 15/11/24.
|
||||
// Copyright © 2015年 f. All rights reserved.
|
||||
//
|
||||
|
||||
#import "FLEXTableContentCell.h"
|
||||
#import "FLEXMultiColumnTableView.h"
|
||||
|
||||
@interface FLEXTableContentCell ()
|
||||
|
||||
@end
|
||||
|
||||
@implementation FLEXTableContentCell
|
||||
|
||||
+ (instancetype)cellWithTableView:(UITableView *)tableView columnNumber:(NSInteger)number;
|
||||
{
|
||||
static NSString *identifier = @"FLEXTableContentCell";
|
||||
FLEXTableContentCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier];
|
||||
if (!cell) {
|
||||
cell = [[FLEXTableContentCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier];
|
||||
NSMutableArray<UILabel *> *labels = [NSMutableArray array];
|
||||
for (int i = 0; i < number ; i++) {
|
||||
UILabel *label = [[UILabel alloc] initWithFrame:CGRectZero];
|
||||
label.backgroundColor = [UIColor whiteColor];
|
||||
label.font = [UIFont systemFontOfSize:13.0];
|
||||
label.textAlignment = NSTextAlignmentLeft;
|
||||
label.backgroundColor = [UIColor greenColor];
|
||||
[labels addObject:label];
|
||||
|
||||
UITapGestureRecognizer *gesture = [[UITapGestureRecognizer alloc] initWithTarget:cell
|
||||
action:@selector(labelDidTap:)];
|
||||
[label addGestureRecognizer:gesture];
|
||||
label.userInteractionEnabled = YES;
|
||||
|
||||
[cell.contentView addSubview:label];
|
||||
cell.contentView.backgroundColor = [UIColor whiteColor];
|
||||
}
|
||||
cell.labels = labels;
|
||||
}
|
||||
return cell;
|
||||
}
|
||||
|
||||
- (void)layoutSubviews
|
||||
{
|
||||
[super layoutSubviews];
|
||||
CGFloat labelWidth = self.contentView.frame.size.width / self.labels.count;
|
||||
CGFloat labelHeight = self.contentView.frame.size.height;
|
||||
for (int i = 0; i < self.labels.count; i++) {
|
||||
UILabel *label = self.labels[i];
|
||||
label.frame = CGRectMake(labelWidth * i + 5, 0, (labelWidth - 10), labelHeight);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
- (void)labelDidTap:(UIGestureRecognizer *)gesture
|
||||
{
|
||||
UILabel *label = (UILabel *)gesture.view;
|
||||
if ([self.delegate respondsToSelector:@selector(tableContentCell:labelDidTapWithText:)]) {
|
||||
[self.delegate tableContentCell:self labelDidTapWithText:label.text];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,16 @@
|
||||
//
|
||||
// PTTableContentViewController.h
|
||||
// PTDatabaseReader
|
||||
//
|
||||
// Created by Peng Tao on 15/11/23.
|
||||
// Copyright © 2015年 Peng Tao. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@interface FLEXTableContentViewController : UIViewController
|
||||
|
||||
@property (nonatomic, strong) NSArray<NSString *> *columnsArray;
|
||||
@property (nonatomic, strong) NSArray<NSDictionary<NSString *, id> *> *contentsArray;
|
||||
|
||||
@end
|
||||
+183
@@ -0,0 +1,183 @@
|
||||
//
|
||||
// PTTableContentViewController.m
|
||||
// PTDatabaseReader
|
||||
//
|
||||
// Created by Peng Tao on 15/11/23.
|
||||
// Copyright © 2015年 Peng Tao. All rights reserved.
|
||||
//
|
||||
|
||||
#import "FLEXTableContentViewController.h"
|
||||
#import "FLEXMultiColumnTableView.h"
|
||||
#import "FLEXWebViewController.h"
|
||||
|
||||
|
||||
@interface FLEXTableContentViewController ()<FLEXMultiColumnTableViewDataSource, FLEXMultiColumnTableViewDelegate>
|
||||
|
||||
@property (nonatomic, strong) FLEXMultiColumnTableView *multiColumView;
|
||||
|
||||
@end
|
||||
|
||||
@implementation FLEXTableContentViewController
|
||||
|
||||
- (void)viewDidLoad {
|
||||
[super viewDidLoad];
|
||||
self.edgesForExtendedLayout = UIRectEdgeNone;
|
||||
[self.view addSubview:self.multiColumView];
|
||||
}
|
||||
|
||||
- (void)viewWillAppear:(BOOL)animated
|
||||
{
|
||||
[super viewWillAppear:animated];
|
||||
[self.multiColumView reloadData];
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
|
||||
#pragma mark init SubView
|
||||
- (FLEXMultiColumnTableView *)multiColumView {
|
||||
if (!_multiColumView) {
|
||||
_multiColumView = [[FLEXMultiColumnTableView alloc] initWithFrame:
|
||||
CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height)];
|
||||
|
||||
_multiColumView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleTopMargin;
|
||||
_multiColumView.backgroundColor = [UIColor whiteColor];
|
||||
_multiColumView.dataSource = self;
|
||||
_multiColumView.delegate = self;
|
||||
}
|
||||
return _multiColumView;
|
||||
}
|
||||
#pragma mark MultiColumnTableView DataSource
|
||||
|
||||
- (NSInteger)numberOfColumnsInTableView:(FLEXMultiColumnTableView *)tableView
|
||||
{
|
||||
return self.columnsArray.count;
|
||||
}
|
||||
- (NSInteger)numberOfRowsInTableView:(FLEXMultiColumnTableView *)tableView
|
||||
{
|
||||
return self.contentsArray.count;
|
||||
}
|
||||
|
||||
|
||||
- (NSString *)columnNameInColumn:(NSInteger)column
|
||||
{
|
||||
return self.columnsArray[column];
|
||||
}
|
||||
|
||||
|
||||
- (NSString *)rowNameInRow:(NSInteger)row
|
||||
{
|
||||
return [NSString stringWithFormat:@"%ld",(long)row];
|
||||
}
|
||||
|
||||
- (NSString *)contentAtColumn:(NSInteger)column row:(NSInteger)row
|
||||
{
|
||||
if (self.contentsArray.count > row) {
|
||||
NSDictionary<NSString *, id> *dic = self.contentsArray[row];
|
||||
if (self.contentsArray.count > column) {
|
||||
return [NSString stringWithFormat:@"%@",[dic objectForKey:self.columnsArray[column]]];
|
||||
}
|
||||
}
|
||||
return @"";
|
||||
}
|
||||
|
||||
- (NSArray *)contentAtRow:(NSInteger)row
|
||||
{
|
||||
NSMutableArray *result = [NSMutableArray array];
|
||||
if (self.contentsArray.count > row) {
|
||||
NSDictionary<NSString *, id> *dic = self.contentsArray[row];
|
||||
for (int i = 0; i < self.columnsArray.count; i ++) {
|
||||
[result addObject:dic[self.columnsArray[i]]];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (CGFloat)multiColumnTableView:(FLEXMultiColumnTableView *)tableView
|
||||
heightForContentCellInRow:(NSInteger)row
|
||||
{
|
||||
return 40;
|
||||
}
|
||||
|
||||
- (CGFloat)multiColumnTableView:(FLEXMultiColumnTableView *)tableView
|
||||
widthForContentCellInColumn:(NSInteger)column
|
||||
{
|
||||
return 120;
|
||||
}
|
||||
|
||||
- (CGFloat)heightForTopHeaderInTableView:(FLEXMultiColumnTableView *)tableView
|
||||
{
|
||||
return 40;
|
||||
}
|
||||
|
||||
- (CGFloat)widthForLeftHeaderInTableView:(FLEXMultiColumnTableView *)tableView
|
||||
{
|
||||
NSString *str = [NSString stringWithFormat:@"%lu",(unsigned long)self.contentsArray.count];
|
||||
NSDictionary<NSString *, id> *attrs = @{@"NSFontAttributeName":[UIFont systemFontOfSize:17.0]};
|
||||
CGSize size = [str boundingRectWithSize:CGSizeMake(CGFLOAT_MAX, 14)
|
||||
options:NSStringDrawingUsesLineFragmentOrigin
|
||||
attributes:attrs context:nil].size;
|
||||
return size.width + 20;
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark MultiColumnTableView Delegate
|
||||
|
||||
|
||||
- (void)multiColumnTableView:(FLEXMultiColumnTableView *)tableView didTapLabelWithText:(NSString *)text
|
||||
{
|
||||
FLEXWebViewController * detailViewController = [[FLEXWebViewController alloc] initWithText:text];
|
||||
[self.navigationController pushViewController:detailViewController animated:YES];
|
||||
}
|
||||
|
||||
- (void)multiColumnTableView:(FLEXMultiColumnTableView *)tableView didTapHeaderWithText:(NSString *)text sortType:(FLEXTableColumnHeaderSortType)sortType
|
||||
{
|
||||
|
||||
NSArray<NSDictionary<NSString *, id> *> *sortContentData = [self.contentsArray sortedArrayUsingComparator:^NSComparisonResult(NSDictionary<NSString *, id> * obj1, NSDictionary<NSString *, id> * obj2) {
|
||||
|
||||
if ([obj1 objectForKey:text] == [NSNull null]) {
|
||||
return NSOrderedAscending;
|
||||
}
|
||||
if ([obj2 objectForKey:text] == [NSNull null]) {
|
||||
return NSOrderedDescending;
|
||||
}
|
||||
|
||||
if (![[obj1 objectForKey:text] respondsToSelector:@selector(compare:)] && ![[obj2 objectForKey:text] respondsToSelector:@selector(compare:)]) {
|
||||
return NSOrderedSame;
|
||||
}
|
||||
|
||||
NSComparisonResult result = [[obj1 objectForKey:text] compare:[obj2 objectForKey:text]];
|
||||
|
||||
return result;
|
||||
}];
|
||||
if (sortType == FLEXTableColumnHeaderSortTypeDesc) {
|
||||
NSEnumerator *contentReverseEvumerator = [sortContentData reverseObjectEnumerator];
|
||||
sortContentData = [NSArray arrayWithArray:[contentReverseEvumerator allObjects]];
|
||||
}
|
||||
|
||||
self.contentsArray = sortContentData;
|
||||
[self.multiColumView reloadData];
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark About Transition
|
||||
|
||||
- (void)willTransitionToTraitCollection:(UITraitCollection *)newCollection
|
||||
withTransitionCoordinator:(id <UIViewControllerTransitionCoordinator>)coordinator
|
||||
{
|
||||
[super willTransitionToTraitCollection:newCollection
|
||||
withTransitionCoordinator:coordinator];
|
||||
[coordinator animateAlongsideTransition:^(id <UIViewControllerTransitionCoordinatorContext> context) {
|
||||
if (newCollection.verticalSizeClass == UIUserInterfaceSizeClassCompact) {
|
||||
|
||||
self->_multiColumView.frame = CGRectMake(0, 32, self.view.frame.size.width, self.view.frame.size.height - 32);
|
||||
}
|
||||
else {
|
||||
self->_multiColumView.frame = CGRectMake(0, 64, self.view.frame.size.width, self.view.frame.size.height - 64);
|
||||
}
|
||||
[self.view setNeedsLayout];
|
||||
} completion:nil];
|
||||
}
|
||||
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,17 @@
|
||||
//
|
||||
// FLEXTableLeftCell.h
|
||||
// UICatalog
|
||||
//
|
||||
// Created by Peng Tao on 15/11/24.
|
||||
// Copyright © 2015年 f. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@interface FLEXTableLeftCell : UITableViewCell
|
||||
|
||||
@property (nonatomic, strong) UILabel *titlelabel;
|
||||
|
||||
+ (instancetype)cellWithTableView:(UITableView *)tableView;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,35 @@
|
||||
//
|
||||
// FLEXTableLeftCell.m
|
||||
// UICatalog
|
||||
//
|
||||
// Created by Peng Tao on 15/11/24.
|
||||
// Copyright © 2015年 f. All rights reserved.
|
||||
//
|
||||
|
||||
#import "FLEXTableLeftCell.h"
|
||||
|
||||
@implementation FLEXTableLeftCell
|
||||
|
||||
+ (instancetype)cellWithTableView:(UITableView *)tableView
|
||||
{
|
||||
static NSString *identifier = @"FLEXTableLeftCell";
|
||||
FLEXTableLeftCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier];
|
||||
|
||||
if (!cell) {
|
||||
cell = [[FLEXTableLeftCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier];
|
||||
UILabel *textLabel = [[UILabel alloc] initWithFrame:CGRectZero];
|
||||
textLabel.textAlignment = NSTextAlignmentCenter;
|
||||
textLabel.font = [UIFont systemFontOfSize:13.0];
|
||||
textLabel.backgroundColor = [UIColor clearColor];
|
||||
[cell.contentView addSubview:textLabel];
|
||||
cell.titlelabel = textLabel;
|
||||
}
|
||||
return cell;
|
||||
}
|
||||
|
||||
- (void)layoutSubviews
|
||||
{
|
||||
[super layoutSubviews];
|
||||
self.titlelabel.frame = self.contentView.frame;
|
||||
}
|
||||
@end
|
||||
@@ -0,0 +1,16 @@
|
||||
//
|
||||
// PTTableListViewController.h
|
||||
// PTDatabaseReader
|
||||
//
|
||||
// Created by Peng Tao on 15/11/23.
|
||||
// Copyright © 2015年 Peng Tao. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@interface FLEXTableListViewController : UITableViewController
|
||||
|
||||
+ (BOOL)supportsExtension:(NSString *)extension;
|
||||
- (instancetype)initWithPath:(NSString *)path;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,137 @@
|
||||
//
|
||||
// PTTableListViewController.m
|
||||
// PTDatabaseReader
|
||||
//
|
||||
// Created by Peng Tao on 15/11/23.
|
||||
// Copyright © 2015年 Peng Tao. All rights reserved.
|
||||
//
|
||||
|
||||
#import "FLEXTableListViewController.h"
|
||||
|
||||
#import "FLEXDatabaseManager.h"
|
||||
#import "FLEXSQLiteDatabaseManager.h"
|
||||
#import "FLEXRealmDatabaseManager.h"
|
||||
|
||||
#import "FLEXTableContentViewController.h"
|
||||
|
||||
@interface FLEXTableListViewController ()
|
||||
{
|
||||
id<FLEXDatabaseManager> _dbm;
|
||||
NSString *_databasePath;
|
||||
}
|
||||
|
||||
@property (nonatomic, strong) NSArray<NSString *> *tables;
|
||||
|
||||
+ (NSArray<NSString *> *)supportedSQLiteExtensions;
|
||||
+ (NSArray<NSString *> *)supportedRealmExtensions;
|
||||
|
||||
@end
|
||||
|
||||
@implementation FLEXTableListViewController
|
||||
|
||||
- (instancetype)initWithPath:(NSString *)path
|
||||
{
|
||||
self = [super initWithStyle:UITableViewStyleGrouped];
|
||||
if (self) {
|
||||
_databasePath = [path copy];
|
||||
_dbm = [self databaseManagerForFileAtPath:_databasePath];
|
||||
[_dbm open];
|
||||
[self getAllTables];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (id<FLEXDatabaseManager>)databaseManagerForFileAtPath:(NSString *)path
|
||||
{
|
||||
NSString *pathExtension = path.pathExtension.lowercaseString;
|
||||
|
||||
NSArray<NSString *> *sqliteExtensions = [FLEXTableListViewController supportedSQLiteExtensions];
|
||||
if ([sqliteExtensions indexOfObject:pathExtension] != NSNotFound) {
|
||||
return [[FLEXSQLiteDatabaseManager alloc] initWithPath:path];
|
||||
}
|
||||
|
||||
NSArray<NSString *> *realmExtensions = [FLEXTableListViewController supportedRealmExtensions];
|
||||
if (realmExtensions != nil && [realmExtensions indexOfObject:pathExtension] != NSNotFound) {
|
||||
return [[FLEXRealmDatabaseManager alloc] initWithPath:path];
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (void)getAllTables
|
||||
{
|
||||
NSArray<NSDictionary<NSString *, id> *> *resultArray = [_dbm queryAllTables];
|
||||
NSMutableArray<NSString *> *array = [NSMutableArray array];
|
||||
for (NSDictionary<NSString *, id> *dict in resultArray) {
|
||||
NSString *columnName = (NSString *)dict[@"name"] ?: @"";
|
||||
[array addObject:columnName];
|
||||
}
|
||||
self.tables = array;
|
||||
}
|
||||
|
||||
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
|
||||
{
|
||||
return self.tables.count;
|
||||
}
|
||||
|
||||
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"FLEXTableListViewControllerCell"];
|
||||
if (!cell) {
|
||||
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
|
||||
reuseIdentifier:@"FLEXTableListViewControllerCell"];
|
||||
}
|
||||
cell.textLabel.text = self.tables[indexPath.row];
|
||||
return cell;
|
||||
}
|
||||
|
||||
|
||||
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
FLEXTableContentViewController *contentViewController = [[FLEXTableContentViewController alloc] init];
|
||||
|
||||
contentViewController.contentsArray = [_dbm queryAllDataWithTableName:self.tables[indexPath.row]];
|
||||
contentViewController.columnsArray = [_dbm queryAllColumnsWithTableName:self.tables[indexPath.row]];
|
||||
|
||||
contentViewController.title = self.tables[indexPath.row];
|
||||
[self.navigationController pushViewController:contentViewController animated:YES];
|
||||
}
|
||||
|
||||
|
||||
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
|
||||
{
|
||||
return [NSString stringWithFormat:@"%lu tables", (unsigned long)self.tables.count];
|
||||
}
|
||||
|
||||
+ (BOOL)supportsExtension:(NSString *)extension
|
||||
{
|
||||
extension = extension.lowercaseString;
|
||||
|
||||
NSArray<NSString *> *sqliteExtensions = [FLEXTableListViewController supportedSQLiteExtensions];
|
||||
if (sqliteExtensions.count > 0 && [sqliteExtensions indexOfObject:extension] != NSNotFound) {
|
||||
return YES;
|
||||
}
|
||||
|
||||
NSArray<NSString *> *realmExtensions = [FLEXTableListViewController supportedRealmExtensions];
|
||||
if (realmExtensions.count > 0 && [realmExtensions indexOfObject:extension] != NSNotFound) {
|
||||
return YES;
|
||||
}
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
+ (NSArray<NSString *> *)supportedSQLiteExtensions
|
||||
{
|
||||
return @[@"db", @"sqlite", @"sqlite3"];
|
||||
}
|
||||
|
||||
+ (NSArray<NSString *> *)supportedRealmExtensions
|
||||
{
|
||||
if (NSClassFromString(@"RLMRealm") == nil) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
return @[@"realm"];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,21 @@
|
||||
|
||||
FMDB
|
||||
Copyright (c) 2008-2014 Flying Meat Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
@@ -14,8 +14,8 @@
|
||||
|
||||
@interface FLEXClassesTableViewController () <UISearchBarDelegate>
|
||||
|
||||
@property (nonatomic, strong) NSArray *classNames;
|
||||
@property (nonatomic, strong) NSArray *filteredClassNames;
|
||||
@property (nonatomic, strong) NSArray<NSString *> *classNames;
|
||||
@property (nonatomic, strong) NSArray<NSString *> *filteredClassNames;
|
||||
@property (nonatomic, strong) UISearchBar *searchBar;
|
||||
|
||||
@end
|
||||
@@ -42,7 +42,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setClassNames:(NSArray *)classNames
|
||||
- (void)setClassNames:(NSArray<NSString *> *)classNames
|
||||
{
|
||||
_classNames = classNames;
|
||||
self.filteredClassNames = classNames;
|
||||
@@ -53,7 +53,7 @@
|
||||
unsigned int classNamesCount = 0;
|
||||
const char **classNames = objc_copyClassNamesForImage([self.binaryImageName UTF8String], &classNamesCount);
|
||||
if (classNames) {
|
||||
NSMutableArray *classNameStrings = [NSMutableArray array];
|
||||
NSMutableArray<NSString *> *classNameStrings = [NSMutableArray array];
|
||||
for (unsigned int i = 0; i < classNamesCount; i++) {
|
||||
const char *className = classNames[i];
|
||||
NSString *classNameString = [NSString stringWithUTF8String:className];
|
||||
@@ -68,7 +68,7 @@
|
||||
|
||||
- (void)updateTitle
|
||||
{
|
||||
NSString *shortImageName = [[self.binaryImageName componentsSeparatedByString:@"/"] lastObject];
|
||||
NSString *shortImageName = self.binaryImageName.lastPathComponent;
|
||||
self.title = [NSString stringWithFormat:@"%@ Classes (%lu)", shortImageName, (unsigned long)[self.filteredClassNames count]];
|
||||
}
|
||||
|
||||
@@ -131,7 +131,7 @@
|
||||
|
||||
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
NSString *className = [self.filteredClassNames objectAtIndex:indexPath.row];
|
||||
NSString *className = self.filteredClassNames[indexPath.row];
|
||||
Class selectedClass = objc_getClass([className UTF8String]);
|
||||
FLEXObjectExplorerViewController *objectExplorer = [FLEXObjectExplorerFactory explorerViewControllerForObject:selectedClass];
|
||||
[self.navigationController pushViewController:objectExplorer animated:YES];
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
//
|
||||
// FLEXCookiesTableViewController.h
|
||||
// FLEX
|
||||
//
|
||||
// Created by Rich Robinson on 19/10/2015.
|
||||
// Copyright © 2015 Flipboard. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@interface FLEXCookiesTableViewController : UITableViewController
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,71 @@
|
||||
//
|
||||
// FLEXCookiesTableViewController.m
|
||||
// FLEX
|
||||
//
|
||||
// Created by Rich Robinson on 19/10/2015.
|
||||
// Copyright © 2015 Flipboard. All rights reserved.
|
||||
//
|
||||
|
||||
#import "FLEXCookiesTableViewController.h"
|
||||
#import "FLEXObjectExplorerFactory.h"
|
||||
#import "FLEXUtility.h"
|
||||
|
||||
@interface FLEXCookiesTableViewController ()
|
||||
|
||||
@property (nonatomic, strong) NSArray<NSHTTPCookie *> *cookies;
|
||||
|
||||
@end
|
||||
|
||||
@implementation FLEXCookiesTableViewController
|
||||
|
||||
- (id)initWithStyle:(UITableViewStyle)style {
|
||||
self = [super initWithStyle:style];
|
||||
|
||||
if (self) {
|
||||
self.title = @"Cookies";
|
||||
|
||||
NSSortDescriptor *nameSortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"name" ascending:YES selector:@selector(caseInsensitiveCompare:)];
|
||||
_cookies = [[NSHTTPCookieStorage sharedHTTPCookieStorage].cookies sortedArrayUsingDescriptors:@[nameSortDescriptor]];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSHTTPCookie *)cookieForRowAtIndexPath:(NSIndexPath *)indexPath {
|
||||
return self.cookies[indexPath.row];
|
||||
}
|
||||
|
||||
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
|
||||
return 1;
|
||||
}
|
||||
|
||||
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
|
||||
return self.cookies.count;
|
||||
}
|
||||
|
||||
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
|
||||
static NSString *CellIdentifier = @"Cell";
|
||||
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
|
||||
if (!cell) {
|
||||
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
|
||||
cell.textLabel.font = [FLEXUtility defaultTableViewCellLabelFont];
|
||||
cell.detailTextLabel.font = [FLEXUtility defaultTableViewCellLabelFont];
|
||||
cell.detailTextLabel.textColor = [UIColor grayColor];
|
||||
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
|
||||
}
|
||||
|
||||
NSHTTPCookie *cookie = [self cookieForRowAtIndexPath:indexPath];
|
||||
cell.textLabel.text = [NSString stringWithFormat:@"%@ (%@)", cookie.name, cookie.value];
|
||||
cell.detailTextLabel.text = cookie.domain;
|
||||
|
||||
return cell;
|
||||
}
|
||||
|
||||
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
|
||||
NSHTTPCookie *cookie = [self cookieForRowAtIndexPath:indexPath];
|
||||
UIViewController *cookieViewController = (UIViewController *)[FLEXObjectExplorerFactory explorerViewControllerForObject:cookie];
|
||||
|
||||
[self.navigationController pushViewController:cookieViewController animated:YES];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -20,6 +20,6 @@
|
||||
|
||||
@protocol FLEXFileBrowserSearchOperationDelegate <NSObject>
|
||||
|
||||
- (void)fileBrowserSearchOperationResult:(NSArray *)searchResult size:(uint64_t)size;
|
||||
- (void)fileBrowserSearchOperationResult:(NSArray<NSString *> *)searchResult size:(uint64_t)size;
|
||||
|
||||
@end
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
- (uint64_t)totalSizeAtPath:(NSString *)path
|
||||
{
|
||||
NSFileManager *fileManager = [NSFileManager defaultManager];
|
||||
NSDictionary *attributes = [fileManager attributesOfItemAtPath:path error:NULL];
|
||||
NSDictionary<NSString *, id> *attributes = [fileManager attributesOfItemAtPath:path error:NULL];
|
||||
uint64_t totalSize = [attributes fileSize];
|
||||
|
||||
for (NSString *fileName in [fileManager enumeratorAtPath:path]) {
|
||||
@@ -65,16 +65,16 @@
|
||||
- (void)main
|
||||
{
|
||||
NSFileManager *fileManager = [NSFileManager defaultManager];
|
||||
NSMutableArray *searchPaths = [NSMutableArray array];
|
||||
NSMutableDictionary *sizeMapping = [NSMutableDictionary dictionary];
|
||||
NSMutableArray<NSString *> *searchPaths = [NSMutableArray array];
|
||||
NSMutableDictionary<NSString *, NSNumber *> *sizeMapping = [NSMutableDictionary dictionary];
|
||||
uint64_t totalSize = 0;
|
||||
NSMutableArray *stack = [NSMutableArray array];
|
||||
NSMutableArray<NSString *> *stack = [NSMutableArray array];
|
||||
[stack flex_push:self.path];
|
||||
|
||||
//recursive found all match searchString paths, and precomputing there size
|
||||
while ([stack count]) {
|
||||
NSString *currentPath = [stack flex_pop];
|
||||
NSArray *directoryPath = [fileManager contentsOfDirectoryAtPath:currentPath error:nil];
|
||||
NSArray<NSString *> *directoryPath = [fileManager contentsOfDirectoryAtPath:currentPath error:nil];
|
||||
|
||||
for (NSString *subPath in directoryPath) {
|
||||
NSString *fullPath = [currentPath stringByAppendingPathComponent:subPath];
|
||||
@@ -99,7 +99,7 @@
|
||||
}
|
||||
|
||||
//sort
|
||||
NSArray *sortedArray = [searchPaths sortedArrayUsingComparator:^NSComparisonResult(NSString *path1, NSString *path2) {
|
||||
NSArray<NSString *> *sortedArray = [searchPaths sortedArrayUsingComparator:^NSComparisonResult(NSString *path1, NSString *path2) {
|
||||
uint64_t pathSize1 = [sizeMapping[path1] unsignedLongLongValue];
|
||||
uint64_t pathSize2 = [sizeMapping[path2] unsignedLongLongValue];
|
||||
if (pathSize1 < pathSize2) {
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
#import "FLEXFileBrowserSearchOperation.h"
|
||||
|
||||
@interface FLEXFileBrowserTableViewController : UITableViewController <UISearchDisplayDelegate, FLEXFileBrowserSearchOperationDelegate>
|
||||
@interface FLEXFileBrowserTableViewController : UITableViewController
|
||||
|
||||
- (id)initWithPath:(NSString *)path;
|
||||
|
||||
|
||||
@@ -11,22 +11,19 @@
|
||||
#import "FLEXUtility.h"
|
||||
#import "FLEXWebViewController.h"
|
||||
#import "FLEXImagePreviewViewController.h"
|
||||
#import "FLEXTableListViewController.h"
|
||||
|
||||
@interface FLEXFileBrowserTableViewCell : UITableViewCell
|
||||
@end
|
||||
|
||||
@interface FLEXFileBrowserTableViewController () <FLEXFileBrowserFileOperationControllerDelegate>
|
||||
@interface FLEXFileBrowserTableViewController () <FLEXFileBrowserFileOperationControllerDelegate, FLEXFileBrowserSearchOperationDelegate, UISearchResultsUpdating, UISearchControllerDelegate>
|
||||
|
||||
@property (nonatomic, copy) NSString *path;
|
||||
@property (nonatomic, copy) NSArray *childPaths;
|
||||
@property (nonatomic, copy) NSString *searchString;
|
||||
@property (nonatomic, strong) NSArray *searchPaths;
|
||||
@property (nonatomic, copy) NSArray<NSString *> *childPaths;
|
||||
@property (nonatomic, strong) NSArray<NSString *> *searchPaths;
|
||||
@property (nonatomic, strong) NSNumber *recursiveSize;
|
||||
@property (nonatomic, strong) NSNumber *searchPathsSize;
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
||||
@property (nonatomic, strong) UISearchDisplayController *searchController;
|
||||
#pragma clang diagnostic pop
|
||||
@property (nonatomic, strong) UISearchController *searchController;
|
||||
@property (nonatomic) NSOperationQueue *operationQueue;
|
||||
@property (nonatomic, strong) UIDocumentInteractionController *documentController;
|
||||
@property (nonatomic, strong) id<FLEXFileBrowserFileOperationController> fileOperationController;
|
||||
@@ -47,36 +44,30 @@
|
||||
self.path = path;
|
||||
self.title = [path lastPathComponent];
|
||||
self.operationQueue = [NSOperationQueue new];
|
||||
|
||||
//add search controller
|
||||
UISearchBar *searchBar = [UISearchBar new];
|
||||
[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 = [[UISearchController alloc] initWithSearchResultsController:nil];
|
||||
self.searchController.searchResultsUpdater = self;
|
||||
self.searchController.delegate = self;
|
||||
self.searchController.searchResultsDataSource = self;
|
||||
self.searchController.searchResultsDelegate = self;
|
||||
self.searchController.dimsBackgroundDuringPresentation = NO;
|
||||
self.tableView.tableHeaderView = self.searchController.searchBar;
|
||||
|
||||
|
||||
//computing path size
|
||||
FLEXFileBrowserTableViewController *__weak weakSelf = self;
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||
NSFileManager *fileManager = [NSFileManager defaultManager];
|
||||
NSDictionary *attributes = [fileManager attributesOfItemAtPath:path error:NULL];
|
||||
NSDictionary<NSString *, id> *attributes = [fileManager attributesOfItemAtPath:path error:NULL];
|
||||
uint64_t totalSize = [attributes fileSize];
|
||||
|
||||
|
||||
for (NSString *fileName in [fileManager enumeratorAtPath:path]) {
|
||||
attributes = [fileManager attributesOfItemAtPath:[path stringByAppendingPathComponent:fileName] error:NULL];
|
||||
totalSize += [attributes fileSize];
|
||||
|
||||
|
||||
// Bail if the interested view controller has gone away.
|
||||
if (!weakSelf) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
FLEXFileBrowserTableViewController *__strong strongSelf = weakSelf;
|
||||
strongSelf.recursiveSize = @(totalSize);
|
||||
@@ -95,33 +86,28 @@
|
||||
{
|
||||
[super viewDidLoad];
|
||||
|
||||
UIMenuItem *renameMenuItem = [[UIMenuItem alloc] initWithTitle:@"Rename" action:@selector(fileBrowserRename:)];
|
||||
UIMenuItem *deleteMenuItem = [[UIMenuItem alloc] initWithTitle:@"Delete" action:@selector(fileBrowserDelete:)];
|
||||
[UIMenuController sharedMenuController].menuItems = @[renameMenuItem, deleteMenuItem];
|
||||
}
|
||||
|
||||
#pragma mark - FLEXFileBrowserSearchOperationDelegate
|
||||
|
||||
- (void)fileBrowserSearchOperationResult:(NSArray *)searchResult size:(uint64_t)size
|
||||
- (void)fileBrowserSearchOperationResult:(NSArray<NSString *> *)searchResult size:(uint64_t)size
|
||||
{
|
||||
self.searchPaths = searchResult;
|
||||
self.searchPathsSize = @(size);
|
||||
[self.searchController.searchResultsTableView reloadData];
|
||||
[self.tableView reloadData];
|
||||
}
|
||||
|
||||
#pragma mark - UISearchDisplayDelegate
|
||||
#pragma mark - UISearchResultsUpdating
|
||||
|
||||
- (BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchString:(NSString *)searchString
|
||||
- (void)updateSearchResultsForSearchController:(UISearchController *)searchController
|
||||
{
|
||||
self.searchString = searchString;
|
||||
[self reloadSearchPaths];
|
||||
|
||||
return YES;
|
||||
[self reloadDisplayedPaths];
|
||||
}
|
||||
|
||||
- (void)searchDisplayController:(UISearchDisplayController *)controller willHideSearchResultsTableView:(UITableView *)tableView
|
||||
#pragma mark - UISearchControllerDelegate
|
||||
|
||||
- (void)willDismissSearchController:(UISearchController *)searchController
|
||||
{
|
||||
//confirm to clear all operations
|
||||
[self.operationQueue cancelAllOperations];
|
||||
[self reloadChildPaths];
|
||||
[self.tableView reloadData];
|
||||
@@ -137,47 +123,29 @@
|
||||
|
||||
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
|
||||
{
|
||||
if (tableView == self.tableView) {
|
||||
return [self.childPaths count];
|
||||
} else {
|
||||
return [self.searchPaths count];
|
||||
}
|
||||
return self.searchController.isActive ? [self.searchPaths count] : [self.childPaths count];
|
||||
}
|
||||
|
||||
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
|
||||
{
|
||||
NSNumber *currentSize = nil;
|
||||
NSArray *currentPaths = nil;
|
||||
|
||||
if (tableView == self.tableView) {
|
||||
currentSize = self.recursiveSize;
|
||||
currentPaths = self.childPaths;
|
||||
} else {
|
||||
currentSize = self.searchPathsSize;
|
||||
currentPaths = self.searchPaths;
|
||||
}
|
||||
|
||||
BOOL isSearchActive = self.searchController.isActive;
|
||||
NSNumber *currentSize = isSearchActive ? self.searchPathsSize : self.recursiveSize;
|
||||
NSArray<NSString *> *currentPaths = isSearchActive ? self.searchPaths : self.childPaths;
|
||||
|
||||
NSString *sizeString = nil;
|
||||
if (!currentSize) {
|
||||
sizeString = @"Computing size…";
|
||||
} else {
|
||||
sizeString = [NSByteCountFormatter stringFromByteCount:[currentSize longLongValue] countStyle:NSByteCountFormatterCountStyleFile];
|
||||
}
|
||||
|
||||
|
||||
return [NSString stringWithFormat:@"%lu files (%@)", (unsigned long)[currentPaths count], sizeString];
|
||||
}
|
||||
|
||||
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
NSString *fullPath = nil;
|
||||
if (tableView == self.tableView) {
|
||||
NSString *subpath = [self.childPaths objectAtIndex:indexPath.row];
|
||||
fullPath = [self.path stringByAppendingPathComponent:subpath];
|
||||
} else {
|
||||
fullPath = [self.searchPaths objectAtIndex:indexPath.row];
|
||||
}
|
||||
|
||||
NSDictionary *attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:fullPath error:NULL];
|
||||
NSString *fullPath = [self filePathAtIndexPath:indexPath];
|
||||
NSDictionary<NSString *, id> *attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:fullPath error:NULL];
|
||||
BOOL isDirectory = [[attributes fileType] isEqual:NSFileTypeDirectory];
|
||||
NSString *subtitle = nil;
|
||||
if (isDirectory) {
|
||||
@@ -187,15 +155,15 @@
|
||||
NSString *sizeString = [NSByteCountFormatter stringFromByteCount:[attributes fileSize] countStyle:NSByteCountFormatterCountStyleFile];
|
||||
subtitle = [NSString stringWithFormat:@"%@ - %@", sizeString, [attributes fileModificationDate]];
|
||||
}
|
||||
|
||||
|
||||
static NSString *textCellIdentifier = @"textCell";
|
||||
static NSString *imageCellIdentifier = @"imageCell";
|
||||
UITableViewCell *cell = nil;
|
||||
|
||||
|
||||
// Separate image and text only cells because otherwise the separator lines get out-of-whack on image cells reused with text only.
|
||||
BOOL showImagePreview = [FLEXUtility isImagePathExtension:[fullPath pathExtension]];
|
||||
NSString *cellIdentifier = showImagePreview ? imageCellIdentifier : textCellIdentifier;
|
||||
|
||||
|
||||
if (!cell) {
|
||||
cell = [[FLEXFileBrowserTableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:cellIdentifier];
|
||||
cell.textLabel.font = [FLEXUtility defaultTableViewCellLabelFont];
|
||||
@@ -206,61 +174,57 @@
|
||||
NSString *cellTitle = [fullPath lastPathComponent];
|
||||
cell.textLabel.text = cellTitle;
|
||||
cell.detailTextLabel.text = subtitle;
|
||||
|
||||
|
||||
if (showImagePreview) {
|
||||
cell.imageView.contentMode = UIViewContentModeScaleAspectFit;
|
||||
cell.imageView.image = [UIImage imageWithContentsOfFile:fullPath];
|
||||
}
|
||||
|
||||
|
||||
return cell;
|
||||
}
|
||||
|
||||
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
NSString *subpath = nil;
|
||||
NSString *fullPath = nil;
|
||||
|
||||
if (tableView == self.tableView) {
|
||||
subpath = [self.childPaths objectAtIndex:indexPath.row];
|
||||
fullPath = [self.path stringByAppendingPathComponent:subpath];
|
||||
} else {
|
||||
fullPath = [self.searchPaths objectAtIndex:indexPath.row];
|
||||
subpath = [fullPath lastPathComponent];
|
||||
}
|
||||
|
||||
NSString *fullPath = [self filePathAtIndexPath:indexPath];
|
||||
NSString *subpath = [fullPath lastPathComponent];
|
||||
NSString *pathExtension = [subpath pathExtension];
|
||||
|
||||
BOOL isDirectory = NO;
|
||||
BOOL stillExists = [[NSFileManager defaultManager] fileExistsAtPath:fullPath isDirectory:&isDirectory];
|
||||
if (stillExists) {
|
||||
UIViewController *drillInViewController = nil;
|
||||
if (isDirectory) {
|
||||
drillInViewController = [[[self class] alloc] initWithPath:fullPath];
|
||||
} else if ([FLEXUtility isImagePathExtension:[fullPath pathExtension]]) {
|
||||
} else if ([FLEXUtility isImagePathExtension:pathExtension]) {
|
||||
UIImage *image = [UIImage imageWithContentsOfFile:fullPath];
|
||||
drillInViewController = [[FLEXImagePreviewViewController alloc] initWithImage:image];
|
||||
} else {
|
||||
// Special case keyed archives, json, and plists to get more readable data.
|
||||
NSString *prettyString = nil;
|
||||
if ([[subpath pathExtension] isEqual:@"archive"]) {
|
||||
if ([pathExtension isEqual:@"archive"] || [pathExtension isEqual:@"coded"]) {
|
||||
prettyString = [[NSKeyedUnarchiver unarchiveObjectWithFile:fullPath] description];
|
||||
} else if ([[subpath pathExtension] isEqualToString:@"json"]) {
|
||||
} else if ([pathExtension isEqualToString:@"json"]) {
|
||||
prettyString = [FLEXUtility prettyJSONStringFromData:[NSData dataWithContentsOfFile:fullPath]];
|
||||
} else if ([[subpath pathExtension] isEqualToString:@"plist"]) {
|
||||
} else if ([pathExtension isEqualToString:@"plist"]) {
|
||||
NSData *fileData = [NSData dataWithContentsOfFile:fullPath];
|
||||
prettyString = [[NSPropertyListSerialization propertyListWithData:fileData options:0 format:NULL error:NULL] description];
|
||||
}
|
||||
|
||||
|
||||
if ([prettyString length] > 0) {
|
||||
drillInViewController = [[FLEXWebViewController alloc] initWithText:prettyString];
|
||||
} else if ([FLEXWebViewController supportsPathExtension:[subpath pathExtension]]) {
|
||||
} else if ([FLEXWebViewController supportsPathExtension:pathExtension]) {
|
||||
drillInViewController = [[FLEXWebViewController alloc] initWithURL:[NSURL fileURLWithPath:fullPath]];
|
||||
} else {
|
||||
} else if ([FLEXTableListViewController supportsExtension:subpath.pathExtension]) {
|
||||
drillInViewController = [[FLEXTableListViewController alloc] initWithPath:fullPath];
|
||||
}
|
||||
else {
|
||||
NSString *fileString = [NSString stringWithContentsOfFile:fullPath encoding:NSUTF8StringEncoding error:NULL];
|
||||
if ([fileString length] > 0) {
|
||||
drillInViewController = [[FLEXWebViewController alloc] initWithText:fileString];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (drillInViewController) {
|
||||
drillInViewController.title = [subpath lastPathComponent];
|
||||
[self.navigationController pushViewController:drillInViewController animated:YES];
|
||||
@@ -276,12 +240,25 @@
|
||||
|
||||
- (BOOL)tableView:(UITableView *)tableView shouldShowMenuForRowAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
UIMenuItem *renameMenuItem = [[UIMenuItem alloc] initWithTitle:@"Rename" action:@selector(fileBrowserRename:)];
|
||||
UIMenuItem *deleteMenuItem = [[UIMenuItem alloc] initWithTitle:@"Delete" action:@selector(fileBrowserDelete:)];
|
||||
NSMutableArray *menus = [NSMutableArray arrayWithObjects:renameMenuItem, deleteMenuItem, nil];
|
||||
|
||||
NSString *fullPath = [self filePathAtIndexPath:indexPath];
|
||||
NSError *error = nil;
|
||||
NSDictionary *attributes = [NSFileManager.defaultManager attributesOfItemAtPath:fullPath error:&error];
|
||||
if (error == nil && [attributes fileType] != NSFileTypeDirectory) {
|
||||
UIMenuItem *shareMenuItem = [[UIMenuItem alloc] initWithTitle:@"Share" action:@selector(fileBrowserShare:)];
|
||||
[menus addObject:shareMenuItem];
|
||||
}
|
||||
[UIMenuController sharedMenuController].menuItems = menus;
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)tableView:(UITableView *)tableView canPerformAction:(SEL)action forRowAtIndexPath:(NSIndexPath *)indexPath withSender:(id)sender
|
||||
{
|
||||
return action == @selector(fileBrowserDelete:) || action == @selector(fileBrowserRename:);
|
||||
return action == @selector(fileBrowserDelete:) || action == @selector(fileBrowserRename:) || action == @selector(fileBrowserShare:);
|
||||
}
|
||||
|
||||
- (void)tableView:(UITableView *)tableView performAction:(SEL)action forRowAtIndexPath:(NSIndexPath *)indexPath withSender:(id)sender
|
||||
@@ -309,16 +286,8 @@
|
||||
|
||||
- (void)fileBrowserRename:(UITableViewCell *)sender
|
||||
{
|
||||
NSString *fullPath = nil;
|
||||
|
||||
NSIndexPath *indexPath = [self.tableView indexPathForCell:sender];
|
||||
if (indexPath) {
|
||||
NSString *subpath = [self.childPaths objectAtIndex:indexPath.row];
|
||||
fullPath = [self.path stringByAppendingPathComponent:subpath];
|
||||
} else {
|
||||
indexPath = [self.searchController.searchResultsTableView indexPathForCell:sender];
|
||||
fullPath = [self.searchPaths objectAtIndex:indexPath.row];
|
||||
}
|
||||
NSString *fullPath = [self filePathAtIndexPath:indexPath];
|
||||
|
||||
self.fileOperationController = [[FLEXFileBrowserFileRenameOperationController alloc] initWithPath:fullPath];
|
||||
self.fileOperationController.delegate = self;
|
||||
@@ -327,36 +296,41 @@
|
||||
|
||||
- (void)fileBrowserDelete:(UITableViewCell *)sender
|
||||
{
|
||||
NSString *fullPath = nil;
|
||||
|
||||
NSIndexPath *indexPath = [self.tableView indexPathForCell:sender];
|
||||
if (indexPath) {
|
||||
NSString *subpath = [self.childPaths objectAtIndex:indexPath.row];
|
||||
fullPath = [self.path stringByAppendingPathComponent:subpath];
|
||||
} else {
|
||||
indexPath = [self.searchController.searchResultsTableView indexPathForCell:sender];
|
||||
fullPath = [self.searchPaths objectAtIndex:indexPath.row];
|
||||
}
|
||||
NSString *fullPath = [self filePathAtIndexPath:indexPath];
|
||||
|
||||
self.fileOperationController = [[FLEXFileBrowserFileDeleteOperationController alloc] initWithPath:fullPath];
|
||||
self.fileOperationController.delegate = self;
|
||||
[self.fileOperationController show];
|
||||
}
|
||||
|
||||
- (void)fileBrowserShare:(UITableViewCell *)sender
|
||||
{
|
||||
NSIndexPath *indexPath = [self.tableView indexPathForCell:sender];
|
||||
NSString *fullPath = [self filePathAtIndexPath:indexPath];
|
||||
|
||||
UIActivityViewController *activityViewController = [[UIActivityViewController alloc] initWithActivityItems:@[fullPath] applicationActivities:nil];
|
||||
[self presentViewController:activityViewController animated:true completion:nil];
|
||||
}
|
||||
|
||||
- (void)reloadDisplayedPaths
|
||||
{
|
||||
if (self.searchController.isActive) {
|
||||
[self reloadSearchPaths];
|
||||
[self.searchController.searchResultsTableView reloadData];
|
||||
} else {
|
||||
[self reloadChildPaths];
|
||||
[self.tableView reloadData];
|
||||
}
|
||||
[self.tableView reloadData];
|
||||
}
|
||||
|
||||
- (void)reloadChildPaths
|
||||
{
|
||||
self.childPaths = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:self.path error:NULL];
|
||||
NSMutableArray<NSString *> *childPaths = [NSMutableArray array];
|
||||
NSArray<NSString *> *subpaths = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:self.path error:NULL];
|
||||
for (NSString *subpath in subpaths) {
|
||||
[childPaths addObject:[self.path stringByAppendingPathComponent:subpath]];
|
||||
}
|
||||
self.childPaths = childPaths;
|
||||
}
|
||||
|
||||
- (void)reloadSearchPaths
|
||||
@@ -366,11 +340,16 @@
|
||||
|
||||
//clear pre search request and start a new one
|
||||
[self.operationQueue cancelAllOperations];
|
||||
FLEXFileBrowserSearchOperation *newOperation = [[FLEXFileBrowserSearchOperation alloc] initWithPath:self.path searchString:self.searchString];
|
||||
FLEXFileBrowserSearchOperation *newOperation = [[FLEXFileBrowserSearchOperation alloc] initWithPath:self.path searchString:self.searchController.searchBar.text];
|
||||
newOperation.delegate = self;
|
||||
[self.operationQueue addOperation:newOperation];
|
||||
}
|
||||
|
||||
- (NSString *)filePathAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
return self.searchController.isActive ? self.searchPaths[indexPath.row] : self.childPaths[indexPath.row];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@@ -388,4 +367,10 @@
|
||||
[[UIApplication sharedApplication] sendAction:_cmd to:target from:self forEvent:nil];
|
||||
}
|
||||
|
||||
- (void)fileBrowserShare:(UIMenuController *)sender
|
||||
{
|
||||
id target = [self.nextResponder targetForAction:_cmd withSender:sender];
|
||||
[[UIApplication sharedApplication] sendAction:_cmd to:target from:self forEvent:nil];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#import "FLEXObjectExplorerFactory.h"
|
||||
#import "FLEXLiveObjectsTableViewController.h"
|
||||
#import "FLEXFileBrowserTableViewController.h"
|
||||
#import "FLEXCookiesTableViewController.h"
|
||||
#import "FLEXGlobalsTableViewControllerEntry.h"
|
||||
#import "FLEXManager+Private.h"
|
||||
#import "FLEXSystemLogTableViewController.h"
|
||||
@@ -26,11 +27,13 @@ typedef NS_ENUM(NSUInteger, FLEXGlobalsRow) {
|
||||
FLEXGlobalsRowSystemLog,
|
||||
FLEXGlobalsRowLiveObjects,
|
||||
FLEXGlobalsRowFileBrowser,
|
||||
FLEXGlobalsCookies,
|
||||
FLEXGlobalsRowSystemLibraries,
|
||||
FLEXGlobalsRowAppClasses,
|
||||
FLEXGlobalsRowAppDelegate,
|
||||
FLEXGlobalsRowRootViewController,
|
||||
FLEXGlobalsRowUserDefaults,
|
||||
FLEXGlobalsRowMainBundle,
|
||||
FLEXGlobalsRowApplication,
|
||||
FLEXGlobalsRowKeyWindow,
|
||||
FLEXGlobalsRowMainScreen,
|
||||
@@ -40,17 +43,15 @@ typedef NS_ENUM(NSUInteger, FLEXGlobalsRow) {
|
||||
|
||||
@interface FLEXGlobalsTableViewController ()
|
||||
|
||||
/// [FLEXGlobalsTableViewControllerEntry *]
|
||||
@property (nonatomic, readonly, copy) NSArray *entries;
|
||||
@property (nonatomic, readonly, copy) NSArray<FLEXGlobalsTableViewControllerEntry *> *entries;
|
||||
|
||||
@end
|
||||
|
||||
@implementation FLEXGlobalsTableViewController
|
||||
|
||||
/// [FLEXGlobalsTableViewControllerEntry *]
|
||||
+ (NSArray *)defaultGlobalEntries
|
||||
+ (NSArray<FLEXGlobalsTableViewControllerEntry *> *)defaultGlobalEntries
|
||||
{
|
||||
NSMutableArray *defaultGlobalEntries = [NSMutableArray array];
|
||||
NSMutableArray<FLEXGlobalsTableViewControllerEntry *> *defaultGlobalEntries = [NSMutableArray array];
|
||||
|
||||
for (FLEXGlobalsRow defaultRowIndex = 0; defaultRowIndex < FLEXGlobalsRowCount; defaultRowIndex++) {
|
||||
FLEXGlobalsTableViewControllerEntryNameFuture titleFuture = nil;
|
||||
@@ -123,6 +124,16 @@ typedef NS_ENUM(NSUInteger, FLEXGlobalsRow) {
|
||||
};
|
||||
break;
|
||||
|
||||
case FLEXGlobalsRowMainBundle:
|
||||
titleFuture = ^NSString *{
|
||||
return @"📦 +[NSBundle mainBundle]";
|
||||
};
|
||||
viewControllerFuture = ^UIViewController *{
|
||||
NSBundle *mainBundle = [NSBundle mainBundle];
|
||||
return [FLEXObjectExplorerFactory explorerViewControllerForObject:mainBundle];
|
||||
};
|
||||
break;
|
||||
|
||||
case FLEXGlobalsRowApplication:
|
||||
titleFuture = ^NSString *{
|
||||
return @"💾 +[UIApplication sharedApplication]";
|
||||
@@ -162,6 +173,15 @@ typedef NS_ENUM(NSUInteger, FLEXGlobalsRow) {
|
||||
};
|
||||
break;
|
||||
|
||||
case FLEXGlobalsCookies:
|
||||
titleFuture = ^NSString *{
|
||||
return @"🍪 Cookies";
|
||||
};
|
||||
viewControllerFuture = ^UIViewController *{
|
||||
return [[FLEXCookiesTableViewController alloc] init];
|
||||
};
|
||||
break;
|
||||
|
||||
case FLEXGlobalsRowFileBrowser:
|
||||
titleFuture = ^NSString *{
|
||||
return @"📁 File Browser";
|
||||
|
||||
@@ -12,16 +12,49 @@
|
||||
#import "FLEXRuntimeUtility.h"
|
||||
#import "FLEXUtility.h"
|
||||
#import "FLEXHeapEnumerator.h"
|
||||
#import "FLEXObjectRef.h"
|
||||
#import <malloc/malloc.h>
|
||||
|
||||
|
||||
@interface FLEXInstancesTableViewController ()
|
||||
|
||||
@property (nonatomic, strong) NSArray *instances;
|
||||
@property (nonatomic, strong) NSArray *fieldNames;
|
||||
/// Array of [[section], [section], ...]
|
||||
/// where [section] is [["row title", instance], ["row title", instance], ...]
|
||||
@property (nonatomic) NSArray<FLEXObjectRef *> *instances;
|
||||
@property (nonatomic) NSArray<NSArray<FLEXObjectRef*>*> *sections;
|
||||
@property (nonatomic) NSArray<NSString *> *sectionTitles;
|
||||
@property (nonatomic) NSArray<NSPredicate *> *predicates;
|
||||
@property (nonatomic, readonly) NSInteger maxSections;
|
||||
|
||||
@end
|
||||
|
||||
@implementation FLEXInstancesTableViewController
|
||||
|
||||
- (id)initWithReferences:(NSArray<FLEXObjectRef *> *)references {
|
||||
return [self initWithReferences:references predicates:nil sectionTitles:nil];
|
||||
}
|
||||
|
||||
- (id)initWithReferences:(NSArray<FLEXObjectRef *> *)references
|
||||
predicates:(NSArray<NSPredicate *> *)predicates
|
||||
sectionTitles:(NSArray<NSString *> *)sectionTitles {
|
||||
NSParameterAssert(predicates.count == sectionTitles.count);
|
||||
|
||||
self = [super init];
|
||||
if (self) {
|
||||
self.instances = references;
|
||||
self.predicates = predicates;
|
||||
self.sectionTitles = sectionTitles;
|
||||
|
||||
if (predicates.count) {
|
||||
[self buildSections];
|
||||
} else {
|
||||
self.sections = @[references];
|
||||
}
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
+ (instancetype)instancesTableViewControllerForClassName:(NSString *)className
|
||||
{
|
||||
const char *classNameCString = [className UTF8String];
|
||||
@@ -31,20 +64,26 @@
|
||||
// Note: objects of certain classes crash when retain is called. It is up to the user to avoid tapping into instance lists for these classes.
|
||||
// Ex. OS_dispatch_queue_specific_queue
|
||||
// In the future, we could provide some kind of warning for classes that are known to be problematic.
|
||||
[instances addObject:object];
|
||||
if (malloc_size((__bridge const void *)(object)) > 0) {
|
||||
[instances addObject:object];
|
||||
}
|
||||
}
|
||||
}];
|
||||
FLEXInstancesTableViewController *instancesViewController = [[self alloc] init];
|
||||
instancesViewController.instances = instances;
|
||||
instancesViewController.title = [NSString stringWithFormat:@"%@ (%lu)", className, (unsigned long)[instances count]];
|
||||
return instancesViewController;
|
||||
NSArray<FLEXObjectRef *> *references = [FLEXObjectRef referencingAll:instances];
|
||||
FLEXInstancesTableViewController *viewController = [[self alloc] initWithReferences:references];
|
||||
viewController.title = [NSString stringWithFormat:@"%@ (%lu)", className, (unsigned long)[instances count]];
|
||||
return viewController;
|
||||
}
|
||||
|
||||
+ (instancetype)instancesTableViewControllerForInstancesReferencingObject:(id)object
|
||||
{
|
||||
NSMutableArray *instances = [NSMutableArray array];
|
||||
NSMutableArray *fieldNames = [NSMutableArray array];
|
||||
NSMutableArray<FLEXObjectRef *> *instances = [NSMutableArray array];
|
||||
[FLEXHeapEnumerator enumerateLiveObjectsUsingBlock:^(__unsafe_unretained id tryObject, __unsafe_unretained Class actualClass) {
|
||||
// Skip Swift objects
|
||||
if ([actualClass isKindOfClass:NSClassFromString(@"SwiftObject")]) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get all the ivars on the object. Start with the class and and travel up the inheritance chain.
|
||||
// Once we find a match, record it and move on to the next object. There's no reason to find multiple matches within the same object.
|
||||
Class tryClass = actualClass;
|
||||
@@ -58,8 +97,7 @@
|
||||
ptrdiff_t offset = ivar_getOffset(ivar);
|
||||
uintptr_t *fieldPointer = (__bridge void *)tryObject + offset;
|
||||
if (*fieldPointer == (uintptr_t)(__bridge void *)object) {
|
||||
[instances addObject:tryObject];
|
||||
[fieldNames addObject:@(ivar_getName(ivar))];
|
||||
[instances addObject:[FLEXObjectRef referencing:tryObject ivar:@(ivar_getName(ivar))]];
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -67,11 +105,85 @@
|
||||
tryClass = class_getSuperclass(tryClass);
|
||||
}
|
||||
}];
|
||||
FLEXInstancesTableViewController *instancesViewController = [[self alloc] init];
|
||||
instancesViewController.instances = instances;
|
||||
instancesViewController.fieldNames = fieldNames;
|
||||
instancesViewController.title = [NSString stringWithFormat:@"Referencing %@ %p", NSStringFromClass(object_getClass(object)), object];
|
||||
return instancesViewController;
|
||||
|
||||
NSArray<NSPredicate *> *predicates = [self defaultPredicates];
|
||||
NSArray<NSString *> *sectionTitles = [self defaultSectionTitles];
|
||||
FLEXInstancesTableViewController *viewController = [[self alloc] initWithReferences:instances
|
||||
predicates:predicates
|
||||
sectionTitles:sectionTitles];
|
||||
viewController.title = [NSString stringWithFormat:@"Referencing %@ %p", NSStringFromClass(object_getClass(object)), object];
|
||||
return viewController;
|
||||
}
|
||||
|
||||
+ (NSPredicate *)defaultPredicateForSection:(NSInteger)section
|
||||
{
|
||||
// These are the types of references that we typically don't care about.
|
||||
// We want this list of "object-ivar pairs" split into two sections.
|
||||
BOOL(^isObserver)(FLEXObjectRef *, NSDictionary *) = ^BOOL(FLEXObjectRef *ref, NSDictionary *bindings) {
|
||||
NSString *row = ref.reference;
|
||||
return [row isEqualToString:@"__NSObserver object"] ||
|
||||
[row isEqualToString:@"_CFXNotificationObjcObserverRegistration _object"];
|
||||
};
|
||||
|
||||
/// These are common AutoLayout related references we also rarely care about.
|
||||
BOOL(^isConstraintRelated)(FLEXObjectRef *, NSDictionary *) = ^BOOL(FLEXObjectRef *ref, NSDictionary *bindings) {
|
||||
static NSSet *ignored = nil;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
ignored = [NSSet setWithArray:@[
|
||||
@"NSLayoutConstraint _container",
|
||||
@"NSContentSizeLayoutConstraint _container",
|
||||
@"NSAutoresizingMaskLayoutConstraint _container",
|
||||
@"MASViewConstraint _installedView",
|
||||
@"MASLayoutConstraint _container",
|
||||
@"MASViewAttribute _view"
|
||||
]];
|
||||
});
|
||||
|
||||
NSString *row = ref.reference;
|
||||
return ([row hasPrefix:@"NSLayout"] && [row hasSuffix:@" _referenceItem"]) ||
|
||||
([row hasPrefix:@"NSIS"] && [row hasSuffix:@" _delegate"]) ||
|
||||
([row hasPrefix:@"_NSAutoresizingMask"] && [row hasSuffix:@" _referenceItem"]) ||
|
||||
[ignored containsObject:row];
|
||||
};
|
||||
|
||||
BOOL(^isEssential)(FLEXObjectRef *, NSDictionary *) = ^BOOL(FLEXObjectRef *ref, NSDictionary *bindings) {
|
||||
return !(isObserver(ref, bindings) || isConstraintRelated(ref, bindings));
|
||||
};
|
||||
|
||||
switch (section) {
|
||||
case 0: return [NSPredicate predicateWithBlock:isEssential];
|
||||
case 1: return [NSPredicate predicateWithBlock:isConstraintRelated];
|
||||
case 2: return [NSPredicate predicateWithBlock:isObserver];
|
||||
|
||||
default: return nil;
|
||||
}
|
||||
}
|
||||
|
||||
+ (NSArray<NSPredicate *> *)defaultPredicates {
|
||||
return @[[self defaultPredicateForSection:0],
|
||||
[self defaultPredicateForSection:1],
|
||||
[self defaultPredicateForSection:2]];
|
||||
}
|
||||
|
||||
+ (NSArray<NSString *> *)defaultSectionTitles {
|
||||
return @[@"", @"AutoLayout", @"Trivial"];
|
||||
}
|
||||
|
||||
- (void)buildSections
|
||||
{
|
||||
NSInteger maxSections = self.maxSections;
|
||||
NSMutableArray *sections = [NSMutableArray array];
|
||||
for (NSInteger i = 0; i < maxSections; i++) {
|
||||
NSPredicate *predicate = self.predicates[i];
|
||||
[sections addObject:[self.instances filteredArrayUsingPredicate:predicate]];
|
||||
}
|
||||
|
||||
self.sections = sections;
|
||||
}
|
||||
|
||||
- (NSInteger)maxSections {
|
||||
return self.predicates.count ?: 1;
|
||||
}
|
||||
|
||||
|
||||
@@ -79,12 +191,12 @@
|
||||
|
||||
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
|
||||
{
|
||||
return 1;
|
||||
return self.maxSections;
|
||||
}
|
||||
|
||||
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
|
||||
{
|
||||
return [self.instances count];
|
||||
return self.sections[section].count;
|
||||
}
|
||||
|
||||
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
|
||||
@@ -99,26 +211,33 @@
|
||||
cell.detailTextLabel.font = cellFont;
|
||||
cell.detailTextLabel.textColor = [UIColor grayColor];
|
||||
}
|
||||
|
||||
id instance = [self.instances objectAtIndex:indexPath.row];
|
||||
NSString *title = nil;
|
||||
if ((NSInteger)[self.fieldNames count] > indexPath.row) {
|
||||
title = [NSString stringWithFormat:@"%@ %@", NSStringFromClass(object_getClass(instance)), [self.fieldNames objectAtIndex:indexPath.row]];
|
||||
} else {
|
||||
title = [NSString stringWithFormat:@"%@ %p", NSStringFromClass(object_getClass(instance)), instance];
|
||||
}
|
||||
cell.textLabel.text = title;
|
||||
cell.detailTextLabel.text = [FLEXRuntimeUtility descriptionForIvarOrPropertyValue:instance];
|
||||
|
||||
FLEXObjectRef *row = self.sections[indexPath.section][indexPath.row];
|
||||
cell.textLabel.text = row.reference;
|
||||
cell.detailTextLabel.text = [FLEXRuntimeUtility descriptionForIvarOrPropertyValue:row.object];
|
||||
|
||||
return cell;
|
||||
}
|
||||
|
||||
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
|
||||
{
|
||||
if (self.sectionTitles.count) {
|
||||
// Return nil instead of empty strings
|
||||
NSString *title = self.sectionTitles[section];
|
||||
if (title.length) {
|
||||
return title;
|
||||
}
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Table View Delegate
|
||||
|
||||
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
id instance = [self.instances objectAtIndex:indexPath.row];
|
||||
id instance = self.instances[indexPath.row].object;
|
||||
FLEXObjectExplorerViewController *drillInViewController = [FLEXObjectExplorerFactory explorerViewControllerForObject:instance];
|
||||
[self.navigationController pushViewController:drillInViewController animated:YES];
|
||||
}
|
||||
|
||||
@@ -9,14 +9,16 @@
|
||||
#import "FLEXLibrariesTableViewController.h"
|
||||
#import "FLEXUtility.h"
|
||||
#import "FLEXClassesTableViewController.h"
|
||||
#import "FLEXClassExplorerViewController.h"
|
||||
#import <objc/runtime.h>
|
||||
|
||||
@interface FLEXLibrariesTableViewController () <UISearchBarDelegate>
|
||||
|
||||
@property (nonatomic, strong) NSArray *imageNames;
|
||||
@property (nonatomic, strong) NSArray *filteredImageNames;
|
||||
@property (nonatomic, strong) NSArray<NSString *> *imageNames;
|
||||
@property (nonatomic, strong) NSArray<NSString *> *filteredImageNames;
|
||||
|
||||
@property (nonatomic, strong) UISearchBar *searchBar;
|
||||
@property (nonatomic, strong) Class foundClass;
|
||||
|
||||
@end
|
||||
|
||||
@@ -50,7 +52,7 @@
|
||||
unsigned int imageNamesCount = 0;
|
||||
const char **imageNames = objc_copyImageNames(&imageNamesCount);
|
||||
if (imageNames) {
|
||||
NSMutableArray *imageNameStrings = [NSMutableArray array];
|
||||
NSMutableArray<NSString *> *imageNameStrings = [NSMutableArray array];
|
||||
NSString *appImageName = [FLEXUtility applicationImageName];
|
||||
for (unsigned int i = 0; i < imageNamesCount; i++) {
|
||||
const char *imageName = imageNames[i];
|
||||
@@ -63,9 +65,9 @@
|
||||
|
||||
// Sort alphabetically
|
||||
self.imageNames = [imageNameStrings sortedArrayWithOptions:0 usingComparator:^NSComparisonResult(NSString *name1, NSString *name2) {
|
||||
NSString *shortName1 = [self shortNameForImageName:name1];
|
||||
NSString *shortName2 = [self shortNameForImageName:name2];
|
||||
return [shortName1 caseInsensitiveCompare:shortName2];
|
||||
NSString *shortName1 = [self shortNameForImageName:name1];
|
||||
NSString *shortName2 = [self shortNameForImageName:name2];
|
||||
return [shortName1 caseInsensitiveCompare:shortName2];
|
||||
}];
|
||||
|
||||
free(imageNames);
|
||||
@@ -74,16 +76,14 @@
|
||||
|
||||
- (NSString *)shortNameForImageName:(NSString *)imageName
|
||||
{
|
||||
NSString *shortName = nil;
|
||||
NSArray *components = [imageName componentsSeparatedByString:@"/"];
|
||||
NSUInteger componentsCount = [components count];
|
||||
if (componentsCount >= 2) {
|
||||
shortName = [NSString stringWithFormat:@"%@/%@", components[componentsCount - 2], components[componentsCount - 1]];
|
||||
NSArray<NSString *> *components = [imageName componentsSeparatedByString:@"/"];
|
||||
if (components.count >= 2) {
|
||||
return [NSString stringWithFormat:@"%@/%@", components[components.count - 2], components[components.count - 1]];
|
||||
}
|
||||
return shortName;
|
||||
return imageName.lastPathComponent;
|
||||
}
|
||||
|
||||
- (void)setImageNames:(NSArray *)imageNames
|
||||
- (void)setImageNames:(NSArray<NSString *> *)imageNames
|
||||
{
|
||||
if (![_imageNames isEqual:imageNames]) {
|
||||
_imageNames = imageNames;
|
||||
@@ -97,7 +97,7 @@
|
||||
- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText
|
||||
{
|
||||
if ([searchText length] > 0) {
|
||||
NSPredicate *searchPreidcate = [NSPredicate predicateWithBlock:^BOOL(id evaluatedObject, NSDictionary *bindings) {
|
||||
NSPredicate *searchPreidcate = [NSPredicate predicateWithBlock:^BOOL(NSString *evaluatedObject, NSDictionary<NSString *, id> *bindings) {
|
||||
BOOL matches = NO;
|
||||
NSString *shortName = [self shortNameForImageName:evaluatedObject];
|
||||
if ([shortName rangeOfString:searchText options:NSCaseInsensitiveSearch].length > 0) {
|
||||
@@ -109,6 +109,8 @@
|
||||
} else {
|
||||
self.filteredImageNames = self.imageNames;
|
||||
}
|
||||
|
||||
self.foundClass = NSClassFromString(searchText);
|
||||
[self.tableView reloadData];
|
||||
}
|
||||
|
||||
@@ -127,22 +129,32 @@
|
||||
|
||||
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
|
||||
{
|
||||
return [self.filteredImageNames count];
|
||||
return self.filteredImageNames.count + (self.foundClass ? 1 : 0);
|
||||
}
|
||||
|
||||
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
static NSString *CellIdentifier = @"Cell";
|
||||
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
|
||||
static NSString *cellIdentifier = @"Cell";
|
||||
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
|
||||
if (!cell) {
|
||||
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
|
||||
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier];
|
||||
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
|
||||
cell.textLabel.font = [FLEXUtility defaultTableViewCellLabelFont];
|
||||
}
|
||||
|
||||
NSString *fullImageName = self.filteredImageNames[indexPath.row];
|
||||
cell.textLabel.text = [self shortNameForImageName:fullImageName];
|
||||
NSString *executablePath;
|
||||
if (self.foundClass) {
|
||||
if (indexPath.row == 0) {
|
||||
cell.textLabel.text = [NSString stringWithFormat:@"Class \"%@\"", self.searchBar.text];
|
||||
return cell;
|
||||
} else {
|
||||
executablePath = self.filteredImageNames[indexPath.row-1];
|
||||
}
|
||||
} else {
|
||||
executablePath = self.filteredImageNames[indexPath.row];
|
||||
}
|
||||
|
||||
cell.textLabel.text = [self shortNameForImageName:executablePath];
|
||||
return cell;
|
||||
}
|
||||
|
||||
@@ -151,9 +163,15 @@
|
||||
|
||||
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
FLEXClassesTableViewController *classesViewController = [[FLEXClassesTableViewController alloc] init];
|
||||
classesViewController.binaryImageName = self.filteredImageNames[indexPath.row];
|
||||
[self.navigationController pushViewController:classesViewController animated:YES];
|
||||
if (indexPath.row == 0 && self.foundClass) {
|
||||
FLEXClassExplorerViewController *objectExplorer = [FLEXClassExplorerViewController new];
|
||||
objectExplorer.object = self.foundClass;
|
||||
[self.navigationController pushViewController:objectExplorer animated:YES];
|
||||
} else {
|
||||
FLEXClassesTableViewController *classesViewController = [[FLEXClassesTableViewController alloc] init];
|
||||
classesViewController.binaryImageName = self.filteredImageNames[self.foundClass ? indexPath.row-1 : indexPath.row];
|
||||
[self.navigationController pushViewController:classesViewController animated:YES];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -14,12 +14,14 @@
|
||||
|
||||
static const NSInteger kFLEXLiveObjectsSortAlphabeticallyIndex = 0;
|
||||
static const NSInteger kFLEXLiveObjectsSortByCountIndex = 1;
|
||||
static const NSInteger kFLEXLiveObjectsSortBySizeIndex = 2;
|
||||
|
||||
@interface FLEXLiveObjectsTableViewController () <UISearchBarDelegate>
|
||||
|
||||
@property (nonatomic, strong) NSDictionary *instanceCountsForClassNames;
|
||||
@property (nonatomic, readonly) NSArray *allClassNames;
|
||||
@property (nonatomic, strong) NSArray *filteredClassNames;
|
||||
@property (nonatomic, strong) NSDictionary<NSString *, NSNumber *> *instanceCountsForClassNames;
|
||||
@property (nonatomic, strong) NSDictionary<NSString *, NSNumber *> *instanceSizesForClassNames;
|
||||
@property (nonatomic, readonly) NSArray<NSString *> *allClassNames;
|
||||
@property (nonatomic, strong) NSArray<NSString *> *filteredClassNames;
|
||||
@property (nonatomic, strong) UISearchBar *searchBar;
|
||||
|
||||
@end
|
||||
@@ -34,7 +36,7 @@ static const NSInteger kFLEXLiveObjectsSortByCountIndex = 1;
|
||||
self.searchBar.placeholder = [FLEXUtility searchBarPlaceholderText];
|
||||
self.searchBar.delegate = self;
|
||||
self.searchBar.showsScopeBar = YES;
|
||||
self.searchBar.scopeButtonTitles = @[@"Sort Alphabetically", @"Sort by Count"];
|
||||
self.searchBar.scopeButtonTitles = @[@"Sort Alphabetically", @"Sort by Count", @"Sort by Size"];
|
||||
[self.searchBar sizeToFit];
|
||||
self.tableView.tableHeaderView = self.searchBar;
|
||||
|
||||
@@ -44,7 +46,7 @@ static const NSInteger kFLEXLiveObjectsSortByCountIndex = 1;
|
||||
[self reloadTableData];
|
||||
}
|
||||
|
||||
- (NSArray *)allClassNames
|
||||
- (NSArray<NSString *> *)allClassNames
|
||||
{
|
||||
return [self.instanceCountsForClassNames allKeys];
|
||||
}
|
||||
@@ -72,18 +74,21 @@ static const NSInteger kFLEXLiveObjectsSortByCountIndex = 1;
|
||||
}];
|
||||
|
||||
// Convert our CF primitive dictionary into a nicer mapping of class name strings to counts that we will use as the table's model.
|
||||
NSMutableDictionary *mutableCountsForClassNames = [NSMutableDictionary dictionary];
|
||||
NSMutableDictionary<NSString *, NSNumber *> *mutableCountsForClassNames = [NSMutableDictionary dictionary];
|
||||
NSMutableDictionary<NSString *, NSNumber *> *mutableSizesForClassNames = [NSMutableDictionary dictionary];
|
||||
for (unsigned int i = 0; i < classCount; i++) {
|
||||
Class class = classes[i];
|
||||
NSUInteger instanceCount = (NSUInteger)CFDictionaryGetValue(mutableCountsForClasses, (__bridge const void *)(class));
|
||||
NSString *className = @(class_getName(class));
|
||||
if (instanceCount > 0) {
|
||||
NSString *className = @(class_getName(class));
|
||||
[mutableCountsForClassNames setObject:@(instanceCount) forKey:className];
|
||||
}
|
||||
[mutableSizesForClassNames setObject:@(class_getInstanceSize(class)) forKey:className];
|
||||
}
|
||||
free(classes);
|
||||
|
||||
self.instanceCountsForClassNames = mutableCountsForClassNames;
|
||||
self.instanceSizesForClassNames = mutableSizesForClassNames;
|
||||
|
||||
[self updateTableDataForSearchFilter];
|
||||
}
|
||||
@@ -99,19 +104,27 @@ static const NSInteger kFLEXLiveObjectsSortByCountIndex = 1;
|
||||
NSString *title = @"Live Objects";
|
||||
|
||||
NSUInteger totalCount = 0;
|
||||
NSUInteger totalSize = 0;
|
||||
for (NSString *className in self.allClassNames) {
|
||||
totalCount += [[self.instanceCountsForClassNames objectForKey:className] unsignedIntegerValue];
|
||||
NSUInteger count = [self.instanceCountsForClassNames[className] unsignedIntegerValue];
|
||||
totalCount += count;
|
||||
totalSize += count * [self.instanceSizesForClassNames[className] unsignedIntegerValue];
|
||||
}
|
||||
NSUInteger filteredCount = 0;
|
||||
NSUInteger filteredSize = 0;
|
||||
for (NSString *className in self.filteredClassNames) {
|
||||
filteredCount += [[self.instanceCountsForClassNames objectForKey:className] unsignedIntegerValue];
|
||||
NSUInteger count = [self.instanceCountsForClassNames[className] unsignedIntegerValue];
|
||||
filteredCount += count;
|
||||
filteredSize += count * [self.instanceSizesForClassNames[className] unsignedIntegerValue];
|
||||
}
|
||||
|
||||
if (filteredCount == totalCount) {
|
||||
// Unfiltered
|
||||
title = [title stringByAppendingFormat:@" (%lu)", (unsigned long)totalCount];
|
||||
title = [title stringByAppendingFormat:@" (%lu, %@)", (unsigned long)totalCount,
|
||||
[NSByteCountFormatter stringFromByteCount:totalSize countStyle:NSByteCountFormatterCountStyleFile]];
|
||||
} else {
|
||||
title = [title stringByAppendingFormat:@" (filtered, %lu)", (unsigned long)filteredCount];
|
||||
title = [title stringByAppendingFormat:@" (filtered, %lu, %@)", (unsigned long)filteredCount,
|
||||
[NSByteCountFormatter stringFromByteCount:filteredSize countStyle:NSByteCountFormatterCountStyleFile]];
|
||||
}
|
||||
|
||||
self.title = title;
|
||||
@@ -154,11 +167,20 @@ static const NSInteger kFLEXLiveObjectsSortByCountIndex = 1;
|
||||
self.filteredClassNames = [self.filteredClassNames sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)];
|
||||
} else if (self.searchBar.selectedScopeButtonIndex == kFLEXLiveObjectsSortByCountIndex) {
|
||||
self.filteredClassNames = [self.filteredClassNames sortedArrayUsingComparator:^NSComparisonResult(NSString *className1, NSString *className2) {
|
||||
NSNumber *count1 = [self.instanceCountsForClassNames objectForKey:className1];
|
||||
NSNumber *count2 = [self.instanceCountsForClassNames objectForKey:className2];
|
||||
NSNumber *count1 = self.instanceCountsForClassNames[className1];
|
||||
NSNumber *count2 = self.instanceCountsForClassNames[className2];
|
||||
// Reversed for descending counts.
|
||||
return [count2 compare:count1];
|
||||
}];
|
||||
} else if (self.searchBar.selectedScopeButtonIndex == kFLEXLiveObjectsSortBySizeIndex) {
|
||||
self.filteredClassNames = [self.filteredClassNames sortedArrayUsingComparator:^NSComparisonResult(NSString *className1, NSString *className2) {
|
||||
NSNumber *count1 = self.instanceCountsForClassNames[className1];
|
||||
NSNumber *count2 = self.instanceCountsForClassNames[className2];
|
||||
NSNumber *size1 = self.instanceSizesForClassNames[className1];
|
||||
NSNumber *size2 = self.instanceSizesForClassNames[className2];
|
||||
// Reversed for descending sizes.
|
||||
return [@(count2.integerValue * size2.integerValue) compare:@(count1.integerValue * size1.integerValue)];
|
||||
}];
|
||||
}
|
||||
|
||||
[self updateTitle];
|
||||
@@ -189,8 +211,11 @@ static const NSInteger kFLEXLiveObjectsSortByCountIndex = 1;
|
||||
}
|
||||
|
||||
NSString *className = self.filteredClassNames[indexPath.row];
|
||||
NSNumber *count = [self.instanceCountsForClassNames objectForKey:className];
|
||||
cell.textLabel.text = [NSString stringWithFormat:@"%@ (%ld)", className, (long)[count integerValue]];
|
||||
NSNumber *count = self.instanceCountsForClassNames[className];
|
||||
NSNumber *size = self.instanceSizesForClassNames[className];
|
||||
unsigned long totalSize = count.unsignedIntegerValue * size.unsignedIntegerValue;
|
||||
cell.textLabel.text = [NSString stringWithFormat:@"%@ (%ld, %@)", className, (long)[count integerValue],
|
||||
[NSByteCountFormatter stringFromByteCount:totalSize countStyle:NSByteCountFormatterCountStyleFile]];
|
||||
|
||||
return cell;
|
||||
}
|
||||
@@ -200,7 +225,7 @@ static const NSInteger kFLEXLiveObjectsSortByCountIndex = 1;
|
||||
|
||||
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
NSString *className = [self.filteredClassNames objectAtIndex:indexPath.row];
|
||||
NSString *className = self.filteredClassNames[indexPath.row];
|
||||
FLEXInstancesTableViewController *instancesViewController = [FLEXInstancesTableViewController instancesTableViewControllerForClassName:className];
|
||||
[self.navigationController pushViewController:instancesViewController animated:YES];
|
||||
}
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
//
|
||||
// FLEXObjectRef.h
|
||||
// FLEX
|
||||
//
|
||||
// Created by Tanner Bennett on 7/24/18.
|
||||
// Copyright (c) 2018 Flipboard. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@interface FLEXObjectRef : NSObject
|
||||
|
||||
+ (instancetype)referencing:(id)object;
|
||||
+ (instancetype)referencing:(id)object ivar:(NSString *)ivarName;
|
||||
|
||||
+ (NSArray<FLEXObjectRef *> *)referencingAll:(NSArray *)objects;
|
||||
|
||||
/// For example, "NSString 0x1d4085d0" or "NSLayoutConstraint _object"
|
||||
@property (nonatomic, readonly) NSString *reference;
|
||||
@property (nonatomic, readonly) id object;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,47 @@
|
||||
//
|
||||
// FLEXObjectRef.m
|
||||
// FLEX
|
||||
//
|
||||
// Created by Tanner Bennett on 7/24/18.
|
||||
// Copyright (c) 2018 Flipboard. All rights reserved.
|
||||
//
|
||||
|
||||
#import "FLEXObjectRef.h"
|
||||
#import <objc/runtime.h>
|
||||
|
||||
@implementation FLEXObjectRef
|
||||
|
||||
+ (instancetype)referencing:(id)object {
|
||||
return [[self alloc] initWithObject:object ivarName:nil];
|
||||
}
|
||||
|
||||
+ (instancetype)referencing:(id)object ivar:(NSString *)ivarName {
|
||||
return [[self alloc] initWithObject:object ivarName:ivarName];
|
||||
}
|
||||
|
||||
+ (NSArray<FLEXObjectRef *> *)referencingAll:(NSArray *)objects {
|
||||
NSMutableArray<FLEXObjectRef *> *refs = [NSMutableArray array];
|
||||
for (id obj in objects) {
|
||||
[refs addObject:[self referencing:obj]];
|
||||
}
|
||||
|
||||
return refs;
|
||||
}
|
||||
|
||||
- (id)initWithObject:(id)object ivarName:(NSString *)ivar {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_object = object;
|
||||
|
||||
NSString *class = NSStringFromClass(object_getClass(object));
|
||||
if (ivar) {
|
||||
_reference = [NSString stringWithFormat:@"%@ %@", class, ivar];
|
||||
} else {
|
||||
_reference = [NSString stringWithFormat:@"%@ %p", class, object];
|
||||
}
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -102,23 +102,23 @@
|
||||
+ (BOOL)supportsPathExtension:(NSString *)extension
|
||||
{
|
||||
BOOL supported = NO;
|
||||
NSSet *supportedExtensions = [self webViewSupportedPathExtensions];
|
||||
NSSet<NSString *> *supportedExtensions = [self webViewSupportedPathExtensions];
|
||||
if ([supportedExtensions containsObject:[extension lowercaseString]]) {
|
||||
supported = YES;
|
||||
}
|
||||
return supported;
|
||||
}
|
||||
|
||||
+ (NSSet *)webViewSupportedPathExtensions
|
||||
+ (NSSet<NSString *> *)webViewSupportedPathExtensions
|
||||
{
|
||||
static NSSet *pathExtenstions = nil;
|
||||
static NSSet<NSString *> *pathExtenstions = nil;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
// Note that this is not exhaustive, but all these extensions should work well in the web view.
|
||||
// See https://developer.apple.com/library/ios/documentation/AppleApplications/Reference/SafariWebContent/CreatingContentforSafarioniPhone/CreatingContentforSafarioniPhone.html#//apple_ref/doc/uid/TP40006482-SW7
|
||||
pathExtenstions = [NSSet setWithArray:@[@"jpg", @"jpeg", @"png", @"gif", @"pdf", @"svg", @"tiff", @"3gp", @"3gpp", @"3g2",
|
||||
@"3gp2", @"aiff", @"aif", @"aifc", @"cdda", @"amr", @"mp3", @"swa", @"mp4", @"mpeg",
|
||||
@"mpg", @"mp3", @"wav", @"bwf", @"m4a", @"m4b", @"m4p", @"mov", @"qt", @"mqv", @"m4v"]];
|
||||
pathExtenstions = [NSSet<NSString *> setWithArray:@[@"jpg", @"jpeg", @"png", @"gif", @"pdf", @"svg", @"tiff", @"3gp", @"3gpp", @"3g2",
|
||||
@"3gp2", @"aiff", @"aif", @"aifc", @"cdda", @"amr", @"mp3", @"swa", @"mp4", @"mpeg",
|
||||
@"mpg", @"mp3", @"wav", @"bwf", @"m4a", @"m4b", @"m4p", @"mov", @"qt", @"mqv", @"m4v"]];
|
||||
|
||||
});
|
||||
return pathExtenstions;
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
@implementation FLEXSystemLogMessage
|
||||
|
||||
+(instancetype)logMessageFromASLMessage:(aslmsg)aslMessage
|
||||
+ (instancetype)logMessageFromASLMessage:(aslmsg)aslMessage
|
||||
{
|
||||
FLEXSystemLogMessage *logMessage = [[FLEXSystemLogMessage alloc] init];
|
||||
|
||||
|
||||
@@ -74,12 +74,12 @@ static const UIEdgeInsets kFLEXLogMessageCellInsets = {10.0, 10.0, 10.0, 10.0};
|
||||
+ (NSAttributedString *)attributedTextForLogMessage:(FLEXSystemLogMessage *)logMessage highlightedText:(NSString *)highlightedText
|
||||
{
|
||||
NSString *text = [self displayedTextForLogMessage:logMessage];
|
||||
NSDictionary *attributes = @{ NSFontAttributeName : [UIFont fontWithName:@"CourierNewPSMT" size:12.0] };
|
||||
NSDictionary<NSString *, id> *attributes = @{ NSFontAttributeName : [UIFont fontWithName:@"CourierNewPSMT" size:12.0] };
|
||||
NSAttributedString *attributedText = [[NSAttributedString alloc] initWithString:text attributes:attributes];
|
||||
|
||||
if ([highlightedText length] > 0) {
|
||||
NSMutableAttributedString *mutableAttributedText = [attributedText mutableCopy];
|
||||
NSMutableDictionary *highlightAttributes = [@{ NSBackgroundColorAttributeName : [UIColor yellowColor] } mutableCopy];
|
||||
NSMutableDictionary<NSString *, id> *highlightAttributes = [@{ NSBackgroundColorAttributeName : [UIColor yellowColor] } mutableCopy];
|
||||
[highlightAttributes addEntriesFromDictionary:attributes];
|
||||
|
||||
NSRange remainingSearchRange = NSMakeRange(0, text.length);
|
||||
|
||||
@@ -12,15 +12,13 @@
|
||||
#import "FLEXSystemLogTableViewCell.h"
|
||||
#import <asl.h>
|
||||
|
||||
@interface FLEXSystemLogTableViewController () <UISearchDisplayDelegate>
|
||||
@interface FLEXSystemLogTableViewController () <UISearchResultsUpdating, UISearchControllerDelegate>
|
||||
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
||||
@property (nonatomic, strong) UISearchDisplayController *searchController;
|
||||
#pragma clang diagnostic pop
|
||||
@property (nonatomic, copy) NSArray *logMessages;
|
||||
@property (nonatomic, copy) NSArray *filteredLogMessages;
|
||||
@property (nonatomic, strong) UISearchController *searchController;
|
||||
@property (nonatomic, readonly) NSMutableArray<FLEXSystemLogMessage *> *logMessages;
|
||||
@property (nonatomic, copy) NSArray<FLEXSystemLogMessage *> *filteredLogMessages;
|
||||
@property (nonatomic, strong) NSTimer *logUpdateTimer;
|
||||
@property (nonatomic, readonly) NSMutableIndexSet *logMessageIdentifiers;
|
||||
|
||||
@end
|
||||
|
||||
@@ -30,22 +28,18 @@
|
||||
{
|
||||
[super viewDidLoad];
|
||||
|
||||
_logMessages = [NSMutableArray array];
|
||||
_logMessageIdentifiers = [NSMutableIndexSet indexSet];
|
||||
|
||||
[self.tableView registerClass:[FLEXSystemLogTableViewCell class] forCellReuseIdentifier:kFLEXSystemLogTableViewCellIdentifier];
|
||||
self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
|
||||
self.title = @"Loading...";
|
||||
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@" ⬇︎ " style:UIBarButtonItemStylePlain target:self action:@selector(scrollToLastRow)];
|
||||
|
||||
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 = [[UISearchController alloc] initWithSearchResultsController:nil];
|
||||
self.searchController.delegate = self;
|
||||
self.searchController.searchResultsDataSource = self;
|
||||
self.searchController.searchResultsDelegate = self;
|
||||
[self.searchController.searchResultsTableView registerClass:[FLEXSystemLogTableViewCell class] forCellReuseIdentifier:kFLEXSystemLogTableViewCellIdentifier];
|
||||
self.searchController.searchResultsTableView.separatorStyle = UITableViewCellSeparatorStyleNone;
|
||||
self.searchController.searchResultsUpdater = self;
|
||||
self.searchController.dimsBackgroundDuringPresentation = NO;
|
||||
self.tableView.tableHeaderView = self.searchController.searchBar;
|
||||
|
||||
[self updateLogMessages];
|
||||
@@ -75,10 +69,18 @@
|
||||
- (void)updateLogMessages
|
||||
{
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||
NSArray *logMessages = [[self class] allLogMessagesForCurrentProcess];
|
||||
NSArray<FLEXSystemLogMessage *> *newMessages = [self newLogMessagesForCurrentProcess];
|
||||
if (!newMessages.count) {
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
self.title = @"System Log";
|
||||
self.logMessages = logMessages;
|
||||
|
||||
[self.logMessages addObjectsFromArray:newMessages];
|
||||
for (FLEXSystemLogMessage *message in newMessages) {
|
||||
[self.logMessageIdentifiers addIndex:(NSUInteger)message.messageID];
|
||||
}
|
||||
|
||||
// "Follow" the log as new messages stream in if we were previously near the bottom.
|
||||
BOOL wasNearBottom = self.tableView.contentOffset.y >= self.tableView.contentSize.height - self.tableView.frame.size.height - 100.0;
|
||||
@@ -108,25 +110,15 @@
|
||||
|
||||
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
|
||||
{
|
||||
NSInteger numberOfRows = 0;
|
||||
if (tableView == self.tableView) {
|
||||
numberOfRows = [self.logMessages count];
|
||||
} else if (tableView == self.searchController.searchResultsTableView) {
|
||||
numberOfRows = [self.filteredLogMessages count];
|
||||
}
|
||||
return numberOfRows;
|
||||
return self.searchController.isActive ? [self.filteredLogMessages count] : [self.logMessages count];
|
||||
}
|
||||
|
||||
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
FLEXSystemLogTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:kFLEXSystemLogTableViewCellIdentifier forIndexPath:indexPath];
|
||||
if (tableView == self.tableView) {
|
||||
cell.logMessage = [self.logMessages objectAtIndex:indexPath.row];
|
||||
cell.highlightedText = nil;
|
||||
} else if (tableView == self.searchController.searchResultsTableView) {
|
||||
cell.logMessage = [self.filteredLogMessages objectAtIndex:indexPath.row];
|
||||
cell.highlightedText = self.searchController.searchBar.text;
|
||||
}
|
||||
cell.logMessage = [self logMessageAtIndexPath:indexPath];
|
||||
cell.highlightedText = self.searchController.searchBar.text;
|
||||
|
||||
if (indexPath.row % 2 == 0) {
|
||||
cell.backgroundColor = [UIColor colorWithWhite:0.95 alpha:1.0];
|
||||
} else {
|
||||
@@ -138,12 +130,7 @@
|
||||
|
||||
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
FLEXSystemLogMessage *logMessage = nil;
|
||||
if (tableView == self.tableView) {
|
||||
logMessage = [self.logMessages objectAtIndex:indexPath.row];
|
||||
} else if (tableView == self.searchController.searchResultsTableView) {
|
||||
logMessage = [self.filteredLogMessages objectAtIndex:indexPath.row];
|
||||
}
|
||||
FLEXSystemLogMessage *logMessage = [self logMessageAtIndexPath:indexPath];
|
||||
return [FLEXSystemLogTableViewCell preferredHeightForLogMessage:logMessage inWidth:self.tableView.bounds.size.width];
|
||||
}
|
||||
|
||||
@@ -162,75 +149,88 @@
|
||||
- (void)tableView:(UITableView *)tableView performAction:(SEL)action forRowAtIndexPath:(NSIndexPath *)indexPath withSender:(id)sender
|
||||
{
|
||||
if (action == @selector(copy:)) {
|
||||
FLEXSystemLogMessage *logMessage = nil;
|
||||
if (tableView == self.tableView) {
|
||||
logMessage = [self.logMessages objectAtIndex:indexPath.row];
|
||||
} else if (tableView == self.searchController.searchResultsTableView) {
|
||||
logMessage = [self.filteredLogMessages objectAtIndex:indexPath.row];
|
||||
}
|
||||
|
||||
FLEXSystemLogMessage *logMessage = [self logMessageAtIndexPath:indexPath];
|
||||
NSString *stringToCopy = [FLEXSystemLogTableViewCell displayedTextForLogMessage:logMessage] ?: @"";
|
||||
[[UIPasteboard generalPasteboard] setString:stringToCopy];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Search display delegate
|
||||
|
||||
- (BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchString:(NSString *)searchString
|
||||
- (FLEXSystemLogMessage *)logMessageAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
return self.searchController.isActive ? self.filteredLogMessages[indexPath.row] : self.logMessages[indexPath.row];
|
||||
}
|
||||
|
||||
#pragma mark - UISearchResultsUpdating
|
||||
|
||||
- (void)updateSearchResultsForSearchController:(UISearchController *)searchController
|
||||
{
|
||||
NSString *searchString = searchController.searchBar.text;
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||
NSArray *filteredLogMessages = [self.logMessages filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(FLEXSystemLogMessage *logMessage, NSDictionary *bindings) {
|
||||
NSArray<FLEXSystemLogMessage *> *filteredLogMessages = [self.logMessages filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(FLEXSystemLogMessage *logMessage, NSDictionary<NSString *, id> *bindings) {
|
||||
NSString *displayedText = [FLEXSystemLogTableViewCell displayedTextForLogMessage:logMessage];
|
||||
return [displayedText rangeOfString:searchString options:NSCaseInsensitiveSearch].length > 0;
|
||||
}]];
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
if ([self.searchDisplayController.searchBar.text isEqual:searchString]) {
|
||||
if ([searchController.searchBar.text isEqual:searchString]) {
|
||||
self.filteredLogMessages = filteredLogMessages;
|
||||
[self.searchDisplayController.searchResultsTableView reloadData];
|
||||
[self.tableView reloadData];
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Reload done after the data fetches asynchronously
|
||||
return NO;
|
||||
}
|
||||
|
||||
#pragma mark - Log Message Fetching
|
||||
|
||||
// Due to a mistake in asl.h, things get a little messy. We need to mark these symbols as weak since they won't exist on iOS 7 despite the compiler thinking otherwise.
|
||||
// asl.h in the iOS 8.1 SDK claims that asl_next() and asl_release() were introduced in iOS 7 to replace aslresponse_next() and aslresponse_free(). However, they were actually added in iOS 8.0.
|
||||
extern aslmsg asl_next(asl_object_t obj) __attribute__((weak_import));
|
||||
extern void asl_release(asl_object_t obj) __attribute__((weak_import));
|
||||
|
||||
+ (NSArray *)allLogMessagesForCurrentProcess
|
||||
- (NSArray<FLEXSystemLogMessage *> *)newLogMessagesForCurrentProcess
|
||||
{
|
||||
asl_object_t query = asl_new(ASL_TYPE_QUERY);
|
||||
if (!self.logMessages.count) {
|
||||
return [[self class] allLogMessagesForCurrentProcess];
|
||||
}
|
||||
|
||||
// Filter for messages from the current process. Note that this appears to happen by default on device, but is required in the simulator.
|
||||
NSString *pidString = [NSString stringWithFormat:@"%d", [[NSProcessInfo processInfo] processIdentifier]];
|
||||
asl_set_query(query, ASL_KEY_PID, [pidString UTF8String], ASL_QUERY_OP_EQUAL);
|
||||
|
||||
aslresponse response = asl_search(NULL, query);
|
||||
aslresponse response = [FLEXSystemLogTableViewController ASLMessageListForCurrentProcess];
|
||||
aslmsg aslMessage = NULL;
|
||||
|
||||
NSMutableArray *logMessages = [NSMutableArray array];
|
||||
NSMutableArray<FLEXSystemLogMessage *> *newMessages = [NSMutableArray array];
|
||||
|
||||
if (&asl_next != NULL && &asl_release != NULL) {
|
||||
while ((aslMessage = asl_next(response))) {
|
||||
[logMessages addObject:[FLEXSystemLogMessage logMessageFromASLMessage:aslMessage]];
|
||||
while ((aslMessage = asl_next(response))) {
|
||||
NSUInteger messageID = (NSUInteger)atoll(asl_get(aslMessage, ASL_KEY_MSG_ID));
|
||||
if (![self.logMessageIdentifiers containsIndex:messageID]) {
|
||||
[newMessages addObject:[FLEXSystemLogMessage logMessageFromASLMessage:aslMessage]];
|
||||
}
|
||||
asl_release(response);
|
||||
} else {
|
||||
// Mute incorrect deprecated warnings. We'll need the "deprecated" functions on iOS 7, where their replacements don't yet exist.
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
||||
while ((aslMessage = aslresponse_next(response))) {
|
||||
[logMessages addObject:[FLEXSystemLogMessage logMessageFromASLMessage:aslMessage]];
|
||||
}
|
||||
aslresponse_free(response);
|
||||
#pragma clang diagnostic pop
|
||||
}
|
||||
|
||||
asl_release(response);
|
||||
return newMessages;
|
||||
}
|
||||
|
||||
+ (aslresponse)ASLMessageListForCurrentProcess
|
||||
{
|
||||
static NSString *pidString = nil;
|
||||
if (!pidString) {
|
||||
pidString = @([[NSProcessInfo processInfo] processIdentifier]).stringValue;
|
||||
}
|
||||
|
||||
// Create system log query object.
|
||||
asl_object_t query = asl_new(ASL_TYPE_QUERY);
|
||||
|
||||
// Filter for messages from the current process.
|
||||
// Note that this appears to happen by default on device, but is required in the simulator.
|
||||
asl_set_query(query, ASL_KEY_PID, pidString.UTF8String, ASL_QUERY_OP_EQUAL);
|
||||
|
||||
return asl_search(NULL, query);
|
||||
}
|
||||
|
||||
+ (NSArray<FLEXSystemLogMessage *> *)allLogMessagesForCurrentProcess
|
||||
{
|
||||
aslresponse response = [self ASLMessageListForCurrentProcess];
|
||||
aslmsg aslMessage = NULL;
|
||||
|
||||
NSMutableArray<FLEXSystemLogMessage *> *logMessages = [NSMutableArray array];
|
||||
while ((aslMessage = asl_next(response))) {
|
||||
[logMessages addObject:[FLEXSystemLogMessage logMessageFromASLMessage:aslMessage]];
|
||||
}
|
||||
asl_release(response);
|
||||
|
||||
return logMessages;
|
||||
}
|
||||
|
||||
|
||||
+5
-1
@@ -8,9 +8,13 @@
|
||||
|
||||
#import "FLEXManager.h"
|
||||
|
||||
@class FLEXGlobalsTableViewControllerEntry;
|
||||
|
||||
@interface FLEXManager ()
|
||||
|
||||
/// An array of FLEXGlobalsTableViewControllerEntry objects that have been registered by the user.
|
||||
@property (nonatomic, readonly, strong) NSArray *userGlobalEntries;
|
||||
@property (nonatomic, readonly, strong) NSArray<FLEXGlobalsTableViewControllerEntry *> *userGlobalEntries;
|
||||
|
||||
@property (nonatomic, readonly, strong) NSDictionary<NSString *, FLEXCustomContentViewerFuture> *customContentTypeViewers;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,381 @@
|
||||
//
|
||||
// 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<FLEXGlobalsTableViewControllerEntry *> *userGlobalEntries;
|
||||
@property (nonatomic, readonly, strong) NSMutableDictionary<NSString *, FLEXCustomContentViewerFuture> *customContentTypeViewers;
|
||||
|
||||
@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 array];
|
||||
_customContentTypeViewers = [NSMutableDictionary dictionary];
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
- (void)toggleExplorer {
|
||||
if (self.explorerWindow.isHidden) {
|
||||
[self showExplorer];
|
||||
} else {
|
||||
[self hideExplorer];
|
||||
}
|
||||
}
|
||||
|
||||
- (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];
|
||||
}
|
||||
|
||||
- (void)setNetworkRequestHostBlacklist:(NSArray<NSString *> *)networkRequestHostBlacklist
|
||||
{
|
||||
[FLEXNetworkRecorder defaultRecorder].hostBlacklist = networkRequestHostBlacklist;
|
||||
}
|
||||
|
||||
- (NSArray<NSString *> *)hostBlacklist
|
||||
{
|
||||
return [FLEXNetworkRecorder defaultRecorder].hostBlacklist;
|
||||
}
|
||||
|
||||
#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:^{
|
||||
[self toggleExplorer];
|
||||
} description:@"Toggle FLEX toolbar"];
|
||||
|
||||
[self registerSimulatorShortcutWithKey:@"g" modifiers:0 action:^{
|
||||
[self showExplorerIfNeeded];
|
||||
[self.explorerViewController toggleMenuTool];
|
||||
} description:@"Toggle FLEX globals 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)setCustomViewerForContentType:(NSString *)contentType viewControllerFutureBlock:(FLEXCustomContentViewerFuture)viewControllerFutureBlock
|
||||
{
|
||||
NSParameterAssert(contentType.length);
|
||||
NSParameterAssert(viewControllerFutureBlock);
|
||||
NSAssert([NSThread isMainThread], @"This method must be called from the main thread.");
|
||||
|
||||
self.customContentTypeViewers[contentType.lowercaseString] = viewControllerFutureBlock;
|
||||
}
|
||||
|
||||
- (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<UIView *> *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
|
||||
@@ -0,0 +1,19 @@
|
||||
//
|
||||
// FLEXCurlLogger.h
|
||||
//
|
||||
//
|
||||
// Created by Ji Pei on 07/27/16
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@interface FLEXNetworkCurlLogger : NSObject
|
||||
|
||||
/**
|
||||
* Generates a cURL command equivalent to the given request.
|
||||
*
|
||||
* @param request The request to be translated
|
||||
*/
|
||||
+ (NSString *)curlCommandString:(NSURLRequest *)request;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,42 @@
|
||||
//
|
||||
// FLEXCurlLogger.m
|
||||
//
|
||||
//
|
||||
// Created by Ji Pei on 07/27/16
|
||||
//
|
||||
|
||||
#import "FLEXNetworkCurlLogger.h"
|
||||
|
||||
@implementation FLEXNetworkCurlLogger
|
||||
|
||||
+ (NSString *)curlCommandString:(NSURLRequest *)request {
|
||||
__block NSMutableString *curlCommandString = [NSMutableString stringWithFormat:@"curl -v -X %@ ", request.HTTPMethod];
|
||||
|
||||
[curlCommandString appendFormat:@"\'%@\' ", request.URL.absoluteString];
|
||||
|
||||
[request.allHTTPHeaderFields enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSString *val, BOOL *stop) {
|
||||
[curlCommandString appendFormat:@"-H \'%@: %@\' ", key, val];
|
||||
}];
|
||||
|
||||
NSArray<NSHTTPCookie *> *cookies = [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookiesForURL:request.URL];
|
||||
if (cookies) {
|
||||
[curlCommandString appendFormat:@"-H \'Cookie:"];
|
||||
for (NSHTTPCookie *cookie in cookies) {
|
||||
[curlCommandString appendFormat:@" %@=%@;", cookie.name, cookie.value];
|
||||
}
|
||||
[curlCommandString appendFormat:@"\' "];
|
||||
}
|
||||
|
||||
if (request.HTTPBody) {
|
||||
if ([request.allHTTPHeaderFields[@"Content-Length"] intValue] < 1024) {
|
||||
[curlCommandString appendFormat:@"-d \'%@\'",
|
||||
[[NSString alloc] initWithData:request.HTTPBody encoding:NSUTF8StringEncoding]];
|
||||
} else {
|
||||
[curlCommandString appendFormat:@"[TOO MUCH DATA TO INCLUDE]"];
|
||||
}
|
||||
}
|
||||
|
||||
return curlCommandString;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -14,20 +14,18 @@
|
||||
#import "FLEXNetworkObserver.h"
|
||||
#import "FLEXNetworkSettingsTableViewController.h"
|
||||
|
||||
@interface FLEXNetworkHistoryTableViewController () <UISearchDisplayDelegate>
|
||||
@interface FLEXNetworkHistoryTableViewController () <UISearchResultsUpdating, UISearchControllerDelegate>
|
||||
|
||||
/// Backing model
|
||||
@property (nonatomic, copy) NSArray *networkTransactions;
|
||||
@property (nonatomic, copy) NSArray<FLEXNetworkTransaction *> *networkTransactions;
|
||||
@property (nonatomic, assign) long long bytesReceived;
|
||||
@property (nonatomic, copy) NSArray *filteredNetworkTransactions;
|
||||
@property (nonatomic, copy) NSArray<FLEXNetworkTransaction *> *filteredNetworkTransactions;
|
||||
@property (nonatomic, assign) long long filteredBytesReceived;
|
||||
|
||||
@property (nonatomic, assign) BOOL rowInsertInProgress;
|
||||
@property (nonatomic, assign) BOOL isPresentingSearch;
|
||||
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
||||
@property (nonatomic, strong) UISearchDisplayController *searchController;
|
||||
#pragma clang diagnostic pop
|
||||
@property (nonatomic, strong) UISearchController *searchController;
|
||||
|
||||
@end
|
||||
|
||||
@@ -43,6 +41,10 @@
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleNetworkObserverEnabledStateChangedNotification:) name:kFLEXNetworkObserverEnabledStateChangedNotification object:nil];
|
||||
self.title = @"📡 Network";
|
||||
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"Settings" style:UIBarButtonItemStylePlain target:self action:@selector(settingsButtonTapped:)];
|
||||
|
||||
// Needed to avoid search bar showing over detail pages pushed on the nav stack
|
||||
// see http://asciiwwdc.com/2014/sessions/228
|
||||
self.definesPresentationContext = YES;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
@@ -60,18 +62,10 @@
|
||||
self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
|
||||
self.tableView.rowHeight = [FLEXNetworkTransactionTableViewCell preferredCellHeight];
|
||||
|
||||
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 = [[UISearchController alloc] initWithSearchResultsController:nil];
|
||||
self.searchController.delegate = self;
|
||||
self.searchController.searchResultsDataSource = self;
|
||||
self.searchController.searchResultsDelegate = self;
|
||||
[self.searchController.searchResultsTableView registerClass:[FLEXNetworkTransactionTableViewCell class] forCellReuseIdentifier:kFLEXNetworkTransactionCellIdentifier];
|
||||
self.searchController.searchResultsTableView.separatorStyle = UITableViewCellSeparatorStyleNone;
|
||||
self.searchController.searchResultsTableView.rowHeight = [FLEXNetworkTransactionTableViewCell preferredCellHeight];
|
||||
self.searchController.searchResultsUpdater = self;
|
||||
self.searchController.dimsBackgroundDuringPresentation = NO;
|
||||
self.tableView.tableHeaderView = self.searchController.searchBar;
|
||||
|
||||
[self updateTransactions];
|
||||
@@ -96,7 +90,7 @@
|
||||
self.networkTransactions = [[FLEXNetworkRecorder defaultRecorder] networkTransactions];
|
||||
}
|
||||
|
||||
- (void)setNetworkTransactions:(NSArray *)networkTransactions
|
||||
- (void)setNetworkTransactions:(NSArray<FLEXNetworkTransaction *> *)networkTransactions
|
||||
{
|
||||
if (![_networkTransactions isEqual:networkTransactions]) {
|
||||
_networkTransactions = networkTransactions;
|
||||
@@ -112,10 +106,10 @@
|
||||
bytesReceived += transaction.receivedDataLength;
|
||||
}
|
||||
self.bytesReceived = bytesReceived;
|
||||
[self updateFirstSectionHeaderInTableView:self.tableView];
|
||||
[self updateFirstSectionHeader];
|
||||
}
|
||||
|
||||
- (void)setFilteredNetworkTransactions:(NSArray *)filteredNetworkTransactions
|
||||
- (void)setFilteredNetworkTransactions:(NSArray<FLEXNetworkTransaction *> *)filteredNetworkTransactions
|
||||
{
|
||||
if (![_filteredNetworkTransactions isEqual:filteredNetworkTransactions]) {
|
||||
_filteredNetworkTransactions = filteredNetworkTransactions;
|
||||
@@ -130,31 +124,31 @@
|
||||
filteredBytesReceived += transaction.receivedDataLength;
|
||||
}
|
||||
self.filteredBytesReceived = filteredBytesReceived;
|
||||
[self updateFirstSectionHeaderInTableView:self.searchController.searchResultsTableView];
|
||||
[self updateFirstSectionHeader];
|
||||
}
|
||||
|
||||
- (void)updateFirstSectionHeaderInTableView:(UITableView *)tableView
|
||||
- (void)updateFirstSectionHeader
|
||||
{
|
||||
UIView *view = [tableView headerViewForSection:0];
|
||||
UIView *view = [self.tableView headerViewForSection:0];
|
||||
if ([view isKindOfClass:[UITableViewHeaderFooterView class]]) {
|
||||
UITableViewHeaderFooterView *headerView = (UITableViewHeaderFooterView *)view;
|
||||
headerView.textLabel.text = [self headerTextForTableView:tableView];
|
||||
headerView.textLabel.text = [self headerText];
|
||||
[headerView setNeedsLayout];
|
||||
}
|
||||
}
|
||||
|
||||
- (NSString *)headerTextForTableView:(UITableView *)tableView
|
||||
- (NSString *)headerText
|
||||
{
|
||||
NSString *headerText = nil;
|
||||
if ([FLEXNetworkObserver isEnabled]) {
|
||||
long long bytesReceived = 0;
|
||||
NSInteger totalRequests = 0;
|
||||
if (tableView == self.tableView) {
|
||||
bytesReceived = self.bytesReceived;
|
||||
totalRequests = [self.networkTransactions count];
|
||||
} else if (tableView == self.searchController.searchResultsTableView) {
|
||||
if (self.searchController.isActive) {
|
||||
bytesReceived = self.filteredBytesReceived;
|
||||
totalRequests = [self.filteredNetworkTransactions count];
|
||||
} else {
|
||||
bytesReceived = self.bytesReceived;
|
||||
totalRequests = [self.networkTransactions count];
|
||||
}
|
||||
NSString *byteCountText = [NSByteCountFormatter stringFromByteCount:bytesReceived countStyle:NSByteCountFormatterCountStyleBinary];
|
||||
NSString *requestsText = totalRequests == 1 ? @"Request" : @"Requests";
|
||||
@@ -179,13 +173,19 @@
|
||||
if (self.rowInsertInProgress) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (self.searchController.isActive) {
|
||||
[self updateTransactions];
|
||||
[self updateSearchResults];
|
||||
return;
|
||||
}
|
||||
|
||||
NSInteger existingRowCount = [self.networkTransactions count];
|
||||
[self updateTransactions];
|
||||
NSInteger newRowCount = [self.networkTransactions count];
|
||||
NSInteger addedRowCount = newRowCount - existingRowCount;
|
||||
|
||||
if (addedRowCount != 0) {
|
||||
if (addedRowCount != 0 && !self.isPresentingSearch) {
|
||||
// Insert animation if we're at the top.
|
||||
if (self.tableView.contentOffset.y <= 0.0 && addedRowCount > 0) {
|
||||
[CATransaction begin];
|
||||
@@ -196,7 +196,7 @@
|
||||
[self tryUpdateTransactions];
|
||||
}];
|
||||
|
||||
NSMutableArray *indexPathsToReload = [NSMutableArray array];
|
||||
NSMutableArray<NSIndexPath *> *indexPathsToReload = [NSMutableArray array];
|
||||
for (NSInteger row = 0; row < addedRowCount; row++) {
|
||||
[indexPathsToReload addObject:[NSIndexPath indexPathForRow:row inSection:0]];
|
||||
}
|
||||
@@ -210,10 +210,6 @@
|
||||
CGFloat contentHeightChange = self.tableView.contentSize.height - existingContentSize.height;
|
||||
self.tableView.contentOffset = CGPointMake(self.tableView.contentOffset.x, self.tableView.contentOffset.y + contentHeightChange);
|
||||
}
|
||||
|
||||
if (self.searchController.isActive) {
|
||||
[self updateSearchResultsWithSearchString:self.searchController.searchBar.text];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -222,38 +218,31 @@
|
||||
[self updateBytesReceived];
|
||||
[self updateFilteredBytesReceived];
|
||||
|
||||
FLEXNetworkTransaction *transaction = [notification.userInfo objectForKey:kFLEXNetworkRecorderUserInfoTransactionKey];
|
||||
NSArray *tableViews = @[self.tableView];
|
||||
if (self.searchController.searchResultsTableView) {
|
||||
tableViews = [tableViews arrayByAddingObject:self.searchController.searchResultsTableView];
|
||||
}
|
||||
FLEXNetworkTransaction *transaction = notification.userInfo[kFLEXNetworkRecorderUserInfoTransactionKey];
|
||||
|
||||
// Update both the main table view and search table view if needed.
|
||||
for (UITableView *tableView in tableViews) {
|
||||
for (FLEXNetworkTransactionTableViewCell *cell in [tableView visibleCells]) {
|
||||
if ([cell.transaction isEqual:transaction]) {
|
||||
// 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;
|
||||
}
|
||||
for (FLEXNetworkTransactionTableViewCell *cell in [self.tableView visibleCells]) {
|
||||
if ([cell.transaction isEqual:transaction]) {
|
||||
// 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;
|
||||
}
|
||||
[self updateFirstSectionHeaderInTableView:tableView];
|
||||
}
|
||||
[self updateFirstSectionHeader];
|
||||
}
|
||||
|
||||
- (void)handleTransactionsClearedNotification:(NSNotification *)notification
|
||||
{
|
||||
[self updateTransactions];
|
||||
[self.tableView reloadData];
|
||||
[self.searchController.searchResultsTableView reloadData];
|
||||
}
|
||||
|
||||
- (void)handleNetworkObserverEnabledStateChangedNotification:(NSNotification *)notification
|
||||
{
|
||||
// Update the header, which displays a warning when network debugging is disabled
|
||||
[self updateFirstSectionHeaderInTableView:self.tableView];
|
||||
[self updateFirstSectionHeader];
|
||||
}
|
||||
|
||||
#pragma mark - Table view data source
|
||||
@@ -265,18 +254,12 @@
|
||||
|
||||
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
|
||||
{
|
||||
NSInteger numberOfRows = 0;
|
||||
if (tableView == self.tableView) {
|
||||
numberOfRows = [self.networkTransactions count];
|
||||
} else if (tableView == self.searchController.searchResultsTableView) {
|
||||
numberOfRows = [self.filteredNetworkTransactions count];
|
||||
}
|
||||
return numberOfRows;
|
||||
return self.searchController.isActive ? [self.filteredNetworkTransactions count] : [self.networkTransactions count];
|
||||
}
|
||||
|
||||
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
|
||||
{
|
||||
return [self headerTextForTableView:tableView];
|
||||
return [self headerText];
|
||||
}
|
||||
|
||||
- (void)tableView:(UITableView *)tableView willDisplayHeaderView:(UIView *)view forSection:(NSInteger)section
|
||||
@@ -335,38 +318,47 @@
|
||||
|
||||
- (FLEXNetworkTransaction *)transactionAtIndexPath:(NSIndexPath *)indexPath inTableView:(UITableView *)tableView
|
||||
{
|
||||
FLEXNetworkTransaction *transaction = nil;
|
||||
if (tableView == self.tableView) {
|
||||
transaction = [self.networkTransactions objectAtIndex:indexPath.row];
|
||||
} else if (tableView == self.searchController.searchResultsTableView) {
|
||||
transaction = [self.filteredNetworkTransactions objectAtIndex:indexPath.row];
|
||||
}
|
||||
return transaction;
|
||||
return self.searchController.isActive ? self.filteredNetworkTransactions[indexPath.row] : self.networkTransactions[indexPath.row];
|
||||
}
|
||||
|
||||
#pragma mark - Search display delegate
|
||||
#pragma mark - UISearchResultsUpdating
|
||||
|
||||
- (BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchString:(NSString *)searchString
|
||||
- (void)updateSearchResultsForSearchController:(UISearchController *)searchController
|
||||
{
|
||||
[self updateSearchResultsWithSearchString:searchString];
|
||||
|
||||
// Reload done after the data is filtered asynchronously
|
||||
return NO;
|
||||
[self updateSearchResults];
|
||||
}
|
||||
|
||||
- (void)updateSearchResultsWithSearchString:(NSString *)searchString
|
||||
- (void)updateSearchResults
|
||||
{
|
||||
NSString *searchString = self.searchController.searchBar.text;
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||
NSArray *filteredNetworkTransactions = [self.networkTransactions filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(FLEXNetworkTransaction *transaction, NSDictionary *bindings) {
|
||||
NSArray<FLEXNetworkTransaction *> *filteredNetworkTransactions = [self.networkTransactions filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(FLEXNetworkTransaction *transaction, NSDictionary<NSString *, id> *bindings) {
|
||||
return [[transaction.request.URL absoluteString] rangeOfString:searchString options:NSCaseInsensitiveSearch].length > 0;
|
||||
}]];
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
if ([self.searchController.searchBar.text isEqual:searchString]) {
|
||||
self.filteredNetworkTransactions = filteredNetworkTransactions;
|
||||
[self.searchController.searchResultsTableView reloadData];
|
||||
[self.tableView reloadData];
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
#pragma mark - UISearchControllerDelegate
|
||||
|
||||
- (void)willPresentSearchController:(UISearchController *)searchController
|
||||
{
|
||||
self.isPresentingSearch = YES;
|
||||
}
|
||||
|
||||
- (void)didPresentSearchController:(UISearchController *)searchController
|
||||
{
|
||||
self.isPresentingSearch = NO;
|
||||
}
|
||||
|
||||
- (void)willDismissSearchController:(UISearchController *)searchController
|
||||
{
|
||||
[self.tableView reloadData];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -27,10 +27,13 @@ extern NSString *const kFLEXNetworkRecorderTransactionsClearedNotification;
|
||||
/// If NO, the recorder not cache will not cache response for content types with an "image", "video", or "audio" prefix.
|
||||
@property (nonatomic, assign) BOOL shouldCacheMediaResponses;
|
||||
|
||||
@property (nonatomic, copy) NSArray<NSString *> *hostBlacklist;
|
||||
|
||||
|
||||
// Accessing recorded network activity
|
||||
|
||||
/// Array of FLEXNetworkTransaction objects ordered by start time with the newest first.
|
||||
- (NSArray *)networkTransactions;
|
||||
- (NSArray<FLEXNetworkTransaction *> *)networkTransactions;
|
||||
|
||||
/// The full response data IFF it hasn't been purged due to memory pressure.
|
||||
- (NSData *)cachedResponseBodyForTransaction:(FLEXNetworkTransaction *)transaction;
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
//
|
||||
|
||||
#import "FLEXNetworkRecorder.h"
|
||||
#import "FLEXNetworkCurlLogger.h"
|
||||
#import "FLEXNetworkTransaction.h"
|
||||
#import "FLEXUtility.h"
|
||||
#import "FLEXResources.h"
|
||||
@@ -21,8 +22,8 @@ NSString *const kFLEXNetworkRecorderResponseCacheLimitDefaultsKey = @"com.flex.r
|
||||
@interface FLEXNetworkRecorder ()
|
||||
|
||||
@property (nonatomic, strong) NSCache *responseCache;
|
||||
@property (nonatomic, strong) NSMutableArray *orderedTransactions;
|
||||
@property (nonatomic, strong) NSMutableDictionary *networkTransactionsForRequestIdentifiers;
|
||||
@property (nonatomic, strong) NSMutableArray<FLEXNetworkTransaction *> *orderedTransactions;
|
||||
@property (nonatomic, strong) NSMutableDictionary<NSString *, FLEXNetworkTransaction *> *networkTransactionsForRequestIdentifiers;
|
||||
@property (nonatomic, strong) dispatch_queue_t queue;
|
||||
|
||||
@end
|
||||
@@ -73,9 +74,9 @@ NSString *const kFLEXNetworkRecorderResponseCacheLimitDefaultsKey = @"com.flex.r
|
||||
[[NSUserDefaults standardUserDefaults] setObject:@(responseCacheByteLimit) forKey:kFLEXNetworkRecorderResponseCacheLimitDefaultsKey];
|
||||
}
|
||||
|
||||
- (NSArray *)networkTransactions
|
||||
- (NSArray<FLEXNetworkTransaction *> *)networkTransactions
|
||||
{
|
||||
__block NSArray *transactions = nil;
|
||||
__block NSArray<FLEXNetworkTransaction *> *transactions = nil;
|
||||
dispatch_sync(self.queue, ^{
|
||||
transactions = [self.orderedTransactions copy];
|
||||
});
|
||||
@@ -103,6 +104,12 @@ NSString *const kFLEXNetworkRecorderResponseCacheLimitDefaultsKey = @"com.flex.r
|
||||
|
||||
- (void)recordRequestWillBeSentWithRequestID:(NSString *)requestID request:(NSURLRequest *)request redirectResponse:(NSURLResponse *)redirectResponse
|
||||
{
|
||||
for (NSString *host in self.hostBlacklist) {
|
||||
if ([request.URL.host hasSuffix:host]) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
NSDate *startDate = [NSDate date];
|
||||
|
||||
if (redirectResponse) {
|
||||
@@ -129,7 +136,7 @@ NSString *const kFLEXNetworkRecorderResponseCacheLimitDefaultsKey = @"com.flex.r
|
||||
NSDate *responseDate = [NSDate date];
|
||||
|
||||
dispatch_async(self.queue, ^{
|
||||
FLEXNetworkTransaction *transaction = [self.networkTransactionsForRequestIdentifiers objectForKey:requestID];
|
||||
FLEXNetworkTransaction *transaction = self.networkTransactionsForRequestIdentifiers[requestID];
|
||||
if (!transaction) {
|
||||
return;
|
||||
}
|
||||
@@ -144,7 +151,7 @@ NSString *const kFLEXNetworkRecorderResponseCacheLimitDefaultsKey = @"com.flex.r
|
||||
- (void)recordDataReceivedWithRequestID:(NSString *)requestID dataLength:(int64_t)dataLength
|
||||
{
|
||||
dispatch_async(self.queue, ^{
|
||||
FLEXNetworkTransaction *transaction = [self.networkTransactionsForRequestIdentifiers objectForKey:requestID];
|
||||
FLEXNetworkTransaction *transaction = self.networkTransactionsForRequestIdentifiers[requestID];
|
||||
if (!transaction) {
|
||||
return;
|
||||
}
|
||||
@@ -159,7 +166,7 @@ NSString *const kFLEXNetworkRecorderResponseCacheLimitDefaultsKey = @"com.flex.r
|
||||
NSDate *finishedDate = [NSDate date];
|
||||
|
||||
dispatch_async(self.queue, ^{
|
||||
FLEXNetworkTransaction *transaction = [self.networkTransactionsForRequestIdentifiers objectForKey:requestID];
|
||||
FLEXNetworkTransaction *transaction = self.networkTransactionsForRequestIdentifiers[requestID];
|
||||
if (!transaction) {
|
||||
return;
|
||||
}
|
||||
@@ -168,7 +175,7 @@ NSString *const kFLEXNetworkRecorderResponseCacheLimitDefaultsKey = @"com.flex.r
|
||||
|
||||
BOOL shouldCache = [responseBody length] > 0;
|
||||
if (!self.shouldCacheMediaResponses) {
|
||||
NSArray *ignoredMIMETypePrefixes = @[ @"audio", @"image", @"video" ];
|
||||
NSArray<NSString *> *ignoredMIMETypePrefixes = @[ @"audio", @"image", @"video" ];
|
||||
for (NSString *ignoredPrefix in ignoredMIMETypePrefixes) {
|
||||
shouldCache = shouldCache && ![transaction.response.MIMEType hasPrefix:ignoredPrefix];
|
||||
}
|
||||
@@ -215,7 +222,7 @@ NSString *const kFLEXNetworkRecorderResponseCacheLimitDefaultsKey = @"com.flex.r
|
||||
- (void)recordLoadingFailedWithRequestID:(NSString *)requestID error:(NSError *)error
|
||||
{
|
||||
dispatch_async(self.queue, ^{
|
||||
FLEXNetworkTransaction *transaction = [self.networkTransactionsForRequestIdentifiers objectForKey:requestID];
|
||||
FLEXNetworkTransaction *transaction = self.networkTransactionsForRequestIdentifiers[requestID];
|
||||
if (!transaction) {
|
||||
return;
|
||||
}
|
||||
@@ -230,7 +237,7 @@ NSString *const kFLEXNetworkRecorderResponseCacheLimitDefaultsKey = @"com.flex.r
|
||||
- (void)recordMechanism:(NSString *)mechanism forRequestID:(NSString *)requestID
|
||||
{
|
||||
dispatch_async(self.queue, ^{
|
||||
FLEXNetworkTransaction *transaction = [self.networkTransactionsForRequestIdentifiers objectForKey:requestID];
|
||||
FLEXNetworkTransaction *transaction = self.networkTransactionsForRequestIdentifiers[requestID];
|
||||
if (!transaction) {
|
||||
return;
|
||||
}
|
||||
@@ -245,7 +252,7 @@ NSString *const kFLEXNetworkRecorderResponseCacheLimitDefaultsKey = @"com.flex.r
|
||||
- (void)postNewTransactionNotificationWithTransaction:(FLEXNetworkTransaction *)transaction
|
||||
{
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
NSDictionary *userInfo = @{ kFLEXNetworkRecorderUserInfoTransactionKey : transaction };
|
||||
NSDictionary<NSString *, id> *userInfo = @{ kFLEXNetworkRecorderUserInfoTransactionKey : transaction };
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:kFLEXNetworkRecorderNewTransactionNotification object:self userInfo:userInfo];
|
||||
});
|
||||
}
|
||||
@@ -253,7 +260,7 @@ NSString *const kFLEXNetworkRecorderResponseCacheLimitDefaultsKey = @"com.flex.r
|
||||
- (void)postUpdateNotificationForTransaction:(FLEXNetworkTransaction *)transaction
|
||||
{
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
NSDictionary *userInfo = @{ kFLEXNetworkRecorderUserInfoTransactionKey : transaction };
|
||||
NSDictionary<NSString *, id> *userInfo = @{ kFLEXNetworkRecorderUserInfoTransactionKey : transaction };
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:kFLEXNetworkRecorderTransactionUpdatedNotification object:self userInfo:userInfo];
|
||||
});
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
|
||||
@interface FLEXNetworkSettingsTableViewController () <UIActionSheetDelegate>
|
||||
|
||||
@property (nonatomic, copy) NSArray *cells;
|
||||
@property (nonatomic, copy) NSArray<UITableViewCell *> *cells;
|
||||
|
||||
@property (nonatomic, strong) UITableViewCell *cacheLimitCell;
|
||||
|
||||
@@ -34,14 +34,11 @@
|
||||
{
|
||||
[super viewDidLoad];
|
||||
|
||||
NSMutableArray *mutableCells = [NSMutableArray array];
|
||||
NSMutableArray<UITableViewCell *> *mutableCells = [NSMutableArray array];
|
||||
|
||||
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];
|
||||
@@ -100,7 +92,7 @@
|
||||
|
||||
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
return [self.cells objectAtIndex:indexPath.row];
|
||||
return self.cells[indexPath.row];
|
||||
}
|
||||
|
||||
#pragma mark - UIActionSheetDelegate
|
||||
@@ -144,7 +136,7 @@
|
||||
if (isDestructive) {
|
||||
actionButton.tintColor = [UIColor redColor];
|
||||
}
|
||||
actionButton.titleLabel.font = [[self class] cellTitleFont];;
|
||||
actionButton.titleLabel.font = [[self class] cellTitleFont];
|
||||
[actionButton addTarget:self action:@selector(clearRequestsTapped:) forControlEvents:UIControlEventTouchUpInside];
|
||||
|
||||
[buttonCell.contentView addSubview:actionButton];
|
||||
|
||||
@@ -36,6 +36,9 @@ typedef NS_ENUM(NSInteger, FLEXNetworkTransactionState) {
|
||||
/// Only applicable for image downloads. A small thumbnail to preview the full response.
|
||||
@property (nonatomic, strong) UIImage *responseThumbnail;
|
||||
|
||||
/// Populated lazily. Handles both normal HTTPBody data and HTTPBodyStreams.
|
||||
@property (nonatomic, strong, readonly) NSData *cachedRequestBody;
|
||||
|
||||
+ (NSString *)readableStringFromTransactionState:(FLEXNetworkTransactionState)state;
|
||||
|
||||
@end
|
||||
|
||||
@@ -8,6 +8,12 @@
|
||||
|
||||
#import "FLEXNetworkTransaction.h"
|
||||
|
||||
@interface FLEXNetworkTransaction ()
|
||||
|
||||
@property (nonatomic, strong, readwrite) NSData *cachedRequestBody;
|
||||
|
||||
@end
|
||||
|
||||
@implementation FLEXNetworkTransaction
|
||||
|
||||
- (NSString *)description
|
||||
@@ -22,6 +28,28 @@
|
||||
return description;
|
||||
}
|
||||
|
||||
- (NSData *)cachedRequestBody {
|
||||
if (!_cachedRequestBody) {
|
||||
if (self.request.HTTPBody != nil) {
|
||||
_cachedRequestBody = self.request.HTTPBody;
|
||||
} else if ([self.request.HTTPBodyStream conformsToProtocol:@protocol(NSCopying)]) {
|
||||
NSInputStream *bodyStream = [self.request.HTTPBodyStream copy];
|
||||
const NSUInteger bufferSize = 1024;
|
||||
uint8_t buffer[bufferSize];
|
||||
NSMutableData *data = [NSMutableData data];
|
||||
[bodyStream open];
|
||||
NSInteger readBytes = 0;
|
||||
do {
|
||||
readBytes = [bodyStream read:buffer maxLength:bufferSize];
|
||||
[data appendBytes:buffer length:readBytes];
|
||||
} while (readBytes > 0);
|
||||
[bodyStream close];
|
||||
_cachedRequestBody = data;
|
||||
}
|
||||
}
|
||||
return _cachedRequestBody;
|
||||
}
|
||||
|
||||
+ (NSString *)readableStringFromTransactionState:(FLEXNetworkTransactionState)state
|
||||
{
|
||||
NSString *readableString = nil;
|
||||
|
||||
@@ -7,23 +7,14 @@
|
||||
//
|
||||
|
||||
#import "FLEXNetworkTransactionDetailTableViewController.h"
|
||||
#import "FLEXNetworkCurlLogger.h"
|
||||
#import "FLEXNetworkRecorder.h"
|
||||
#import "FLEXNetworkTransaction.h"
|
||||
#import "FLEXWebViewController.h"
|
||||
#import "FLEXImagePreviewViewController.h"
|
||||
#import "FLEXMultilineTableViewCell.h"
|
||||
#import "FLEXUtility.h"
|
||||
|
||||
@interface FLEXNetworkDetailSection : NSObject
|
||||
|
||||
@property (nonatomic, copy) NSString *title;
|
||||
@property (nonatomic, copy) NSArray *rows;
|
||||
|
||||
@end
|
||||
|
||||
@implementation FLEXNetworkDetailSection
|
||||
|
||||
@end
|
||||
#import "FLEXManager+Private.h"
|
||||
|
||||
typedef UIViewController *(^FLEXNetworkDetailRowSelectionFuture)(void);
|
||||
|
||||
@@ -39,9 +30,20 @@ typedef UIViewController *(^FLEXNetworkDetailRowSelectionFuture)(void);
|
||||
|
||||
@end
|
||||
|
||||
@interface FLEXNetworkDetailSection : NSObject
|
||||
|
||||
@property (nonatomic, copy) NSString *title;
|
||||
@property (nonatomic, copy) NSArray<FLEXNetworkDetailRow *> *rows;
|
||||
|
||||
@end
|
||||
|
||||
@implementation FLEXNetworkDetailSection
|
||||
|
||||
@end
|
||||
|
||||
@interface FLEXNetworkTransactionDetailTableViewController ()
|
||||
|
||||
@property (nonatomic, copy) NSArray *sections;
|
||||
@property (nonatomic, copy) NSArray<FLEXNetworkDetailSection *> *sections;
|
||||
|
||||
@end
|
||||
|
||||
@@ -53,7 +55,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:)];
|
||||
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"Copy curl" style:UIBarButtonItemStylePlain target:self action:@selector(copyButtonPressed:)];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
@@ -74,7 +76,7 @@ typedef UIViewController *(^FLEXNetworkDetailRowSelectionFuture)(void);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setSections:(NSArray *)sections
|
||||
- (void)setSections:(NSArray<FLEXNetworkDetailSection *> *)sections
|
||||
{
|
||||
if (![_sections isEqual:sections]) {
|
||||
_sections = [sections copy];
|
||||
@@ -84,7 +86,7 @@ typedef UIViewController *(^FLEXNetworkDetailRowSelectionFuture)(void);
|
||||
|
||||
- (void)rebuildTableSections
|
||||
{
|
||||
NSMutableArray *sections = [NSMutableArray array];
|
||||
NSMutableArray<FLEXNetworkDetailSection *> *sections = [NSMutableArray array];
|
||||
|
||||
FLEXNetworkDetailSection *generalSection = [[self class] generalSectionForTransaction:self.transaction];
|
||||
if ([generalSection.rows count] > 0) {
|
||||
@@ -120,26 +122,7 @@ 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];
|
||||
[[UIPasteboard generalPasteboard] setString:[FLEXNetworkCurlLogger curlCommandString:_transaction.request]];
|
||||
}
|
||||
|
||||
#pragma mark - Table view data source
|
||||
@@ -151,13 +134,13 @@ typedef UIViewController *(^FLEXNetworkDetailRowSelectionFuture)(void);
|
||||
|
||||
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
|
||||
{
|
||||
FLEXNetworkDetailSection *sectionModel = [self.sections objectAtIndex:section];
|
||||
FLEXNetworkDetailSection *sectionModel = self.sections[section];
|
||||
return [sectionModel.rows count];
|
||||
}
|
||||
|
||||
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
|
||||
{
|
||||
FLEXNetworkDetailSection *sectionModel = [self.sections objectAtIndex:section];
|
||||
FLEXNetworkDetailSection *sectionModel = self.sections[section];
|
||||
return sectionModel.title;
|
||||
}
|
||||
|
||||
@@ -200,8 +183,8 @@ typedef UIViewController *(^FLEXNetworkDetailRowSelectionFuture)(void);
|
||||
|
||||
- (FLEXNetworkDetailRow *)rowModelAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
FLEXNetworkDetailSection *sectionModel = [self.sections objectAtIndex:indexPath.section];
|
||||
return [sectionModel.rows objectAtIndex:indexPath.row];
|
||||
FLEXNetworkDetailSection *sectionModel = self.sections[indexPath.section];
|
||||
return sectionModel.rows[indexPath.row];
|
||||
}
|
||||
|
||||
#pragma mark - Cell Copying
|
||||
@@ -228,10 +211,10 @@ typedef UIViewController *(^FLEXNetworkDetailRowSelectionFuture)(void);
|
||||
|
||||
+ (NSAttributedString *)attributedTextForRow:(FLEXNetworkDetailRow *)row
|
||||
{
|
||||
NSDictionary *titleAttributes = @{ NSFontAttributeName : [UIFont fontWithName:@"HelveticaNeue-Medium" size:12.0],
|
||||
NSForegroundColorAttributeName : [UIColor colorWithWhite:0.5 alpha:1.0] };
|
||||
NSDictionary *detailAttributes = @{ NSFontAttributeName : [FLEXUtility defaultTableViewCellLabelFont],
|
||||
NSForegroundColorAttributeName : [UIColor blackColor] };
|
||||
NSDictionary<NSString *, id> *titleAttributes = @{ NSFontAttributeName : [UIFont fontWithName:@"HelveticaNeue-Medium" size:12.0],
|
||||
NSForegroundColorAttributeName : [UIColor colorWithWhite:0.5 alpha:1.0] };
|
||||
NSDictionary<NSString *, id> *detailAttributes = @{ NSFontAttributeName : [FLEXUtility defaultTableViewCellLabelFont],
|
||||
NSForegroundColorAttributeName : [UIColor blackColor] };
|
||||
|
||||
NSString *title = [NSString stringWithFormat:@"%@: ", row.title];
|
||||
NSString *detailText = row.detailText ?: @"";
|
||||
@@ -246,7 +229,7 @@ typedef UIViewController *(^FLEXNetworkDetailRowSelectionFuture)(void);
|
||||
|
||||
+ (FLEXNetworkDetailSection *)generalSectionForTransaction:(FLEXNetworkTransaction *)transaction
|
||||
{
|
||||
NSMutableArray *rows = [NSMutableArray array];
|
||||
NSMutableArray<FLEXNetworkDetailRow *> *rows = [NSMutableArray array];
|
||||
|
||||
FLEXNetworkDetailRow *requestURLRow = [[FLEXNetworkDetailRow alloc] init];
|
||||
requestURLRow.title = @"Request URL";
|
||||
@@ -264,10 +247,10 @@ typedef UIViewController *(^FLEXNetworkDetailRowSelectionFuture)(void);
|
||||
requestMethodRow.detailText = transaction.request.HTTPMethod;
|
||||
[rows addObject:requestMethodRow];
|
||||
|
||||
if ([transaction.request.HTTPBody length] > 0) {
|
||||
if ([transaction.cachedRequestBody length] > 0) {
|
||||
FLEXNetworkDetailRow *postBodySizeRow = [[FLEXNetworkDetailRow alloc] init];
|
||||
postBodySizeRow.title = @"Request Body Size";
|
||||
postBodySizeRow.detailText = [NSByteCountFormatter stringFromByteCount:[transaction.request.HTTPBody length] countStyle:NSByteCountFormatterCountStyleBinary];
|
||||
postBodySizeRow.detailText = [NSByteCountFormatter stringFromByteCount:[transaction.cachedRequestBody length] countStyle:NSByteCountFormatterCountStyleBinary];
|
||||
[rows addObject:postBodySizeRow];
|
||||
|
||||
FLEXNetworkDetailRow *postBodyRow = [[FLEXNetworkDetailRow alloc] init];
|
||||
@@ -396,7 +379,7 @@ typedef UIViewController *(^FLEXNetworkDetailRowSelectionFuture)(void);
|
||||
{
|
||||
FLEXNetworkDetailSection *postBodySection = [[FLEXNetworkDetailSection alloc] init];
|
||||
postBodySection.title = @"Request Body Parameters";
|
||||
if ([transaction.request.HTTPBody length] > 0) {
|
||||
if ([transaction.cachedRequestBody length] > 0) {
|
||||
NSString *contentType = [transaction.request valueForHTTPHeaderField:@"Content-Type"];
|
||||
if ([contentType hasPrefix:@"application/x-www-form-urlencoded"]) {
|
||||
NSString *bodyString = [[NSString alloc] initWithData:[self postBodyDataForTransaction:transaction] encoding:NSUTF8StringEncoding];
|
||||
@@ -408,7 +391,7 @@ typedef UIViewController *(^FLEXNetworkDetailRowSelectionFuture)(void);
|
||||
|
||||
+ (FLEXNetworkDetailSection *)queryParametersSectionForTransaction:(FLEXNetworkTransaction *)transaction
|
||||
{
|
||||
NSDictionary *queryDictionary = [FLEXUtility dictionaryFromQuery:transaction.request.URL.query];
|
||||
NSDictionary<NSString *, id> *queryDictionary = [FLEXUtility dictionaryFromQuery:transaction.request.URL.query];
|
||||
FLEXNetworkDetailSection *querySection = [[FLEXNetworkDetailSection alloc] init];
|
||||
querySection.title = @"Query Parameters";
|
||||
querySection.rows = [self networkDetailRowsFromDictionary:queryDictionary];
|
||||
@@ -427,12 +410,12 @@ typedef UIViewController *(^FLEXNetworkDetailRowSelectionFuture)(void);
|
||||
return responseHeadersSection;
|
||||
}
|
||||
|
||||
+ (NSArray *)networkDetailRowsFromDictionary:(NSDictionary *)dictionary
|
||||
+ (NSArray<FLEXNetworkDetailRow *> *)networkDetailRowsFromDictionary:(NSDictionary<NSString *, id> *)dictionary
|
||||
{
|
||||
NSMutableArray *rows = [NSMutableArray arrayWithCapacity:[dictionary count]];
|
||||
NSArray *sortedKeys = [[dictionary allKeys] sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)];
|
||||
NSMutableArray<FLEXNetworkDetailRow *> *rows = [NSMutableArray arrayWithCapacity:[dictionary count]];
|
||||
NSArray<NSString *> *sortedKeys = [[dictionary allKeys] sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)];
|
||||
for (NSString *key in sortedKeys) {
|
||||
NSString *value = [dictionary objectForKey:key];
|
||||
id value = dictionary[key];
|
||||
FLEXNetworkDetailRow *row = [[FLEXNetworkDetailRow alloc] init];
|
||||
row.title = key;
|
||||
row.detailText = [value description];
|
||||
@@ -443,6 +426,16 @@ typedef UIViewController *(^FLEXNetworkDetailRowSelectionFuture)(void);
|
||||
|
||||
+ (UIViewController *)detailViewControllerForMIMEType:(NSString *)mimeType data:(NSData *)data
|
||||
{
|
||||
FLEXCustomContentViewerFuture makeCustomViewer = [FLEXManager sharedManager].customContentTypeViewers[mimeType.lowercaseString];
|
||||
|
||||
if (makeCustomViewer) {
|
||||
UIViewController *viewer = makeCustomViewer(data);
|
||||
|
||||
if (viewer) {
|
||||
return viewer;
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME (RKO): Don't rely on UTF8 string encoding
|
||||
UIViewController *detailViewController = nil;
|
||||
if ([FLEXUtility isValidJSONData:data]) {
|
||||
@@ -470,7 +463,7 @@ typedef UIViewController *(^FLEXNetworkDetailRowSelectionFuture)(void);
|
||||
|
||||
+ (NSData *)postBodyDataForTransaction:(FLEXNetworkTransaction *)transaction
|
||||
{
|
||||
NSData *bodyData = transaction.request.HTTPBody;
|
||||
NSData *bodyData = transaction.cachedRequestBody;
|
||||
if ([bodyData length] > 0) {
|
||||
NSString *contentEncoding = [transaction.request valueForHTTPHeaderField:@"Content-Encoding"];
|
||||
if ([contentEncoding rangeOfString:@"deflate" options:NSCaseInsensitiveSearch].length > 0 || [contentEncoding rangeOfString:@"gzip" options:NSCaseInsensitiveSearch].length > 0) {
|
||||
|
||||
@@ -79,7 +79,7 @@ NSString *const kFLEXNetworkTransactionCellIdentifier = @"kFLEXNetworkTransactio
|
||||
self.nameLabel.text = [self nameLabelText];
|
||||
CGSize nameLabelPreferredSize = [self.nameLabel sizeThatFits:CGSizeMake(availableTextWidth, CGFLOAT_MAX)];
|
||||
self.nameLabel.frame = CGRectMake(textOriginX, kVerticalPadding, availableTextWidth, nameLabelPreferredSize.height);
|
||||
self.nameLabel.textColor = self.transaction.error ? [UIColor redColor] : [UIColor blackColor];
|
||||
self.nameLabel.textColor = (self.transaction.error || [FLEXUtility isErrorStatusCodeFromURLResponse:self.transaction.response]) ? [UIColor redColor] : [UIColor blackColor];
|
||||
|
||||
self.pathLabel.text = [self pathLabelText];
|
||||
CGSize pathLabelPreferredSize = [self.pathLabel sizeThatFits:CGSizeMake(availableTextWidth, CGFLOAT_MAX)];
|
||||
@@ -111,7 +111,7 @@ NSString *const kFLEXNetworkTransactionCellIdentifier = @"kFLEXNetworkTransactio
|
||||
- (NSString *)pathLabelText
|
||||
{
|
||||
NSURL *url = self.transaction.request.URL;
|
||||
NSMutableArray *mutablePathComponents = [[url pathComponents] mutableCopy];
|
||||
NSMutableArray<NSString *> *mutablePathComponents = [[url pathComponents] mutableCopy];
|
||||
if ([mutablePathComponents count] > 0) {
|
||||
[mutablePathComponents removeLastObject];
|
||||
}
|
||||
@@ -124,14 +124,16 @@ NSString *const kFLEXNetworkTransactionCellIdentifier = @"kFLEXNetworkTransactio
|
||||
|
||||
- (NSString *)transactionDetailsLabelText
|
||||
{
|
||||
NSMutableArray *detailComponents = [NSMutableArray array];
|
||||
NSMutableArray<NSString *> *detailComponents = [NSMutableArray array];
|
||||
|
||||
NSString *timestamp = [[self class] timestampStringFromRequestDate:self.transaction.startTime];
|
||||
[detailComponents addObject:timestamp];
|
||||
if ([timestamp length] > 0) {
|
||||
[detailComponents addObject:timestamp];
|
||||
}
|
||||
|
||||
// Omit method for GET (assumed as default)
|
||||
NSString *httpMethod = self.transaction.request.HTTPMethod;
|
||||
if (httpMethod) {
|
||||
if ([httpMethod length] > 0) {
|
||||
[detailComponents addObject:httpMethod];
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
extern NSString *const kFLEXNetworkObserverEnabledStateChangedNotification;
|
||||
FOUNDATION_EXTERN NSString *const kFLEXNetworkObserverEnabledStateChangedNotification;
|
||||
|
||||
/// This class swizzles NSURLConnection and NSURLSession delegate methods to observe events in the URL loading system.
|
||||
/// High level network events are sent to the default FLEXNetworkRecorder instance which maintains the request history and caches response bodies.
|
||||
@@ -22,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,8 +68,7 @@ didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask delegate:(id <NSU
|
||||
|
||||
@interface FLEXNetworkObserver ()
|
||||
|
||||
@property (nonatomic, assign, getter=isEnabled) BOOL enabled;
|
||||
@property (nonatomic, strong) NSMutableDictionary *requestStatesForRequestIDs;
|
||||
@property (nonatomic, strong) NSMutableDictionary<NSString *, FLEXInternalRequestState *> *requestStatesForRequestIDs;
|
||||
@property (nonatomic, strong) dispatch_queue_t queue;
|
||||
|
||||
@end
|
||||
@@ -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] setBool:shouldEnableOnLaunch 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,11 +128,6 @@ 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.
|
||||
@@ -170,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
|
||||
@@ -248,7 +172,7 @@ didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask delegate:(id <NSU
|
||||
@selector(URLSession:dataTask:didReceiveData:),
|
||||
@selector(URLSession:dataTask:didReceiveResponse:completionHandler:),
|
||||
@selector(URLSession:task:didCompleteWithError:),
|
||||
@selector(URLSession:dataTask:didBecomeDownloadTask:delegate:),
|
||||
@selector(URLSession:dataTask:didBecomeDownloadTask:),
|
||||
@selector(URLSession:downloadTask:didWriteData:totalBytesWritten:totalBytesExpectedToWrite:),
|
||||
@selector(URLSession:downloadTask:didFinishDownloadingToURL:)
|
||||
};
|
||||
@@ -333,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) {
|
||||
@@ -364,7 +288,7 @@ didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask delegate:(id <NSU
|
||||
class = NSClassFromString([@[@"__", @"NSC", @"FURLS", @"ession", @"Task"] componentsJoinedByString:@""]);
|
||||
}
|
||||
SEL selector = @selector(resume);
|
||||
SEL swizzledSelector = [self swizzledSelectorForSelector:selector];
|
||||
SEL swizzledSelector = [FLEXUtility swizzledSelectorForSelector:selector];
|
||||
|
||||
Method originalResume = class_getInstanceMethod(class, selector);
|
||||
|
||||
@@ -386,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);
|
||||
|
||||
@@ -394,7 +318,7 @@ didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask delegate:(id <NSU
|
||||
if ([FLEXNetworkObserver isEnabled]) {
|
||||
NSString *requestID = [self nextRequestID];
|
||||
[[FLEXNetworkRecorder defaultRecorder] recordRequestWillBeSentWithRequestID:requestID request:request redirectResponse:nil];
|
||||
NSString *mechanism = [self mechansimFromClassMethod:selector onClass:class];
|
||||
NSString *mechanism = [self mechanismFromClassMethod:selector onClass:class];
|
||||
[[FLEXNetworkRecorder defaultRecorder] recordMechanism:mechanism forRequestID:requestID];
|
||||
NSURLConnectionAsyncCompletion completionWrapper = ^(NSURLResponse *response, NSData *data, NSError *connectionError) {
|
||||
[[FLEXNetworkRecorder defaultRecorder] recordResponseReceivedWithRequestID:requestID response:response];
|
||||
@@ -416,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];
|
||||
});
|
||||
}
|
||||
|
||||
@@ -426,14 +350,14 @@ 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;
|
||||
if ([FLEXNetworkObserver isEnabled]) {
|
||||
NSString *requestID = [self nextRequestID];
|
||||
[[FLEXNetworkRecorder defaultRecorder] recordRequestWillBeSentWithRequestID:requestID request:request redirectResponse:nil];
|
||||
NSString *mechanism = [self mechansimFromClassMethod:selector onClass:class];
|
||||
NSString *mechanism = [self mechanismFromClassMethod:selector onClass:class];
|
||||
[[FLEXNetworkRecorder defaultRecorder] recordMechanism:mechanism forRequestID:requestID];
|
||||
NSError *temporaryError = nil;
|
||||
NSURLResponse *temporaryResponse = nil;
|
||||
@@ -458,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];
|
||||
});
|
||||
}
|
||||
|
||||
@@ -481,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];
|
||||
@@ -496,7 +420,7 @@ didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask delegate:(id <NSU
|
||||
// with nil completion block.
|
||||
if ([FLEXNetworkObserver isEnabled] && completion) {
|
||||
NSString *requestID = [self nextRequestID];
|
||||
NSString *mechanism = [self mechansimFromClassMethod:selector onClass:class];
|
||||
NSString *mechanism = [self mechanismFromClassMethod:selector onClass:class];
|
||||
NSURLSessionAsyncCompletion completionWrapper = [self asyncCompletionWrapperForRequestID:requestID mechanism:mechanism completion:completion];
|
||||
task = ((id(*)(id, SEL, id, id))objc_msgSend)(slf, swizzledSelector, argument, completionWrapper);
|
||||
[self setRequestID:requestID forConnectionOrTask:task];
|
||||
@@ -506,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];
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -528,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];
|
||||
@@ -538,9 +462,9 @@ didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask delegate:(id <NSU
|
||||
|
||||
NSURLSessionUploadTask *(^asyncUploadTaskSwizzleBlock)(Class, NSURLRequest *, id, NSURLSessionAsyncCompletion) = ^NSURLSessionUploadTask *(Class slf, NSURLRequest *request, id argument, NSURLSessionAsyncCompletion completion) {
|
||||
NSURLSessionUploadTask *task = nil;
|
||||
if ([FLEXNetworkObserver isEnabled]) {
|
||||
if ([FLEXNetworkObserver isEnabled] && completion) {
|
||||
NSString *requestID = [self nextRequestID];
|
||||
NSString *mechanism = [self mechansimFromClassMethod:selector onClass:class];
|
||||
NSString *mechanism = [self mechanismFromClassMethod:selector onClass:class];
|
||||
NSURLSessionAsyncCompletion completionWrapper = [self asyncCompletionWrapperForRequestID:requestID mechanism:mechanism completion:completion];
|
||||
task = ((id(*)(id, SEL, id, id, id))objc_msgSend)(slf, swizzledSelector, request, argument, completionWrapper);
|
||||
[self setRequestID:requestID forConnectionOrTask:task];
|
||||
@@ -550,12 +474,12 @@ 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];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
+ (NSString *)mechansimFromClassMethod:(SEL)selector onClass:(Class)class
|
||||
+ (NSString *)mechanismFromClassMethod:(SEL)selector onClass:(Class)class
|
||||
{
|
||||
return [NSString stringWithFormat:@"+[%@ %@]", NSStringFromClass(class), NSStringFromSelector(selector)];
|
||||
}
|
||||
@@ -589,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) {
|
||||
@@ -615,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) {
|
||||
@@ -644,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) {
|
||||
@@ -673,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) {
|
||||
@@ -702,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);
|
||||
@@ -727,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);
|
||||
|
||||
@@ -743,24 +667,25 @@ didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask delegate:(id <NSU
|
||||
|
||||
NSURLSessionWillPerformHTTPRedirectionBlock undefinedBlock = ^(id <NSURLSessionTaskDelegate> slf, NSURLSession *session, NSURLSessionTask *task, NSHTTPURLResponse *response, NSURLRequest *newRequest, void(^completionHandler)(NSURLRequest *)) {
|
||||
[[FLEXNetworkObserver sharedObserver] URLSession:session task:task willPerformHTTPRedirection:response newRequest:newRequest completionHandler:completionHandler delegate:slf];
|
||||
completionHandler(newRequest);
|
||||
};
|
||||
|
||||
NSURLSessionWillPerformHTTPRedirectionBlock implementationBlock = ^(id <NSURLSessionTaskDelegate> slf, NSURLSession *session, NSURLSessionTask *task, NSHTTPURLResponse *response, NSURLRequest *newRequest, void(^completionHandler)(NSURLRequest *)) {
|
||||
[self sniffWithoutDuplicationForObject:session selector:selector sniffingBlock:^{
|
||||
undefinedBlock(slf, session, task, response, newRequest, completionHandler);
|
||||
[[FLEXNetworkObserver sharedObserver] URLSession:session task:task willPerformHTTPRedirection:response newRequest:newRequest completionHandler:completionHandler delegate:slf];
|
||||
} originalImplementationBlock:^{
|
||||
((id(*)(id, SEL, id, id, id, id, void(^)()))objc_msgSend)(slf, swizzledSelector, session, task, response, newRequest, completionHandler);
|
||||
((id(*)(id, SEL, id, id, id, id, void(^)(NSURLRequest *)))objc_msgSend)(slf, swizzledSelector, session, task, response, newRequest, completionHandler);
|
||||
}];
|
||||
};
|
||||
|
||||
[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);
|
||||
|
||||
@@ -780,14 +705,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);
|
||||
|
||||
@@ -807,13 +732,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);
|
||||
|
||||
@@ -823,24 +748,25 @@ didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask delegate:(id <NSU
|
||||
|
||||
NSURLSessionDidReceiveResponseBlock undefinedBlock = ^(id <NSURLSessionDelegate> slf, NSURLSession *session, NSURLSessionDataTask *dataTask, NSURLResponse *response, void(^completionHandler)(NSURLSessionResponseDisposition disposition)) {
|
||||
[[FLEXNetworkObserver sharedObserver] URLSession:session dataTask:dataTask didReceiveResponse:response completionHandler:completionHandler delegate:slf];
|
||||
completionHandler(NSURLSessionResponseAllow);
|
||||
};
|
||||
|
||||
NSURLSessionDidReceiveResponseBlock implementationBlock = ^(id <NSURLSessionDelegate> slf, NSURLSession *session, NSURLSessionDataTask *dataTask, NSURLResponse *response, void(^completionHandler)(NSURLSessionResponseDisposition disposition)) {
|
||||
[self sniffWithoutDuplicationForObject:session selector:selector sniffingBlock:^{
|
||||
undefinedBlock(slf, session, dataTask, response, completionHandler);
|
||||
[[FLEXNetworkObserver sharedObserver] URLSession:session dataTask:dataTask didReceiveResponse:response completionHandler:completionHandler delegate:slf];
|
||||
} originalImplementationBlock:^{
|
||||
((void(*)(id, SEL, id, id, id, void(^)()))objc_msgSend)(slf, swizzledSelector, session, dataTask, response, completionHandler);
|
||||
((void(*)(id, SEL, id, id, id, void(^)(NSURLSessionResponseDisposition)))objc_msgSend)(slf, swizzledSelector, session, dataTask, response, completionHandler);
|
||||
}];
|
||||
};
|
||||
|
||||
[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);
|
||||
@@ -859,14 +785,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];
|
||||
}
|
||||
|
||||
// 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);
|
||||
@@ -883,14 +809,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);
|
||||
@@ -910,13 +836,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);
|
||||
@@ -935,7 +861,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];
|
||||
|
||||
}
|
||||
|
||||
@@ -972,14 +898,14 @@ static char const * const kFLEXRequestIDKey = "kFLEXRequestIDKey";
|
||||
|
||||
- (void)performBlock:(dispatch_block_t)block
|
||||
{
|
||||
if (self.isEnabled) {
|
||||
if ([[self class] isEnabled]) {
|
||||
dispatch_async(_queue, block);
|
||||
}
|
||||
}
|
||||
|
||||
- (FLEXInternalRequestState *)requestStateForRequestID:(NSString *)requestID
|
||||
{
|
||||
FLEXInternalRequestState *requestState = [self.requestStatesForRequestIDs objectForKey:requestID];
|
||||
FLEXInternalRequestState *requestState = self.requestStatesForRequestIDs[requestID];
|
||||
if (!requestState) {
|
||||
requestState = [[FLEXInternalRequestState alloc] init];
|
||||
[self.requestStatesForRequestIDs setObject:requestState forKey:requestID];
|
||||
@@ -1018,7 +944,7 @@ static char const * const kFLEXRequestIDKey = "kFLEXRequestIDKey";
|
||||
NSMutableData *dataAccumulator = nil;
|
||||
if (response.expectedContentLength < 0) {
|
||||
dataAccumulator = [[NSMutableData alloc] init];
|
||||
} else {
|
||||
} else if (response.expectedContentLength < 52428800) {
|
||||
dataAccumulator = [[NSMutableData alloc] initWithCapacity:(NSUInteger)response.expectedContentLength];
|
||||
}
|
||||
requestState.dataAccumulator = dataAccumulator;
|
||||
@@ -1069,7 +995,7 @@ static char const * const kFLEXRequestIDKey = "kFLEXRequestIDKey";
|
||||
{
|
||||
[self performBlock:^{
|
||||
// Mimic the behavior of NSURLSession which is to create an error on cancellation.
|
||||
NSDictionary *userInfo = @{ NSLocalizedDescriptionKey : @"cancelled" };
|
||||
NSDictionary<NSString *, id> *userInfo = @{ NSLocalizedDescriptionKey : @"cancelled" };
|
||||
NSError *error = [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorCancelled userInfo:userInfo];
|
||||
[self connection:connection didFailWithError:error delegate:nil];
|
||||
}];
|
||||
@@ -1156,7 +1082,7 @@ static char const * const kFLEXRequestIDKey = "kFLEXRequestIDKey";
|
||||
FLEXInternalRequestState *requestState = [self requestStateForRequestID:requestID];
|
||||
|
||||
if (!requestState.dataAccumulator) {
|
||||
NSUInteger unsignedBytesExpectedToWrite = totalBytesExpectedToWrite > 0 ? totalBytesExpectedToWrite : 0;
|
||||
NSUInteger unsignedBytesExpectedToWrite = totalBytesExpectedToWrite > 0 ? (NSUInteger)totalBytesExpectedToWrite : 0;
|
||||
requestState.dataAccumulator = [[NSMutableData alloc] initWithCapacity:unsignedBytesExpectedToWrite];
|
||||
[[FLEXNetworkRecorder defaultRecorder] recordResponseReceivedWithRequestID:requestID response:downloadTask.response];
|
||||
|
||||
|
||||
@@ -72,7 +72,7 @@
|
||||
- (id)detailObjectForRowCookie:(id)rowCookie
|
||||
{
|
||||
NSUInteger index = [rowCookie unsignedIntegerValue];
|
||||
return [self.array objectAtIndex:index];
|
||||
return self.array[index];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -35,11 +35,11 @@ typedef NS_ENUM(NSUInteger, FLEXClassExplorerRow) {
|
||||
|
||||
#pragma mark - Superclass Overrides
|
||||
|
||||
- (NSArray *)possibleExplorerSections
|
||||
- (NSArray<NSNumber *> *)possibleExplorerSections
|
||||
{
|
||||
// Move class methods to between our custom section and the properties section since
|
||||
// we are more interested in the class sections than in the instance level sections.
|
||||
NSMutableArray *mutableSections = [[super possibleExplorerSections] mutableCopy];
|
||||
NSMutableArray<NSNumber *> *mutableSections = [[super possibleExplorerSections] mutableCopy];
|
||||
[mutableSections removeObject:@(FLEXObjectExplorerSectionClassMethods)];
|
||||
[mutableSections insertObject:@(FLEXObjectExplorerSectionClassMethods) atIndex:[mutableSections indexOfObject:@(FLEXObjectExplorerSectionProperties)]];
|
||||
return mutableSections;
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
//
|
||||
// FLEXColorExplorerViewController.h
|
||||
// Flipboard
|
||||
//
|
||||
// Created by Tanner on 10/18/18.
|
||||
// Copyright © 2018 Flipboard. All rights reserved.
|
||||
//
|
||||
|
||||
#import "FLEXObjectExplorerViewController.h"
|
||||
|
||||
@interface FLEXColorExplorerViewController : FLEXObjectExplorerViewController
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,49 @@
|
||||
//
|
||||
// FLEXColorExplorerViewController.m
|
||||
// Flipboard
|
||||
//
|
||||
// Created by Tanner on 10/18/18.
|
||||
// Copyright © 2018 Flipboard. All rights reserved.
|
||||
//
|
||||
|
||||
#import "FLEXColorExplorerViewController.h"
|
||||
|
||||
@interface FLEXColorExplorerViewController ()
|
||||
|
||||
@end
|
||||
|
||||
@implementation FLEXColorExplorerViewController
|
||||
|
||||
- (BOOL)shouldShowDescription
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (NSString *)customSectionTitle
|
||||
{
|
||||
return @"Color";
|
||||
}
|
||||
|
||||
- (NSArray *)customSectionRowCookies
|
||||
{
|
||||
return @[@0];
|
||||
}
|
||||
|
||||
- (UIView *)customViewForRowCookie:(id)rowCookie
|
||||
{
|
||||
CGFloat width = [UIScreen mainScreen].bounds.size.width;
|
||||
UIView *square = [[UIView alloc] initWithFrame:CGRectMake(0, 0, width, 44)];
|
||||
square.backgroundColor = (UIColor *)self.object;
|
||||
return square;
|
||||
}
|
||||
|
||||
//- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
|
||||
// UITableViewCell *cell = [super tableView:tableView cellForRowAtIndexPath:indexPath];
|
||||
// if (indexPath.section == 0 && indexPath.row == 0) {
|
||||
// cell.contentView.backgroundColor = (UIColor *)self.object;
|
||||
// }
|
||||
//
|
||||
// return cell;
|
||||
//}
|
||||
|
||||
@end
|
||||
@@ -43,7 +43,7 @@
|
||||
|
||||
- (NSString *)customSectionSubtitleForRowCookie:(id)rowCookie
|
||||
{
|
||||
return [FLEXRuntimeUtility descriptionForIvarOrPropertyValue:[self.dictionary objectForKey:rowCookie]];
|
||||
return [FLEXRuntimeUtility descriptionForIvarOrPropertyValue:self.dictionary[rowCookie]];
|
||||
}
|
||||
|
||||
- (BOOL)customSectionCanDrillIntoRowWithCookie:(id)rowCookie
|
||||
@@ -53,7 +53,7 @@
|
||||
|
||||
- (UIViewController *)customSectionDrillInViewControllerForRowCookie:(id)rowCookie
|
||||
{
|
||||
return [FLEXObjectExplorerFactory explorerViewControllerForObject:[self.dictionary objectForKey:rowCookie]];
|
||||
return [FLEXObjectExplorerFactory explorerViewControllerForObject:self.dictionary[rowCookie]];
|
||||
}
|
||||
|
||||
- (BOOL)shouldShowDescription
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#import "FLEXImageExplorerViewController.h"
|
||||
#import "FLEXClassExplorerViewController.h"
|
||||
#import "FLEXLayerExplorerViewController.h"
|
||||
#import "FLEXColorExplorerViewController.h"
|
||||
#import <objc/runtime.h>
|
||||
|
||||
@implementation FLEXObjectExplorerFactory
|
||||
@@ -28,7 +29,7 @@
|
||||
return nil;
|
||||
}
|
||||
|
||||
static NSDictionary *explorerSubclassesForObjectTypeStrings = nil;
|
||||
static NSDictionary<NSString *, Class> *explorerSubclassesForObjectTypeStrings = nil;
|
||||
static dispatch_once_t once;
|
||||
dispatch_once(&once, ^{
|
||||
explorerSubclassesForObjectTypeStrings = @{NSStringFromClass([NSArray class]) : [FLEXArrayExplorerViewController class],
|
||||
@@ -38,7 +39,9 @@
|
||||
NSStringFromClass([UIViewController class]) : [FLEXViewControllerExplorerViewController class],
|
||||
NSStringFromClass([UIView class]) : [FLEXViewExplorerViewController class],
|
||||
NSStringFromClass([UIImage class]) : [FLEXImageExplorerViewController class],
|
||||
NSStringFromClass([CALayer class]) : [FLEXLayerExplorerViewController class]};
|
||||
NSStringFromClass([CALayer class]) : [FLEXLayerExplorerViewController class],
|
||||
NSStringFromClass([UIColor class]) : [FLEXColorExplorerViewController class]
|
||||
};
|
||||
});
|
||||
|
||||
Class explorerClass = nil;
|
||||
@@ -50,7 +53,7 @@
|
||||
for (NSString *objectTypeString in explorerSubclassesForObjectTypeStrings) {
|
||||
Class objectClass = NSClassFromString(objectTypeString);
|
||||
if ([object isKindOfClass:objectClass]) {
|
||||
explorerClass = [explorerSubclassesForObjectTypeStrings objectForKey:objectTypeString];
|
||||
explorerClass = explorerSubclassesForObjectTypeStrings[objectTypeString];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@ typedef NS_ENUM(NSUInteger, FLEXObjectExplorerSection) {
|
||||
- (NSString *)customSectionSubtitleForRowCookie:(id)rowCookie;
|
||||
- (BOOL)customSectionCanDrillIntoRowWithCookie:(id)rowCookie;
|
||||
- (UIViewController *)customSectionDrillInViewControllerForRowCookie:(id)rowCookie;
|
||||
- (UIView *)customViewForRowCookie:(id)rowCookie;
|
||||
|
||||
// More subclass configuration hooks.
|
||||
|
||||
|
||||
@@ -17,6 +17,20 @@
|
||||
#import "FLEXInstancesTableViewController.h"
|
||||
#import <objc/runtime.h>
|
||||
|
||||
typedef NS_ENUM(NSUInteger, FLEXObjectExplorerScope) {
|
||||
FLEXObjectExplorerScopeNoInheritance,
|
||||
FLEXObjectExplorerScopeWithParent,
|
||||
FLEXObjectExplorerScopeAllButNSObject,
|
||||
FLEXObjectExplorerScopeNSObjectOnly
|
||||
};
|
||||
|
||||
typedef NS_ENUM(NSUInteger, FLEXMetadataKind) {
|
||||
FLEXMetadataKindProperties,
|
||||
FLEXMetadataKindIvars,
|
||||
FLEXMetadataKindMethods,
|
||||
FLEXMetadataKindClassMethods
|
||||
};
|
||||
|
||||
// Convenience boxes to keep runtime properties, ivars, and methods in foundation collections.
|
||||
@interface FLEXPropertyBox : NSObject
|
||||
@property (nonatomic, assign) objc_property_t property;
|
||||
@@ -36,36 +50,41 @@
|
||||
@implementation FLEXMethodBox
|
||||
@end
|
||||
|
||||
static const NSInteger kFLEXObjectExplorerScopeNoInheritanceIndex = 0;
|
||||
static const NSInteger kFLEXObjectExplorerScopeIncludeInheritanceIndex = 1;
|
||||
|
||||
@interface FLEXObjectExplorerViewController () <UISearchBarDelegate>
|
||||
|
||||
@property (nonatomic, strong) NSArray *properties;
|
||||
@property (nonatomic, strong) NSArray *inheritedProperties;
|
||||
@property (nonatomic, strong) NSArray *filteredProperties;
|
||||
@property (nonatomic, strong) NSArray<FLEXPropertyBox *> *properties;
|
||||
@property (nonatomic, strong) NSArray<FLEXPropertyBox *> *propertiesWithParent;
|
||||
@property (nonatomic, strong) NSArray<FLEXPropertyBox *> *inheritedProperties;
|
||||
@property (nonatomic, strong) NSArray<FLEXPropertyBox *> *NSObjectProperties;
|
||||
@property (nonatomic, strong) NSArray<FLEXPropertyBox *> *filteredProperties;
|
||||
|
||||
@property (nonatomic, strong) NSArray *ivars;
|
||||
@property (nonatomic, strong) NSArray *inheritedIvars;
|
||||
@property (nonatomic, strong) NSArray *filteredIvars;
|
||||
@property (nonatomic, strong) NSArray<FLEXIvarBox *> *ivars;
|
||||
@property (nonatomic, strong) NSArray<FLEXIvarBox *> *ivarsWithParent;
|
||||
@property (nonatomic, strong) NSArray<FLEXIvarBox *> *inheritedIvars;
|
||||
@property (nonatomic, strong) NSArray<FLEXIvarBox *> *NSObjectIvars;
|
||||
@property (nonatomic, strong) NSArray<FLEXIvarBox *> *filteredIvars;
|
||||
|
||||
@property (nonatomic, strong) NSArray *methods;
|
||||
@property (nonatomic, strong) NSArray *inheritedMethods;
|
||||
@property (nonatomic, strong) NSArray *filteredMethods;
|
||||
@property (nonatomic, strong) NSArray<FLEXMethodBox *> *methods;
|
||||
@property (nonatomic, strong) NSArray<FLEXMethodBox *> *methodsWithParent;
|
||||
@property (nonatomic, strong) NSArray<FLEXMethodBox *> *inheritedMethods;
|
||||
@property (nonatomic, strong) NSArray<FLEXMethodBox *> *NSObjectMethods;
|
||||
@property (nonatomic, strong) NSArray<FLEXMethodBox *> *filteredMethods;
|
||||
|
||||
@property (nonatomic, strong) NSArray *classMethods;
|
||||
@property (nonatomic, strong) NSArray *inheritedClassMethods;
|
||||
@property (nonatomic, strong) NSArray *filteredClassMethods;
|
||||
@property (nonatomic, strong) NSArray<FLEXMethodBox *> *classMethods;
|
||||
@property (nonatomic, strong) NSArray<FLEXMethodBox *> *classMethodsWithParent;
|
||||
@property (nonatomic, strong) NSArray<FLEXMethodBox *> *inheritedClassMethods;
|
||||
@property (nonatomic, strong) NSArray<FLEXMethodBox *> *NSObjectClassMethods;
|
||||
@property (nonatomic, strong) NSArray<FLEXMethodBox *> *filteredClassMethods;
|
||||
|
||||
@property (nonatomic, strong) NSArray *superclasses;
|
||||
@property (nonatomic, strong) NSArray *filteredSuperclasses;
|
||||
@property (nonatomic, strong) NSArray<Class> *superclasses;
|
||||
@property (nonatomic, strong) NSArray<Class> *filteredSuperclasses;
|
||||
|
||||
@property (nonatomic, strong) NSArray *cachedCustomSectionRowCookies;
|
||||
@property (nonatomic, strong) NSIndexSet *customSectionVisibleIndexes;
|
||||
|
||||
@property (nonatomic, strong) UISearchBar *searchBar;
|
||||
@property (nonatomic, strong) NSString *filterText;
|
||||
@property (nonatomic, assign) BOOL includeInheritance;
|
||||
@property (nonatomic, assign) FLEXObjectExplorerScope scope;
|
||||
|
||||
@end
|
||||
|
||||
@@ -85,8 +104,7 @@ static const NSInteger kFLEXObjectExplorerScopeIncludeInheritanceIndex = 1;
|
||||
self.searchBar.placeholder = [FLEXUtility searchBarPlaceholderText];
|
||||
self.searchBar.delegate = self;
|
||||
self.searchBar.showsScopeBar = YES;
|
||||
self.searchBar.scopeButtonTitles = @[@"No Inheritance", @"Include Inheritance"];
|
||||
[self.searchBar sizeToFit];
|
||||
[self refreshScopeTitles];
|
||||
self.tableView.tableHeaderView = self.searchBar;
|
||||
|
||||
self.refreshControl = [[UIRefreshControl alloc] init];
|
||||
@@ -116,6 +134,28 @@ static const NSInteger kFLEXObjectExplorerScopeIncludeInheritanceIndex = 1;
|
||||
|
||||
#pragma mark - Search
|
||||
|
||||
- (void)refreshScopeTitles {
|
||||
if (!self.searchBar) return;
|
||||
|
||||
Class parent = [self.object superclass];
|
||||
Class parentSuper = [parent superclass];
|
||||
|
||||
NSMutableArray *scopes = [NSMutableArray arrayWithObject:@"Base"];
|
||||
if (parent) {
|
||||
[scopes addObject:@"+ Parent"];
|
||||
}
|
||||
if (parentSuper && parentSuper != [NSObject class]) {
|
||||
[scopes addObject:@"+ Inherited"];
|
||||
}
|
||||
if ([self.object isKindOfClass:[NSObject class]]) {
|
||||
[scopes addObject:@"NSObject"];
|
||||
}
|
||||
|
||||
self.searchBar.scopeButtonTitles = scopes;
|
||||
[self.searchBar sizeToFit];
|
||||
[self updateTableData];
|
||||
}
|
||||
|
||||
- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText
|
||||
{
|
||||
self.filterText = searchText;
|
||||
@@ -128,13 +168,62 @@ static const NSInteger kFLEXObjectExplorerScopeIncludeInheritanceIndex = 1;
|
||||
|
||||
- (void)searchBar:(UISearchBar *)searchBar selectedScopeButtonIndexDidChange:(NSInteger)selectedScope
|
||||
{
|
||||
if (selectedScope == kFLEXObjectExplorerScopeIncludeInheritanceIndex) {
|
||||
self.includeInheritance = YES;
|
||||
} else if (selectedScope == kFLEXObjectExplorerScopeNoInheritanceIndex) {
|
||||
self.includeInheritance = NO;
|
||||
self.scope = selectedScope;
|
||||
[self updateDisplayedData];
|
||||
}
|
||||
|
||||
- (NSArray *)metadata:(FLEXMetadataKind)metadataKind forScope:(FLEXObjectExplorerScope)scope {
|
||||
switch (metadataKind) {
|
||||
case FLEXMetadataKindProperties:
|
||||
switch (self.scope) {
|
||||
case FLEXObjectExplorerScopeNoInheritance:
|
||||
return self.properties;
|
||||
case FLEXObjectExplorerScopeWithParent:
|
||||
return self.propertiesWithParent;
|
||||
case FLEXObjectExplorerScopeAllButNSObject:
|
||||
return self.inheritedProperties;
|
||||
case FLEXObjectExplorerScopeNSObjectOnly:
|
||||
return self.NSObjectProperties;
|
||||
}
|
||||
case FLEXMetadataKindIvars:
|
||||
switch (self.scope) {
|
||||
case FLEXObjectExplorerScopeNoInheritance:
|
||||
return self.ivars;
|
||||
case FLEXObjectExplorerScopeWithParent:
|
||||
return self.ivarsWithParent;
|
||||
case FLEXObjectExplorerScopeAllButNSObject:
|
||||
return self.inheritedIvars;
|
||||
case FLEXObjectExplorerScopeNSObjectOnly:
|
||||
return self.NSObjectIvars;
|
||||
}
|
||||
case FLEXMetadataKindMethods:
|
||||
switch (self.scope) {
|
||||
case FLEXObjectExplorerScopeNoInheritance:
|
||||
return self.methods;
|
||||
case FLEXObjectExplorerScopeWithParent:
|
||||
return self.methodsWithParent;
|
||||
case FLEXObjectExplorerScopeAllButNSObject:
|
||||
return self.inheritedMethods;
|
||||
case FLEXObjectExplorerScopeNSObjectOnly:
|
||||
return self.NSObjectMethods;
|
||||
}
|
||||
case FLEXMetadataKindClassMethods:
|
||||
switch (self.scope) {
|
||||
case FLEXObjectExplorerScopeNoInheritance:
|
||||
return self.classMethods;
|
||||
case FLEXObjectExplorerScopeWithParent:
|
||||
return self.classMethodsWithParent;
|
||||
case FLEXObjectExplorerScopeAllButNSObject:
|
||||
return self.inheritedClassMethods;
|
||||
case FLEXObjectExplorerScopeNSObjectOnly:
|
||||
return self.NSObjectClassMethods;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (NSInteger)totalCountOfMetadata:(FLEXMetadataKind)metadataKind forScope:(FLEXObjectExplorerScope)scope {
|
||||
return [self metadata:metadataKind forScope:scope].count;
|
||||
}
|
||||
|
||||
#pragma mark - Setter overrides
|
||||
|
||||
@@ -143,15 +232,7 @@ static const NSInteger kFLEXObjectExplorerScopeIncludeInheritanceIndex = 1;
|
||||
_object = object;
|
||||
// Use [object class] here rather than object_getClass because we don't want to show the KVO prefix for observed objects.
|
||||
self.title = [[object class] description];
|
||||
[self updateTableData];
|
||||
}
|
||||
|
||||
- (void)setIncludeInheritance:(BOOL)includeInheritance
|
||||
{
|
||||
if (_includeInheritance != includeInheritance) {
|
||||
_includeInheritance = includeInheritance;
|
||||
[self updateDisplayedData];
|
||||
}
|
||||
[self refreshScopeTitles];
|
||||
}
|
||||
|
||||
- (void)setFilterText:(NSString *)filterText
|
||||
@@ -215,12 +296,18 @@ static const NSInteger kFLEXObjectExplorerScopeIncludeInheritanceIndex = 1;
|
||||
{
|
||||
Class class = [self.object class];
|
||||
self.properties = [[self class] propertiesForClass:class];
|
||||
self.inheritedProperties = [[self class] inheritedPropertiesForClass:class];
|
||||
self.propertiesWithParent = [self.properties arrayByAddingObjectsFromArray:[[self class] propertiesForClass:[class superclass]]];
|
||||
self.inheritedProperties = [self.properties arrayByAddingObjectsFromArray:[[self class] inheritedPropertiesForClass:class]];
|
||||
self.NSObjectProperties = [[self class] propertiesForClass:[NSObject class]];
|
||||
}
|
||||
|
||||
+ (NSArray *)propertiesForClass:(Class)class
|
||||
+ (NSArray<FLEXPropertyBox *> *)propertiesForClass:(Class)class
|
||||
{
|
||||
NSMutableArray *boxedProperties = [NSMutableArray array];
|
||||
if (!class) {
|
||||
return @[];
|
||||
}
|
||||
|
||||
NSMutableArray<FLEXPropertyBox *> *boxedProperties = [NSMutableArray array];
|
||||
unsigned int propertyCount = 0;
|
||||
objc_property_t *propertyList = class_copyPropertyList(class, &propertyCount);
|
||||
if (propertyList) {
|
||||
@@ -234,10 +321,11 @@ static const NSInteger kFLEXObjectExplorerScopeIncludeInheritanceIndex = 1;
|
||||
return boxedProperties;
|
||||
}
|
||||
|
||||
+ (NSArray *)inheritedPropertiesForClass:(Class)class
|
||||
/// Skips NSObject
|
||||
+ (NSArray<FLEXPropertyBox *> *)inheritedPropertiesForClass:(Class)class
|
||||
{
|
||||
NSMutableArray *inheritedProperties = [NSMutableArray array];
|
||||
while ((class = [class superclass])) {
|
||||
NSMutableArray<FLEXPropertyBox *> *inheritedProperties = [NSMutableArray array];
|
||||
while ((class = [class superclass]) && class != [NSObject class]) {
|
||||
[inheritedProperties addObjectsFromArray:[self propertiesForClass:class]];
|
||||
}
|
||||
return inheritedProperties;
|
||||
@@ -245,14 +333,11 @@ static const NSInteger kFLEXObjectExplorerScopeIncludeInheritanceIndex = 1;
|
||||
|
||||
- (void)updateFilteredProperties
|
||||
{
|
||||
NSArray *candidateProperties = self.properties;
|
||||
if (self.includeInheritance) {
|
||||
candidateProperties = [candidateProperties arrayByAddingObjectsFromArray:self.inheritedProperties];
|
||||
}
|
||||
NSArray<FLEXPropertyBox *> *candidateProperties = [self metadata:FLEXMetadataKindProperties forScope:self.scope];
|
||||
|
||||
NSArray *unsortedFilteredProperties = nil;
|
||||
NSArray<FLEXPropertyBox *> *unsortedFilteredProperties = nil;
|
||||
if ([self.filterText length] > 0) {
|
||||
NSMutableArray *mutableUnsortedFilteredProperties = [NSMutableArray array];
|
||||
NSMutableArray<FLEXPropertyBox *> *mutableUnsortedFilteredProperties = [NSMutableArray array];
|
||||
for (FLEXPropertyBox *propertyBox in candidateProperties) {
|
||||
NSString *prettyName = [FLEXRuntimeUtility prettyNameForProperty:propertyBox.property];
|
||||
if ([prettyName rangeOfString:self.filterText options:NSCaseInsensitiveSearch].location != NSNotFound) {
|
||||
@@ -273,7 +358,7 @@ static const NSInteger kFLEXObjectExplorerScopeIncludeInheritanceIndex = 1;
|
||||
|
||||
- (NSString *)titleForPropertyAtIndex:(NSInteger)index
|
||||
{
|
||||
FLEXPropertyBox *propertyBox = [self.filteredProperties objectAtIndex:index];
|
||||
FLEXPropertyBox *propertyBox = self.filteredProperties[index];
|
||||
return [FLEXRuntimeUtility prettyNameForProperty:propertyBox.property];
|
||||
}
|
||||
|
||||
@@ -281,8 +366,11 @@ static const NSInteger kFLEXObjectExplorerScopeIncludeInheritanceIndex = 1;
|
||||
{
|
||||
id value = nil;
|
||||
if ([self canHaveInstanceState]) {
|
||||
FLEXPropertyBox *propertyBox = [self.filteredProperties objectAtIndex:index];
|
||||
FLEXPropertyBox *propertyBox = self.filteredProperties[index];
|
||||
NSString *typeString = [FLEXRuntimeUtility typeEncodingForProperty:propertyBox.property];
|
||||
const FLEXTypeEncoding *encoding = [typeString cStringUsingEncoding:NSUTF8StringEncoding];
|
||||
value = [FLEXRuntimeUtility valueForProperty:propertyBox.property onObject:self.object];
|
||||
value = [FLEXRuntimeUtility potentiallyUnwrapBoxedPointer:value type:encoding];
|
||||
}
|
||||
return value;
|
||||
}
|
||||
@@ -294,12 +382,17 @@ static const NSInteger kFLEXObjectExplorerScopeIncludeInheritanceIndex = 1;
|
||||
{
|
||||
Class class = [self.object class];
|
||||
self.ivars = [[self class] ivarsForClass:class];
|
||||
self.inheritedIvars = [[self class] inheritedIvarsForClass:class];
|
||||
self.ivarsWithParent = [self.ivars arrayByAddingObjectsFromArray:[[self class] ivarsForClass:[class superclass]]];
|
||||
self.inheritedIvars = [self.ivars arrayByAddingObjectsFromArray:[[self class] inheritedIvarsForClass:class]];
|
||||
self.NSObjectIvars = [[self class] ivarsForClass:[NSObject class]];
|
||||
}
|
||||
|
||||
+ (NSArray *)ivarsForClass:(Class)class
|
||||
+ (NSArray<FLEXIvarBox *> *)ivarsForClass:(Class)class
|
||||
{
|
||||
NSMutableArray *boxedIvars = [NSMutableArray array];
|
||||
if (!class) {
|
||||
return @[];
|
||||
}
|
||||
NSMutableArray<FLEXIvarBox *> *boxedIvars = [NSMutableArray array];
|
||||
unsigned int ivarCount = 0;
|
||||
Ivar *ivarList = class_copyIvarList(class, &ivarCount);
|
||||
if (ivarList) {
|
||||
@@ -313,10 +406,11 @@ static const NSInteger kFLEXObjectExplorerScopeIncludeInheritanceIndex = 1;
|
||||
return boxedIvars;
|
||||
}
|
||||
|
||||
+ (NSArray *)inheritedIvarsForClass:(Class)class
|
||||
/// Skips NSObject
|
||||
+ (NSArray<FLEXIvarBox *> *)inheritedIvarsForClass:(Class)class
|
||||
{
|
||||
NSMutableArray *inheritedIvars = [NSMutableArray array];
|
||||
while ((class = [class superclass])) {
|
||||
NSMutableArray<FLEXIvarBox *> *inheritedIvars = [NSMutableArray array];
|
||||
while ((class = [class superclass]) && class != [NSObject class]) {
|
||||
[inheritedIvars addObjectsFromArray:[self ivarsForClass:class]];
|
||||
}
|
||||
return inheritedIvars;
|
||||
@@ -324,14 +418,11 @@ static const NSInteger kFLEXObjectExplorerScopeIncludeInheritanceIndex = 1;
|
||||
|
||||
- (void)updateFilteredIvars
|
||||
{
|
||||
NSArray *candidateIvars = self.ivars;
|
||||
if (self.includeInheritance) {
|
||||
candidateIvars = [candidateIvars arrayByAddingObjectsFromArray:self.inheritedIvars];
|
||||
}
|
||||
NSArray<FLEXIvarBox *> *candidateIvars = [self metadata:FLEXMetadataKindIvars forScope:self.scope];
|
||||
|
||||
NSArray *unsortedFilteredIvars = nil;
|
||||
NSArray<FLEXIvarBox *> *unsortedFilteredIvars = nil;
|
||||
if ([self.filterText length] > 0) {
|
||||
NSMutableArray *mutableUnsortedFilteredIvars = [NSMutableArray array];
|
||||
NSMutableArray<FLEXIvarBox *> *mutableUnsortedFilteredIvars = [NSMutableArray array];
|
||||
for (FLEXIvarBox *ivarBox in candidateIvars) {
|
||||
NSString *prettyName = [FLEXRuntimeUtility prettyNameForIvar:ivarBox.ivar];
|
||||
if ([prettyName rangeOfString:self.filterText options:NSCaseInsensitiveSearch].location != NSNotFound) {
|
||||
@@ -352,7 +443,7 @@ static const NSInteger kFLEXObjectExplorerScopeIncludeInheritanceIndex = 1;
|
||||
|
||||
- (NSString *)titleForIvarAtIndex:(NSInteger)index
|
||||
{
|
||||
FLEXIvarBox *ivarBox = [self.filteredIvars objectAtIndex:index];
|
||||
FLEXIvarBox *ivarBox = self.filteredIvars[index];
|
||||
return [FLEXRuntimeUtility prettyNameForIvar:ivarBox.ivar];
|
||||
}
|
||||
|
||||
@@ -360,8 +451,10 @@ static const NSInteger kFLEXObjectExplorerScopeIncludeInheritanceIndex = 1;
|
||||
{
|
||||
id value = nil;
|
||||
if ([self canHaveInstanceState]) {
|
||||
FLEXIvarBox *ivarBox = [self.filteredIvars objectAtIndex:index];
|
||||
FLEXIvarBox *ivarBox = self.filteredIvars[index];
|
||||
const FLEXTypeEncoding *encoding = ivar_getTypeEncoding(ivarBox.ivar);
|
||||
value = [FLEXRuntimeUtility valueForIvar:ivarBox.ivar onObject:self.object];
|
||||
value = [FLEXRuntimeUtility potentiallyUnwrapBoxedPointer:value type:encoding];
|
||||
}
|
||||
return value;
|
||||
}
|
||||
@@ -373,12 +466,15 @@ static const NSInteger kFLEXObjectExplorerScopeIncludeInheritanceIndex = 1;
|
||||
{
|
||||
Class class = [self.object class];
|
||||
self.methods = [[self class] methodsForClass:class];
|
||||
self.inheritedMethods = [[self class] inheritedMethodsForClass:class];
|
||||
self.methodsWithParent = [self.methods arrayByAddingObjectsFromArray:[[self class] methodsForClass:[class superclass]]];
|
||||
self.inheritedMethods = [self.methods arrayByAddingObjectsFromArray:[[self class] inheritedMethodsForClass:class]];
|
||||
self.NSObjectMethods = [[self class] methodsForClass:[NSObject class]];
|
||||
}
|
||||
|
||||
- (void)updateFilteredMethods
|
||||
{
|
||||
self.filteredMethods = [self filteredMethodsFromMethods:self.methods inheritedMethods:self.inheritedMethods areClassMethods:NO];
|
||||
NSArray<FLEXMethodBox *> *candidateMethods = [self metadata:FLEXMetadataKindMethods forScope:self.scope];
|
||||
self.filteredMethods = [self filteredMethodsFromMethods:candidateMethods areClassMethods:NO];
|
||||
}
|
||||
|
||||
- (void)updateClassMethods
|
||||
@@ -386,17 +482,24 @@ static const NSInteger kFLEXObjectExplorerScopeIncludeInheritanceIndex = 1;
|
||||
const char *className = [NSStringFromClass([self.object class]) UTF8String];
|
||||
Class metaClass = objc_getMetaClass(className);
|
||||
self.classMethods = [[self class] methodsForClass:metaClass];
|
||||
self.inheritedClassMethods = [[self class] inheritedMethodsForClass:metaClass];
|
||||
self.classMethodsWithParent = [self.classMethods arrayByAddingObjectsFromArray:[[self class] methodsForClass:[metaClass superclass]]];
|
||||
self.inheritedClassMethods = [self.classMethods arrayByAddingObjectsFromArray:[[self class] inheritedMethodsForClass:metaClass]];
|
||||
self.NSObjectClassMethods = [[self class] methodsForClass:[NSObject class]];
|
||||
}
|
||||
|
||||
- (void)updateFilteredClassMethods
|
||||
{
|
||||
self.filteredClassMethods = [self filteredMethodsFromMethods:self.classMethods inheritedMethods:self.inheritedClassMethods areClassMethods:YES];
|
||||
NSArray<FLEXMethodBox *> *candidateMethods = [self metadata:FLEXMetadataKindClassMethods forScope:self.scope];
|
||||
self.filteredClassMethods = [self filteredMethodsFromMethods:candidateMethods areClassMethods:YES];
|
||||
}
|
||||
|
||||
+ (NSArray *)methodsForClass:(Class)class
|
||||
+ (NSArray<FLEXMethodBox *> *)methodsForClass:(Class)class
|
||||
{
|
||||
NSMutableArray *boxedMethods = [NSMutableArray array];
|
||||
if (!class) {
|
||||
return @[];
|
||||
}
|
||||
|
||||
NSMutableArray<FLEXMethodBox *> *boxedMethods = [NSMutableArray array];
|
||||
unsigned int methodCount = 0;
|
||||
Method *methodList = class_copyMethodList(class, &methodCount);
|
||||
if (methodList) {
|
||||
@@ -410,25 +513,22 @@ static const NSInteger kFLEXObjectExplorerScopeIncludeInheritanceIndex = 1;
|
||||
return boxedMethods;
|
||||
}
|
||||
|
||||
+ (NSArray *)inheritedMethodsForClass:(Class)class
|
||||
/// Skips NSObject
|
||||
+ (NSArray<FLEXMethodBox *> *)inheritedMethodsForClass:(Class)class
|
||||
{
|
||||
NSMutableArray *inheritedMethods = [NSMutableArray array];
|
||||
while ((class = [class superclass])) {
|
||||
NSMutableArray<FLEXMethodBox *> *inheritedMethods = [NSMutableArray array];
|
||||
while ((class = [class superclass]) && class != [NSObject class]) {
|
||||
[inheritedMethods addObjectsFromArray:[self methodsForClass:class]];
|
||||
}
|
||||
return inheritedMethods;
|
||||
}
|
||||
|
||||
- (NSArray *)filteredMethodsFromMethods:(NSArray *)methods inheritedMethods:(NSArray *)inheritedMethods areClassMethods:(BOOL)areClassMethods
|
||||
- (NSArray<FLEXMethodBox *> *)filteredMethodsFromMethods:(NSArray<FLEXMethodBox *> *)methods areClassMethods:(BOOL)areClassMethods
|
||||
{
|
||||
NSArray *candidateMethods = methods;
|
||||
if (self.includeInheritance) {
|
||||
candidateMethods = [candidateMethods arrayByAddingObjectsFromArray:inheritedMethods];
|
||||
}
|
||||
|
||||
NSArray *unsortedFilteredMethods = nil;
|
||||
NSArray<FLEXMethodBox *> *candidateMethods = methods;
|
||||
NSArray<FLEXMethodBox *> *unsortedFilteredMethods = nil;
|
||||
if ([self.filterText length] > 0) {
|
||||
NSMutableArray *mutableUnsortedFilteredMethods = [NSMutableArray array];
|
||||
NSMutableArray<FLEXMethodBox *> *mutableUnsortedFilteredMethods = [NSMutableArray array];
|
||||
for (FLEXMethodBox *methodBox in candidateMethods) {
|
||||
NSString *prettyName = [FLEXRuntimeUtility prettyNameForMethod:methodBox.method isClassMethod:areClassMethods];
|
||||
if ([prettyName rangeOfString:self.filterText options:NSCaseInsensitiveSearch].location != NSNotFound) {
|
||||
@@ -440,7 +540,7 @@ static const NSInteger kFLEXObjectExplorerScopeIncludeInheritanceIndex = 1;
|
||||
unsortedFilteredMethods = candidateMethods;
|
||||
}
|
||||
|
||||
NSArray *sortedFilteredMethods = [unsortedFilteredMethods sortedArrayUsingComparator:^NSComparisonResult(FLEXMethodBox *methodBox1, FLEXMethodBox *methodBox2) {
|
||||
NSArray<FLEXMethodBox *> *sortedFilteredMethods = [unsortedFilteredMethods sortedArrayUsingComparator:^NSComparisonResult(FLEXMethodBox *methodBox1, FLEXMethodBox *methodBox2) {
|
||||
NSString *name1 = NSStringFromSelector(method_getName(methodBox1.method));
|
||||
NSString *name2 = NSStringFromSelector(method_getName(methodBox2.method));
|
||||
return [name1 caseInsensitiveCompare:name2];
|
||||
@@ -451,22 +551,22 @@ static const NSInteger kFLEXObjectExplorerScopeIncludeInheritanceIndex = 1;
|
||||
|
||||
- (NSString *)titleForMethodAtIndex:(NSInteger)index
|
||||
{
|
||||
FLEXMethodBox *methodBox = [self.filteredMethods objectAtIndex:index];
|
||||
FLEXMethodBox *methodBox = self.filteredMethods[index];
|
||||
return [FLEXRuntimeUtility prettyNameForMethod:methodBox.method isClassMethod:NO];
|
||||
}
|
||||
|
||||
- (NSString *)titleForClassMethodAtIndex:(NSInteger)index
|
||||
{
|
||||
FLEXMethodBox *classMethodBox = [self.filteredClassMethods objectAtIndex:index];
|
||||
FLEXMethodBox *classMethodBox = self.filteredClassMethods[index];
|
||||
return [FLEXRuntimeUtility prettyNameForMethod:classMethodBox.method isClassMethod:YES];
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Superclasses
|
||||
|
||||
+ (NSArray *)superclassesForClass:(Class)class
|
||||
+ (NSArray<Class> *)superclassesForClass:(Class)class
|
||||
{
|
||||
NSMutableArray *superClasses = [NSMutableArray array];
|
||||
NSMutableArray<Class> *superClasses = [NSMutableArray array];
|
||||
while ((class = [class superclass])) {
|
||||
[superClasses addObject:class];
|
||||
}
|
||||
@@ -481,7 +581,7 @@ static const NSInteger kFLEXObjectExplorerScopeIncludeInheritanceIndex = 1;
|
||||
- (void)updateFilteredSuperclasses
|
||||
{
|
||||
if ([self.filterText length] > 0) {
|
||||
NSMutableArray *filteredSuperclasses = [NSMutableArray array];
|
||||
NSMutableArray<Class> *filteredSuperclasses = [NSMutableArray array];
|
||||
for (Class superclass in self.superclasses) {
|
||||
if ([NSStringFromClass(superclass) rangeOfString:self.filterText options:NSCaseInsensitiveSearch].length > 0) {
|
||||
[filteredSuperclasses addObject:superclass];
|
||||
@@ -496,9 +596,9 @@ static const NSInteger kFLEXObjectExplorerScopeIncludeInheritanceIndex = 1;
|
||||
|
||||
#pragma mark - Table View Data Helpers
|
||||
|
||||
- (NSArray *)possibleExplorerSections
|
||||
- (NSArray<NSNumber *> *)possibleExplorerSections
|
||||
{
|
||||
static NSArray *possibleSections = nil;
|
||||
static NSArray<NSNumber *> *possibleSections = nil;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
possibleSections = @[@(FLEXObjectExplorerSectionDescription),
|
||||
@@ -513,9 +613,9 @@ static const NSInteger kFLEXObjectExplorerScopeIncludeInheritanceIndex = 1;
|
||||
return possibleSections;
|
||||
}
|
||||
|
||||
- (NSArray *)visibleExplorerSections
|
||||
- (NSArray<NSNumber *> *)visibleExplorerSections
|
||||
{
|
||||
NSMutableArray *visibleSections = [NSMutableArray array];
|
||||
NSMutableArray<NSNumber *> *visibleSections = [NSMutableArray array];
|
||||
|
||||
for (NSNumber *possibleSection in [self possibleExplorerSections]) {
|
||||
FLEXObjectExplorerSection explorerSection = [possibleSection unsignedIntegerValue];
|
||||
@@ -612,7 +712,7 @@ static const NSInteger kFLEXObjectExplorerScopeIncludeInheritanceIndex = 1;
|
||||
break;
|
||||
|
||||
case FLEXObjectExplorerSectionSuperclasses:
|
||||
title = NSStringFromClass([self.filteredSuperclasses objectAtIndex:row]);
|
||||
title = NSStringFromClass(self.filteredSuperclasses[row]);
|
||||
break;
|
||||
|
||||
case FLEXObjectExplorerSectionReferencingInstances:
|
||||
@@ -669,7 +769,7 @@ static const NSInteger kFLEXObjectExplorerScopeIncludeInheritanceIndex = 1;
|
||||
|
||||
case FLEXObjectExplorerSectionProperties: {
|
||||
if ([self canHaveInstanceState]) {
|
||||
FLEXPropertyBox *propertyBox = [self.filteredProperties objectAtIndex:row];
|
||||
FLEXPropertyBox *propertyBox = self.filteredProperties[row];
|
||||
objc_property_t property = propertyBox.property;
|
||||
id currentValue = [self valueForPropertyAtIndex:row];
|
||||
BOOL canEdit = [FLEXPropertyEditorViewController canEditProperty:property currentValue:currentValue];
|
||||
@@ -680,7 +780,7 @@ static const NSInteger kFLEXObjectExplorerScopeIncludeInheritanceIndex = 1;
|
||||
|
||||
case FLEXObjectExplorerSectionIvars: {
|
||||
if ([self canHaveInstanceState]) {
|
||||
FLEXIvarBox *ivarBox = [self.filteredIvars objectAtIndex:row];
|
||||
FLEXIvarBox *ivarBox = self.filteredIvars[row];
|
||||
Ivar ivar = ivarBox.ivar;
|
||||
id currentValue = [self valueForIvarAtIndex:row];
|
||||
BOOL canEdit = [FLEXIvarEditorViewController canEditIvar:ivar currentValue:currentValue];
|
||||
@@ -736,34 +836,22 @@ static const NSInteger kFLEXObjectExplorerScopeIncludeInheritanceIndex = 1;
|
||||
} break;
|
||||
|
||||
case FLEXObjectExplorerSectionProperties: {
|
||||
NSUInteger totalCount = [self.properties count];
|
||||
if (self.includeInheritance) {
|
||||
totalCount += [self.inheritedProperties count];
|
||||
}
|
||||
NSUInteger totalCount = [self totalCountOfMetadata:FLEXMetadataKindProperties forScope:self.scope];
|
||||
title = [self sectionTitleWithBaseName:@"Properties" totalCount:totalCount filteredCount:[self.filteredProperties count]];
|
||||
} break;
|
||||
|
||||
case FLEXObjectExplorerSectionIvars: {
|
||||
NSUInteger totalCount = [self.ivars count];
|
||||
if (self.includeInheritance) {
|
||||
totalCount += [self.inheritedIvars count];
|
||||
}
|
||||
NSUInteger totalCount = [self totalCountOfMetadata:FLEXMetadataKindIvars forScope:self.scope];
|
||||
title = [self sectionTitleWithBaseName:@"Ivars" totalCount:totalCount filteredCount:[self.filteredIvars count]];
|
||||
} break;
|
||||
|
||||
case FLEXObjectExplorerSectionMethods: {
|
||||
NSUInteger totalCount = [self.methods count];
|
||||
if (self.includeInheritance) {
|
||||
totalCount += [self.inheritedMethods count];
|
||||
}
|
||||
NSUInteger totalCount = [self totalCountOfMetadata:FLEXMetadataKindMethods forScope:self.scope];
|
||||
title = [self sectionTitleWithBaseName:@"Methods" totalCount:totalCount filteredCount:[self.filteredMethods count]];
|
||||
} break;
|
||||
|
||||
case FLEXObjectExplorerSectionClassMethods: {
|
||||
NSUInteger totalCount = [self.classMethods count];
|
||||
if (self.includeInheritance) {
|
||||
totalCount += [self.inheritedClassMethods count];
|
||||
}
|
||||
NSUInteger totalCount = [self totalCountOfMetadata:FLEXMetadataKindClassMethods forScope:self.scope];
|
||||
title = [self sectionTitleWithBaseName:@"Class Methods" totalCount:totalCount filteredCount:[self.filteredClassMethods count]];
|
||||
} break;
|
||||
|
||||
@@ -790,7 +878,7 @@ static const NSInteger kFLEXObjectExplorerScopeIncludeInheritanceIndex = 1;
|
||||
break;
|
||||
|
||||
case FLEXObjectExplorerSectionProperties: {
|
||||
FLEXPropertyBox *propertyBox = [self.filteredProperties objectAtIndex:row];
|
||||
FLEXPropertyBox *propertyBox = self.filteredProperties[row];
|
||||
objc_property_t property = propertyBox.property;
|
||||
id currentValue = [self valueForPropertyAtIndex:row];
|
||||
if ([FLEXPropertyEditorViewController canEditProperty:property currentValue:currentValue]) {
|
||||
@@ -801,7 +889,7 @@ static const NSInteger kFLEXObjectExplorerScopeIncludeInheritanceIndex = 1;
|
||||
} break;
|
||||
|
||||
case FLEXObjectExplorerSectionIvars: {
|
||||
FLEXIvarBox *ivarBox = [self.filteredIvars objectAtIndex:row];
|
||||
FLEXIvarBox *ivarBox = self.filteredIvars[row];
|
||||
Ivar ivar = ivarBox.ivar;
|
||||
id currentValue = [self valueForIvarAtIndex:row];
|
||||
if ([FLEXIvarEditorViewController canEditIvar:ivar currentValue:currentValue]) {
|
||||
@@ -812,19 +900,19 @@ static const NSInteger kFLEXObjectExplorerScopeIncludeInheritanceIndex = 1;
|
||||
} break;
|
||||
|
||||
case FLEXObjectExplorerSectionMethods: {
|
||||
FLEXMethodBox *methodBox = [self.filteredMethods objectAtIndex:row];
|
||||
FLEXMethodBox *methodBox = self.filteredMethods[row];
|
||||
Method method = methodBox.method;
|
||||
viewController = [[FLEXMethodCallingViewController alloc] initWithTarget:self.object method:method];
|
||||
} break;
|
||||
|
||||
case FLEXObjectExplorerSectionClassMethods: {
|
||||
FLEXMethodBox *methodBox = [self.filteredClassMethods objectAtIndex:row];
|
||||
FLEXMethodBox *methodBox = self.filteredClassMethods[row];
|
||||
Method method = methodBox.method;
|
||||
viewController = [[FLEXMethodCallingViewController alloc] initWithTarget:[self.object class] method:method];
|
||||
} break;
|
||||
|
||||
case FLEXObjectExplorerSectionSuperclasses: {
|
||||
Class superclass = [self.filteredSuperclasses objectAtIndex:row];
|
||||
Class superclass = self.filteredSuperclasses[row];
|
||||
viewController = [FLEXObjectExplorerFactory explorerViewControllerForObject:superclass];
|
||||
} break;
|
||||
|
||||
@@ -858,7 +946,8 @@ static const NSInteger kFLEXObjectExplorerScopeIncludeInheritanceIndex = 1;
|
||||
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
FLEXObjectExplorerSection explorerSection = [self explorerSectionAtIndex:indexPath.section];
|
||||
|
||||
|
||||
BOOL isCustomSection = explorerSection == FLEXObjectExplorerSectionCustom;
|
||||
BOOL useDescriptionCell = explorerSection == FLEXObjectExplorerSectionDescription;
|
||||
NSString *cellIdentifier = useDescriptionCell ? kFLEXMultilineTableViewCellIdentifier : @"cell";
|
||||
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
|
||||
@@ -874,7 +963,16 @@ static const NSInteger kFLEXObjectExplorerScopeIncludeInheritanceIndex = 1;
|
||||
cell.detailTextLabel.textColor = [UIColor grayColor];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
UIView *customView;
|
||||
if (isCustomSection) {
|
||||
customView = [self customViewForRowCookie:[self customSectionRowCookieForVisibleRow:indexPath.row]];
|
||||
if (customView) {
|
||||
[cell.contentView addSubview:customView];
|
||||
}
|
||||
}
|
||||
|
||||
cell.textLabel.text = [self titleForRow:indexPath.row inExplorerSection:explorerSection];
|
||||
cell.detailTextLabel.text = [self subtitleForRow:indexPath.row inExplorerSection:explorerSection];
|
||||
cell.accessoryType = [self canDrillInToRow:indexPath.row inExplorerSection:explorerSection] ? UITableViewCellAccessoryDisclosureIndicator : UITableViewCellAccessoryNone;
|
||||
@@ -891,7 +989,11 @@ static const NSInteger kFLEXObjectExplorerScopeIncludeInheritanceIndex = 1;
|
||||
NSAttributedString *attributedText = [[NSAttributedString alloc] initWithString:text attributes:@{ NSFontAttributeName : [FLEXUtility defaultTableViewCellLabelFont] }];
|
||||
CGFloat preferredHeight = [FLEXMultilineTableViewCell preferredHeightWithAttributedText:attributedText inTableViewWidth:self.tableView.frame.size.width style:tableView.style showsAccessory:NO];
|
||||
height = MAX(height, preferredHeight);
|
||||
} else if (explorerSection == FLEXObjectExplorerSectionCustom) {
|
||||
id cookie = [self customSectionRowCookieForVisibleRow:indexPath.row];
|
||||
height = [self heightForCustomViewRowForRowCookie:cookie];
|
||||
}
|
||||
|
||||
return height;
|
||||
}
|
||||
|
||||
@@ -972,7 +1074,7 @@ static const NSInteger kFLEXObjectExplorerScopeIncludeInheritanceIndex = 1;
|
||||
if ([self.filterText length] > 0) {
|
||||
filteredIndexSet = [filteredIndexSet indexesPassingTest:^BOOL(NSUInteger index, BOOL *stop) {
|
||||
BOOL matches = NO;
|
||||
NSString *rowTitle = [self customSectionTitleForRowCookie:[self.cachedCustomSectionRowCookies objectAtIndex:index]];
|
||||
NSString *rowTitle = [self customSectionTitleForRowCookie:self.cachedCustomSectionRowCookies[index]];
|
||||
if ([rowTitle rangeOfString:self.filterText options:NSCaseInsensitiveSearch].location != NSNotFound) {
|
||||
matches = YES;
|
||||
}
|
||||
@@ -1020,6 +1122,16 @@ static const NSInteger kFLEXObjectExplorerScopeIncludeInheritanceIndex = 1;
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (UIView *)customViewForRowCookie:(id)rowCookie
|
||||
{
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (CGFloat)heightForCustomViewRowForRowCookie:(id)rowCookie
|
||||
{
|
||||
return self.tableView.rowHeight;
|
||||
}
|
||||
|
||||
- (BOOL)canHaveInstanceState
|
||||
{
|
||||
return YES;
|
||||
|
||||
@@ -15,7 +15,8 @@
|
||||
|
||||
typedef NS_ENUM(NSUInteger, FLEXViewExplorerRow) {
|
||||
FLEXViewExplorerRowViewController,
|
||||
FLEXViewExplorerRowPreview
|
||||
FLEXViewExplorerRowPreview,
|
||||
FLEXViewExplorerRowViewControllerForAncestor
|
||||
};
|
||||
|
||||
@interface FLEXViewExplorerViewController ()
|
||||
@@ -46,6 +47,8 @@ typedef NS_ENUM(NSUInteger, FLEXViewExplorerRow) {
|
||||
|
||||
if ([FLEXUtility viewControllerForView:self.viewToExplore]) {
|
||||
[rowCookies addObject:@(FLEXViewExplorerRowViewController)];
|
||||
}else{
|
||||
[rowCookies addObject:@(FLEXViewExplorerRowViewControllerForAncestor)];
|
||||
}
|
||||
|
||||
[rowCookies addObject:@(FLEXViewExplorerRowPreview)];
|
||||
@@ -54,9 +57,9 @@ typedef NS_ENUM(NSUInteger, FLEXViewExplorerRow) {
|
||||
return rowCookies;
|
||||
}
|
||||
|
||||
- (NSArray *)shortcutPropertyNames
|
||||
- (NSArray<NSString *> *)shortcutPropertyNames
|
||||
{
|
||||
NSArray *propertyNames = @[@"frame", @"bounds", @"center", @"transform", @"backgroundColor", @"alpha", @"opaque", @"hidden", @"clipsToBounds", @"userInteractionEnabled", @"layer"];
|
||||
NSArray<NSString *> *propertyNames = @[@"frame", @"bounds", @"center", @"transform", @"backgroundColor", @"alpha", @"opaque", @"hidden", @"clipsToBounds", @"userInteractionEnabled", @"layer"];
|
||||
|
||||
if ([self.viewToExplore isKindOfClass:[UILabel class]]) {
|
||||
propertyNames = [@[@"text", @"font", @"textColor"] arrayByAddingObjectsFromArray:propertyNames];
|
||||
@@ -79,6 +82,10 @@ typedef NS_ENUM(NSUInteger, FLEXViewExplorerRow) {
|
||||
case FLEXViewExplorerRowPreview:
|
||||
title = @"Preview Image";
|
||||
break;
|
||||
|
||||
case FLEXViewExplorerRowViewControllerForAncestor:
|
||||
title = @"View Controller For Ancestor";
|
||||
break;
|
||||
}
|
||||
} else if ([rowCookie isKindOfClass:[NSString class]]) {
|
||||
objc_property_t property = [self viewPropertyForName:rowCookie];
|
||||
@@ -105,6 +112,10 @@ typedef NS_ENUM(NSUInteger, FLEXViewExplorerRow) {
|
||||
|
||||
case FLEXViewExplorerRowPreview:
|
||||
break;
|
||||
|
||||
case FLEXViewExplorerRowViewControllerForAncestor:
|
||||
subtitle = [FLEXRuntimeUtility descriptionForIvarOrPropertyValue:[FLEXUtility viewControllerForAncestralView:self.viewToExplore]];
|
||||
break;
|
||||
}
|
||||
} else if ([rowCookie isKindOfClass:[NSString class]]) {
|
||||
objc_property_t property = [self viewPropertyForName:rowCookie];
|
||||
@@ -141,6 +152,10 @@ typedef NS_ENUM(NSUInteger, FLEXViewExplorerRow) {
|
||||
case FLEXViewExplorerRowPreview:
|
||||
drillInViewController = [[self class] imagePreviewViewControllerForView:self.viewToExplore];
|
||||
break;
|
||||
|
||||
case FLEXViewExplorerRowViewControllerForAncestor:
|
||||
drillInViewController = [FLEXObjectExplorerFactory explorerViewControllerForObject:[FLEXUtility viewControllerForAncestralView:self.viewToExplore]];
|
||||
break;
|
||||
}
|
||||
} else if ([rowCookie isKindOfClass:[NSString class]]) {
|
||||
objc_property_t property = [self viewPropertyForName:rowCookie];
|
||||
@@ -180,22 +195,22 @@ typedef NS_ENUM(NSUInteger, FLEXViewExplorerRow) {
|
||||
// We add these properties to the class at runtime if they haven't been added yet.
|
||||
// This way, we can use our property editor to access and change them.
|
||||
// The property attributes match the declared attributes in UIView.h
|
||||
NSDictionary *frameAttributes = @{kFLEXUtilityAttributeTypeEncoding : @(@encode(CGRect)), kFLEXUtilityAttributeNonAtomic : @""};
|
||||
NSDictionary<NSString *, NSString *> *frameAttributes = @{kFLEXUtilityAttributeTypeEncoding : @(@encode(CGRect)), kFLEXUtilityAttributeNonAtomic : @""};
|
||||
[FLEXRuntimeUtility tryAddPropertyWithName:"frame" attributes:frameAttributes toClass:[UIView class]];
|
||||
|
||||
NSDictionary *alphaAttributes = @{kFLEXUtilityAttributeTypeEncoding : @(@encode(CGFloat)), kFLEXUtilityAttributeNonAtomic : @""};
|
||||
NSDictionary<NSString *, NSString *> *alphaAttributes = @{kFLEXUtilityAttributeTypeEncoding : @(@encode(CGFloat)), kFLEXUtilityAttributeNonAtomic : @""};
|
||||
[FLEXRuntimeUtility tryAddPropertyWithName:"alpha" attributes:alphaAttributes toClass:[UIView class]];
|
||||
|
||||
NSDictionary *clipsAttributes = @{kFLEXUtilityAttributeTypeEncoding : @(@encode(BOOL)), kFLEXUtilityAttributeNonAtomic : @""};
|
||||
NSDictionary<NSString *, NSString *> *clipsAttributes = @{kFLEXUtilityAttributeTypeEncoding : @(@encode(BOOL)), kFLEXUtilityAttributeNonAtomic : @""};
|
||||
[FLEXRuntimeUtility tryAddPropertyWithName:"clipsToBounds" attributes:clipsAttributes toClass:[UIView class]];
|
||||
|
||||
NSDictionary *opaqueAttributes = @{kFLEXUtilityAttributeTypeEncoding : @(@encode(BOOL)), kFLEXUtilityAttributeNonAtomic : @"", kFLEXUtilityAttributeCustomGetter : @"isOpaque"};
|
||||
NSDictionary<NSString *, NSString *> *opaqueAttributes = @{kFLEXUtilityAttributeTypeEncoding : @(@encode(BOOL)), kFLEXUtilityAttributeNonAtomic : @"", kFLEXUtilityAttributeCustomGetter : @"isOpaque"};
|
||||
[FLEXRuntimeUtility tryAddPropertyWithName:"opaque" attributes:opaqueAttributes toClass:[UIView class]];
|
||||
|
||||
NSDictionary *hiddenAttributes = @{kFLEXUtilityAttributeTypeEncoding : @(@encode(BOOL)), kFLEXUtilityAttributeNonAtomic : @"", kFLEXUtilityAttributeCustomGetter : @"isHidden"};
|
||||
NSDictionary<NSString *, NSString *> *hiddenAttributes = @{kFLEXUtilityAttributeTypeEncoding : @(@encode(BOOL)), kFLEXUtilityAttributeNonAtomic : @"", kFLEXUtilityAttributeCustomGetter : @"isHidden"};
|
||||
[FLEXRuntimeUtility tryAddPropertyWithName:"hidden" attributes:hiddenAttributes toClass:[UIView class]];
|
||||
|
||||
NSDictionary *backgroundColorAttributes = @{kFLEXUtilityAttributeTypeEncoding : @(FLEXEncodeClass(UIColor)), kFLEXUtilityAttributeNonAtomic : @"", kFLEXUtilityAttributeCopy : @""};
|
||||
NSDictionary<NSString *, NSString *> *backgroundColorAttributes = @{kFLEXUtilityAttributeTypeEncoding : @(FLEXEncodeClass(UIColor)), kFLEXUtilityAttributeNonAtomic : @"", kFLEXUtilityAttributeCopy : @""};
|
||||
[FLEXRuntimeUtility tryAddPropertyWithName:"backgroundColor" attributes:backgroundColorAttributes toClass:[UIView class]];
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,10 @@
|
||||
|
||||
@interface FLEXExplorerToolbar : UIView
|
||||
|
||||
/// The items to be displayed in the toolbar. Defaults to:
|
||||
/// globalsItem, hierarchyItem, selectItem, moveItem, closeItem
|
||||
@property (nonatomic, copy) NSArray<FLEXToolbarItem *> *toolbarItems;
|
||||
|
||||
/// Toolbar item for selecting views.
|
||||
/// Users of the toolbar can configure the enabled/selected state and event targets/actions.
|
||||
@property (nonatomic, strong, readonly) FLEXToolbarItem *selectItem;
|
||||
+76
-30
@@ -22,12 +22,13 @@
|
||||
|
||||
@property (nonatomic, strong) UIImageView *dragHandleImageView;
|
||||
|
||||
@property (nonatomic, strong) NSArray *toolbarItems;
|
||||
|
||||
@property (nonatomic, strong) UIView *selectedViewDescriptionContainer;
|
||||
@property (nonatomic, strong) UIView *selectedViewDescriptionSafeAreaContainer;
|
||||
@property (nonatomic, strong) UIView *selectedViewColorIndicator;
|
||||
@property (nonatomic, strong) UILabel *selectedViewDescriptionLabel;
|
||||
|
||||
@property (nonatomic, strong,readwrite) UIView *backgroundView;
|
||||
|
||||
@end
|
||||
|
||||
@implementation FLEXExplorerToolbar
|
||||
@@ -36,10 +37,12 @@
|
||||
{
|
||||
self = [super initWithFrame:frame];
|
||||
if (self) {
|
||||
NSMutableArray *toolbarItems = [NSMutableArray array];
|
||||
|
||||
self.backgroundView = [[UIView alloc] init];
|
||||
self.backgroundView.backgroundColor = [UIColor colorWithWhite:1.0 alpha:0.95];
|
||||
[self addSubview:self.backgroundView];
|
||||
|
||||
self.dragHandle = [[UIView alloc] init];
|
||||
self.dragHandle.backgroundColor = [FLEXToolbarItem defaultBackgroundColor];
|
||||
self.dragHandle.backgroundColor = [UIColor clearColor];
|
||||
[self addSubview:self.dragHandle];
|
||||
|
||||
UIImage *dragHandle = [FLEXResources dragHandle];
|
||||
@@ -48,56 +51,52 @@
|
||||
|
||||
UIImage *globalsIcon = [FLEXResources globeIcon];
|
||||
self.globalsItem = [FLEXToolbarItem toolbarItemWithTitle:@"menu" image:globalsIcon];
|
||||
[self addSubview:self.globalsItem];
|
||||
[toolbarItems addObject:self.globalsItem];
|
||||
|
||||
UIImage *listIcon = [FLEXResources listIcon];
|
||||
self.hierarchyItem = [FLEXToolbarItem toolbarItemWithTitle:@"views" image:listIcon];
|
||||
[self addSubview:self.hierarchyItem];
|
||||
[toolbarItems addObject:self.hierarchyItem];
|
||||
|
||||
UIImage *selectIcon = [FLEXResources selectIcon];
|
||||
self.selectItem = [FLEXToolbarItem toolbarItemWithTitle:@"select" image:selectIcon];
|
||||
[self addSubview:self.selectItem];
|
||||
[toolbarItems addObject:self.selectItem];
|
||||
|
||||
UIImage *moveIcon = [FLEXResources moveIcon];
|
||||
self.moveItem = [FLEXToolbarItem toolbarItemWithTitle:@"move" image:moveIcon];
|
||||
[self addSubview:self.moveItem];
|
||||
[toolbarItems addObject:self.moveItem];
|
||||
|
||||
UIImage *closeIcon = [FLEXResources closeIcon];
|
||||
self.closeItem = [FLEXToolbarItem toolbarItemWithTitle:@"close" image:closeIcon];
|
||||
[self addSubview:self.closeItem];
|
||||
[toolbarItems addObject:self.closeItem];
|
||||
|
||||
self.toolbarItems = toolbarItems;
|
||||
self.backgroundColor = [UIColor clearColor];
|
||||
|
||||
|
||||
self.selectedViewDescriptionContainer = [[UIView alloc] init];
|
||||
self.selectedViewDescriptionContainer.backgroundColor = [UIColor colorWithWhite:0.9 alpha:0.95];
|
||||
self.selectedViewDescriptionContainer.hidden = YES;
|
||||
[self addSubview:self.selectedViewDescriptionContainer];
|
||||
|
||||
self.selectedViewDescriptionSafeAreaContainer = [[UIView alloc] init];
|
||||
self.selectedViewDescriptionSafeAreaContainer.backgroundColor = [UIColor clearColor];
|
||||
[self.selectedViewDescriptionContainer addSubview:self.selectedViewDescriptionSafeAreaContainer];
|
||||
|
||||
self.selectedViewColorIndicator = [[UIView alloc] init];
|
||||
self.selectedViewColorIndicator.backgroundColor = [UIColor redColor];
|
||||
[self.selectedViewDescriptionContainer addSubview:self.selectedViewColorIndicator];
|
||||
[self.selectedViewDescriptionSafeAreaContainer addSubview:self.selectedViewColorIndicator];
|
||||
|
||||
self.selectedViewDescriptionLabel = [[UILabel alloc] init];
|
||||
self.selectedViewDescriptionLabel.backgroundColor = [UIColor clearColor];
|
||||
self.selectedViewDescriptionLabel.font = [[self class] descriptionLabelFont];
|
||||
[self.selectedViewDescriptionContainer addSubview:self.selectedViewDescriptionLabel];
|
||||
[self.selectedViewDescriptionSafeAreaContainer addSubview:self.selectedViewDescriptionLabel];
|
||||
|
||||
self.toolbarItems = @[_globalsItem, _hierarchyItem, _selectItem, _moveItem, _closeItem];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)layoutSubviews
|
||||
{
|
||||
[super layoutSubviews];
|
||||
|
||||
|
||||
|
||||
CGRect safeArea = [self safeArea];
|
||||
// Drag Handle
|
||||
const CGFloat kToolbarItemHeight = [[self class] toolbarItemHeight];
|
||||
self.dragHandle.frame = CGRectMake(self.bounds.origin.x, self.bounds.origin.y, [[self class] dragHandleWidth], kToolbarItemHeight);
|
||||
self.dragHandle.frame = CGRectMake(CGRectGetMinX(safeArea), CGRectGetMinY(safeArea), [[self class] dragHandleWidth], kToolbarItemHeight);
|
||||
CGRect dragHandleImageFrame = self.dragHandleImageView.frame;
|
||||
dragHandleImageFrame.origin.x = FLEXFloor((self.dragHandle.frame.size.width - dragHandleImageFrame.size.width) / 2.0);
|
||||
dragHandleImageFrame.origin.y = FLEXFloor((self.dragHandle.frame.size.height - dragHandleImageFrame.size.height) / 2.0);
|
||||
@@ -106,9 +105,9 @@
|
||||
|
||||
// Toolbar Items
|
||||
CGFloat originX = CGRectGetMaxX(self.dragHandle.frame);
|
||||
CGFloat originY = self.bounds.origin.y;
|
||||
CGFloat originY = CGRectGetMinY(safeArea);
|
||||
CGFloat height = kToolbarItemHeight;
|
||||
CGFloat width = FLEXFloor((CGRectGetMaxX(self.bounds) - originX) / [self.toolbarItems count]);
|
||||
CGFloat width = FLEXFloor((CGRectGetWidth(safeArea) - CGRectGetWidth(self.dragHandle.frame)) / [self.toolbarItems count]);
|
||||
for (UIView *toolbarItem in self.toolbarItems) {
|
||||
toolbarItem.frame = CGRectMake(originX, originY, width, height);
|
||||
originX = CGRectGetMaxX(toolbarItem.frame);
|
||||
@@ -117,8 +116,10 @@
|
||||
// Make sure the last toolbar item goes to the edge to account for any accumulated rounding effects.
|
||||
UIView *lastToolbarItem = [self.toolbarItems lastObject];
|
||||
CGRect lastToolbarItemFrame = lastToolbarItem.frame;
|
||||
lastToolbarItemFrame.size.width = CGRectGetMaxX(self.bounds) - lastToolbarItemFrame.origin.x;
|
||||
lastToolbarItemFrame.size.width = CGRectGetMaxX(safeArea) - lastToolbarItemFrame.origin.x;
|
||||
lastToolbarItem.frame = lastToolbarItemFrame;
|
||||
|
||||
self.backgroundView.frame = CGRectMake(0, 0, CGRectGetWidth(self.bounds), kToolbarItemHeight);
|
||||
|
||||
const CGFloat kSelectedViewColorDiameter = [[self class] selectedViewColorIndicatorDiameter];
|
||||
const CGFloat kDescriptionLabelHeight = [[self class] descriptionLabelHeight];
|
||||
@@ -127,11 +128,19 @@
|
||||
const CGFloat kDescriptionContainerHeight = [[self class] descriptionContainerHeight];
|
||||
|
||||
CGRect descriptionContainerFrame = CGRectZero;
|
||||
descriptionContainerFrame.size.width = CGRectGetWidth(self.bounds);
|
||||
descriptionContainerFrame.size.height = kDescriptionContainerHeight;
|
||||
descriptionContainerFrame.origin.x = CGRectGetMinX(self.bounds);
|
||||
descriptionContainerFrame.origin.y = CGRectGetMaxY(self.bounds) - kDescriptionContainerHeight;
|
||||
descriptionContainerFrame.size.width = self.bounds.size.width;
|
||||
self.selectedViewDescriptionContainer.frame = descriptionContainerFrame;
|
||||
|
||||
|
||||
CGRect descriptionSafeAreaContainerFrame = CGRectZero;
|
||||
descriptionSafeAreaContainerFrame.size.width = CGRectGetWidth(safeArea);
|
||||
descriptionSafeAreaContainerFrame.size.height = kDescriptionContainerHeight;
|
||||
descriptionSafeAreaContainerFrame.origin.x = CGRectGetMinX(safeArea);
|
||||
descriptionSafeAreaContainerFrame.origin.y = CGRectGetMinY(safeArea);
|
||||
self.selectedViewDescriptionSafeAreaContainer.frame = descriptionSafeAreaContainerFrame;
|
||||
|
||||
// Selected View Color
|
||||
CGRect selectedViewColorFrame = CGRectZero;
|
||||
selectedViewColorFrame.size.width = kSelectedViewColorDiameter;
|
||||
@@ -150,10 +159,36 @@
|
||||
descriptionLabelFrame.size.width = CGRectGetMaxX(self.selectedViewDescriptionContainer.bounds) - kHorizontalPadding - descriptionOriginX;
|
||||
self.selectedViewDescriptionLabel.frame = descriptionLabelFrame;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
#pragma mark - Setter Overrides
|
||||
|
||||
- (void)setToolbarItems:(NSArray<FLEXToolbarItem *> *)toolbarItems {
|
||||
if (_toolbarItems == toolbarItems) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove old toolbar items, if any
|
||||
for (FLEXToolbarItem *item in _toolbarItems) {
|
||||
[item removeFromSuperview];
|
||||
}
|
||||
|
||||
// Trim to 5 items if necessary
|
||||
if (toolbarItems.count > 5) {
|
||||
toolbarItems = [toolbarItems subarrayWithRange:NSMakeRange(0, 5)];
|
||||
}
|
||||
|
||||
for (FLEXToolbarItem *item in toolbarItems) {
|
||||
[self addSubview:item];
|
||||
}
|
||||
|
||||
_toolbarItems = toolbarItems.copy;
|
||||
|
||||
// Lay out new items
|
||||
[self setNeedsLayout];
|
||||
[self layoutIfNeeded];
|
||||
}
|
||||
|
||||
- (void)setSelectedViewOverlayColor:(UIColor *)selectedViewOverlayColor
|
||||
{
|
||||
if (![_selectedViewOverlayColor isEqual:selectedViewOverlayColor]) {
|
||||
@@ -223,4 +258,15 @@
|
||||
return CGSizeMake(size.width, height);
|
||||
}
|
||||
|
||||
- (CGRect)safeArea
|
||||
{
|
||||
CGRect safeArea = self.bounds;
|
||||
#if FLEX_AT_LEAST_IOS11_SDK
|
||||
if (@available(iOS 11, *)) {
|
||||
safeArea = UIEdgeInsetsInsetRect(self.bounds, self.safeAreaInsets);
|
||||
}
|
||||
#endif
|
||||
return safeArea;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -12,6 +12,4 @@
|
||||
|
||||
+ (instancetype)toolbarItemWithTitle:(NSString *)title image:(UIImage *)image;
|
||||
|
||||
+ (UIColor *)defaultBackgroundColor;
|
||||
|
||||
@end
|
||||
@@ -43,7 +43,7 @@
|
||||
|
||||
#pragma mark - Display Defaults
|
||||
|
||||
+ (NSDictionary *)titleAttributes
|
||||
+ (NSDictionary<NSString *, id> *)titleAttributes
|
||||
{
|
||||
return @{NSFontAttributeName : [FLEXUtility defaultFontOfSize:12.0]};
|
||||
}
|
||||
@@ -70,7 +70,7 @@
|
||||
|
||||
+ (UIColor *)defaultBackgroundColor
|
||||
{
|
||||
return [UIColor colorWithWhite:1.0 alpha:0.95];
|
||||
return [UIColor clearColor];
|
||||
}
|
||||
|
||||
+ (CGFloat)topMargin
|
||||
@@ -0,0 +1,367 @@
|
||||
APPLE PUBLIC SOURCE LICENSE
|
||||
Version 2.0 - August 6, 2003
|
||||
|
||||
Please read this License carefully before downloading this software.
|
||||
By downloading or using this software, you are agreeing to be bound by
|
||||
the terms of this License. If you do not or cannot agree to the terms
|
||||
of this License, please do not download or use the software.
|
||||
|
||||
1. General; Definitions. This License applies to any program or other
|
||||
work which Apple Computer, Inc. ("Apple") makes publicly available and
|
||||
which contains a notice placed by Apple identifying such program or
|
||||
work as "Original Code" and stating that it is subject to the terms of
|
||||
this Apple Public Source License version 2.0 ("License"). As used in
|
||||
this License:
|
||||
|
||||
1.1 "Applicable Patent Rights" mean: (a) in the case where Apple is
|
||||
the grantor of rights, (i) claims of patents that are now or hereafter
|
||||
acquired, owned by or assigned to Apple and (ii) that cover subject
|
||||
matter contained in the Original Code, but only to the extent
|
||||
necessary to use, reproduce and/or distribute the Original Code
|
||||
without infringement; and (b) in the case where You are the grantor of
|
||||
rights, (i) claims of patents that are now or hereafter acquired,
|
||||
owned by or assigned to You and (ii) that cover subject matter in Your
|
||||
Modifications, taken alone or in combination with Original Code.
|
||||
|
||||
1.2 "Contributor" means any person or entity that creates or
|
||||
contributes to the creation of Modifications.
|
||||
|
||||
1.3 "Covered Code" means the Original Code, Modifications, the
|
||||
combination of Original Code and any Modifications, and/or any
|
||||
respective portions thereof.
|
||||
|
||||
1.4 "Externally Deploy" means: (a) to sublicense, distribute or
|
||||
otherwise make Covered Code available, directly or indirectly, to
|
||||
anyone other than You; and/or (b) to use Covered Code, alone or as
|
||||
part of a Larger Work, in any way to provide a service, including but
|
||||
not limited to delivery of content, through electronic communication
|
||||
with a client other than You.
|
||||
|
||||
1.5 "Larger Work" means a work which combines Covered Code or portions
|
||||
thereof with code not governed by the terms of this License.
|
||||
|
||||
1.6 "Modifications" mean any addition to, deletion from, and/or change
|
||||
to, the substance and/or structure of the Original Code, any previous
|
||||
Modifications, the combination of Original Code and any previous
|
||||
Modifications, and/or any respective portions thereof. When code is
|
||||
released as a series of files, a Modification is: (a) any addition to
|
||||
or deletion from the contents of a file containing Covered Code;
|
||||
and/or (b) any new file or other representation of computer program
|
||||
statements that contains any part of Covered Code.
|
||||
|
||||
1.7 "Original Code" means (a) the Source Code of a program or other
|
||||
work as originally made available by Apple under this License,
|
||||
including the Source Code of any updates or upgrades to such programs
|
||||
or works made available by Apple under this License, and that has been
|
||||
expressly identified by Apple as such in the header file(s) of such
|
||||
work; and (b) the object code compiled from such Source Code and
|
||||
originally made available by Apple under this License.
|
||||
|
||||
1.8 "Source Code" means the human readable form of a program or other
|
||||
work that is suitable for making modifications to it, including all
|
||||
modules it contains, plus any associated interface definition files,
|
||||
scripts used to control compilation and installation of an executable
|
||||
(object code).
|
||||
|
||||
1.9 "You" or "Your" means an individual or a legal entity exercising
|
||||
rights under this License. For legal entities, "You" or "Your"
|
||||
includes any entity which controls, is controlled by, or is under
|
||||
common control with, You, where "control" means (a) the power, direct
|
||||
or indirect, to cause the direction or management of such entity,
|
||||
whether by contract or otherwise, or (b) ownership of fifty percent
|
||||
(50%) or more of the outstanding shares or beneficial ownership of
|
||||
such entity.
|
||||
|
||||
2. Permitted Uses; Conditions & Restrictions. Subject to the terms
|
||||
and conditions of this License, Apple hereby grants You, effective on
|
||||
the date You accept this License and download the Original Code, a
|
||||
world-wide, royalty-free, non-exclusive license, to the extent of
|
||||
Apple's Applicable Patent Rights and copyrights covering the Original
|
||||
Code, to do the following:
|
||||
|
||||
2.1 Unmodified Code. You may use, reproduce, display, perform,
|
||||
internally distribute within Your organization, and Externally Deploy
|
||||
verbatim, unmodified copies of the Original Code, for commercial or
|
||||
non-commercial purposes, provided that in each instance:
|
||||
|
||||
(a) You must retain and reproduce in all copies of Original Code the
|
||||
copyright and other proprietary notices and disclaimers of Apple as
|
||||
they appear in the Original Code, and keep intact all notices in the
|
||||
Original Code that refer to this License; and
|
||||
|
||||
(b) You must include a copy of this License with every copy of Source
|
||||
Code of Covered Code and documentation You distribute or Externally
|
||||
Deploy, and You may not offer or impose any terms on such Source Code
|
||||
that alter or restrict this License or the recipients' rights
|
||||
hereunder, except as permitted under Section 6.
|
||||
|
||||
2.2 Modified Code. You may modify Covered Code and use, reproduce,
|
||||
display, perform, internally distribute within Your organization, and
|
||||
Externally Deploy Your Modifications and Covered Code, for commercial
|
||||
or non-commercial purposes, provided that in each instance You also
|
||||
meet all of these conditions:
|
||||
|
||||
(a) You must satisfy all the conditions of Section 2.1 with respect to
|
||||
the Source Code of the Covered Code;
|
||||
|
||||
(b) You must duplicate, to the extent it does not already exist, the
|
||||
notice in Exhibit A in each file of the Source Code of all Your
|
||||
Modifications, and cause the modified files to carry prominent notices
|
||||
stating that You changed the files and the date of any change; and
|
||||
|
||||
(c) If You Externally Deploy Your Modifications, You must make
|
||||
Source Code of all Your Externally Deployed Modifications either
|
||||
available to those to whom You have Externally Deployed Your
|
||||
Modifications, or publicly available. Source Code of Your Externally
|
||||
Deployed Modifications must be released under the terms set forth in
|
||||
this License, including the license grants set forth in Section 3
|
||||
below, for as long as you Externally Deploy the Covered Code or twelve
|
||||
(12) months from the date of initial External Deployment, whichever is
|
||||
longer. You should preferably distribute the Source Code of Your
|
||||
Externally Deployed Modifications electronically (e.g. download from a
|
||||
web site).
|
||||
|
||||
2.3 Distribution of Executable Versions. In addition, if You
|
||||
Externally Deploy Covered Code (Original Code and/or Modifications) in
|
||||
object code, executable form only, You must include a prominent
|
||||
notice, in the code itself as well as in related documentation,
|
||||
stating that Source Code of the Covered Code is available under the
|
||||
terms of this License with information on how and where to obtain such
|
||||
Source Code.
|
||||
|
||||
2.4 Third Party Rights. You expressly acknowledge and agree that
|
||||
although Apple and each Contributor grants the licenses to their
|
||||
respective portions of the Covered Code set forth herein, no
|
||||
assurances are provided by Apple or any Contributor that the Covered
|
||||
Code does not infringe the patent or other intellectual property
|
||||
rights of any other entity. Apple and each Contributor disclaim any
|
||||
liability to You for claims brought by any other entity based on
|
||||
infringement of intellectual property rights or otherwise. As a
|
||||
condition to exercising the rights and licenses granted hereunder, You
|
||||
hereby assume sole responsibility to secure any other intellectual
|
||||
property rights needed, if any. For example, if a third party patent
|
||||
license is required to allow You to distribute the Covered Code, it is
|
||||
Your responsibility to acquire that license before distributing the
|
||||
Covered Code.
|
||||
|
||||
3. Your Grants. In consideration of, and as a condition to, the
|
||||
licenses granted to You under this License, You hereby grant to any
|
||||
person or entity receiving or distributing Covered Code under this
|
||||
License a non-exclusive, royalty-free, perpetual, irrevocable license,
|
||||
under Your Applicable Patent Rights and other intellectual property
|
||||
rights (other than patent) owned or controlled by You, to use,
|
||||
reproduce, display, perform, modify, sublicense, distribute and
|
||||
Externally Deploy Your Modifications of the same scope and extent as
|
||||
Apple's licenses under Sections 2.1 and 2.2 above.
|
||||
|
||||
4. Larger Works. You may create a Larger Work by combining Covered
|
||||
Code with other code not governed by the terms of this License and
|
||||
distribute the Larger Work as a single product. In each such instance,
|
||||
You must make sure the requirements of this License are fulfilled for
|
||||
the Covered Code or any portion thereof.
|
||||
|
||||
5. Limitations on Patent License. Except as expressly stated in
|
||||
Section 2, no other patent rights, express or implied, are granted by
|
||||
Apple herein. Modifications and/or Larger Works may require additional
|
||||
patent licenses from Apple which Apple may grant in its sole
|
||||
discretion.
|
||||
|
||||
6. Additional Terms. You may choose to offer, and to charge a fee for,
|
||||
warranty, support, indemnity or liability obligations and/or other
|
||||
rights consistent with the scope of the license granted herein
|
||||
("Additional Terms") to one or more recipients of Covered Code.
|
||||
However, You may do so only on Your own behalf and as Your sole
|
||||
responsibility, and not on behalf of Apple or any Contributor. You
|
||||
must obtain the recipient's agreement that any such Additional Terms
|
||||
are offered by You alone, and You hereby agree to indemnify, defend
|
||||
and hold Apple and every Contributor harmless for any liability
|
||||
incurred by or claims asserted against Apple or such Contributor by
|
||||
reason of any such Additional Terms.
|
||||
|
||||
7. Versions of the License. Apple may publish revised and/or new
|
||||
versions of this License from time to time. Each version will be given
|
||||
a distinguishing version number. Once Original Code has been published
|
||||
under a particular version of this License, You may continue to use it
|
||||
under the terms of that version. You may also choose to use such
|
||||
Original Code under the terms of any subsequent version of this
|
||||
License published by Apple. No one other than Apple has the right to
|
||||
modify the terms applicable to Covered Code created under this
|
||||
License.
|
||||
|
||||
8. NO WARRANTY OR SUPPORT. The Covered Code may contain in whole or in
|
||||
part pre-release, untested, or not fully tested works. The Covered
|
||||
Code may contain errors that could cause failures or loss of data, and
|
||||
may be incomplete or contain inaccuracies. You expressly acknowledge
|
||||
and agree that use of the Covered Code, or any portion thereof, is at
|
||||
Your sole and entire risk. THE COVERED CODE IS PROVIDED "AS IS" AND
|
||||
WITHOUT WARRANTY, UPGRADES OR SUPPORT OF ANY KIND AND APPLE AND
|
||||
APPLE'S LICENSOR(S) (COLLECTIVELY REFERRED TO AS "APPLE" FOR THE
|
||||
PURPOSES OF SECTIONS 8 AND 9) AND ALL CONTRIBUTORS EXPRESSLY DISCLAIM
|
||||
ALL WARRANTIES AND/OR CONDITIONS, EXPRESS OR IMPLIED, INCLUDING, BUT
|
||||
NOT LIMITED TO, THE IMPLIED WARRANTIES AND/OR CONDITIONS OF
|
||||
MERCHANTABILITY, OF SATISFACTORY QUALITY, OF FITNESS FOR A PARTICULAR
|
||||
PURPOSE, OF ACCURACY, OF QUIET ENJOYMENT, AND NONINFRINGEMENT OF THIRD
|
||||
PARTY RIGHTS. APPLE AND EACH CONTRIBUTOR DOES NOT WARRANT AGAINST
|
||||
INTERFERENCE WITH YOUR ENJOYMENT OF THE COVERED CODE, THAT THE
|
||||
FUNCTIONS CONTAINED IN THE COVERED CODE WILL MEET YOUR REQUIREMENTS,
|
||||
THAT THE OPERATION OF THE COVERED CODE WILL BE UNINTERRUPTED OR
|
||||
ERROR-FREE, OR THAT DEFECTS IN THE COVERED CODE WILL BE CORRECTED. NO
|
||||
ORAL OR WRITTEN INFORMATION OR ADVICE GIVEN BY APPLE, AN APPLE
|
||||
AUTHORIZED REPRESENTATIVE OR ANY CONTRIBUTOR SHALL CREATE A WARRANTY.
|
||||
You acknowledge that the Covered Code is not intended for use in the
|
||||
operation of nuclear facilities, aircraft navigation, communication
|
||||
systems, or air traffic control machines in which case the failure of
|
||||
the Covered Code could lead to death, personal injury, or severe
|
||||
physical or environmental damage.
|
||||
|
||||
9. LIMITATION OF LIABILITY. TO THE EXTENT NOT PROHIBITED BY LAW, IN NO
|
||||
EVENT SHALL APPLE OR ANY CONTRIBUTOR BE LIABLE FOR ANY INCIDENTAL,
|
||||
SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES ARISING OUT OF OR RELATING
|
||||
TO THIS LICENSE OR YOUR USE OR INABILITY TO USE THE COVERED CODE, OR
|
||||
ANY PORTION THEREOF, WHETHER UNDER A THEORY OF CONTRACT, WARRANTY,
|
||||
TORT (INCLUDING NEGLIGENCE), PRODUCTS LIABILITY OR OTHERWISE, EVEN IF
|
||||
APPLE OR SUCH CONTRIBUTOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
|
||||
DAMAGES AND NOTWITHSTANDING THE FAILURE OF ESSENTIAL PURPOSE OF ANY
|
||||
REMEDY. SOME JURISDICTIONS DO NOT ALLOW THE LIMITATION OF LIABILITY OF
|
||||
INCIDENTAL OR CONSEQUENTIAL DAMAGES, SO THIS LIMITATION MAY NOT APPLY
|
||||
TO YOU. In no event shall Apple's total liability to You for all
|
||||
damages (other than as may be required by applicable law) under this
|
||||
License exceed the amount of fifty dollars ($50.00).
|
||||
|
||||
10. Trademarks. This License does not grant any rights to use the
|
||||
trademarks or trade names "Apple", "Apple Computer", "Mac", "Mac OS",
|
||||
"QuickTime", "QuickTime Streaming Server" or any other trademarks,
|
||||
service marks, logos or trade names belonging to Apple (collectively
|
||||
"Apple Marks") or to any trademark, service mark, logo or trade name
|
||||
belonging to any Contributor. You agree not to use any Apple Marks in
|
||||
or as part of the name of products derived from the Original Code or
|
||||
to endorse or promote products derived from the Original Code other
|
||||
than as expressly permitted by and in strict compliance at all times
|
||||
with Apple's third party trademark usage guidelines which are posted
|
||||
at http://www.apple.com/legal/guidelinesfor3rdparties.html.
|
||||
|
||||
11. Ownership. Subject to the licenses granted under this License,
|
||||
each Contributor retains all rights, title and interest in and to any
|
||||
Modifications made by such Contributor. Apple retains all rights,
|
||||
title and interest in and to the Original Code and any Modifications
|
||||
made by or on behalf of Apple ("Apple Modifications"), and such Apple
|
||||
Modifications will not be automatically subject to this License. Apple
|
||||
may, at its sole discretion, choose to license such Apple
|
||||
Modifications under this License, or on different terms from those
|
||||
contained in this License or may choose not to license them at all.
|
||||
|
||||
12. Termination.
|
||||
|
||||
12.1 Termination. This License and the rights granted hereunder will
|
||||
terminate:
|
||||
|
||||
(a) automatically without notice from Apple if You fail to comply with
|
||||
any term(s) of this License and fail to cure such breach within 30
|
||||
days of becoming aware of such breach;
|
||||
|
||||
(b) immediately in the event of the circumstances described in Section
|
||||
13.5(b); or
|
||||
|
||||
(c) automatically without notice from Apple if You, at any time during
|
||||
the term of this License, commence an action for patent infringement
|
||||
against Apple; provided that Apple did not first commence
|
||||
an action for patent infringement against You in that instance.
|
||||
|
||||
12.2 Effect of Termination. Upon termination, You agree to immediately
|
||||
stop any further use, reproduction, modification, sublicensing and
|
||||
distribution of the Covered Code. All sublicenses to the Covered Code
|
||||
which have been properly granted prior to termination shall survive
|
||||
any termination of this License. Provisions which, by their nature,
|
||||
should remain in effect beyond the termination of this License shall
|
||||
survive, including but not limited to Sections 3, 5, 8, 9, 10, 11,
|
||||
12.2 and 13. No party will be liable to any other for compensation,
|
||||
indemnity or damages of any sort solely as a result of terminating
|
||||
this License in accordance with its terms, and termination of this
|
||||
License will be without prejudice to any other right or remedy of
|
||||
any party.
|
||||
|
||||
13. Miscellaneous.
|
||||
|
||||
13.1 Government End Users. The Covered Code is a "commercial item" as
|
||||
defined in FAR 2.101. Government software and technical data rights in
|
||||
the Covered Code include only those rights customarily provided to the
|
||||
public as defined in this License. This customary commercial license
|
||||
in technical data and software is provided in accordance with FAR
|
||||
12.211 (Technical Data) and 12.212 (Computer Software) and, for
|
||||
Department of Defense purchases, DFAR 252.227-7015 (Technical Data --
|
||||
Commercial Items) and 227.7202-3 (Rights in Commercial Computer
|
||||
Software or Computer Software Documentation). Accordingly, all U.S.
|
||||
Government End Users acquire Covered Code with only those rights set
|
||||
forth herein.
|
||||
|
||||
13.2 Relationship of Parties. This License will not be construed as
|
||||
creating an agency, partnership, joint venture or any other form of
|
||||
legal association between or among You, Apple or any Contributor, and
|
||||
You will not represent to the contrary, whether expressly, by
|
||||
implication, appearance or otherwise.
|
||||
|
||||
13.3 Independent Development. Nothing in this License will impair
|
||||
Apple's right to acquire, license, develop, have others develop for
|
||||
it, market and/or distribute technology or products that perform the
|
||||
same or similar functions as, or otherwise compete with,
|
||||
Modifications, Larger Works, technology or products that You may
|
||||
develop, produce, market or distribute.
|
||||
|
||||
13.4 Waiver; Construction. Failure by Apple or any Contributor to
|
||||
enforce any provision of this License will not be deemed a waiver of
|
||||
future enforcement of that or any other provision. Any law or
|
||||
regulation which provides that the language of a contract shall be
|
||||
construed against the drafter will not apply to this License.
|
||||
|
||||
13.5 Severability. (a) If for any reason a court of competent
|
||||
jurisdiction finds any provision of this License, or portion thereof,
|
||||
to be unenforceable, that provision of the License will be enforced to
|
||||
the maximum extent permissible so as to effect the economic benefits
|
||||
and intent of the parties, and the remainder of this License will
|
||||
continue in full force and effect. (b) Notwithstanding the foregoing,
|
||||
if applicable law prohibits or restricts You from fully and/or
|
||||
specifically complying with Sections 2 and/or 3 or prevents the
|
||||
enforceability of either of those Sections, this License will
|
||||
immediately terminate and You must immediately discontinue any use of
|
||||
the Covered Code and destroy all copies of it that are in your
|
||||
possession or control.
|
||||
|
||||
13.6 Dispute Resolution. Any litigation or other dispute resolution
|
||||
between You and Apple relating to this License shall take place in the
|
||||
Northern District of California, and You and Apple hereby consent to
|
||||
the personal jurisdiction of, and venue in, the state and federal
|
||||
courts within that District with respect to this License. The
|
||||
application of the United Nations Convention on Contracts for the
|
||||
International Sale of Goods is expressly excluded.
|
||||
|
||||
13.7 Entire Agreement; Governing Law. This License constitutes the
|
||||
entire agreement between the parties with respect to the subject
|
||||
matter hereof. This License shall be governed by the laws of the
|
||||
United States and the State of California, except that body of
|
||||
California law concerning conflicts of law.
|
||||
|
||||
Where You are located in the province of Quebec, Canada, the following
|
||||
clause applies: The parties hereby confirm that they have requested
|
||||
that this License and all related documents be drafted in English. Les
|
||||
parties ont exige que le present contrat et tous les documents
|
||||
connexes soient rediges en anglais.
|
||||
|
||||
EXHIBIT A.
|
||||
|
||||
"Portions Copyright (c) 1999-2003 Apple Computer, Inc. All Rights
|
||||
Reserved.
|
||||
|
||||
This file contains Original Code and/or Modifications of Original Code
|
||||
as defined in and that are subject to the Apple Public Source License
|
||||
Version 2.0 (the 'License'). You may not use this file except in
|
||||
compliance with the License. Please obtain a copy of the License at
|
||||
http://www.opensource.apple.com/apsl/ and read it before using this
|
||||
file.
|
||||
|
||||
The Original Code and all software distributed under the License are
|
||||
distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
|
||||
EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
|
||||
INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
|
||||
Please see the License for the specific language governing rights and
|
||||
limitations under the License."
|
||||
@@ -20,16 +20,9 @@ typedef struct {
|
||||
|
||||
@implementation FLEXHeapEnumerator
|
||||
|
||||
static kern_return_t memory_reader(task_t task, vm_address_t remote_address, vm_size_t size, void **local_memory)
|
||||
{
|
||||
*local_memory = (void *)remote_address;
|
||||
return KERN_SUCCESS;
|
||||
}
|
||||
|
||||
static void range_callback(task_t task, void *context, unsigned type, vm_range_t *ranges, unsigned rangeCount)
|
||||
{
|
||||
flex_object_enumeration_block_t block = (__bridge flex_object_enumeration_block_t)context;
|
||||
if (!block) {
|
||||
if (!context) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -46,11 +39,17 @@ static void range_callback(task_t task, void *context, unsigned type, vm_range_t
|
||||
#endif
|
||||
// If the class pointer matches one in our set of class pointers from the runtime, then we should have an object.
|
||||
if (CFSetContainsValue(registeredClasses, (__bridge const void *)(tryClass))) {
|
||||
block((__bridge id)tryObject, tryClass);
|
||||
(*(flex_object_enumeration_block_t __unsafe_unretained *)context)((__bridge id)tryObject, tryClass);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static kern_return_t reader(__unused task_t remote_task, vm_address_t remote_address, __unused vm_size_t size, void **local_memory)
|
||||
{
|
||||
*local_memory = (void *)remote_address;
|
||||
return KERN_SUCCESS;
|
||||
}
|
||||
|
||||
+ (void)enumerateLiveObjectsUsingBlock:(flex_object_enumeration_block_t)block
|
||||
{
|
||||
if (!block) {
|
||||
@@ -60,18 +59,58 @@ static void range_callback(task_t task, void *context, unsigned type, vm_range_t
|
||||
// Refresh the class list on every call in case classes are added to the runtime.
|
||||
[self updateRegisteredClasses];
|
||||
|
||||
// For another exmple of enumerating through malloc ranges (which helped my understanding of the api) see:
|
||||
// Inspired by:
|
||||
// http://llvm.org/svn/llvm-project/lldb/tags/RELEASE_34/final/examples/darwin/heap_find/heap/heap_find.cpp
|
||||
// Also https://gist.github.com/samdmarshall/17f4e66b5e2e579fd396
|
||||
// https://gist.github.com/samdmarshall/17f4e66b5e2e579fd396
|
||||
|
||||
vm_address_t *zones = NULL;
|
||||
unsigned int zoneCount = 0;
|
||||
kern_return_t result = malloc_get_all_zones(mach_task_self(), &memory_reader, &zones, &zoneCount);
|
||||
kern_return_t result = malloc_get_all_zones(TASK_NULL, reader, &zones, &zoneCount);
|
||||
|
||||
if (result == KERN_SUCCESS) {
|
||||
for (unsigned int i = 0; i < zoneCount; i++) {
|
||||
malloc_zone_t *zone = (malloc_zone_t *)zones[i];
|
||||
if (zone->introspect && zone->introspect->enumerator) {
|
||||
zone->introspect->enumerator(mach_task_self(), (__bridge void *)(block), MALLOC_PTR_IN_USE_RANGE_TYPE, zones[i], &memory_reader, &range_callback);
|
||||
malloc_introspection_t *introspection = zone->introspect;
|
||||
NSString *zoneName = @(zone->zone_name);
|
||||
|
||||
if (![zoneName isEqualToString:@"DefaultMallocZone"] || !introspection) {
|
||||
continue;
|
||||
}
|
||||
|
||||
void (*lock_zone)(malloc_zone_t *zone) = introspection->force_lock;
|
||||
void (*unlock_zone)(malloc_zone_t *zone) = introspection->force_unlock;
|
||||
|
||||
// Callback has to unlock the zone so we freely allocate memory inside the given block
|
||||
flex_object_enumeration_block_t callback = ^(__unsafe_unretained id object, __unsafe_unretained Class actualClass) {
|
||||
unlock_zone(zone);
|
||||
block(object, actualClass);
|
||||
lock_zone(zone);
|
||||
};
|
||||
|
||||
// The largest realistic memory address varies by platform.
|
||||
// Only 48 bits are used by 64 bit machines while
|
||||
// 32 bit machines use all bits.
|
||||
#if __arm64__
|
||||
static uintptr_t MAX_REALISTIC_ADDRESS = 0xFFFFFFFFFFFF;
|
||||
#else
|
||||
static uintptr_t MAX_REALISTIC_ADDRESS = INT_MAX;
|
||||
#endif
|
||||
|
||||
// There is little documentation on when and why
|
||||
// any of these function pointers might be NULL
|
||||
// or garbage, so we resort to checking for NULL
|
||||
// and impossible memory addresses at least
|
||||
if (lock_zone && unlock_zone &&
|
||||
introspection->enumerator &&
|
||||
(uintptr_t)lock_zone < MAX_REALISTIC_ADDRESS &&
|
||||
(uintptr_t)unlock_zone < MAX_REALISTIC_ADDRESS) {
|
||||
lock_zone(zone);
|
||||
introspection->enumerator(TASK_NULL, (void *)&callback, MALLOC_PTR_IN_USE_RANGE_TYPE, (vm_address_t)zone, reader, &range_callback);
|
||||
unlock_zone(zone);
|
||||
}
|
||||
|
||||
// Only one zone to enumerate
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -0,0 +1,310 @@
|
||||
//
|
||||
// FLEXKeyboardShortcutManager.m
|
||||
// FLEX
|
||||
//
|
||||
// Created by Ryan Olson on 9/19/15.
|
||||
// Copyright © 2015 Flipboard. All rights reserved.
|
||||
//
|
||||
|
||||
#import "FLEXKeyboardShortcutManager.h"
|
||||
#import "FLEXUtility.h"
|
||||
#import <objc/runtime.h>
|
||||
#import <objc/message.h>
|
||||
|
||||
#if TARGET_OS_SIMULATOR
|
||||
|
||||
@interface UIEvent (UIPhysicalKeyboardEvent)
|
||||
|
||||
@property (nonatomic, strong) NSString *_modifiedInput;
|
||||
@property (nonatomic, strong) NSString *_unmodifiedInput;
|
||||
@property (nonatomic, assign) UIKeyModifierFlags _modifierFlags;
|
||||
@property (nonatomic, assign) BOOL _isKeyDown;
|
||||
@property (nonatomic, assign) long _keyCode;
|
||||
|
||||
@end
|
||||
|
||||
@interface FLEXKeyInput : NSObject <NSCopying>
|
||||
|
||||
@property (nonatomic, copy, readonly) NSString *key;
|
||||
@property (nonatomic, assign, readonly) UIKeyModifierFlags flags;
|
||||
@property (nonatomic, copy, readonly) NSString *helpDescription;
|
||||
|
||||
@end
|
||||
|
||||
@implementation FLEXKeyInput
|
||||
|
||||
- (BOOL)isEqual:(id)object
|
||||
{
|
||||
BOOL isEqual = NO;
|
||||
if ([object isKindOfClass:[FLEXKeyInput class]]) {
|
||||
FLEXKeyInput *keyCommand = (FLEXKeyInput *)object;
|
||||
BOOL equalKeys = self.key == keyCommand.key || [self.key isEqual:keyCommand.key];
|
||||
BOOL equalFlags = self.flags == keyCommand.flags;
|
||||
isEqual = equalKeys && equalFlags;
|
||||
}
|
||||
return isEqual;
|
||||
}
|
||||
|
||||
- (NSUInteger)hash
|
||||
{
|
||||
return [self.key hash] ^ self.flags;
|
||||
}
|
||||
|
||||
- (id)copyWithZone:(NSZone *)zone
|
||||
{
|
||||
return [[self class] keyInputForKey:self.key flags:self.flags helpDescription:self.helpDescription];
|
||||
}
|
||||
|
||||
- (NSString *)description
|
||||
{
|
||||
NSDictionary<NSString *, NSString *> *keyMappings = @{ UIKeyInputUpArrow : @"↑",
|
||||
UIKeyInputDownArrow : @"↓",
|
||||
UIKeyInputLeftArrow : @"←",
|
||||
UIKeyInputRightArrow : @"→",
|
||||
UIKeyInputEscape : @"␛",
|
||||
@" " : @"␠"};
|
||||
|
||||
NSString *prettyKey = nil;
|
||||
if (self.key && keyMappings[self.key]) {
|
||||
prettyKey = keyMappings[self.key];
|
||||
} else {
|
||||
prettyKey = [self.key uppercaseString];
|
||||
}
|
||||
|
||||
NSString *prettyFlags = @"";
|
||||
if (self.flags & UIKeyModifierControl) {
|
||||
prettyFlags = [prettyFlags stringByAppendingString:@"⌃"];
|
||||
}
|
||||
if (self.flags & UIKeyModifierAlternate) {
|
||||
prettyFlags = [prettyFlags stringByAppendingString:@"⌥"];
|
||||
}
|
||||
if (self.flags & UIKeyModifierShift) {
|
||||
prettyFlags = [prettyFlags stringByAppendingString:@"⇧"];
|
||||
}
|
||||
if (self.flags & UIKeyModifierCommand) {
|
||||
prettyFlags = [prettyFlags stringByAppendingString:@"⌘"];
|
||||
}
|
||||
|
||||
// Fudging to get easy columns with tabs
|
||||
if ([prettyFlags length] < 2) {
|
||||
prettyKey = [prettyKey stringByAppendingString:@"\t"];
|
||||
}
|
||||
|
||||
return [NSString stringWithFormat:@"%@%@\t%@", prettyFlags, prettyKey, self.helpDescription];
|
||||
}
|
||||
|
||||
+ (instancetype)keyInputForKey:(NSString *)key flags:(UIKeyModifierFlags)flags
|
||||
{
|
||||
return [self keyInputForKey:key flags:flags helpDescription:nil];
|
||||
}
|
||||
|
||||
+ (instancetype)keyInputForKey:(NSString *)key flags:(UIKeyModifierFlags)flags helpDescription:(NSString *)helpDescription
|
||||
{
|
||||
FLEXKeyInput *keyInput = [[self alloc] init];
|
||||
if (keyInput) {
|
||||
keyInput->_key = key;
|
||||
keyInput->_flags = flags;
|
||||
keyInput->_helpDescription = helpDescription;
|
||||
}
|
||||
return keyInput;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@interface FLEXKeyboardShortcutManager ()
|
||||
|
||||
@property (nonatomic, strong) NSMutableDictionary<FLEXKeyInput *, dispatch_block_t> *actionsForKeyInputs;
|
||||
|
||||
@property (nonatomic, assign, getter=isPressingShift) BOOL pressingShift;
|
||||
@property (nonatomic, assign, getter=isPressingCommand) BOOL pressingCommand;
|
||||
@property (nonatomic, assign, getter=isPressingControl) BOOL pressingControl;
|
||||
|
||||
@end
|
||||
|
||||
@implementation FLEXKeyboardShortcutManager
|
||||
|
||||
+ (instancetype)sharedManager
|
||||
{
|
||||
static FLEXKeyboardShortcutManager *sharedManager = nil;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
sharedManager = [[[self class] alloc] init];
|
||||
});
|
||||
return sharedManager;
|
||||
}
|
||||
|
||||
+ (void)load
|
||||
{
|
||||
SEL originalKeyEventSelector = NSSelectorFromString(@"handleKeyUIEvent:");
|
||||
SEL swizzledKeyEventSelector = [FLEXUtility swizzledSelectorForSelector:originalKeyEventSelector];
|
||||
|
||||
void (^handleKeyUIEventSwizzleBlock)(UIApplication *, UIEvent *) = ^(UIApplication *slf, UIEvent *event) {
|
||||
|
||||
[[[self class] sharedManager] handleKeyboardEvent:event];
|
||||
|
||||
((void(*)(id, SEL, id))objc_msgSend)(slf, swizzledKeyEventSelector, event);
|
||||
};
|
||||
|
||||
[FLEXUtility replaceImplementationOfKnownSelector:originalKeyEventSelector onClass:[UIApplication class] withBlock:handleKeyUIEventSwizzleBlock swizzledSelector:swizzledKeyEventSelector];
|
||||
|
||||
if ([[UITouch class] instancesRespondToSelector:@selector(maximumPossibleForce)]) {
|
||||
SEL originalSendEventSelector = NSSelectorFromString(@"sendEvent:");
|
||||
SEL swizzledSendEventSelector = [FLEXUtility swizzledSelectorForSelector:originalSendEventSelector];
|
||||
|
||||
void (^sendEventSwizzleBlock)(UIApplication *, UIEvent *) = ^(UIApplication *slf, UIEvent *event) {
|
||||
if (event.type == UIEventTypeTouches) {
|
||||
FLEXKeyboardShortcutManager *keyboardManager = [FLEXKeyboardShortcutManager sharedManager];
|
||||
NSInteger pressureLevel = 0;
|
||||
if (keyboardManager.isPressingShift) {
|
||||
pressureLevel++;
|
||||
}
|
||||
if (keyboardManager.isPressingCommand) {
|
||||
pressureLevel++;
|
||||
}
|
||||
if (keyboardManager.isPressingControl) {
|
||||
pressureLevel++;
|
||||
}
|
||||
if (pressureLevel > 0) {
|
||||
#if FLEX_AT_LEAST_IOS11_SDK
|
||||
if (@available(iOS 9.0, *)) {
|
||||
for (UITouch *touch in [event allTouches]) {
|
||||
double adjustedPressureLevel = pressureLevel * 20 * touch.maximumPossibleForce;
|
||||
[touch setValue:@(adjustedPressureLevel) forKey:@"_pressure"];
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
((void(*)(id, SEL, id))objc_msgSend)(slf, swizzledSendEventSelector, event);
|
||||
};
|
||||
|
||||
[FLEXUtility replaceImplementationOfKnownSelector:originalSendEventSelector onClass:[UIApplication class] withBlock:sendEventSwizzleBlock swizzledSelector:swizzledSendEventSelector];
|
||||
|
||||
SEL originalSupportsTouchPressureSelector = NSSelectorFromString(@"_supportsForceTouch");
|
||||
SEL swizzledSupportsTouchPressureSelector = [FLEXUtility swizzledSelectorForSelector:originalSupportsTouchPressureSelector];
|
||||
|
||||
BOOL (^supportsTouchPressureSwizzleBlock)(UIDevice *) = ^BOOL(UIDevice *slf) {
|
||||
return YES;
|
||||
};
|
||||
|
||||
[FLEXUtility replaceImplementationOfKnownSelector:originalSupportsTouchPressureSelector onClass:[UIDevice class] withBlock:supportsTouchPressureSwizzleBlock swizzledSelector:swizzledSupportsTouchPressureSelector];
|
||||
}
|
||||
}
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
if (self) {
|
||||
_actionsForKeyInputs = [NSMutableDictionary dictionary];
|
||||
_enabled = YES;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)registerSimulatorShortcutWithKey:(NSString *)key modifiers:(UIKeyModifierFlags)modifiers action:(dispatch_block_t)action description:(NSString *)description
|
||||
{
|
||||
FLEXKeyInput *keyInput = [FLEXKeyInput keyInputForKey:key flags:modifiers helpDescription:description];
|
||||
[self.actionsForKeyInputs setObject:action forKey:keyInput];
|
||||
}
|
||||
|
||||
static const long kFLEXControlKeyCode = 0xe0;
|
||||
static const long kFLEXShiftKeyCode = 0xe1;
|
||||
static const long kFLEXCommandKeyCode = 0xe3;
|
||||
|
||||
- (void)handleKeyboardEvent:(UIEvent *)event
|
||||
{
|
||||
if (!self.enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
NSString *modifiedInput = nil;
|
||||
NSString *unmodifiedInput = nil;
|
||||
UIKeyModifierFlags flags = 0;
|
||||
BOOL isKeyDown = NO;
|
||||
|
||||
if ([event respondsToSelector:@selector(_modifiedInput)]) {
|
||||
modifiedInput = [event _modifiedInput];
|
||||
}
|
||||
|
||||
if ([event respondsToSelector:@selector(_unmodifiedInput)]) {
|
||||
unmodifiedInput = [event _unmodifiedInput];
|
||||
}
|
||||
|
||||
if ([event respondsToSelector:@selector(_modifierFlags)]) {
|
||||
flags = [event _modifierFlags];
|
||||
}
|
||||
|
||||
if ([event respondsToSelector:@selector(_isKeyDown)]) {
|
||||
isKeyDown = [event _isKeyDown];
|
||||
}
|
||||
|
||||
BOOL interactionEnabled = ![[UIApplication sharedApplication] isIgnoringInteractionEvents];
|
||||
BOOL hasFirstResponder = NO;
|
||||
if (isKeyDown && [modifiedInput length] > 0 && interactionEnabled) {
|
||||
UIResponder *firstResponder = nil;
|
||||
for (UIWindow *window in [FLEXUtility allWindows]) {
|
||||
firstResponder = [window valueForKey:@"firstResponder"];
|
||||
if (firstResponder) {
|
||||
hasFirstResponder = YES;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Ignore key commands (except escape) when there's an active responder
|
||||
if (firstResponder) {
|
||||
if ([unmodifiedInput isEqual:UIKeyInputEscape]) {
|
||||
[firstResponder resignFirstResponder];
|
||||
}
|
||||
} else {
|
||||
FLEXKeyInput *exactMatch = [FLEXKeyInput keyInputForKey:unmodifiedInput flags:flags];
|
||||
|
||||
dispatch_block_t actionBlock = self.actionsForKeyInputs[exactMatch];
|
||||
|
||||
if (!actionBlock) {
|
||||
FLEXKeyInput *shiftMatch = [FLEXKeyInput keyInputForKey:modifiedInput flags:flags&(~UIKeyModifierShift)];
|
||||
actionBlock = self.actionsForKeyInputs[shiftMatch];
|
||||
}
|
||||
|
||||
if (!actionBlock) {
|
||||
FLEXKeyInput *capitalMatch = [FLEXKeyInput keyInputForKey:[unmodifiedInput uppercaseString] flags:flags];
|
||||
actionBlock = self.actionsForKeyInputs[capitalMatch];
|
||||
}
|
||||
|
||||
if (actionBlock) {
|
||||
actionBlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Calling _keyCode on events from the simulator keyboard will crash.
|
||||
// It is only safe to call _keyCode when there's not an active responder.
|
||||
if (!hasFirstResponder && [event respondsToSelector:@selector(_keyCode)]) {
|
||||
long keyCode = [event _keyCode];
|
||||
if (keyCode == kFLEXControlKeyCode) {
|
||||
self.pressingControl = isKeyDown;
|
||||
} else if (keyCode == kFLEXCommandKeyCode) {
|
||||
self.pressingCommand = isKeyDown;
|
||||
} else if (keyCode == kFLEXShiftKeyCode) {
|
||||
self.pressingShift = isKeyDown;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (NSString *)keyboardShortcutsDescription
|
||||
{
|
||||
NSMutableString *description = [NSMutableString string];
|
||||
NSArray<FLEXKeyInput *> *keyInputs = [[self.actionsForKeyInputs allKeys] sortedArrayUsingComparator:^NSComparisonResult(FLEXKeyInput *_Nonnull input1, FLEXKeyInput *_Nonnull input2) {
|
||||
return [input1.key caseInsensitiveCompare:input2.key];
|
||||
}];
|
||||
for (FLEXKeyInput *keyInput in keyInputs) {
|
||||
[description appendFormat:@"%@\n", keyInput];
|
||||
}
|
||||
return [description copy];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,23 @@
|
||||
//
|
||||
// FLEXObjcInternal.h
|
||||
// FLEX
|
||||
//
|
||||
// Created by Tanner Bennett on 11/1/18.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/// @brief Assumes memory is valid and readable.
|
||||
/// @discussion objc-internal.h, objc-private.h, and objc-config.h
|
||||
/// https://blog.timac.org/2016/1124-testing-if-an-arbitrary-pointer-is-a-valid-objective-c-object/
|
||||
/// http://llvm.org/svn/llvm-project/lldb/trunk/examples/summaries/cocoa/objc_runtime.py
|
||||
/// https://blog.timac.org/2016/1124-testing-if-an-arbitrary-pointer-is-a-valid-objective-c-object/
|
||||
BOOL FLEXPointerIsValidObjcObject(const void * ptr);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,309 @@
|
||||
//
|
||||
// FLEXObjcInternal.mm
|
||||
// FLEX
|
||||
//
|
||||
// Created by Tanner Bennett on 11/1/18.
|
||||
//
|
||||
|
||||
/*
|
||||
* Copyright (c) 2005-2007 Apple Inc. All Rights Reserved.
|
||||
*
|
||||
* @APPLE_LICENSE_HEADER_START@
|
||||
*
|
||||
* This file contains Original Code and/or Modifications of Original Code
|
||||
* as defined in and that are subject to the Apple Public Source License
|
||||
* Version 2.0 (the 'License'). You may not use this file except in
|
||||
* compliance with the License. Please obtain a copy of the License at
|
||||
* http://www.opensource.apple.com/apsl/ and read it before using this
|
||||
* file.
|
||||
*
|
||||
* The Original Code and all software distributed under the License are
|
||||
* distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
|
||||
* EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
|
||||
* INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
|
||||
* Please see the License for the specific language governing rights and
|
||||
* limitations under the License.
|
||||
*
|
||||
* @APPLE_LICENSE_HEADER_END@
|
||||
*/
|
||||
|
||||
#import "FLEXObjcInternal.h"
|
||||
#import <objc/runtime.h>
|
||||
#import <malloc/malloc.h>
|
||||
|
||||
#define ALWAYS_INLINE inline __attribute__((always_inline))
|
||||
#define NEVER_INLINE inline __attribute__((noinline))
|
||||
|
||||
// The macros below are copied straight from
|
||||
// objc-internal.h, objc-private.h, objc-object.h, and objc-config.h with
|
||||
// as few modifications as possible. Changes are noted in boxed comments.
|
||||
// https://opensource.apple.com/source/objc4/objc4-723/
|
||||
// https://opensource.apple.com/source/objc4/objc4-723/runtime/objc-internal.h.auto.html
|
||||
// https://opensource.apple.com/source/objc4/objc4-723/runtime/objc-private.h.auto.html
|
||||
// https://opensource.apple.com/source/objc4/objc4-723/runtime/objc-config.h.auto.html
|
||||
// https://opensource.apple.com/source/objc4/objc4-723/runtime/objc-object.h.auto.html
|
||||
|
||||
/////////////////////
|
||||
// objc-internal.h //
|
||||
/////////////////////
|
||||
|
||||
#if TARGET_OS_OSX && __x86_64__
|
||||
// 64-bit Mac - tag bit is LSB
|
||||
# define OBJC_MSB_TAGGED_POINTERS 0
|
||||
#else
|
||||
// Everything else - tag bit is MSB
|
||||
# define OBJC_MSB_TAGGED_POINTERS 1
|
||||
#endif
|
||||
|
||||
#define _OBJC_TAG_INDEX_MASK 0x7
|
||||
// array slot includes the tag bit itself
|
||||
#define _OBJC_TAG_SLOT_COUNT 16
|
||||
#define _OBJC_TAG_SLOT_MASK 0xf
|
||||
|
||||
#define _OBJC_TAG_EXT_INDEX_MASK 0xff
|
||||
// array slot has no extra bits
|
||||
#define _OBJC_TAG_EXT_SLOT_COUNT 256
|
||||
#define _OBJC_TAG_EXT_SLOT_MASK 0xff
|
||||
|
||||
#if OBJC_MSB_TAGGED_POINTERS
|
||||
# define _OBJC_TAG_MASK (1UL<<63)
|
||||
# define _OBJC_TAG_INDEX_SHIFT 60
|
||||
# define _OBJC_TAG_SLOT_SHIFT 60
|
||||
# define _OBJC_TAG_PAYLOAD_LSHIFT 4
|
||||
# define _OBJC_TAG_PAYLOAD_RSHIFT 4
|
||||
# define _OBJC_TAG_EXT_MASK (0xfUL<<60)
|
||||
# define _OBJC_TAG_EXT_INDEX_SHIFT 52
|
||||
# define _OBJC_TAG_EXT_SLOT_SHIFT 52
|
||||
# define _OBJC_TAG_EXT_PAYLOAD_LSHIFT 12
|
||||
# define _OBJC_TAG_EXT_PAYLOAD_RSHIFT 12
|
||||
#else
|
||||
# define _OBJC_TAG_MASK 1UL
|
||||
# define _OBJC_TAG_INDEX_SHIFT 1
|
||||
# define _OBJC_TAG_SLOT_SHIFT 0
|
||||
# define _OBJC_TAG_PAYLOAD_LSHIFT 0
|
||||
# define _OBJC_TAG_PAYLOAD_RSHIFT 4
|
||||
# define _OBJC_TAG_EXT_MASK 0xfUL
|
||||
# define _OBJC_TAG_EXT_INDEX_SHIFT 4
|
||||
# define _OBJC_TAG_EXT_SLOT_SHIFT 4
|
||||
# define _OBJC_TAG_EXT_PAYLOAD_LSHIFT 0
|
||||
# define _OBJC_TAG_EXT_PAYLOAD_RSHIFT 12
|
||||
#endif
|
||||
|
||||
//////////////////////////////////////
|
||||
// originally _objc_isTaggedPointer //
|
||||
//////////////////////////////////////
|
||||
static BOOL flex_isTaggedPointer(const void *ptr)
|
||||
{
|
||||
return ((uintptr_t)ptr & _OBJC_TAG_MASK) == _OBJC_TAG_MASK;
|
||||
}
|
||||
|
||||
///////////////////
|
||||
// objc-config.h //
|
||||
///////////////////
|
||||
|
||||
// Define SUPPORT_INDEXED_ISA=1 on platforms that store the class in the isa
|
||||
// field as an index into a class table.
|
||||
#if __ARM_ARCH_7K__ >= 2
|
||||
# define SUPPORT_INDEXED_ISA 1
|
||||
#else
|
||||
# define SUPPORT_INDEXED_ISA 0
|
||||
#endif
|
||||
|
||||
// Define SUPPORT_PACKED_ISA=1 on platforms that store the class in the isa
|
||||
// field as a maskable pointer with other data around it.
|
||||
#if (!__LP64__ || TARGET_OS_WIN32 || TARGET_OS_SIMULATOR)
|
||||
# define SUPPORT_PACKED_ISA 0
|
||||
#else
|
||||
# define SUPPORT_PACKED_ISA 1
|
||||
#endif
|
||||
|
||||
// Define SUPPORT_NONPOINTER_ISA=1 on any platform that may store something
|
||||
// in the isa field that is not a raw pointer.
|
||||
#if !SUPPORT_INDEXED_ISA && !SUPPORT_PACKED_ISA
|
||||
# define SUPPORT_NONPOINTER_ISA 0
|
||||
#else
|
||||
# define SUPPORT_NONPOINTER_ISA 1
|
||||
#endif
|
||||
|
||||
////////////////////
|
||||
// objc-private.h //
|
||||
////////////////////
|
||||
|
||||
union isa_t
|
||||
{
|
||||
isa_t() { }
|
||||
isa_t(uintptr_t value) : bits(value) { }
|
||||
|
||||
Class cls;
|
||||
uintptr_t bits;
|
||||
|
||||
#if SUPPORT_PACKED_ISA
|
||||
|
||||
// extra_rc must be the MSB-most field (so it matches carry/overflow flags)
|
||||
// nonpointer must be the LSB (fixme or get rid of it)
|
||||
// shiftcls must occupy the same bits that a real class pointer would
|
||||
// bits + RC_ONE is equivalent to extra_rc + 1
|
||||
// RC_HALF is the high bit of extra_rc (i.e. half of its range)
|
||||
|
||||
// future expansion:
|
||||
// uintptr_t fast_rr : 1; // no r/r overrides
|
||||
// uintptr_t lock : 2; // lock for atomic property, @synch
|
||||
// uintptr_t extraBytes : 1; // allocated with extra bytes
|
||||
|
||||
# if __arm64__
|
||||
# define ISA_MASK 0x0000000ffffffff8ULL
|
||||
# define ISA_MAGIC_MASK 0x000003f000000001ULL
|
||||
# define ISA_MAGIC_VALUE 0x000001a000000001ULL
|
||||
struct {
|
||||
uintptr_t nonpointer : 1;
|
||||
uintptr_t has_assoc : 1;
|
||||
uintptr_t has_cxx_dtor : 1;
|
||||
uintptr_t shiftcls : 33; // MACH_VM_MAX_ADDRESS 0x1000000000
|
||||
uintptr_t magic : 6;
|
||||
uintptr_t weakly_referenced : 1;
|
||||
uintptr_t deallocating : 1;
|
||||
uintptr_t has_sidetable_rc : 1;
|
||||
uintptr_t extra_rc : 19;
|
||||
# define RC_ONE (1ULL<<45)
|
||||
# define RC_HALF (1ULL<<18)
|
||||
};
|
||||
|
||||
# elif __x86_64__
|
||||
# define ISA_MASK 0x00007ffffffffff8ULL
|
||||
# define ISA_MAGIC_MASK 0x001f800000000001ULL
|
||||
# define ISA_MAGIC_VALUE 0x001d800000000001ULL
|
||||
struct {
|
||||
uintptr_t nonpointer : 1;
|
||||
uintptr_t has_assoc : 1;
|
||||
uintptr_t has_cxx_dtor : 1;
|
||||
uintptr_t shiftcls : 44; // MACH_VM_MAX_ADDRESS 0x7fffffe00000
|
||||
uintptr_t magic : 6;
|
||||
uintptr_t weakly_referenced : 1;
|
||||
uintptr_t deallocating : 1;
|
||||
uintptr_t has_sidetable_rc : 1;
|
||||
uintptr_t extra_rc : 8;
|
||||
# define RC_ONE (1ULL<<56)
|
||||
# define RC_HALF (1ULL<<7)
|
||||
};
|
||||
|
||||
# else
|
||||
# error unknown architecture for packed isa
|
||||
# endif
|
||||
|
||||
// SUPPORT_PACKED_ISA
|
||||
#endif
|
||||
|
||||
|
||||
#if SUPPORT_INDEXED_ISA
|
||||
|
||||
# if __ARM_ARCH_7K__ >= 2
|
||||
|
||||
# define ISA_INDEX_IS_NPI 1
|
||||
# define ISA_INDEX_MASK 0x0001FFFC
|
||||
# define ISA_INDEX_SHIFT 2
|
||||
# define ISA_INDEX_BITS 15
|
||||
# define ISA_INDEX_COUNT (1 << ISA_INDEX_BITS)
|
||||
# define ISA_INDEX_MAGIC_MASK 0x001E0001
|
||||
# define ISA_INDEX_MAGIC_VALUE 0x001C0001
|
||||
struct {
|
||||
uintptr_t nonpointer : 1;
|
||||
uintptr_t has_assoc : 1;
|
||||
uintptr_t indexcls : 15;
|
||||
uintptr_t magic : 4;
|
||||
uintptr_t has_cxx_dtor : 1;
|
||||
uintptr_t weakly_referenced : 1;
|
||||
uintptr_t deallocating : 1;
|
||||
uintptr_t has_sidetable_rc : 1;
|
||||
uintptr_t extra_rc : 7;
|
||||
# define RC_ONE (1ULL<<25)
|
||||
# define RC_HALF (1ULL<<6)
|
||||
};
|
||||
|
||||
# else
|
||||
# error unknown architecture for indexed isa
|
||||
# endif
|
||||
|
||||
// SUPPORT_INDEXED_ISA
|
||||
#endif
|
||||
|
||||
};
|
||||
|
||||
///////////////////
|
||||
// objc-object.h //
|
||||
///////////////////
|
||||
|
||||
////////////////////////////////////////////////
|
||||
// originally objc_object::isExtTaggedPointer //
|
||||
////////////////////////////////////////////////
|
||||
static BOOL flex_isExtTaggedPointer(const void *ptr)
|
||||
{
|
||||
return ((uintptr_t)ptr & _OBJC_TAG_EXT_MASK) == _OBJC_TAG_EXT_MASK;
|
||||
}
|
||||
|
||||
struct flex_objc_object {
|
||||
isa_t isa;
|
||||
};
|
||||
|
||||
//////////////////////////////////////////////////////////
|
||||
// Returns nil on platforms without nonpointer isa. //
|
||||
// Supporting those platforms would be too complicated //
|
||||
// for such a niche feature anyway. - @NSExceptional //
|
||||
// //
|
||||
// Code modified from objc_object::ISA() on 11/04/18 //
|
||||
//////////////////////////////////////////////////////////
|
||||
static id flex_getIsa(const flex_objc_object *object) {
|
||||
#if SUPPORT_NONPOINTER_ISA
|
||||
if (object->isa.nonpointer) {
|
||||
return object_getClass((__bridge id)object);
|
||||
}
|
||||
return (__bridge Class)(void *)object->isa.bits;
|
||||
#else
|
||||
return nil;
|
||||
#endif
|
||||
}
|
||||
|
||||
/////////////////////////////////////
|
||||
// FLEXObjectInternal //
|
||||
// No Apple code beyond this point //
|
||||
/////////////////////////////////////
|
||||
|
||||
extern "C" {
|
||||
/// Assumes memory is valid and readable.
|
||||
/// https://blog.timac.org/2016/1124-testing-if-an-arbitrary-pointer-is-a-valid-objective-c-object/
|
||||
BOOL FLEXPointerIsValidObjcObject(const void * ptr)
|
||||
{
|
||||
uintptr_t pointer = (uintptr_t)ptr;
|
||||
|
||||
if (!ptr) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
// Tagged pointers have 0x1 set, no other valid pointers do
|
||||
// objc-internal.h -> _objc_isTaggedPointer()
|
||||
if (flex_isTaggedPointer(ptr) || flex_isExtTaggedPointer(ptr)) {
|
||||
return YES;
|
||||
}
|
||||
|
||||
// Check pointer alignment
|
||||
if ((pointer % sizeof(uintptr_t)) != 0) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
// From LLDB:
|
||||
// Pointers in a class_t will only have bits 0 through 46 set,
|
||||
// so if any pointer has bits 47 through 63 high, we know that this is not a valid isa
|
||||
// http://llvm.org/svn/llvm-project/lldb/trunk/examples/summaries/cocoa/objc_runtime.py
|
||||
if ((pointer & 0xFFFF800000000000) != 0) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
// http://www.sealiesoftware.com/blog/archive/2013/09/24/objc_explain_Non-pointer_isa.html :
|
||||
if (flex_getIsa((const flex_objc_object *)ptr)) {
|
||||
return YES;
|
||||
}
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -18,6 +18,7 @@
|
||||
+ (UIImage *)listIcon;
|
||||
+ (UIImage *)moveIcon;
|
||||
+ (UIImage *)selectIcon;
|
||||
+ (UIImage *)checkerPattern;
|
||||
|
||||
+ (UIImage *)jsonIcon;
|
||||
+ (UIImage *)textPlainIcon;
|
||||
|
||||
File diff suppressed because one or more lines are too long
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user