Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4f8b6c05cb |
@@ -1,27 +0,0 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Report a bug in FLEX
|
||||
title: ''
|
||||
labels: bug
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
### Environment
|
||||
- Platform+version: **iOS 14** <!--- Change to match your platform and version -->
|
||||
- FLEX version: **9.9.9** <!--- Change to the version of FLEX you're using -->
|
||||
<!--- FLEXing / libFLEX users: please include FLEXing and libFLEX versions separately -->
|
||||
|
||||
### Bug Report
|
||||
|
||||
Here, you can provide a description of the bug. Some tips:
|
||||
|
||||
- Please do not paste an entire crash log. Upload the crash log to something like [ghostbin.co](https://ghostbin.co/) or another paste service. Alternatively, you can cut out the relevant stack trace and paste that inside a ` ```code block``` `
|
||||
- If the bug is more complex than "this button is broken" or a crash, consider including a sample project. For example, if your app's requests aren't showing up in the network history page.
|
||||
- Providing steps to reproduce is always helpful!
|
||||
- If you want to include a screenshot or GIF, consider modifying the default markdown for uploaded images to use this code to make the image smaller on desktop:
|
||||
```
|
||||
<img width="50%" src=your-image-url >
|
||||
```
|
||||
|
||||
This template is a suggestion. You may format your issue however you want, but generally you should at least include your iOS version and FLEX version.
|
||||
@@ -1,10 +0,0 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest a new feature for FLEX
|
||||
title: ''
|
||||
labels: enhancement
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -36,13 +36,10 @@
|
||||
self.waitingToAddTab = YES;
|
||||
|
||||
// Add gesture to reveal toolbar if hidden
|
||||
UITapGestureRecognizer *navbarTapGesture = [[UITapGestureRecognizer alloc]
|
||||
self.navigationBar.userInteractionEnabled = YES;
|
||||
[self.navigationBar addGestureRecognizer:[[UITapGestureRecognizer alloc]
|
||||
initWithTarget:self action:@selector(handleNavigationBarTap:)
|
||||
];
|
||||
|
||||
// Don't cancel touches to work around bug on versions of iOS prior to 13
|
||||
navbarTapGesture.cancelsTouchesInView = NO;
|
||||
[self.navigationBar addGestureRecognizer:navbarTapGesture];
|
||||
]];
|
||||
|
||||
// Add gesture to dismiss if not presented with a sheet style
|
||||
if (@available(iOS 13, *)) {
|
||||
|
||||
@@ -124,15 +124,18 @@ CGFloat const kFLEXDebounceForExpensiveIO = 0.5;
|
||||
_showsCarousel = showsCarousel;
|
||||
|
||||
if (showsCarousel) {
|
||||
_carousel = ({ weakify(self)
|
||||
|
||||
_carousel = ({
|
||||
__weak __typeof(self) weakSelf = self;
|
||||
|
||||
FLEXScopeCarousel *carousel = [FLEXScopeCarousel new];
|
||||
carousel.selectedIndexChangedAction = ^(NSInteger idx) { strongify(self);
|
||||
carousel.selectedIndexChangedAction = ^(NSInteger idx) {
|
||||
__typeof(self) self = weakSelf;
|
||||
[self.searchDelegate updateSearchResults:self.searchText];
|
||||
};
|
||||
|
||||
// UITableView won't update the header size unless you reset the header view
|
||||
[carousel registerBlockForDynamicTypeChanges:^(FLEXScopeCarousel *_) { strongify(self);
|
||||
[carousel registerBlockForDynamicTypeChanges:^(FLEXScopeCarousel *carousel) {
|
||||
__typeof(self) self = weakSelf;
|
||||
[self layoutTableHeaderIfNeeded];
|
||||
}];
|
||||
|
||||
|
||||
@@ -77,7 +77,7 @@
|
||||
self.selectionIndicatorStripe.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
|
||||
UIView *superview = self.contentView;
|
||||
[self.titleLabel flex_pinEdgesToSuperviewWithInsets:UIEdgeInsetsMake(10, 15, 8 + stripeHeight, 15)];
|
||||
[self.titleLabel pinEdgesToSuperviewWithInsets:UIEdgeInsetsMake(10, 15, 8 + stripeHeight, 15)];
|
||||
|
||||
[self.selectionIndicatorStripe.leadingAnchor constraintEqualToAnchor:superview.leadingAnchor].active = YES;
|
||||
[self.selectionIndicatorStripe.bottomAnchor constraintEqualToAnchor:superview.bottomAnchor].active = YES;
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
#import "FLEXScopeCarousel.h"
|
||||
#import "FLEXCarouselCell.h"
|
||||
#import "FLEXColor.h"
|
||||
#import "FLEXMacros.h"
|
||||
#import "UIView+FLEX_Layout.h"
|
||||
|
||||
const CGFloat kCarouselItemSpacing = 0;
|
||||
@@ -73,14 +72,15 @@ NSString * const kCarouselCellReuseIdentifier = @"kCarouselCellReuseIdentifier";
|
||||
self.sizingCell.title = @"NSObject";
|
||||
|
||||
// Dynamic type
|
||||
weakify(self);
|
||||
__weak __typeof(self) weakSelf = self;
|
||||
_dynamicTypeObserver = [NSNotificationCenter.defaultCenter
|
||||
addObserverForName:UIContentSizeCategoryDidChangeNotification
|
||||
object:nil queue:nil usingBlock:^(NSNotification *note) { strongify(self)
|
||||
object:nil queue:nil usingBlock:^(NSNotification *note) {
|
||||
[self.collectionView setNeedsLayout];
|
||||
[self setNeedsUpdateConstraints];
|
||||
|
||||
// Notify observers
|
||||
__typeof(self) self = weakSelf;
|
||||
for (void (^block)(FLEXScopeCarousel *) in self.dynamicTypeHandlers) {
|
||||
block(self);
|
||||
}
|
||||
@@ -118,7 +118,7 @@ NSString * const kCarouselCellReuseIdentifier = @"kCarouselCellReuseIdentifier";
|
||||
- (void)updateConstraints {
|
||||
if (!self.constraintsInstalled) {
|
||||
self.collectionView.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
[self.collectionView flex_pinEdgesToSuperview];
|
||||
[self.collectionView pinEdgesToSuperview];
|
||||
|
||||
self.constraintsInstalled = YES;
|
||||
}
|
||||
|
||||
@@ -25,8 +25,7 @@
|
||||
///
|
||||
/// 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:(UINavigationController *(^)(void))future
|
||||
completion:(void (^)(void))completion;
|
||||
- (void)toggleToolWithViewControllerProvider:(UINavigationController *(^)(void))future completion:(void(^)(void))completion;
|
||||
|
||||
// Keyboard shortcut helpers
|
||||
|
||||
|
||||
@@ -45,9 +45,6 @@ typedef NS_ENUM(NSUInteger, FLEXExplorerMode) {
|
||||
/// Only valid while a toolbar drag pan gesture is in progress.
|
||||
@property (nonatomic) CGRect toolbarFrameBeforeDragging;
|
||||
|
||||
/// Only valid while a selected view pan gesture is in progress.
|
||||
@property (nonatomic) CGFloat selectedViewLastPanX;
|
||||
|
||||
/// Borders of all the visible views in the hierarchy at the selection point.
|
||||
/// The keys are NSValues with the corresponding view (nonretained).
|
||||
@property (nonatomic) NSDictionary<NSValue *, UIView *> *outlineViewsForVisibleViews;
|
||||
@@ -61,9 +58,6 @@ typedef NS_ENUM(NSUInteger, FLEXExplorerMode) {
|
||||
/// A colored transparent overlay to indicate that the view is selected.
|
||||
@property (nonatomic) UIView *selectedViewOverlay;
|
||||
|
||||
/// Used to actuate changes in view selection on iOS 10+
|
||||
@property (nonatomic, readonly) UISelectionFeedbackGenerator *selectionFBG API_AVAILABLE(ios(10.0));
|
||||
|
||||
/// self.view.window as a \c FLEXWindow
|
||||
@property (nonatomic, readonly) FLEXWindow *window;
|
||||
|
||||
@@ -124,11 +118,6 @@ typedef NS_ENUM(NSUInteger, FLEXExplorerMode) {
|
||||
self.movePanGR = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handleMovePan:)];
|
||||
self.movePanGR.enabled = self.currentMode == FLEXExplorerModeMove;
|
||||
[self.view addGestureRecognizer:self.movePanGR];
|
||||
|
||||
// Feedback
|
||||
if (@available(iOS 10.0, *)) {
|
||||
_selectionFBG = [UISelectionFeedbackGenerator new];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)viewWillAppear:(BOOL)animated {
|
||||
@@ -461,16 +450,16 @@ typedef NS_ENUM(NSUInteger, FLEXExplorerMode) {
|
||||
];
|
||||
[toolbar.selectedViewDescriptionContainer addGestureRecognizer:self.detailsTapGR];
|
||||
// Swipe gestures for selecting deeper / higher views at a point
|
||||
UIPanGestureRecognizer *leftSwipe = [[UIPanGestureRecognizer alloc]
|
||||
UISwipeGestureRecognizer *leftSwipe = [[UISwipeGestureRecognizer alloc]
|
||||
initWithTarget:self action:@selector(handleChangeViewAtPointGesture:)
|
||||
];
|
||||
// UIPanGestureRecognizer *rightSwipe = [[UIPanGestureRecognizer alloc]
|
||||
// initWithTarget:self action:@selector(handleChangeViewAtPointGesture:)
|
||||
// ];
|
||||
// leftSwipe.direction = UISwipeGestureRecognizerDirectionLeft;
|
||||
// rightSwipe.direction = UISwipeGestureRecognizerDirectionRight;
|
||||
UISwipeGestureRecognizer *rightSwipe = [[UISwipeGestureRecognizer alloc]
|
||||
initWithTarget:self action:@selector(handleChangeViewAtPointGesture:)
|
||||
];
|
||||
leftSwipe.direction = UISwipeGestureRecognizerDirectionLeft;
|
||||
rightSwipe.direction = UISwipeGestureRecognizerDirectionRight;
|
||||
[toolbar.selectedViewDescriptionContainer addGestureRecognizer:leftSwipe];
|
||||
// [toolbar.selectedViewDescriptionContainer addGestureRecognizer:rightSwipe];
|
||||
[toolbar.selectedViewDescriptionContainer addGestureRecognizer:rightSwipe];
|
||||
|
||||
// Long press gesture to present tabs manager
|
||||
[toolbar.globalsItem addGestureRecognizer:[[UILongPressGestureRecognizer alloc]
|
||||
@@ -609,54 +598,19 @@ typedef NS_ENUM(NSUInteger, FLEXExplorerMode) {
|
||||
}
|
||||
}
|
||||
|
||||
- (void)handleChangeViewAtPointGesture:(UIPanGestureRecognizer *)sender {
|
||||
- (void)handleChangeViewAtPointGesture:(UISwipeGestureRecognizer *)sender {
|
||||
NSInteger max = self.viewsAtTapPoint.count - 1;
|
||||
NSInteger currentIdx = [self.viewsAtTapPoint indexOfObject:self.selectedView];
|
||||
CGFloat locationX = [sender locationInView:self.view].x;
|
||||
|
||||
// Track the pan gesture: every N points we move along the X axis,
|
||||
// actuate some haptic feedback and move up or down the hierarchy.
|
||||
// We only store the "last" location when we've met the threshold.
|
||||
// We only change the view and actuate feedback if the view selection
|
||||
// changes; that is, as long as we don't go outside or under the array.
|
||||
switch (sender.state) {
|
||||
case UIGestureRecognizerStateBegan: {
|
||||
self.selectedViewLastPanX = locationX;
|
||||
switch (sender.direction) {
|
||||
case UISwipeGestureRecognizerDirectionLeft:
|
||||
self.selectedView = self.viewsAtTapPoint[MIN(max, currentIdx + 1)];
|
||||
break;
|
||||
}
|
||||
case UIGestureRecognizerStateChanged: {
|
||||
static CGFloat kNextLevelThreshold = 20.f;
|
||||
CGFloat lastX = self.selectedViewLastPanX;
|
||||
NSInteger newSelection = currentIdx;
|
||||
|
||||
// Left, go down the hierarchy
|
||||
if (locationX < lastX && (lastX - locationX) >= kNextLevelThreshold) {
|
||||
// Choose a new view index up to the max index
|
||||
newSelection = MIN(max, currentIdx + 1);
|
||||
self.selectedViewLastPanX = locationX;
|
||||
}
|
||||
// Right, go up the hierarchy
|
||||
else if (lastX < locationX && (locationX - lastX) >= kNextLevelThreshold) {
|
||||
// Choose a new view index down to the min index
|
||||
newSelection = MAX(0, currentIdx - 1);
|
||||
self.selectedViewLastPanX = locationX;
|
||||
}
|
||||
|
||||
if (currentIdx != newSelection) {
|
||||
self.selectedView = self.viewsAtTapPoint[newSelection];
|
||||
[self actuateSelectionChangedFeedback];
|
||||
}
|
||||
|
||||
case UISwipeGestureRecognizerDirectionRight:
|
||||
self.selectedView = self.viewsAtTapPoint[MAX(0, currentIdx - 1)];
|
||||
break;
|
||||
}
|
||||
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)actuateSelectionChangedFeedback {
|
||||
if (@available(iOS 10.0, *)) {
|
||||
[self.selectionFBG selectionChanged];
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -918,12 +872,13 @@ typedef NS_ENUM(NSUInteger, FLEXExplorerMode) {
|
||||
[super dismissViewControllerAnimated:animated completion:completion];
|
||||
}
|
||||
|
||||
- (BOOL)wantsWindowToBecomeKey {
|
||||
- (BOOL)wantsWindowToBecomeKey
|
||||
{
|
||||
return self.window.previousKeyWindow != nil;
|
||||
}
|
||||
|
||||
- (void)toggleToolWithViewControllerProvider:(UINavigationController *(^)(void))future
|
||||
completion:(void (^)(void))completion {
|
||||
completion:(void(^)(void))completion {
|
||||
if (self.presentedViewController) {
|
||||
[self dismissViewControllerAnimated:YES completion:completion];
|
||||
} else if (future) {
|
||||
@@ -969,7 +924,11 @@ typedef NS_ENUM(NSUInteger, FLEXExplorerMode) {
|
||||
} else {
|
||||
return [FLEXHierarchyViewController delegate:self];
|
||||
}
|
||||
} completion:completion];
|
||||
} completion:^{
|
||||
if (completion) {
|
||||
completion();
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)toggleMenuTool {
|
||||
|
||||
@@ -12,11 +12,16 @@
|
||||
#import <FLEX/CALayer+FLEX.h>
|
||||
#import <FLEX/UIFont+FLEX.h>
|
||||
#import <FLEX/UIGestureRecognizer+Blocks.h>
|
||||
#import <FLEX/UIView+FLEX_Layout.h>
|
||||
#import <FLEX/UIPasteboard+FLEX.h>
|
||||
#import <FLEX/UIMenu+FLEX.h>
|
||||
#import <FLEX/UITextField+Range.h>
|
||||
|
||||
#import <FLEX/NSObject+FLEX_Reflection.h>
|
||||
#import <FLEX/NSArray+FLEX.h>
|
||||
#import <FLEX/NSDictionary+ObjcRuntime.h>
|
||||
#import <FLEX/NSString+ObjcRuntime.h>
|
||||
#import <FLEX/NSString+FLEX.h>
|
||||
#import <FLEX/NSUserDefaults+FLEX.h>
|
||||
#import <FLEX/NSMapTable+FLEX_Subscripting.h>
|
||||
#import <FLEX/NSTimer+FLEX.h>
|
||||
|
||||
@@ -45,15 +45,6 @@ static const NSInteger kFLEXLiveObjectsSortBySizeIndex = 2;
|
||||
[self reloadTableData];
|
||||
}
|
||||
|
||||
- (void)viewDidAppear:(BOOL)animated {
|
||||
[super viewDidAppear:animated];
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
// This doesn't work unless it's wrapped in this dispatch_async call
|
||||
[self.searchController.searchBar becomeFirstResponder];
|
||||
});
|
||||
}
|
||||
|
||||
- (NSArray<NSString *> *)allClassNames {
|
||||
return self.instanceCountsForClassNames.allKeys;
|
||||
}
|
||||
|
||||
@@ -20,21 +20,7 @@
|
||||
#import <malloc/malloc.h>
|
||||
|
||||
|
||||
typedef NS_ENUM(NSUInteger, FLEXObjectReferenceSection) {
|
||||
FLEXObjectReferenceSectionMain,
|
||||
FLEXObjectReferenceSectionAutoLayout,
|
||||
FLEXObjectReferenceSectionKVO,
|
||||
FLEXObjectReferenceSectionFLEX,
|
||||
|
||||
FLEXObjectReferenceSectionCount
|
||||
};
|
||||
|
||||
@interface FLEXObjectListViewController ()
|
||||
|
||||
@property (nonatomic, readonly, class) NSArray<NSPredicate *> *defaultPredicates;
|
||||
@property (nonatomic, readonly, class) NSArray<NSString *> *defaultSectionTitles;
|
||||
|
||||
|
||||
@property (nonatomic, copy) NSArray<FLEXMutableListSection *> *sections;
|
||||
@property (nonatomic, copy) NSArray<FLEXMutableListSection *> *allSections;
|
||||
|
||||
@@ -52,7 +38,7 @@ typedef NS_ENUM(NSUInteger, FLEXObjectReferenceSection) {
|
||||
+ (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(^isKVORelated)(FLEXObjectRef *, NSDictionary *) = ^BOOL(FLEXObjectRef *ref, NSDictionary *bindings) {
|
||||
BOOL(^isObserver)(FLEXObjectRef *, NSDictionary *) = ^BOOL(FLEXObjectRef *ref, NSDictionary *bindings) {
|
||||
NSString *row = ref.reference;
|
||||
return [row isEqualToString:@"__NSObserver object"] ||
|
||||
[row isEqualToString:@"_CFXNotificationObjcObserverRegistration _object"];
|
||||
@@ -79,44 +65,28 @@ typedef NS_ENUM(NSUInteger, FLEXObjectReferenceSection) {
|
||||
([row hasPrefix:@"_NSAutoresizingMask"] && [row hasSuffix:@" _referenceItem"]) ||
|
||||
[ignored containsObject:row];
|
||||
};
|
||||
|
||||
/// These are FLEX classes and usually you aren't looking for FLEX references inside FLEX itself
|
||||
BOOL(^isFLEXClass)(FLEXObjectRef *, NSDictionary *) = ^BOOL(FLEXObjectRef *ref, NSDictionary *bindings) {
|
||||
return [ref.reference hasPrefix:@"FLEX"];
|
||||
};
|
||||
|
||||
BOOL(^isEssential)(FLEXObjectRef *, NSDictionary *) = ^BOOL(FLEXObjectRef *ref, NSDictionary *bindings) {
|
||||
return !(
|
||||
isKVORelated(ref, bindings) ||
|
||||
isConstraintRelated(ref, bindings) ||
|
||||
isFLEXClass(ref, bindings)
|
||||
);
|
||||
return !(isObserver(ref, bindings) || isConstraintRelated(ref, bindings));
|
||||
};
|
||||
|
||||
switch (section) {
|
||||
case FLEXObjectReferenceSectionMain:
|
||||
return [NSPredicate predicateWithBlock:isEssential];
|
||||
case FLEXObjectReferenceSectionAutoLayout:
|
||||
return [NSPredicate predicateWithBlock:isConstraintRelated];
|
||||
case FLEXObjectReferenceSectionKVO:
|
||||
return [NSPredicate predicateWithBlock:isKVORelated];
|
||||
case FLEXObjectReferenceSectionFLEX:
|
||||
return [NSPredicate predicateWithBlock:isFLEXClass];
|
||||
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 [NSArray flex_forEachUpTo:FLEXObjectReferenceSectionCount map:^id(NSUInteger i) {
|
||||
return [self defaultPredicateForSection:i];
|
||||
}];
|
||||
return @[[self defaultPredicateForSection:0],
|
||||
[self defaultPredicateForSection:1],
|
||||
[self defaultPredicateForSection:2]];
|
||||
}
|
||||
|
||||
+ (NSArray<NSString *> *)defaultSectionTitles {
|
||||
return @[
|
||||
@"", @"AutoLayout", @"Key-Value Observing", @"FLEX"
|
||||
];
|
||||
return @[@"", @"AutoLayout", @"Trivial"];
|
||||
}
|
||||
|
||||
|
||||
@@ -214,18 +184,19 @@ typedef NS_ENUM(NSUInteger, FLEXObjectReferenceSection) {
|
||||
}
|
||||
}
|
||||
|
||||
free(ivars);
|
||||
tryClass = class_getSuperclass(tryClass);
|
||||
}
|
||||
}];
|
||||
|
||||
NSArray<NSPredicate *> *predicates = [self defaultPredicates];
|
||||
NSArray<NSString *> *sectionTitles = [self defaultSectionTitles];
|
||||
FLEXObjectListViewController *viewController = [[self alloc]
|
||||
initWithReferences:instances
|
||||
predicates:self.defaultPredicates
|
||||
sectionTitles:self.defaultSectionTitles
|
||||
predicates:predicates
|
||||
sectionTitles:sectionTitles
|
||||
];
|
||||
viewController.title = [NSString stringWithFormat:@"Referencing %@ %p",
|
||||
[FLEXRuntimeUtility safeClassNameForObject:object], object
|
||||
NSStringFromClass(object_getClass(object)), object
|
||||
];
|
||||
return viewController;
|
||||
}
|
||||
@@ -260,7 +231,7 @@ typedef NS_ENUM(NSUInteger, FLEXObjectReferenceSection) {
|
||||
}];
|
||||
}
|
||||
|
||||
- (FLEXMutableListSection *)makeSection:(NSArray *)rows title:(NSString *)title { weakify(self)
|
||||
- (FLEXMutableListSection *)makeSection:(NSArray *)rows title:(NSString *)title {
|
||||
FLEXMutableListSection *section = [FLEXMutableListSection list:rows
|
||||
cellConfiguration:^(FLEXTableViewCell *cell, FLEXObjectRef *ref, NSInteger row) {
|
||||
cell.textLabel.text = ref.reference;
|
||||
@@ -275,10 +246,14 @@ typedef NS_ENUM(NSUInteger, FLEXObjectReferenceSection) {
|
||||
}
|
||||
];
|
||||
|
||||
section.selectionHandler = ^(UIViewController *host, FLEXObjectRef *ref) { strongify(self)
|
||||
[self.navigationController pushViewController:[
|
||||
FLEXObjectExplorerFactory explorerViewControllerForObject:ref.object
|
||||
] animated:YES];
|
||||
__weak __typeof(self) weakSelf = self;
|
||||
section.selectionHandler = ^(__kindof UIViewController *host, FLEXObjectRef *ref) {
|
||||
__strong __typeof(self) strongSelf = weakSelf;
|
||||
if (strongSelf) {
|
||||
[strongSelf.navigationController pushViewController:[
|
||||
FLEXObjectExplorerFactory explorerViewControllerForObject:ref.object
|
||||
] animated:YES];
|
||||
}
|
||||
};
|
||||
|
||||
section.customTitle = title;
|
||||
|
||||
@@ -47,7 +47,7 @@
|
||||
_object = object;
|
||||
_wantsSummary = showSummary;
|
||||
|
||||
NSString *class = [FLEXRuntimeUtility safeClassNameForObject:object];
|
||||
NSString *class = NSStringFromClass(object_getClass(object));
|
||||
if (ivar) {
|
||||
_reference = [NSString stringWithFormat:@"%@ %@", class, ivar];
|
||||
} else if (showSummary) {
|
||||
|
||||
@@ -54,8 +54,9 @@ typedef NS_ENUM(NSUInteger, FLEXFileBrowserSortAttribute) {
|
||||
self.title = [path lastPathComponent];
|
||||
self.operationQueue = [NSOperationQueue new];
|
||||
|
||||
// Compute path size
|
||||
weakify(self)
|
||||
|
||||
//computing path size
|
||||
FLEXFileBrowserController *__weak weakSelf = self;
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||
NSFileManager *fileManager = NSFileManager.defaultManager;
|
||||
NSDictionary<NSString *, id> *attributes = [fileManager attributesOfItemAtPath:path error:NULL];
|
||||
@@ -65,15 +66,16 @@ typedef NS_ENUM(NSUInteger, FLEXFileBrowserSortAttribute) {
|
||||
attributes = [fileManager attributesOfItemAtPath:[path stringByAppendingPathComponent:fileName] error:NULL];
|
||||
totalSize += [attributes fileSize];
|
||||
|
||||
// Bail if the interested view controller has gone away
|
||||
if (!self) {
|
||||
// Bail if the interested view controller has gone away.
|
||||
if (!weakSelf) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{ strongify(self)
|
||||
self.recursiveSize = @(totalSize);
|
||||
[self.tableView reloadData];
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
FLEXFileBrowserController *__strong strongSelf = weakSelf;
|
||||
strongSelf.recursiveSize = @(totalSize);
|
||||
[strongSelf.tableView reloadData];
|
||||
});
|
||||
});
|
||||
|
||||
@@ -358,41 +360,38 @@ typedef NS_ENUM(NSUInteger, FLEXFileBrowserSortAttribute) {
|
||||
|
||||
#if FLEX_AT_LEAST_IOS13_SDK
|
||||
|
||||
- (UIContextMenuConfiguration *)tableView:(UITableView *)tableView
|
||||
contextMenuConfigurationForRowAtIndexPath:(NSIndexPath *)indexPath
|
||||
point:(CGPoint)point __IOS_AVAILABLE(13.0) {
|
||||
weakify(self)
|
||||
return [UIContextMenuConfiguration configurationWithIdentifier:nil previewProvider:nil
|
||||
actionProvider:^UIMenu *(NSArray<UIMenuElement *> *suggestedActions) {
|
||||
UITableViewCell * const cell = [tableView cellForRowAtIndexPath:indexPath];
|
||||
UIAction *rename = [UIAction actionWithTitle:@"Rename" image:nil identifier:@"Rename"
|
||||
handler:^(UIAction *action) { strongify(self)
|
||||
[self fileBrowserRename:cell];
|
||||
}
|
||||
];
|
||||
UIAction *delete = [UIAction actionWithTitle:@"Delete" image:nil identifier:@"Delete"
|
||||
handler:^(UIAction *action) { strongify(self)
|
||||
[self fileBrowserDelete:cell];
|
||||
}
|
||||
];
|
||||
UIAction *copyPath = [UIAction actionWithTitle:@"Copy Path" image:nil identifier:@"Copy Path"
|
||||
handler:^(UIAction *action) { strongify(self)
|
||||
[self fileBrowserCopyPath:cell];
|
||||
}
|
||||
];
|
||||
UIAction *share = [UIAction actionWithTitle:@"Share" image:nil identifier:@"Share"
|
||||
handler:^(UIAction *action) { strongify(self)
|
||||
[self fileBrowserShare:cell];
|
||||
}
|
||||
];
|
||||
|
||||
return [UIMenu menuWithTitle:@"Manage File" image:nil
|
||||
identifier:@"Manage File"
|
||||
options:UIMenuOptionsDisplayInline
|
||||
children:@[rename, delete, copyPath, share]
|
||||
];
|
||||
}
|
||||
];
|
||||
- (UIContextMenuConfiguration *)tableView:(UITableView *)tableView contextMenuConfigurationForRowAtIndexPath:(NSIndexPath *)indexPath point:(CGPoint)point __IOS_AVAILABLE(13.0) {
|
||||
__weak __typeof__(self) weakSelf = self;
|
||||
return [UIContextMenuConfiguration configurationWithIdentifier:nil
|
||||
previewProvider:nil
|
||||
actionProvider:^UIMenu * _Nullable(NSArray<UIMenuElement *> * _Nonnull suggestedActions) {
|
||||
UITableViewCell * const cell = [tableView cellForRowAtIndexPath:indexPath];
|
||||
UIAction *rename = [UIAction actionWithTitle:@"Rename"
|
||||
image:nil
|
||||
identifier:@"Rename"
|
||||
handler:^(__kindof UIAction * _Nonnull action) {
|
||||
[weakSelf fileBrowserRename:cell];
|
||||
}];
|
||||
UIAction *delete = [UIAction actionWithTitle:@"Delete"
|
||||
image:nil
|
||||
identifier:@"Delete"
|
||||
handler:^(__kindof UIAction * _Nonnull action) {
|
||||
[weakSelf fileBrowserDelete:cell];
|
||||
}];
|
||||
UIAction *copyPath = [UIAction actionWithTitle:@"Copy Path"
|
||||
image:nil
|
||||
identifier:@"Copy Path"
|
||||
handler:^(__kindof UIAction * _Nonnull action) {
|
||||
[weakSelf fileBrowserCopyPath:cell];
|
||||
}];
|
||||
UIAction *share = [UIAction actionWithTitle:@"Share"
|
||||
image:nil
|
||||
identifier:@"Share"
|
||||
handler:^(__kindof UIAction * _Nonnull action) {
|
||||
[weakSelf fileBrowserShare:cell];
|
||||
}];
|
||||
return [UIMenu menuWithTitle:@"Manage File" image:nil identifier:@"Manage File" options:UIMenuOptionsDisplayInline children:@[rename, delete, copyPath, share]];
|
||||
}];
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -170,7 +170,7 @@
|
||||
|
||||
- (NSString *)password {
|
||||
if (self.passwordData.length) {
|
||||
return [[NSString alloc] initWithData:self.passwordData encoding:NSUTF8StringEncoding];
|
||||
return [NSString stringWithCString:self.passwordData.bytes encoding:NSUTF8StringEncoding];
|
||||
}
|
||||
|
||||
return nil;
|
||||
|
||||
@@ -30,10 +30,10 @@
|
||||
- (void)viewDidLoad {
|
||||
[super viewDidLoad];
|
||||
|
||||
[self addToolbarItems:@[
|
||||
FLEXBarButtonItemSystem(Add, self, @selector(addPressed)),
|
||||
[FLEXBarButtonItemSystem(Trash, self, @selector(trashPressed:)) flex_withTintColor:UIColor.redColor],
|
||||
]];
|
||||
self.navigationItem.rightBarButtonItems = @[
|
||||
[UIBarButtonItem flex_systemItem:UIBarButtonSystemItemTrash target:self action:@selector(trashPressed:)],
|
||||
[UIBarButtonItem flex_systemItem:UIBarButtonSystemItemAdd target:self action:@selector(addPressed)],
|
||||
];
|
||||
|
||||
[self reloadData];
|
||||
}
|
||||
@@ -43,15 +43,14 @@
|
||||
cellConfiguration:^(__kindof FLEXTableViewCell *cell, NSDictionary *item, NSInteger row) {
|
||||
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
|
||||
|
||||
id service = item[kFLEXKeychainWhereKey];
|
||||
if ([service isKindOfClass:[NSString class]]) {
|
||||
cell.textLabel.text = service;
|
||||
cell.detailTextLabel.text = item[kFLEXKeychainAccountKey];
|
||||
id account = item[kFLEXKeychainAccountKey];
|
||||
if ([account isKindOfClass:[NSString class]]) {
|
||||
cell.textLabel.text = account;
|
||||
} else {
|
||||
cell.textLabel.text = [NSString stringWithFormat:
|
||||
@"[%@]\n\n%@",
|
||||
NSStringFromClass([service class]),
|
||||
[service description]
|
||||
NSStringFromClass([account class]),
|
||||
[account description]
|
||||
];
|
||||
}
|
||||
} filterMatcher:^BOOL(NSString *filterText, NSDictionary *item) {
|
||||
@@ -130,18 +129,6 @@
|
||||
make.title(@"Clear Keychain");
|
||||
make.message(@"This will remove all keychain items for this app.\n");
|
||||
make.message(@"This action cannot be undone. Are you sure?");
|
||||
make.button(@"Yes, clear the keychain").destructiveStyle().handler(^(NSArray *strings) {
|
||||
[self confirmClearKeychain];
|
||||
});
|
||||
make.button(@"Cancel").cancelStyle();
|
||||
} showFrom:self source:sender];
|
||||
}
|
||||
|
||||
- (void)confirmClearKeychain {
|
||||
[FLEXAlert makeAlert:^(FLEXAlert *make) {
|
||||
make.title(@"ARE YOU SURE?");
|
||||
make.message(@"This action CANNOT BE UNDONE.\nAre you sure you want to continue?\n");
|
||||
make.message(@"If you're sure, scroll to confirm.");
|
||||
make.button(@"Yes, clear the keychain").destructiveStyle().handler(^(NSArray *strings) {
|
||||
for (id account in self.section.list) {
|
||||
[self deleteItem:account];
|
||||
@@ -149,12 +136,8 @@
|
||||
|
||||
[self reloadData];
|
||||
});
|
||||
make.button(@"Cancel"); make.button(@"Cancel"); make.button(@"Cancel"); make.button(@"Cancel");
|
||||
make.button(@"Cancel"); make.button(@"Cancel"); make.button(@"Cancel"); make.button(@"Cancel");
|
||||
make.button(@"Cancel"); make.button(@"Cancel"); make.button(@"Cancel"); make.button(@"Cancel");
|
||||
make.button(@"Cancel"); make.button(@"Cancel"); make.button(@"Cancel"); make.button(@"Cancel");
|
||||
make.button(@"Cancel").cancelStyle();
|
||||
} showFrom:self];
|
||||
} showFrom:self source:sender];
|
||||
}
|
||||
|
||||
- (void)addPressed {
|
||||
|
||||
@@ -303,14 +303,14 @@ static inline NSString * TBWildcardMap(NSString *token, NSString *candidate, TBW
|
||||
if (options == TBWildcardOptionsAny) {
|
||||
return [[bundles flex_flatmapped:^NSArray *(NSString *bundlePath, NSUInteger idx) {
|
||||
return [self classNamesInImageAtPath:bundlePath];
|
||||
}] flex_sortedUsingSelector:@selector(caseInsensitiveCompare:)];
|
||||
}] sortedUsingSelector:@selector(caseInsensitiveCompare:)];
|
||||
}
|
||||
|
||||
return [[bundles flex_flatmapped:^NSArray *(NSString *bundlePath, NSUInteger idx) {
|
||||
return [[self classNamesInImageAtPath:bundlePath] flex_mapped:^id(NSString *className, NSUInteger idx) {
|
||||
return TBWildcardMap(query, className, options);
|
||||
}];
|
||||
}] flex_sortedUsingSelector:@selector(caseInsensitiveCompare:)];
|
||||
}] sortedUsingSelector:@selector(caseInsensitiveCompare:)];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -101,7 +101,7 @@
|
||||
|
||||
// Change "Bundle.fooba" to "Bundle.foobar."
|
||||
NSString *orig = self.delegate.searchController.searchBar.text;
|
||||
NSString *keyPath = [orig flex_stringByReplacingLastKeyPathComponent:text];
|
||||
NSString *keyPath = [orig stringByReplacingLastKeyPathComponent:text];
|
||||
self.delegate.searchController.searchBar.text = keyPath;
|
||||
|
||||
self.keyPath = [FLEXRuntimeKeyPathTokenizer tokenizeString:keyPath];
|
||||
@@ -130,7 +130,7 @@
|
||||
// Available since at least iOS 9, still present in iOS 13
|
||||
UITextField *field = [searchBar valueForKey:@"_searchBarTextField"];
|
||||
|
||||
if ([self searchBar:searchBar shouldChangeTextInRange:field.flex_selectedRange replacementText:text]) {
|
||||
if ([self searchBar:searchBar shouldChangeTextInRange:field.selectedRange replacementText:text]) {
|
||||
[field replaceRange:field.selectedTextRange withText:text];
|
||||
}
|
||||
}
|
||||
@@ -266,7 +266,7 @@
|
||||
self.filteredClasses = nil;
|
||||
}
|
||||
|
||||
self.timer = [NSTimer flex_fireSecondsFromNow:0.15 block:^{
|
||||
self.timer = [NSTimer fireSecondsFromNow:0.15 block:^{
|
||||
[self updateTable];
|
||||
}];
|
||||
}
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
#import "FLEXTableView.h"
|
||||
#import "FLEXObjectExplorerFactory.h"
|
||||
#import "FLEXAlert.h"
|
||||
#import "FLEXRuntimeClient.h"
|
||||
|
||||
@interface FLEXObjcRuntimeViewController () <FLEXKeyPathSearchControllerDelegate>
|
||||
|
||||
@@ -29,20 +28,6 @@
|
||||
- (void)viewDidLoad {
|
||||
[super viewDidLoad];
|
||||
|
||||
// Long press on navigation bar to initialize webkit legacy
|
||||
//
|
||||
// We call initializeWebKitLegacy automatically before you search
|
||||
// all bundles just to be safe (since touching some classes before
|
||||
// WebKit is initialized will initialize it on a thread other than
|
||||
// the main thread), but sometimes you can encounter this crash
|
||||
// without searching through all bundles, of course.
|
||||
[self.navigationController.navigationBar addGestureRecognizer:[
|
||||
[UILongPressGestureRecognizer alloc]
|
||||
initWithTarget:[FLEXRuntimeClient class]
|
||||
action:@selector(initializeWebKitLegacy)
|
||||
]
|
||||
];
|
||||
|
||||
// Search bar stuff, must be first because this creates self.searchController
|
||||
self.showsSearchBar = YES;
|
||||
self.showSearchBarInitially = YES;
|
||||
|
||||
@@ -100,9 +100,10 @@ static BOOL my_os_log_shim_enabled(void *addr) {
|
||||
self.showsSearchBar = YES;
|
||||
self.showSearchBarInitially = NO;
|
||||
|
||||
weakify(self)
|
||||
id logHandler = ^(NSArray<FLEXSystemLogMessage *> *newMessages) { strongify(self)
|
||||
[self handleUpdateWithNewMessages:newMessages];
|
||||
__weak __typeof(self) weakSelf = self;
|
||||
id logHandler = ^(NSArray<FLEXSystemLogMessage *> *newMessages) {
|
||||
__strong __typeof(weakSelf) strongSelf = weakSelf;
|
||||
[strongSelf handleUpdateWithNewMessages:newMessages];
|
||||
};
|
||||
|
||||
if (FLEXOSLogAvailable() && !FLEXNSLogHookWorks) {
|
||||
@@ -136,18 +137,20 @@ static BOOL my_os_log_shim_enabled(void *addr) {
|
||||
[self.logController startMonitoring];
|
||||
}
|
||||
|
||||
- (NSArray<FLEXTableViewSection *> *)makeSections { weakify(self)
|
||||
- (NSArray<FLEXTableViewSection *> *)makeSections {
|
||||
__weak __typeof(self) weakSelf = self;
|
||||
_logMessages = [FLEXMutableListSection list:@[]
|
||||
cellConfiguration:^(FLEXSystemLogCell *cell, FLEXSystemLogMessage *message, NSInteger row) {
|
||||
strongify(self)
|
||||
|
||||
cell.logMessage = message;
|
||||
cell.highlightedText = self.filterText;
|
||||
__strong __typeof(self) strongSelf = weakSelf;
|
||||
if (strongSelf) {
|
||||
cell.logMessage = message;
|
||||
cell.highlightedText = strongSelf.filterText;
|
||||
|
||||
if (row % 2 == 0) {
|
||||
cell.backgroundColor = FLEXColor.primaryBackgroundColor;
|
||||
} else {
|
||||
cell.backgroundColor = FLEXColor.secondaryBackgroundColor;
|
||||
if (row % 2 == 0) {
|
||||
cell.backgroundColor = FLEXColor.primaryBackgroundColor;
|
||||
} else {
|
||||
cell.backgroundColor = FLEXColor.secondaryBackgroundColor;
|
||||
}
|
||||
}
|
||||
} filterMatcher:^BOOL(NSString *filterText, FLEXSystemLogMessage *message) {
|
||||
NSString *displayedText = [FLEXSystemLogCell displayedTextForLogMessage:message];
|
||||
@@ -262,30 +265,8 @@ static BOOL my_os_log_shim_enabled(void *addr) {
|
||||
- (void)tableView:(UITableView *)tableView performAction:(SEL)action forRowAtIndexPath:(NSIndexPath *)indexPath withSender:(id)sender {
|
||||
if (action == @selector(copy:)) {
|
||||
// We usually only want to copy the log message itself, not any metadata associated with it.
|
||||
UIPasteboard.generalPasteboard.string = self.logMessages.filteredList[indexPath.row].messageText ?: @"";
|
||||
UIPasteboard.generalPasteboard.string = self.logMessages.filteredList[indexPath.row].messageText;
|
||||
}
|
||||
}
|
||||
|
||||
#if FLEX_AT_LEAST_IOS13_SDK
|
||||
|
||||
- (UIContextMenuConfiguration *)tableView:(UITableView *)tableView
|
||||
contextMenuConfigurationForRowAtIndexPath:(NSIndexPath *)indexPath
|
||||
point:(CGPoint)point __IOS_AVAILABLE(13.0) {
|
||||
weakify(self)
|
||||
return [UIContextMenuConfiguration configurationWithIdentifier:nil previewProvider:nil
|
||||
actionProvider:^UIMenu *(NSArray<UIMenuElement *> *suggestedActions) {
|
||||
UIAction *copy = [UIAction actionWithTitle:@"Copy"
|
||||
image:nil
|
||||
identifier:@"Copy"
|
||||
handler:^(UIAction *action) { strongify(self)
|
||||
// We usually only want to copy the log message itself, not any metadata associated with it.
|
||||
UIPasteboard.generalPasteboard.string = self.logMessages.filteredList[indexPath.row].messageText ?: @"";
|
||||
}];
|
||||
return [UIMenu menuWithTitle:@"" image:nil identifier:nil options:UIMenuOptionsDisplayInline children:@[copy]];
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@end
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
//
|
||||
|
||||
#import "FLEXManager.h"
|
||||
#import "FLEXGlobalsEntry.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@@ -15,7 +14,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
#pragma mark - Globals Screen Entries
|
||||
|
||||
/// Adds an entry at the top of the list of Global State items.
|
||||
/// 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
|
||||
@@ -27,7 +26,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
/// you may want to use __weak references.
|
||||
- (void)registerGlobalEntryWithName:(NSString *)entryName objectFutureBlock:(id (^)(void))objectFutureBlock;
|
||||
|
||||
/// Adds an entry at the top of the list of Global State items.
|
||||
/// 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
|
||||
@@ -35,23 +34,10 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
/// @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 as needed.
|
||||
/// you may want to use __weak references.
|
||||
- (void)registerGlobalEntryWithName:(NSString *)entryName
|
||||
viewControllerFutureBlock:(UIViewController * (^)(void))viewControllerFutureBlock;
|
||||
|
||||
/// Adds an entry at the top of the list of Global State items.
|
||||
/// @param entryName The string to be displayed in the cell.
|
||||
/// @param rowSelectedAction When you tap on the row, this block will be invoked
|
||||
/// with the host table view view controller. Use it to deselect the row or present an alert.
|
||||
/// @note This method must be called from the main thread.
|
||||
/// The rowSelectedAction will be invoked from the main thread.
|
||||
/// @note The passed block will be copied and retain for the duration of the application,
|
||||
/// you may want to use __weak references as needed.
|
||||
- (void)registerGlobalEntryWithName:(NSString *)entryName action:(FLEXGlobalsEntryRowAction)rowSelectedAction;
|
||||
|
||||
/// Removes all registered global entries.
|
||||
- (void)clearGlobalEntries;
|
||||
|
||||
#pragma mark - Simulator Shortcuts
|
||||
|
||||
/// Simulator keyboard shortcuts are enabled by default.
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#import "FLEXManager+Extensibility.h"
|
||||
#import "FLEXManager+Private.h"
|
||||
#import "FLEXNavigationController.h"
|
||||
#import "FLEXGlobalsEntry.h"
|
||||
#import "FLEXObjectExplorerFactory.h"
|
||||
#import "FLEXKeyboardShortcutManager.h"
|
||||
#import "FLEXExplorerViewController.h"
|
||||
@@ -57,23 +58,6 @@
|
||||
[self.userGlobalEntries addObject:entry];
|
||||
}
|
||||
|
||||
- (void)registerGlobalEntryWithName:(NSString *)entryName action:(FLEXGlobalsEntryRowAction)rowSelectedAction {
|
||||
NSParameterAssert(entryName);
|
||||
NSParameterAssert(rowSelectedAction);
|
||||
NSAssert(NSThread.isMainThread, @"This method must be called from the main thread.");
|
||||
|
||||
entryName = entryName.copy;
|
||||
FLEXGlobalsEntry *entry = [FLEXGlobalsEntry entryWithNameFuture:^NSString * _Nonnull{
|
||||
return entryName;
|
||||
} action:rowSelectedAction];
|
||||
|
||||
[self.userGlobalEntries addObject:entry];
|
||||
}
|
||||
|
||||
- (void)clearGlobalEntries {
|
||||
[self.userGlobalEntries removeAllObjects];
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Simulator Shortcuts
|
||||
|
||||
@@ -111,49 +95,60 @@
|
||||
[self registerDefaultSimulatorShortcutWithKey:@"f" modifiers:0 action:^{
|
||||
[self toggleExplorer];
|
||||
} description:@"Toggle FLEX toolbar"];
|
||||
|
||||
|
||||
[self registerDefaultSimulatorShortcutWithKey:@"g" modifiers:0 action:^{
|
||||
[self showExplorerIfNeeded];
|
||||
[self.explorerViewController toggleMenuTool];
|
||||
} description:@"Toggle FLEX globals menu"];
|
||||
|
||||
|
||||
[self registerDefaultSimulatorShortcutWithKey:@"v" modifiers:0 action:^{
|
||||
[self showExplorerIfNeeded];
|
||||
[self.explorerViewController toggleViewsTool];
|
||||
} description:@"Toggle view hierarchy menu"];
|
||||
|
||||
|
||||
[self registerDefaultSimulatorShortcutWithKey:@"s" modifiers:0 action:^{
|
||||
[self showExplorerIfNeeded];
|
||||
[self.explorerViewController toggleSelectTool];
|
||||
} description:@"Toggle select tool"];
|
||||
|
||||
|
||||
[self registerDefaultSimulatorShortcutWithKey:@"m" modifiers:0 action:^{
|
||||
[self showExplorerIfNeeded];
|
||||
[self.explorerViewController toggleMoveTool];
|
||||
} description:@"Toggle move tool"];
|
||||
|
||||
|
||||
[self registerDefaultSimulatorShortcutWithKey:@"n" modifiers:0 action:^{
|
||||
[self toggleTopViewControllerOfClass:[FLEXNetworkMITMViewController class]];
|
||||
} description:@"Toggle network history view"];
|
||||
|
||||
|
||||
// 't' is for testing: quickly present an object explorer for debugging
|
||||
[self registerDefaultSimulatorShortcutWithKey:@"t" modifiers:0 action:^{
|
||||
[self showExplorerIfNeeded];
|
||||
|
||||
[self.explorerViewController toggleToolWithViewControllerProvider:^UINavigationController *{
|
||||
return [FLEXNavigationController withRootViewController:[FLEXObjectExplorerFactory
|
||||
explorerViewControllerForObject:NSBundle.mainBundle
|
||||
]];
|
||||
} completion:nil];
|
||||
} description:@"Present an object explorer for debugging"];
|
||||
|
||||
[self registerDefaultSimulatorShortcutWithKey:UIKeyInputDownArrow modifiers:0 action:^{
|
||||
if (self.isHidden || ![self.explorerViewController handleDownArrowKeyPressed]) {
|
||||
[self tryScrollDown];
|
||||
}
|
||||
} description:@"Cycle view selection\n\t\tMove view down\n\t\tScroll down"];
|
||||
|
||||
|
||||
[self registerDefaultSimulatorShortcutWithKey:UIKeyInputUpArrow modifiers:0 action:^{
|
||||
if (self.isHidden || ![self.explorerViewController handleUpArrowKeyPressed]) {
|
||||
[self tryScrollUp];
|
||||
}
|
||||
} description:@"Cycle view selection\n\t\tMove view up\n\t\tScroll up"];
|
||||
|
||||
|
||||
[self registerDefaultSimulatorShortcutWithKey:UIKeyInputRightArrow modifiers:0 action:^{
|
||||
if (!self.isHidden) {
|
||||
[self.explorerViewController handleRightArrowKeyPressed];
|
||||
}
|
||||
} description:@"Move selected view right"];
|
||||
|
||||
|
||||
[self registerDefaultSimulatorShortcutWithKey:UIKeyInputLeftArrow modifiers:0 action:^{
|
||||
if (self.isHidden) {
|
||||
[self tryGoBack];
|
||||
@@ -161,15 +156,15 @@
|
||||
[self.explorerViewController handleLeftArrowKeyPressed];
|
||||
}
|
||||
} description:@"Move selected view left"];
|
||||
|
||||
|
||||
[self registerDefaultSimulatorShortcutWithKey:@"?" modifiers:0 action:^{
|
||||
[self toggleTopViewControllerOfClass:[FLEXKeyboardHelpViewController class]];
|
||||
} description:@"Toggle (this) help menu"];
|
||||
|
||||
|
||||
[self registerDefaultSimulatorShortcutWithKey:UIKeyInputEscape modifiers:0 action:^{
|
||||
[[self.topViewController presentingViewController] dismissViewControllerAnimated:YES completion:nil];
|
||||
} description:@"End editing text\n\t\tDismiss top view controller"];
|
||||
|
||||
|
||||
[self registerDefaultSimulatorShortcutWithKey:@"o" modifiers:UIKeyModifierCommand|UIKeyModifierShift action:^{
|
||||
[self toggleTopViewControllerOfClass:[FLEXFileBrowserController class]];
|
||||
} description:@"Toggle file browser menu"];
|
||||
@@ -188,7 +183,7 @@
|
||||
if (@available(iOS 11, *)) {
|
||||
return scrollView.adjustedContentInset;
|
||||
}
|
||||
|
||||
|
||||
return scrollView.contentInset;
|
||||
}
|
||||
|
||||
|
||||
@@ -25,14 +25,6 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
- (void)hideExplorer;
|
||||
- (void)toggleExplorer;
|
||||
|
||||
/// Programmatically dismiss anything presented by FLEX, leaving only the toolbar visible.
|
||||
- (void)dismissAnyPresentedTools:(void (^_Nullable)(void))completion;
|
||||
/// Programmatically present something on top of the FLEX toolbar.
|
||||
/// This method will automatically dismiss any currently presented tool,
|
||||
/// so you do not need to call \c dismissAnyPresentedTools: yourself.
|
||||
- (void)presentTool:(UINavigationController *(^)(void))viewControllerFuture
|
||||
completion:(void (^_Nullable)(void))completion;
|
||||
|
||||
/// Use this to present the explorer in a specific scene when the one
|
||||
/// it chooses by default is not the one you wish to display it in.
|
||||
- (void)showExplorerFromScene:(UIWindowScene *)scene API_AVAILABLE(ios(13.0));
|
||||
|
||||
@@ -91,18 +91,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
- (void)dismissAnyPresentedTools:(void (^)(void))completion {
|
||||
if (self.explorerViewController.presentedViewController) {
|
||||
[self.explorerViewController dismissViewControllerAnimated:YES completion:completion];
|
||||
} else if (completion) {
|
||||
completion();
|
||||
}
|
||||
}
|
||||
|
||||
- (void)presentTool:(UINavigationController * _Nonnull (^)(void))future completion:(void (^)(void))completion {
|
||||
[self.explorerViewController toggleToolWithViewControllerProvider:future completion:completion];
|
||||
}
|
||||
|
||||
- (void)showExplorerFromScene:(UIWindowScene *)scene {
|
||||
#if FLEX_AT_LEAST_IOS13_SDK
|
||||
if (@available(iOS 13.0, *)) {
|
||||
|
||||
@@ -28,8 +28,7 @@
|
||||
}
|
||||
|
||||
if (request.HTTPBody) {
|
||||
NSString *body = [[NSString alloc] initWithData:request.HTTPBody encoding:NSUTF8StringEncoding];
|
||||
[curlCommandString appendFormat:@"-d \'%@\'", body];
|
||||
[curlCommandString appendFormat:@"-d \'%@\'", [NSString stringWithCString:request.HTTPBody.bytes encoding:NSUTF8StringEncoding]];
|
||||
}
|
||||
|
||||
return curlCommandString;
|
||||
|
||||
@@ -328,23 +328,24 @@ typedef UIViewController *(^FLEXNetworkDetailRowSelectionFuture)(void);
|
||||
responseBodyRow.detailText = @"tap to view";
|
||||
|
||||
// Avoid a long lived strong reference to the response data in case we need to purge it from the cache.
|
||||
weakify(responseData)
|
||||
responseBodyRow.selectionFuture = ^UIViewController *() { strongify(responseData)
|
||||
__weak NSData *weakResponseData = responseData;
|
||||
responseBodyRow.selectionFuture = ^UIViewController * () {
|
||||
|
||||
// Show the response if we can
|
||||
NSString *contentType = transaction.response.MIMEType;
|
||||
if (responseData) {
|
||||
UIViewController *bodyDetails = [self detailViewControllerForMIMEType:contentType data:responseData];
|
||||
if (bodyDetails) {
|
||||
bodyDetails.title = @"Response";
|
||||
return bodyDetails;
|
||||
NSData *strongResponseData = weakResponseData;
|
||||
if (strongResponseData) {
|
||||
UIViewController *bodyDetailController = [self detailViewControllerForMIMEType:contentType data:strongResponseData];
|
||||
if (bodyDetailController) {
|
||||
bodyDetailController.title = @"Response";
|
||||
return bodyDetailController;
|
||||
}
|
||||
}
|
||||
|
||||
// We can't show the response, alert user
|
||||
return [FLEXAlert makeAlert:^(FLEXAlert *make) {
|
||||
make.title(@"Unable to View Response");
|
||||
if (responseData) {
|
||||
if (strongResponseData) {
|
||||
make.message(@"No viewer content type: ").message(contentType);
|
||||
} else {
|
||||
make.message(@"The response has been purged from the cache");
|
||||
@@ -425,8 +426,7 @@ typedef UIViewController *(^FLEXNetworkDetailRowSelectionFuture)(void);
|
||||
if (transaction.cachedRequestBody.length > 0) {
|
||||
NSString *contentType = [transaction.request valueForHTTPHeaderField:@"Content-Type"];
|
||||
if ([contentType hasPrefix:@"application/x-www-form-urlencoded"]) {
|
||||
NSData *body = [self postBodyDataForTransaction:transaction];
|
||||
NSString *bodyString = [[NSString alloc] initWithData:body encoding:NSUTF8StringEncoding];
|
||||
NSString *bodyString = [NSString stringWithCString:[self postBodyDataForTransaction:transaction].bytes encoding:NSUTF8StringEncoding];
|
||||
postBodySection.rows = [self networkDetailRowsFromQueryItems:[FLEXUtility itemsFromQueryString:bodyString]];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -136,10 +136,7 @@
|
||||
NSUserDefaults *defaults = NSUserDefaults.standardUserDefaults;
|
||||
BOOL hideBackingIvars = defaults.flex_explorerHidesPropertyIvars;
|
||||
BOOL hidePropertyMethods = defaults.flex_explorerHidesPropertyMethods;
|
||||
BOOL hidePrivateMethods = defaults.flex_explorerHidesPrivateMethods;
|
||||
BOOL showMethodOverrides = defaults.flex_explorerShowsMethodOverrides;
|
||||
|
||||
NSMutableArray<NSArray<FLEXMethod *> *> *allMethods = [NSMutableArray new];
|
||||
|
||||
// Loop over each class and each superclass, collect
|
||||
// the fresh and unique metadata in each category
|
||||
@@ -168,7 +165,7 @@
|
||||
kind:FLEXMetadataKindIvars
|
||||
skip:NO
|
||||
]];
|
||||
[allMethods addObject:[self
|
||||
[_allMethods addObject:[self
|
||||
metadataUniquedByName:[cls flex_allInstanceMethods]
|
||||
superclass:superclass
|
||||
kind:FLEXMetadataKindMethods
|
||||
@@ -224,7 +221,8 @@
|
||||
|
||||
// Potentially filter property-backing methods
|
||||
if (hidePropertyMethods) {
|
||||
allMethods = [allMethods flex_mapped:^id(NSArray<FLEXMethod *> *list, NSUInteger idx) {
|
||||
NSArray<NSArray<FLEXMethod *> *> *methods = _allMethods.copy;
|
||||
_allMethods = [methods flex_mapped:^id(NSArray<FLEXMethod *> *list, NSUInteger idx) {
|
||||
// Get a set of all property method names for the current class in the hierarchy
|
||||
NSSet *methodNames = [NSSet setWithArray:({
|
||||
[properties[idx] flex_flatmapped:^NSArray *(FLEXProperty *p, NSUInteger idx) {
|
||||
@@ -242,23 +240,12 @@
|
||||
}];
|
||||
})];
|
||||
|
||||
// Remove methods whose name is in the property method names list
|
||||
// Remove ivars whose name is in the ivar names list
|
||||
return [list flex_filtered:^BOOL(FLEXMethod *method, NSUInteger idx) {
|
||||
return ![methodNames containsObject:method.selectorString];
|
||||
}];
|
||||
}];
|
||||
}
|
||||
|
||||
if (hidePrivateMethods) {
|
||||
allMethods = [allMethods flex_mapped:^id(NSArray<FLEXMethod *> *list, NSUInteger idx) {
|
||||
// Remove methods which contain an underscore
|
||||
return [list flex_filtered:^BOOL(FLEXMethod *method, NSUInteger idx) {
|
||||
return ![method.selectorString containsString:@"_"];
|
||||
}];
|
||||
}];
|
||||
}
|
||||
|
||||
_allMethods = allMethods;
|
||||
|
||||
// Set up UIKit helper data
|
||||
// Really, we only need to call this on properties and ivars
|
||||
@@ -296,8 +283,8 @@
|
||||
- (NSArray *)metadataUniquedByName:(NSArray *)list
|
||||
superclass:(Class)superclass
|
||||
kind:(FLEXMetadataKind)kind
|
||||
skip:(BOOL)skipUniquing {
|
||||
if (skipUniquing) {
|
||||
skip:(BOOL)skip {
|
||||
if (skip) {
|
||||
return list;
|
||||
}
|
||||
|
||||
|
||||
@@ -21,21 +21,13 @@
|
||||
#import "FLEXUtility.h"
|
||||
|
||||
@implementation FLEXObjectExplorerFactory
|
||||
static NSMutableDictionary<id<NSCopying>, Class> *classesToRegisteredSections = nil;
|
||||
static NSMutableDictionary<Class, Class> *classesToRegisteredSections = nil;
|
||||
|
||||
+ (void)initialize {
|
||||
if (self == [FLEXObjectExplorerFactory class]) {
|
||||
// DO NOT USE STRING KEYS HERE
|
||||
// We NEED to use the class as a key, because we CANNOT
|
||||
// differentiate a class's name from the metaclass's name.
|
||||
// These mappings are per-class-object, not per-class-name.
|
||||
//
|
||||
// For example, if we used class names, this would result in
|
||||
// the object explorer trying to render a color preview for
|
||||
// the UIColor class object, which is not a color itself.
|
||||
#define ClassKey(name) (id<NSCopying>)[name class]
|
||||
#define ClassKeyByName(str) (id<NSCopying>)NSClassFromString(@ #str)
|
||||
#define MetaclassKey(meta) (id<NSCopying>)object_getClass([meta class])
|
||||
#define ClassKey(name) (Class<NSCopying>)[name class]
|
||||
#define ClassKeyByName(str) (Class<NSCopying>)NSClassFromString(@ #str)
|
||||
#define MetaclassKey(meta) (Class<NSCopying>)object_getClass([meta class])
|
||||
classesToRegisteredSections = [NSMutableDictionary dictionaryWithDictionary:@{
|
||||
MetaclassKey(NSObject) : [FLEXClassShortcuts class],
|
||||
ClassKey(NSArray) : [FLEXCollectionContentSection class],
|
||||
@@ -75,7 +67,7 @@ static NSMutableDictionary<id<NSCopying>, Class> *classesToRegisteredSections =
|
||||
Class sectionClass = nil;
|
||||
Class cls = object_getClass(object);
|
||||
do {
|
||||
sectionClass = classesToRegisteredSections[(id<NSCopying>)cls];
|
||||
sectionClass = classesToRegisteredSections[(Class<NSCopying>)cls];
|
||||
} while (!sectionClass && (cls = [cls superclass]));
|
||||
|
||||
if (!sectionClass) {
|
||||
@@ -89,7 +81,7 @@ static NSMutableDictionary<id<NSCopying>, Class> *classesToRegisteredSections =
|
||||
}
|
||||
|
||||
+ (void)registerExplorerSection:(Class)explorerClass forClass:(Class)objectClass {
|
||||
classesToRegisteredSections[(id<NSCopying>)objectClass] = explorerClass;
|
||||
classesToRegisteredSections[(Class<NSCopying>)objectClass] = explorerClass;
|
||||
}
|
||||
|
||||
#pragma mark - FLEXGlobalsEntry
|
||||
@@ -184,7 +176,7 @@ static NSMutableDictionary<id<NSCopying>, Class> *classesToRegisteredSections =
|
||||
return [self explorerViewControllerForObject:NSThread.mainThread];
|
||||
case FLEXGlobalsRowOperationQueue:
|
||||
return [self explorerViewControllerForObject:NSOperationQueue.mainQueue];
|
||||
|
||||
|
||||
case FLEXGlobalsRowKeyWindow:
|
||||
return [FLEXObjectExplorerFactory
|
||||
explorerViewControllerForObject:FLEXUtility.appKeyWindow
|
||||
|
||||
@@ -72,8 +72,7 @@
|
||||
return @[
|
||||
kFLEXDefaultsHidePropertyIvarsKey,
|
||||
kFLEXDefaultsHidePropertyMethodsKey,
|
||||
kFLEXDefaultsHidePrivateMethodsKey,
|
||||
kFLEXDefaultsShowMethodOverridesKey,
|
||||
kFLEXDefaultsHideMethodOverridesKey,
|
||||
kFLEXDefaultsHideVariablePreviewsKey,
|
||||
];
|
||||
}
|
||||
@@ -88,7 +87,7 @@
|
||||
|
||||
// Use [object class] here rather than object_getClass
|
||||
// to avoid the KVO prefix for observed objects
|
||||
self.title = [FLEXRuntimeUtility safeClassNameForObject:self.object];
|
||||
self.title = [[self.object class] description];
|
||||
|
||||
// Search
|
||||
self.showsSearchBar = YES;
|
||||
@@ -266,7 +265,9 @@
|
||||
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)g1 shouldBeRequiredToFailByGestureRecognizer:(UIGestureRecognizer *)g2 {
|
||||
// Prioritize important pan gestures over our swipe gesture
|
||||
if ([g2 isKindOfClass:[UIPanGestureRecognizer class]]) {
|
||||
if (g2 == self.navigationController.interactivePopGestureRecognizer) {
|
||||
if (g2 == self.navigationController.interactivePopGestureRecognizer ||
|
||||
g2 == self.navigationController.barHideOnSwipeGestureRecognizer ||
|
||||
g2 == self.tableView.panGestureRecognizer) {
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
@@ -290,8 +291,7 @@
|
||||
NSDictionary<NSString *, NSString *> *explorerToggles = @{
|
||||
kFLEXDefaultsHidePropertyIvarsKey: @"Property-Backing Ivars",
|
||||
kFLEXDefaultsHidePropertyMethodsKey: @"Property-Backing Methods",
|
||||
kFLEXDefaultsHidePrivateMethodsKey: @"Likely Private Methods",
|
||||
kFLEXDefaultsShowMethodOverridesKey: @"Method Overrides",
|
||||
kFLEXDefaultsHideMethodOverridesKey: @"Method Overrides",
|
||||
kFLEXDefaultsHideVariablePreviewsKey: @"Variable Previews"
|
||||
};
|
||||
|
||||
@@ -302,8 +302,7 @@
|
||||
NSDictionary<NSString *, NSDictionary *> *nextStateDescriptions = @{
|
||||
kFLEXDefaultsHidePropertyIvarsKey: @{ @NO: @"Hide ", @YES: @"Show " },
|
||||
kFLEXDefaultsHidePropertyMethodsKey: @{ @NO: @"Hide ", @YES: @"Show " },
|
||||
kFLEXDefaultsHidePrivateMethodsKey: @{ @NO: @"Hide ", @YES: @"Show " },
|
||||
kFLEXDefaultsShowMethodOverridesKey: @{ @NO: @"Show ", @YES: @"Hide " },
|
||||
kFLEXDefaultsHideMethodOverridesKey: @{ @NO: @"Show ", @YES: @"Hide " },
|
||||
kFLEXDefaultsHideVariablePreviewsKey: @{ @NO: @"Hide ", @YES: @"Show " },
|
||||
};
|
||||
|
||||
|
||||
@@ -78,10 +78,12 @@ configurationBlock:(FLEXMutableListCellForElement)cellConfig
|
||||
}
|
||||
|
||||
- (void (^)(__kindof UIViewController *))didSelectRowAction:(NSInteger)row {
|
||||
if (self.selectionHandler) { weakify(self)
|
||||
return ^(UIViewController *host) { strongify(self)
|
||||
if (self) {
|
||||
self.selectionHandler(host, self.filteredList[row]);
|
||||
if (self.selectionHandler) {
|
||||
__weak __typeof(self) weakSelf = self;
|
||||
return ^(UIViewController *host) {
|
||||
__strong __typeof(self) strongSelf = weakSelf;
|
||||
if (strongSelf) {
|
||||
strongSelf.selectionHandler(host, strongSelf.filteredList[row]);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -17,7 +17,8 @@
|
||||
@implementation FLEXBundleShortcuts
|
||||
#pragma mark Overrides
|
||||
|
||||
+ (instancetype)forObject:(NSBundle *)bundle { weakify(self)
|
||||
+ (instancetype)forObject:(NSBundle *)bundle {
|
||||
__weak __typeof(self) weakSelf = self;
|
||||
return [self forObject:bundle additionalRows:@[
|
||||
[FLEXActionShortcut
|
||||
title:@"Browse Bundle Directory" subtitle:nil
|
||||
@@ -29,8 +30,11 @@
|
||||
}
|
||||
],
|
||||
[FLEXActionShortcut title:@"Browse Bundle as Database…" subtitle:nil
|
||||
selectionHandler:^(UIViewController *host, NSBundle *bundle) { strongify(self)
|
||||
[self promptToExportBundleAsDatabase:bundle host:host];
|
||||
selectionHandler:^(UIViewController *host, NSBundle *bundle) {
|
||||
__strong __typeof(self) strongSelf = weakSelf;
|
||||
if (strongSelf) {
|
||||
[strongSelf promptToExportBundleAsDatabase:bundle host:host];
|
||||
}
|
||||
}
|
||||
accessoryType:^UITableViewCellAccessoryType(NSBundle *bundle) {
|
||||
return UITableViewCellAccessoryDisclosureIndicator;
|
||||
|
||||
@@ -15,11 +15,11 @@
|
||||
+ (instancetype)forObject:(CALayer *)layer {
|
||||
return [self forObject:layer additionalRows:@[
|
||||
[FLEXActionShortcut title:@"Preview Image" subtitle:nil
|
||||
viewer:^UIViewController *(CALayer *layer) {
|
||||
viewer:^UIViewController *(id layer) {
|
||||
return [FLEXImagePreviewViewController previewForLayer:layer];
|
||||
}
|
||||
accessoryType:^UITableViewCellAccessoryType(CALayer *layer) {
|
||||
return CGRectIsEmpty(layer.bounds) ? UITableViewCellAccessoryNone : UITableViewCellAccessoryDisclosureIndicator;
|
||||
accessoryType:^UITableViewCellAccessoryType(id layer) {
|
||||
return UITableViewCellAccessoryDisclosureIndicator;
|
||||
}
|
||||
]
|
||||
]];
|
||||
|
||||
@@ -32,7 +32,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@optional
|
||||
/// Called when the (i) button is pressed if the accessory type includes it
|
||||
- (UIViewController *)editorWith:(id)object forSection:(FLEXTableViewSection *)section;
|
||||
- (UIViewController *)editorWith:(id)object;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@@ -201,10 +201,10 @@
|
||||
|
||||
- (void (^)(__kindof UIViewController *))didPressInfoButtonAction:(NSInteger)row {
|
||||
id<FLEXShortcut> shortcut = self.shortcuts[row];
|
||||
if ([shortcut respondsToSelector:@selector(editorWith:forSection:)]) {
|
||||
if ([shortcut respondsToSelector:@selector(editorWith:)]) {
|
||||
id object = self.object;
|
||||
return ^(UIViewController *host) {
|
||||
UIViewController *editor = [shortcut editorWith:object forSection:self];
|
||||
UIViewController *editor = [shortcut editorWith:object];
|
||||
[host.navigationController pushViewController:editor animated:YES];
|
||||
};
|
||||
}
|
||||
@@ -255,52 +255,31 @@
|
||||
}
|
||||
@end
|
||||
|
||||
#define NewAndSet(ivar) ({ FLEXShortcutsFactory *r = [self sharedFactory]; r->ivar = YES; r; })
|
||||
#define NewAndSet(ivar) ({ FLEXShortcutsFactory *r = [self new]; r->ivar = YES; r; })
|
||||
#define SetIvar(ivar) ({ self->ivar = YES; self; })
|
||||
#define SetParamBlock(ivar) ^(NSArray *p) { self->ivar = p; return self; }
|
||||
|
||||
@implementation FLEXShortcutsFactory
|
||||
|
||||
typedef NSMutableDictionary<Class, NSMutableArray<id<FLEXRuntimeMetadata>> *> RegistrationBuckets;
|
||||
// Class buckets
|
||||
static RegistrationBuckets *cProperties = nil;
|
||||
static RegistrationBuckets *cIvars = nil;
|
||||
static RegistrationBuckets *cMethods = nil;
|
||||
// Metaclass buckets
|
||||
static RegistrationBuckets *mProperties = nil;
|
||||
static RegistrationBuckets *mMethods = nil;
|
||||
|
||||
@implementation FLEXShortcutsFactory {
|
||||
// Class buckets
|
||||
RegistrationBuckets *cProperties;
|
||||
RegistrationBuckets *cIvars;
|
||||
RegistrationBuckets *cMethods;
|
||||
// Metaclass buckets
|
||||
RegistrationBuckets *mProperties;
|
||||
RegistrationBuckets *mMethods;
|
||||
}
|
||||
+ (void)load {
|
||||
cProperties = [NSMutableDictionary new];
|
||||
cIvars = [NSMutableDictionary new];
|
||||
cMethods = [NSMutableDictionary new];
|
||||
|
||||
+ (instancetype)sharedFactory {
|
||||
static FLEXShortcutsFactory *shared = nil;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
shared = [self new];
|
||||
});
|
||||
|
||||
return shared;
|
||||
}
|
||||
|
||||
- (id)init {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
cProperties = [NSMutableDictionary new];
|
||||
cIvars = [NSMutableDictionary new];
|
||||
cMethods = [NSMutableDictionary new];
|
||||
|
||||
mProperties = [NSMutableDictionary new];
|
||||
mMethods = [NSMutableDictionary new];
|
||||
}
|
||||
|
||||
return self;
|
||||
mProperties = [NSMutableDictionary new];
|
||||
mMethods = [NSMutableDictionary new];
|
||||
}
|
||||
|
||||
+ (NSArray<id<FLEXRuntimeMetadata>> *)shortcutsForObjectOrClass:(id)objectOrClass {
|
||||
return [[self sharedFactory] shortcutsForObjectOrClass:objectOrClass];
|
||||
}
|
||||
|
||||
- (NSArray<id<FLEXRuntimeMetadata>> *)shortcutsForObjectOrClass:(id)objectOrClass {
|
||||
|
||||
NSMutableArray<id<FLEXRuntimeMetadata>> *shortcuts = [NSMutableArray new];
|
||||
BOOL isClass = object_isClass(objectOrClass);
|
||||
// The -class does not give you a metaclass, and we want a metaclass
|
||||
@@ -346,43 +325,30 @@ typedef NSMutableDictionary<Class, NSMutableArray<id<FLEXRuntimeMetadata>> *> Re
|
||||
}
|
||||
|
||||
- (void)_register:(NSArray<id<FLEXRuntimeMetadata>> *)items to:(RegistrationBuckets *)global class:(Class)key {
|
||||
@synchronized (self) {
|
||||
// Get (or initialize) the bucket for this class
|
||||
NSMutableArray *bucket = ({
|
||||
id bucket = global[key];
|
||||
if (!bucket) {
|
||||
bucket = [NSMutableArray new];
|
||||
global[(id)key] = bucket;
|
||||
}
|
||||
bucket;
|
||||
});
|
||||
// Get (or initialize) the bucket for this class
|
||||
NSMutableArray *bucket = ({
|
||||
id bucket = global[key];
|
||||
if (!bucket) {
|
||||
bucket = [NSMutableArray new];
|
||||
global[(id)key] = bucket;
|
||||
}
|
||||
bucket;
|
||||
});
|
||||
|
||||
if (self->_append) { [bucket addObjectsFromArray:items]; }
|
||||
if (self->_replace) { [bucket setArray:items]; }
|
||||
if (self->_prepend) {
|
||||
if (bucket.count) {
|
||||
// Set new items as array, add old items behind them
|
||||
id copy = bucket.copy;
|
||||
[bucket setArray:items];
|
||||
[bucket addObjectsFromArray:copy];
|
||||
} else {
|
||||
[bucket addObjectsFromArray:items];
|
||||
}
|
||||
if (self->_append) { [bucket addObjectsFromArray:items]; }
|
||||
if (self->_replace) { [bucket setArray:items]; }
|
||||
if (self->_prepend) {
|
||||
if (bucket.count) {
|
||||
// Set new items as array, add old items behind them
|
||||
id copy = bucket.copy;
|
||||
[bucket setArray:items];
|
||||
[bucket addObjectsFromArray:copy];
|
||||
} else {
|
||||
[bucket addObjectsFromArray:items];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)reset {
|
||||
_append = NO;
|
||||
_prepend = NO;
|
||||
_replace = NO;
|
||||
_notInstance = NO;
|
||||
|
||||
_properties = nil;
|
||||
_ivars = nil;
|
||||
_methods = nil;
|
||||
}
|
||||
|
||||
- (FLEXShortcutsFactory *)class {
|
||||
return SetIvar(_notInstance);
|
||||
}
|
||||
@@ -439,15 +405,9 @@ typedef NSMutableDictionary<Class, NSMutableArray<id<FLEXRuntimeMetadata>> *> Re
|
||||
Class metaclass = isMeta ? cls : object_getClass(cls);
|
||||
Class clsForMetadata = instanceMetadata ? cls : metaclass;
|
||||
|
||||
// The factory is a singleton so we don't need to worry about "leaking" it
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wimplicit-retain-self"
|
||||
|
||||
RegistrationBuckets *propertyBucket = instanceShortcut ? cProperties : mProperties;
|
||||
RegistrationBuckets *methodBucket = instanceShortcut ? cMethods : mMethods;
|
||||
RegistrationBuckets *ivarBucket = instanceShortcut ? cIvars : nil;
|
||||
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
if (self->_properties) {
|
||||
NSArray *items = [self->_properties flex_mapped:^id(NSString *name, NSUInteger idx) {
|
||||
@@ -470,8 +430,6 @@ typedef NSMutableDictionary<Class, NSMutableArray<id<FLEXRuntimeMetadata>> *> Re
|
||||
}];
|
||||
[self _register:items to:ivarBucket class:cls];
|
||||
}
|
||||
|
||||
[self reset];
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -70,18 +70,15 @@
|
||||
return [FLEXObjectExplorerFactory explorerViewControllerForObject:controller];
|
||||
}
|
||||
accessoryType:^UITableViewCellAccessoryType(id view) {
|
||||
return controller ? UITableViewCellAccessoryDisclosureIndicator : UITableViewCellAccessoryNone;
|
||||
return controller ? UITableViewCellAccessoryDisclosureIndicator : 0;
|
||||
}
|
||||
],
|
||||
[FLEXActionShortcut title:@"Preview Image" subtitle:^NSString *(UIView *view) {
|
||||
return !CGRectIsEmpty(view.bounds) ? @"" : @"Unavailable with empty bounds";
|
||||
}
|
||||
viewer:^UIViewController *(UIView *view) {
|
||||
[FLEXActionShortcut title:@"Preview Image" subtitle:nil
|
||||
viewer:^UIViewController *(id view) {
|
||||
return [FLEXImagePreviewViewController previewForView:view];
|
||||
}
|
||||
accessoryType:^UITableViewCellAccessoryType(UIView *view) {
|
||||
// Disable preview if bounds are CGRectZero
|
||||
return !CGRectIsEmpty(view.bounds) ? UITableViewCellAccessoryDisclosureIndicator : UITableViewCellAccessoryNone;
|
||||
accessoryType:^UITableViewCellAccessoryType(id view) {
|
||||
return UITableViewCellAccessoryDisclosureIndicator;
|
||||
}
|
||||
]
|
||||
]];
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
#import "FLEXObjectExplorerFactory.h"
|
||||
#import "FLEXFieldEditorViewController.h"
|
||||
#import "FLEXMethodCallingViewController.h"
|
||||
#import "FLEXObjectListViewController.h"
|
||||
#import "FLEXTableView.h"
|
||||
#import "FLEXUtility.h"
|
||||
#import "NSArray+FLEX.h"
|
||||
@@ -132,37 +131,15 @@ FLEXObjectExplorerDefaultsImpl
|
||||
#if FLEX_AT_LEAST_IOS13_SDK
|
||||
|
||||
- (NSArray<UIAction *> *)additionalActionsWithTarget:(id)object sender:(UIViewController *)sender __IOS_AVAILABLE(13.0) {
|
||||
BOOL returnsObject = self.attributes.typeEncoding.flex_typeIsObjectOrClass;
|
||||
BOOL targetNotNil = [self appropriateTargetForPropertyType:object] != nil;
|
||||
Class propertyClass = self.attributes.typeEncoding.flex_typeClass;
|
||||
|
||||
// "Explore PropertyClass" for properties with a concrete class name
|
||||
if (returnsObject) {
|
||||
NSMutableArray<UIAction *> *actions = [NSMutableArray new];
|
||||
|
||||
// Action for exploring class of this property
|
||||
Class propertyClass = self.attributes.typeEncoding.flex_typeClass;
|
||||
if (propertyClass) {
|
||||
NSString *title = [NSString stringWithFormat:@"Explore %@", NSStringFromClass(propertyClass)];
|
||||
[actions addObject:[UIAction actionWithTitle:title image:nil identifier:nil handler:^(UIAction *action) {
|
||||
UIViewController *explorer = [FLEXObjectExplorerFactory explorerViewControllerForObject:propertyClass];
|
||||
[sender.navigationController pushViewController:explorer animated:YES];
|
||||
}]];
|
||||
}
|
||||
|
||||
// Action for exploring references to this object
|
||||
if (targetNotNil) {
|
||||
// Since the property holder is not nil, check if the property value is nil
|
||||
id value = [self currentValueBeforeUnboxingWithTarget:object];
|
||||
if (value) {
|
||||
NSString *title = @"List all references";
|
||||
[actions addObject:[UIAction actionWithTitle:title image:nil identifier:nil handler:^(UIAction *action) {
|
||||
UIViewController *list = [FLEXObjectListViewController objectsWithReferencesToObject:value];
|
||||
[sender.navigationController pushViewController:list animated:YES];
|
||||
}]];
|
||||
}
|
||||
}
|
||||
|
||||
return actions;
|
||||
if (propertyClass) {
|
||||
NSString *title = [NSString stringWithFormat:@"Explore %@", NSStringFromClass(propertyClass)];
|
||||
return @[[UIAction actionWithTitle:title image:nil identifier:nil handler:^(UIAction *action) {
|
||||
UIViewController *explorer = [FLEXObjectExplorerFactory explorerViewControllerForObject:propertyClass];
|
||||
[sender.navigationController pushViewController:explorer animated:YES];
|
||||
}]];
|
||||
}
|
||||
|
||||
return nil;
|
||||
|
||||
@@ -27,12 +27,6 @@
|
||||
+ (instancetype)flex_forEachUpTo:(NSUInteger)bound map:(T(^)(NSUInteger i))block;
|
||||
+ (instancetype)flex_mapped:(id<NSFastEnumeration>)collection block:(id(^)(T obj, NSUInteger idx))mapFunc;
|
||||
|
||||
- (instancetype)flex_sortedUsingSelector:(SEL)selector;
|
||||
|
||||
@end
|
||||
|
||||
@interface NSMutableArray<T> (Functional)
|
||||
|
||||
- (void)flex_filter:(BOOL(^)(T obj, NSUInteger idx))filterFunc;
|
||||
- (instancetype)sortedUsingSelector:(SEL)selector;
|
||||
|
||||
@end
|
||||
|
||||
@@ -85,6 +85,7 @@
|
||||
return array;
|
||||
}
|
||||
|
||||
|
||||
+ (instancetype)flex_mapped:(id<NSFastEnumeration>)collection block:(id(^)(id obj, NSUInteger idx))mapFunc {
|
||||
NSMutableArray *array = [NSMutableArray new];
|
||||
NSInteger idx = 0;
|
||||
@@ -103,7 +104,7 @@
|
||||
return array;
|
||||
}
|
||||
|
||||
- (instancetype)flex_sortedUsingSelector:(SEL)selector {
|
||||
- (instancetype)sortedUsingSelector:(SEL)selector {
|
||||
if (FLEXArrayClassIsMutable(self)) {
|
||||
NSMutableArray *me = (id)self;
|
||||
[me sortUsingSelector:selector];
|
||||
@@ -114,20 +115,3 @@
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation NSMutableArray (Functional)
|
||||
|
||||
- (void)flex_filter:(BOOL (^)(id, NSUInteger))keepObject {
|
||||
NSMutableIndexSet *toRemove = [NSMutableIndexSet new];
|
||||
|
||||
[self enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
|
||||
if (!keepObject(obj, idx)) {
|
||||
[toRemove addIndex:idx];
|
||||
}
|
||||
}];
|
||||
|
||||
[self removeObjectsAtIndexes:toRemove];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -95,7 +95,6 @@ NSArray<FLEXProtocol *> *FLEXGetConformedProtocols(Class cls) {
|
||||
unsigned int count = 0;
|
||||
Protocol *__unsafe_unretained *list = class_copyProtocolList(cls, &count);
|
||||
NSArray<Protocol *> *protocols = [NSArray arrayWithObjects:list count:count];
|
||||
free(list);
|
||||
|
||||
return [protocols flex_mapped:^id(Protocol *pro, NSUInteger idx) {
|
||||
return [FLEXProtocol protocol:pro];
|
||||
|
||||
+2
-2
@@ -27,7 +27,7 @@
|
||||
|
||||
@interface NSString (KeyPaths)
|
||||
|
||||
- (NSString *)flex_stringByRemovingLastKeyPathComponent;
|
||||
- (NSString *)flex_stringByReplacingLastKeyPathComponent:(NSString *)replacement;
|
||||
- (NSString *)stringByRemovingLastKeyPathComponent;
|
||||
- (NSString *)stringByReplacingLastKeyPathComponent:(NSString *)replacement;
|
||||
|
||||
@end
|
||||
+2
-2
@@ -128,7 +128,7 @@
|
||||
|
||||
@implementation NSString (KeyPaths)
|
||||
|
||||
- (NSString *)flex_stringByRemovingLastKeyPathComponent {
|
||||
- (NSString *)stringByRemovingLastKeyPathComponent {
|
||||
if (![self containsString:@"."]) {
|
||||
return @"";
|
||||
}
|
||||
@@ -138,7 +138,7 @@
|
||||
return mself;
|
||||
}
|
||||
|
||||
- (NSString *)flex_stringByReplacingLastKeyPathComponent:(NSString *)replacement {
|
||||
- (NSString *)stringByReplacingLastKeyPathComponent:(NSString *)replacement {
|
||||
// replacement should not have any escaped '.' in it,
|
||||
// so we escape all '.'
|
||||
if ([replacement containsString:@"."]) {
|
||||
@@ -11,7 +11,7 @@ typedef void (^VoidBlock)(void);
|
||||
|
||||
@interface NSTimer (Blocks)
|
||||
|
||||
+ (instancetype)flex_fireSecondsFromNow:(NSTimeInterval)delay block:(VoidBlock)block;
|
||||
+ (instancetype)fireSecondsFromNow:(NSTimeInterval)delay block:(VoidBlock)block;
|
||||
|
||||
// Forward declaration
|
||||
//+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))block;
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
#pragma clang diagnostic ignored "-Wincomplete-implementation"
|
||||
@implementation NSTimer (Blocks)
|
||||
|
||||
+ (instancetype)flex_fireSecondsFromNow:(NSTimeInterval)delay block:(VoidBlock)block {
|
||||
+ (instancetype)fireSecondsFromNow:(NSTimeInterval)delay block:(VoidBlock)block {
|
||||
if (@available(iOS 10, *)) {
|
||||
return [self scheduledTimerWithTimeInterval:delay repeats:NO block:(id)block];
|
||||
} else {
|
||||
|
||||
@@ -13,8 +13,7 @@ extern NSString * const kFLEXDefaultsToolbarTopMarginKey;
|
||||
extern NSString * const kFLEXDefaultsiOSPersistentOSLogKey;
|
||||
extern NSString * const kFLEXDefaultsHidePropertyIvarsKey;
|
||||
extern NSString * const kFLEXDefaultsHidePropertyMethodsKey;
|
||||
extern NSString * const kFLEXDefaultsHidePrivateMethodsKey;
|
||||
extern NSString * const kFLEXDefaultsShowMethodOverridesKey;
|
||||
extern NSString * const kFLEXDefaultsHideMethodOverridesKey;
|
||||
extern NSString * const kFLEXDefaultsHideVariablePreviewsKey;
|
||||
extern NSString * const kFLEXDefaultsNetworkHostBlacklistKey;
|
||||
extern NSString * const kFLEXDefaultsDisableOSLogForceASLKey;
|
||||
@@ -39,7 +38,6 @@ extern NSString * const kFLEXDefaultsRegisterJSONExplorerKey;
|
||||
|
||||
@property (nonatomic) BOOL flex_explorerHidesPropertyIvars;
|
||||
@property (nonatomic) BOOL flex_explorerHidesPropertyMethods;
|
||||
@property (nonatomic) BOOL flex_explorerHidesPrivateMethods;
|
||||
@property (nonatomic) BOOL flex_explorerShowsMethodOverrides;
|
||||
@property (nonatomic) BOOL flex_explorerHidesVariablePreviews;
|
||||
|
||||
|
||||
@@ -12,8 +12,7 @@ NSString * const kFLEXDefaultsToolbarTopMarginKey = @"com.flex.FLEXToolbar.topMa
|
||||
NSString * const kFLEXDefaultsiOSPersistentOSLogKey = @"com.flipborad.flex.enable_persistent_os_log";
|
||||
NSString * const kFLEXDefaultsHidePropertyIvarsKey = @"com.flipboard.FLEX.hide_property_ivars";
|
||||
NSString * const kFLEXDefaultsHidePropertyMethodsKey = @"com.flipboard.FLEX.hide_property_methods";
|
||||
NSString * const kFLEXDefaultsHidePrivateMethodsKey = @"com.flipboard.FLEX.hide_private_or_namespaced_methods";
|
||||
NSString * const kFLEXDefaultsShowMethodOverridesKey = @"com.flipboard.FLEX.show_method_overrides";
|
||||
NSString * const kFLEXDefaultsHideMethodOverridesKey = @"com.flipboard.FLEX.hide_method_overrides";
|
||||
NSString * const kFLEXDefaultsHideVariablePreviewsKey = @"com.flipboard.FLEX.hide_variable_previews";
|
||||
NSString * const kFLEXDefaultsNetworkHostBlacklistKey = @"com.flipboard.FLEX.network_host_blacklist";
|
||||
NSString * const kFLEXDefaultsDisableOSLogForceASLKey = @"com.flipboard.FLEX.try_disable_os_log";
|
||||
@@ -131,26 +130,14 @@ NSString * const kFLEXDefaultsRegisterJSONExplorerKey = @"com.flipboard.FLEX.vie
|
||||
];
|
||||
}
|
||||
|
||||
- (BOOL)flex_explorerHidesPrivateMethods {
|
||||
return [self boolForKey:kFLEXDefaultsHidePrivateMethodsKey];
|
||||
}
|
||||
|
||||
- (void)setFlex_explorerHidesPrivateMethods:(BOOL)show {
|
||||
[self setBool:show forKey:kFLEXDefaultsHidePrivateMethodsKey];
|
||||
[NSNotificationCenter.defaultCenter
|
||||
postNotificationName:kFLEXDefaultsHidePrivateMethodsKey
|
||||
object:nil
|
||||
];
|
||||
}
|
||||
|
||||
- (BOOL)flex_explorerShowsMethodOverrides {
|
||||
return [self boolForKey:kFLEXDefaultsShowMethodOverridesKey];
|
||||
return [self boolForKey:kFLEXDefaultsHideMethodOverridesKey];
|
||||
}
|
||||
|
||||
- (void)setFlex_explorerShowsMethodOverrides:(BOOL)show {
|
||||
[self setBool:show forKey:kFLEXDefaultsShowMethodOverridesKey];
|
||||
[self setBool:show forKey:kFLEXDefaultsHideMethodOverridesKey];
|
||||
[NSNotificationCenter.defaultCenter
|
||||
postNotificationName:kFLEXDefaultsShowMethodOverridesKey
|
||||
postNotificationName:kFLEXDefaultsHideMethodOverridesKey
|
||||
object:nil
|
||||
];
|
||||
}
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
//
|
||||
// UIView+FLEX_Layout.h
|
||||
// FLEX
|
||||
//
|
||||
// Created by Tanner Bennett on 7/18/19.
|
||||
// Copyright © 2020 FLEX Team. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
#define Padding(p) UIEdgeInsetsMake(p, p, p, p)
|
||||
|
||||
@interface UIView (FLEX_Layout)
|
||||
|
||||
- (void)flex_centerInView:(UIView *)view;
|
||||
- (void)flex_pinEdgesTo:(UIView *)view;
|
||||
- (void)flex_pinEdgesTo:(UIView *)view withInsets:(UIEdgeInsets)insets;
|
||||
- (void)flex_pinEdgesToSuperview;
|
||||
- (void)flex_pinEdgesToSuperviewWithInsets:(UIEdgeInsets)insets;
|
||||
- (void)flex_pinEdgesToSuperviewWithInsets:(UIEdgeInsets)insets aboveView:(UIView *)sibling;
|
||||
- (void)flex_pinEdgesToSuperviewWithInsets:(UIEdgeInsets)insets belowView:(UIView *)sibling;
|
||||
|
||||
@end
|
||||
@@ -64,7 +64,7 @@
|
||||
return item;
|
||||
}
|
||||
|
||||
- (UIBarButtonItem *)flex_withTintColor:(UIColor *)tint {
|
||||
- (UIBarButtonItem *)withTintColor:(UIColor *)tint {
|
||||
self.tintColor = tint;
|
||||
return self;
|
||||
}
|
||||
|
||||
@@ -13,9 +13,9 @@ typedef void (^GestureBlock)(UIGestureRecognizer *gesture);
|
||||
|
||||
@interface UIGestureRecognizer (Blocks)
|
||||
|
||||
+ (instancetype)flex_action:(GestureBlock)action;
|
||||
+ (instancetype)action:(GestureBlock)action;
|
||||
|
||||
@property (nonatomic, setter=flex_setAction:) GestureBlock flex_action;
|
||||
@property (nonatomic) GestureBlock action;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@@ -14,22 +14,22 @@
|
||||
|
||||
static void * actionKey;
|
||||
|
||||
+ (instancetype)flex_action:(GestureBlock)action {
|
||||
+ (instancetype)action:(GestureBlock)action {
|
||||
UIGestureRecognizer *gesture = [[self alloc] initWithTarget:nil action:nil];
|
||||
[gesture addTarget:gesture action:@selector(flex_invoke)];
|
||||
gesture.flex_action = action;
|
||||
gesture.action = action;
|
||||
return gesture;
|
||||
}
|
||||
|
||||
- (void)flex_invoke {
|
||||
self.flex_action(self);
|
||||
self.action(self);
|
||||
}
|
||||
|
||||
- (GestureBlock)flex_action {
|
||||
- (GestureBlock)action {
|
||||
return objc_getAssociatedObject(self, &actionKey);
|
||||
}
|
||||
|
||||
- (void)flex_setAction:(GestureBlock)action {
|
||||
- (void)setAction:(GestureBlock)action {
|
||||
objc_setAssociatedObject(self, &actionKey, action, OBJC_ASSOCIATION_COPY);
|
||||
}
|
||||
|
||||
|
||||
@@ -11,21 +11,16 @@
|
||||
@implementation UIPasteboard (FLEX)
|
||||
|
||||
- (void)flex_copy:(id)object {
|
||||
if (!object) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ([object isKindOfClass:[NSString class]]) {
|
||||
UIPasteboard.generalPasteboard.string = object;
|
||||
} else if([object isKindOfClass:[NSData class]]) {
|
||||
[UIPasteboard.generalPasteboard setData:object forPasteboardType:@"public.data"];
|
||||
} else if ([object isKindOfClass:[NSNumber class]]) {
|
||||
UIPasteboard.generalPasteboard.string = [object stringValue];
|
||||
} else {
|
||||
// TODO: make this an alert instead of an exception
|
||||
[NSException raise:NSInternalInconsistencyException
|
||||
format:@"Tried to copy unsupported type: %@", [object class]];
|
||||
}
|
||||
|
||||
[NSException raise:NSInternalInconsistencyException
|
||||
format:@"Tried to copy unsupported type: %@", [object class]];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -9,6 +9,6 @@
|
||||
|
||||
@interface UITextField (Range)
|
||||
|
||||
@property (nonatomic, readonly) NSRange flex_selectedRange;
|
||||
@property (nonatomic, readonly) NSRange selectedRange;
|
||||
|
||||
@end
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
@implementation UITextField (Range)
|
||||
|
||||
- (NSRange)flex_selectedRange {
|
||||
- (NSRange)selectedRange {
|
||||
UITextRange *r = self.selectedTextRange;
|
||||
if (r) {
|
||||
NSInteger loc = [self offsetFromPosition:self.beginningOfDocument toPosition:r.start];
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
//
|
||||
// UIView+FLEX_Layout.h
|
||||
// FLEX
|
||||
//
|
||||
// Created by Tanner Bennett on 7/18/19.
|
||||
// Copyright © 2020 FLEX Team. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
#define Padding(p) UIEdgeInsetsMake(p, p, p, p)
|
||||
|
||||
@interface UIView (FLEX_Layout)
|
||||
|
||||
- (void)centerInView:(UIView *)view;
|
||||
- (void)pinEdgesTo:(UIView *)view;
|
||||
- (void)pinEdgesTo:(UIView *)view withInsets:(UIEdgeInsets)insets;
|
||||
- (void)pinEdgesToSuperview;
|
||||
- (void)pinEdgesToSuperviewWithInsets:(UIEdgeInsets)insets;
|
||||
- (void)pinEdgesToSuperviewWithInsets:(UIEdgeInsets)insets aboveView:(UIView *)sibling;
|
||||
- (void)pinEdgesToSuperviewWithInsets:(UIEdgeInsets)insets belowView:(UIView *)sibling;
|
||||
|
||||
@end
|
||||
+9
-9
@@ -10,14 +10,14 @@
|
||||
|
||||
@implementation UIView (FLEX_Layout)
|
||||
|
||||
- (void)flex_centerInView:(UIView *)view {
|
||||
- (void)centerInView:(UIView *)view {
|
||||
[NSLayoutConstraint activateConstraints:@[
|
||||
[self.centerXAnchor constraintEqualToAnchor:view.centerXAnchor],
|
||||
[self.centerYAnchor constraintEqualToAnchor:view.centerYAnchor],
|
||||
]];
|
||||
}
|
||||
|
||||
- (void)flex_pinEdgesTo:(UIView *)view {
|
||||
- (void)pinEdgesTo:(UIView *)view {
|
||||
[NSLayoutConstraint activateConstraints:@[
|
||||
[self.topAnchor constraintEqualToAnchor:view.topAnchor],
|
||||
[self.leftAnchor constraintEqualToAnchor:view.leftAnchor],
|
||||
@@ -26,7 +26,7 @@
|
||||
]];
|
||||
}
|
||||
|
||||
- (void)flex_pinEdgesTo:(UIView *)view withInsets:(UIEdgeInsets)i {
|
||||
- (void)pinEdgesTo:(UIView *)view withInsets:(UIEdgeInsets)i {
|
||||
[NSLayoutConstraint activateConstraints:@[
|
||||
[self.topAnchor constraintEqualToAnchor:view.topAnchor constant:i.top],
|
||||
[self.leftAnchor constraintEqualToAnchor:view.leftAnchor constant:i.left],
|
||||
@@ -35,15 +35,15 @@
|
||||
]];
|
||||
}
|
||||
|
||||
- (void)flex_pinEdgesToSuperview {
|
||||
[self flex_pinEdgesTo:self.superview];
|
||||
- (void)pinEdgesToSuperview {
|
||||
[self pinEdgesTo:self.superview];
|
||||
}
|
||||
|
||||
- (void)flex_pinEdgesToSuperviewWithInsets:(UIEdgeInsets)insets {
|
||||
[self flex_pinEdgesTo:self.superview withInsets:insets];
|
||||
- (void)pinEdgesToSuperviewWithInsets:(UIEdgeInsets)insets {
|
||||
[self pinEdgesTo:self.superview withInsets:insets];
|
||||
}
|
||||
|
||||
- (void)flex_pinEdgesToSuperviewWithInsets:(UIEdgeInsets)i aboveView:(UIView *)sibling {
|
||||
- (void)pinEdgesToSuperviewWithInsets:(UIEdgeInsets)i aboveView:(UIView *)sibling {
|
||||
UIView *view = self.superview;
|
||||
[NSLayoutConstraint activateConstraints:@[
|
||||
[self.topAnchor constraintEqualToAnchor:view.topAnchor constant:i.top],
|
||||
@@ -53,7 +53,7 @@
|
||||
]];
|
||||
}
|
||||
|
||||
- (void)flex_pinEdgesToSuperviewWithInsets:(UIEdgeInsets)i belowView:(UIView *)sibling {
|
||||
- (void)pinEdgesToSuperviewWithInsets:(UIEdgeInsets)i belowView:(UIView *)sibling {
|
||||
UIView *view = self.superview;
|
||||
[NSLayoutConstraint activateConstraints:@[
|
||||
[self.topAnchor constraintEqualToAnchor:sibling.bottomAnchor constant:i.top],
|
||||
@@ -7,7 +7,6 @@
|
||||
//
|
||||
|
||||
#import "FLEXAlert.h"
|
||||
#import "FLEXMacros.h"
|
||||
|
||||
@interface FLEXAlert ()
|
||||
@property (nonatomic, readonly) UIAlertController *_controller;
|
||||
@@ -204,9 +203,10 @@ NSAssert(!self._action, @"Cannot mutate action after retreiving underlying UIAle
|
||||
FLEXAlertActionMutationAssertion();
|
||||
|
||||
// Get weak reference to the alert to avoid block <--> alert retain cycle
|
||||
UIAlertController *controller = self._controller; weakify(controller)
|
||||
self._handler = ^(UIAlertAction *action) { strongify(controller)
|
||||
__weak __typeof(self._controller) weakController = self._controller;
|
||||
self._handler = ^(UIAlertAction *action) {
|
||||
// Strongify that reference and pass the text field strings to the handler
|
||||
__strong __typeof(weakController) controller = weakController;
|
||||
NSArray *strings = [controller.textFields valueForKeyPath:@"text"];
|
||||
handler(strings);
|
||||
};
|
||||
|
||||
@@ -13,19 +13,11 @@
|
||||
#define ctor flex_keywordify __attribute__((constructor)) void __flex_ctor_##__LINE__()
|
||||
#define dtor flex_keywordify __attribute__((destructor)) void __flex_dtor_##__LINE__()
|
||||
|
||||
#define weakify(var) __weak __typeof(var) __weak__##var = var;
|
||||
|
||||
#define strongify(var) \
|
||||
_Pragma("clang diagnostic push") \
|
||||
_Pragma("clang diagnostic ignored \"-Wshadow\"") \
|
||||
__strong typeof(var) var = __weak__##var; \
|
||||
_Pragma("clang diagnostic pop")
|
||||
|
||||
// A macro to check if we are running in a test environment
|
||||
#define FLEX_IS_TESTING() (NSClassFromString(@"XCTest") != nil)
|
||||
|
||||
/// Whether we want the majority of constructors to run upon load or not.
|
||||
extern BOOL FLEXConstructorsShouldRun(void);
|
||||
extern BOOL FLEXConstructorsShouldRun();
|
||||
|
||||
/// A macro to return from the current procedure if we don't want to run constructors
|
||||
#define FLEX_EXIT_IF_NO_CTORS() if (!FLEXConstructorsShouldRun()) return;
|
||||
|
||||
@@ -52,8 +52,7 @@
|
||||
|
||||
#pragma mark - Misc Icons
|
||||
|
||||
@property (readonly, class) UIImage *checkerPattern;
|
||||
@property (readonly, class) UIColor *checkerPatternColor;
|
||||
@property (readonly, class) UIImage *hierarchyIndentPattern;
|
||||
@property(readonly, class) UIImage *checkerPattern;
|
||||
@property(readonly, class) UIImage *hierarchyIndentPattern;
|
||||
|
||||
@end
|
||||
|
||||
@@ -8838,10 +8838,6 @@ static const u_int8_t FLEXHierarchyIndentPattern3x[] = {
|
||||
return FLEXImage(FLEXCheckerPattern);
|
||||
}
|
||||
|
||||
+ (UIColor *)checkerPatternColor {
|
||||
return [UIColor colorWithPatternImage:FLEXResources.checkerPattern];
|
||||
}
|
||||
|
||||
+ (UIImage *)hierarchyIndentPattern {
|
||||
return FLEXImageTemplate(FLEXHierarchyIndentPattern);
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#import "FLEXAlert.h"
|
||||
#import "NSArray+FLEX.h"
|
||||
#import "UIFont+FLEX.h"
|
||||
#import "NSMapTable+FLEX_Subscripting.h"
|
||||
#import "FLEXMacros.h"
|
||||
|
||||
#if !FLEX_AT_LEAST_IOS13_SDK
|
||||
|
||||
@@ -15,21 +15,17 @@
|
||||
#import <zlib.h>
|
||||
|
||||
BOOL FLEXConstructorsShouldRun() {
|
||||
#if FLEX_DISABLE_CTORS
|
||||
return NO;
|
||||
#else
|
||||
static BOOL _FLEXConstructorsShouldRun_storage = YES;
|
||||
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
NSString *key = @"FLEX_SKIP_INIT";
|
||||
if (getenv(key.UTF8String) || [NSUserDefaults.standardUserDefaults boolForKey:key]) {
|
||||
_FLEXConstructorsShouldRun_storage = NO;
|
||||
}
|
||||
});
|
||||
|
||||
return _FLEXConstructorsShouldRun_storage;
|
||||
#endif
|
||||
static BOOL _FLEXConstructorsShouldRun_storage = YES;
|
||||
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
NSString *key = @"FLEX_SKIP_INIT";
|
||||
if (getenv(key.UTF8String) || [NSUserDefaults.standardUserDefaults boolForKey:key]) {
|
||||
_FLEXConstructorsShouldRun_storage = NO;
|
||||
}
|
||||
});
|
||||
|
||||
return _FLEXConstructorsShouldRun_storage;
|
||||
}
|
||||
|
||||
@implementation FLEXUtility
|
||||
@@ -135,7 +131,7 @@ BOOL FLEXConstructorsShouldRun() {
|
||||
|
||||
+ (UIImage *)previewImageForView:(UIView *)view {
|
||||
if (CGRectIsEmpty(view.bounds)) {
|
||||
return [UIImage new];
|
||||
return nil;
|
||||
}
|
||||
|
||||
CGSize viewSize = view.bounds.size;
|
||||
@@ -359,18 +355,14 @@ BOOL FLEXConstructorsShouldRun() {
|
||||
|
||||
id jsonObject = [NSJSONSerialization JSONObjectWithData:data options:0 error:NULL];
|
||||
if ([NSJSONSerialization isValidJSONObject:jsonObject]) {
|
||||
// Thanks RaziPour1993
|
||||
prettyString = [[NSString alloc]
|
||||
initWithData:[NSJSONSerialization
|
||||
dataWithJSONObject:jsonObject options:NSJSONWritingPrettyPrinted error:NULL
|
||||
]
|
||||
encoding:NSUTF8StringEncoding
|
||||
];
|
||||
prettyString = [NSString stringWithCString:[NSJSONSerialization
|
||||
dataWithJSONObject:jsonObject options:NSJSONWritingPrettyPrinted error:NULL
|
||||
].bytes encoding:NSUTF8StringEncoding];
|
||||
// NSJSONSerialization escapes forward slashes.
|
||||
// We want pretty json, so run through and unescape the slashes.
|
||||
prettyString = [prettyString stringByReplacingOccurrencesOfString:@"\\/" withString:@"/"];
|
||||
} else {
|
||||
prettyString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
|
||||
prettyString = [NSString stringWithCString:data.bytes encoding:NSUTF8StringEncoding];
|
||||
}
|
||||
|
||||
return prettyString;
|
||||
|
||||
@@ -53,7 +53,6 @@
|
||||
|
||||
/// Used to describe an object in brief within an explorer row
|
||||
+ (NSString *)summaryForObject:(id)value;
|
||||
+ (NSString *)safeClassNameForObject:(id)object;
|
||||
+ (NSString *)safeDescriptionForObject:(id)object;
|
||||
+ (NSString *)safeDebugDescriptionForObject:(id)object;
|
||||
|
||||
|
||||
@@ -96,18 +96,10 @@ typedef NS_ENUM(NSInteger, FLEXRuntimeUtilityErrorCode) {
|
||||
return superClasses;
|
||||
}
|
||||
|
||||
+ (NSString *)safeClassNameForObject:(id)object {
|
||||
// Don't assume that we have an NSObject subclass
|
||||
if ([self safeObject:object respondsToSelector:@selector(class)]) {
|
||||
return NSStringFromClass([object class]);
|
||||
}
|
||||
|
||||
return NSStringFromClass(object_getClass(object));
|
||||
}
|
||||
|
||||
/// Could be nil
|
||||
+ (NSString *)safeDescriptionForObject:(id)object {
|
||||
// Don't assume that we have an NSObject subclass; not all objects respond to -description
|
||||
// Don't assume that we have an NSObject subclass.
|
||||
// Check to make sure the object responds to the description method
|
||||
if ([self safeObject:object respondsToSelector:@selector(description)]) {
|
||||
return [object description];
|
||||
}
|
||||
@@ -119,6 +111,8 @@ typedef NS_ENUM(NSInteger, FLEXRuntimeUtilityErrorCode) {
|
||||
+ (NSString *)safeDebugDescriptionForObject:(id)object {
|
||||
NSString *description = nil;
|
||||
|
||||
// Don't assume that we have an NSObject subclass.
|
||||
// Check to make sure the object responds to the description method
|
||||
if ([self safeObject:object respondsToSelector:@selector(debugDescription)]) {
|
||||
description = [object debugDescription];
|
||||
} else {
|
||||
@@ -183,18 +177,18 @@ typedef NS_ENUM(NSInteger, FLEXRuntimeUtilityErrorCode) {
|
||||
}
|
||||
|
||||
+ (BOOL)safeObject:(id)object respondsToSelector:(SEL)sel {
|
||||
// If we're given a class, we want to know if classes respond to this selector.
|
||||
// Similarly, if we're given an instance, we want to know if instances respond.
|
||||
BOOL isClass = object_isClass(object);
|
||||
Class cls = isClass ? object : object_getClass(object);
|
||||
// BOOL isMetaclass = class_isMetaClass(cls);
|
||||
static BOOL (*respondsToSelector)(id, SEL, SEL) = nil;
|
||||
static BOOL (*respondsToSelector_meta)(id, SEL, SEL) = nil;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
respondsToSelector = (BOOL(*)(id, SEL, SEL))[NSObject instanceMethodForSelector:@selector(respondsToSelector:)];
|
||||
respondsToSelector_meta = (BOOL(*)(id, SEL, SEL))[NSObject methodForSelector:@selector(respondsToSelector:)];
|
||||
});
|
||||
|
||||
if (isClass) {
|
||||
// In theory, this should also work for metaclasses...
|
||||
return class_getClassMethod(cls, sel) != nil;
|
||||
} else {
|
||||
return class_getInstanceMethod(cls, sel) != nil;
|
||||
}
|
||||
BOOL isClass = object_isClass(object);
|
||||
return (isClass ? respondsToSelector_meta : respondsToSelector)(
|
||||
object, @selector(respondsToSelector:), sel
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -15,9 +15,6 @@
|
||||
@property (nonatomic) UIImage *image;
|
||||
@property (nonatomic) UIScrollView *scrollView;
|
||||
@property (nonatomic) UIImageView *imageView;
|
||||
@property (nonatomic) UITapGestureRecognizer *bgColorTapGesture;
|
||||
@property (nonatomic) NSInteger backgroundColorIndex;
|
||||
@property (nonatomic, readonly) NSArray<UIColor *> *backgroundColors;
|
||||
@end
|
||||
|
||||
#pragma mark -
|
||||
@@ -34,19 +31,19 @@
|
||||
}
|
||||
|
||||
+ (instancetype)forImage:(UIImage *)image {
|
||||
if (!image) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
return [[self alloc] initWithImage:image];
|
||||
}
|
||||
|
||||
- (id)initWithImage:(UIImage *)image {
|
||||
NSParameterAssert(image);
|
||||
|
||||
self = [super init];
|
||||
if (self) {
|
||||
self.title = @"Preview";
|
||||
self.image = image;
|
||||
_backgroundColors = @[FLEXResources.checkerPatternColor, UIColor.whiteColor, UIColor.blackColor];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
@@ -56,10 +53,12 @@
|
||||
- (void)viewDidLoad {
|
||||
[super viewDidLoad];
|
||||
|
||||
self.view.backgroundColor = [UIColor colorWithPatternImage:FLEXResources.checkerPattern];
|
||||
|
||||
self.imageView = [[UIImageView alloc] initWithImage:self.image];
|
||||
self.scrollView = [[UIScrollView alloc] initWithFrame:self.view.bounds];
|
||||
self.scrollView.delegate = self;
|
||||
self.scrollView.backgroundColor = self.backgroundColors.firstObject;
|
||||
self.scrollView.backgroundColor = self.view.backgroundColor;
|
||||
self.scrollView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
|
||||
[self.scrollView addSubview:self.imageView];
|
||||
self.scrollView.contentSize = self.imageView.frame.size;
|
||||
@@ -67,14 +66,7 @@
|
||||
self.scrollView.maximumZoomScale = 2.0;
|
||||
[self.view addSubview:self.scrollView];
|
||||
|
||||
self.bgColorTapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(changeBackground)];
|
||||
[self.scrollView addGestureRecognizer:self.bgColorTapGesture];
|
||||
|
||||
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc]
|
||||
initWithBarButtonSystemItem:UIBarButtonSystemItemAction
|
||||
target:self
|
||||
action:@selector(actionButtonPressed:)
|
||||
];
|
||||
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAction target:self action:@selector(actionButtonPressed:)];
|
||||
}
|
||||
|
||||
- (void)viewDidLayoutSubviews {
|
||||
@@ -107,12 +99,6 @@
|
||||
self.scrollView.contentInset = UIEdgeInsetsMake(verticalInset, horizontalInset, verticalInset, horizontalInset);
|
||||
}
|
||||
|
||||
- (void)changeBackground {
|
||||
self.backgroundColorIndex++;
|
||||
self.backgroundColorIndex %= self.backgroundColors.count;
|
||||
self.scrollView.backgroundColor = self.backgroundColors[self.backgroundColorIndex];
|
||||
}
|
||||
|
||||
- (void)actionButtonPressed:(id)sender {
|
||||
static BOOL canSaveToCameraRoll = NO, didShowWarning = NO;
|
||||
static dispatch_once_t onceToken;
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
|
||||
#import "FLEXColor.h"
|
||||
#import "FLEXHierarchyTableViewController.h"
|
||||
#import "NSMapTable+FLEX_Subscripting.h"
|
||||
#import "FLEXUtility.h"
|
||||
#import "FLEXHierarchyTableViewCell.h"
|
||||
#import "FLEXObjectExplorerViewController.h"
|
||||
|
||||
@@ -23,7 +23,6 @@
|
||||
C386D70C241AA67800699085 /* Owner.m in Sources */ = {isa = PBXBuildFile; fileRef = C386D70A241AA67800699085 /* Owner.m */; };
|
||||
C3A67856241AB8AD005A4681 /* MiscNetworkRequests.m in Sources */ = {isa = PBXBuildFile; fileRef = C3A67855241AB8AD005A4681 /* MiscNetworkRequests.m */; };
|
||||
C3A67858241ADDF7005A4681 /* Commit.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3A67857241ADDF7005A4681 /* Commit.swift */; };
|
||||
C3B3760025B8CDA300AD43AB /* Person.m in Sources */ = {isa = PBXBuildFile; fileRef = C3B375FF25B8CDA300AD43AB /* Person.m */; };
|
||||
E211705F801A8167D308F94A /* libPods-FLEXample.a in Frameworks */ = {isa = PBXBuildFile; fileRef = BBD699DDBAC5A16D8CFD39AC /* libPods-FLEXample.a */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
@@ -55,8 +54,6 @@
|
||||
C3A67854241AB8AD005A4681 /* MiscNetworkRequests.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MiscNetworkRequests.h; sourceTree = "<group>"; };
|
||||
C3A67855241AB8AD005A4681 /* MiscNetworkRequests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MiscNetworkRequests.m; sourceTree = "<group>"; };
|
||||
C3A67857241ADDF7005A4681 /* Commit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Commit.swift; sourceTree = "<group>"; };
|
||||
C3B375FE25B8CDA300AD43AB /* Person.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Person.h; sourceTree = "<group>"; };
|
||||
C3B375FF25B8CDA300AD43AB /* Person.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Person.m; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
@@ -141,8 +138,6 @@
|
||||
C3A67852241AB86D005A4681 /* App */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
C3B375FE25B8CDA300AD43AB /* Person.h */,
|
||||
C3B375FF25B8CDA300AD43AB /* Person.m */,
|
||||
C3A67857241ADDF7005A4681 /* Commit.swift */,
|
||||
C386D6E42419984700699085 /* CommitListViewController.h */,
|
||||
C386D6E52419984700699085 /* CommitListViewController.m */,
|
||||
@@ -284,7 +279,6 @@
|
||||
files = (
|
||||
C3A67858241ADDF7005A4681 /* Commit.swift in Sources */,
|
||||
C386D6D02419975A00699085 /* AppDelegate.swift in Sources */,
|
||||
C3B3760025B8CDA300AD43AB /* Person.m in Sources */,
|
||||
C386D6E62419984700699085 /* CommitListViewController.m in Sources */,
|
||||
C386D70B241AA67800699085 /* Dog.m in Sources */,
|
||||
C3A67856241AB8AD005A4681 /* MiscNetworkRequests.m in Sources */,
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
|
||||
#import "CommitListViewController.h"
|
||||
#import "FLEXample-Swift.h"
|
||||
#import "Person.h"
|
||||
#import <FLEX.h>
|
||||
|
||||
@interface CommitListViewController ()
|
||||
@@ -47,18 +46,6 @@
|
||||
];
|
||||
}
|
||||
}];
|
||||
|
||||
FLEXManager *flex = FLEXManager.sharedManager;
|
||||
|
||||
// Register 't' for testing: quickly present an object explorer for debugging
|
||||
[flex registerSimulatorShortcutWithKey:@"t" modifiers:0 action:^{
|
||||
[flex showExplorer];
|
||||
[flex presentTool:^UINavigationController *{
|
||||
return [FLEXNavigationController withRootViewController:[FLEXObjectExplorerFactory
|
||||
explorerViewControllerForObject:Person.bob
|
||||
]];
|
||||
} completion:nil];
|
||||
} description:@"Present an object explorer for debugging"];
|
||||
}
|
||||
|
||||
- (void)viewWillAppear:(BOOL)animated {
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
//
|
||||
// Person.h
|
||||
// UICatalog
|
||||
//
|
||||
// Created by Tanner on 4/17/19.
|
||||
// Copyright © 2019 . All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface Person : NSObject <NSCoding>
|
||||
|
||||
+ (instancetype)bob;
|
||||
|
||||
@property (nonatomic, readonly) NSString *name;
|
||||
@property (nonatomic, readonly) NSInteger age;
|
||||
@property (nonatomic, readonly) CGFloat height;
|
||||
@property (nonatomic, readonly) NSNumber *numberOfKids;
|
||||
|
||||
@property (nonatomic) NSDecimalNumber *netWorth;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -1,63 +0,0 @@
|
||||
//
|
||||
// Person.m
|
||||
// UICatalog
|
||||
//
|
||||
// Created by Tanner on 4/17/19.
|
||||
// Copyright © 2019 f. All rights reserved.
|
||||
//
|
||||
|
||||
#import "Person.h"
|
||||
|
||||
@implementation Person
|
||||
|
||||
+ (id)bob {
|
||||
Person *bob = [Person new];
|
||||
bob->_name = @"Bob";
|
||||
bob->_age = 50;
|
||||
bob->_height = 5.8;
|
||||
bob->_numberOfKids = @3;
|
||||
bob->_netWorth = [NSDecimalNumber decimalNumberWithString:@"12345.67"];
|
||||
return bob;
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(nonnull NSCoder *)coder {
|
||||
[coder encodeObject:self.name forKey:@"name"];
|
||||
[coder encodeInteger:self.age forKey:@"age"];
|
||||
[coder encodeDouble:self.height forKey:@"height"];
|
||||
[coder encodeObject:self.numberOfKids forKey:@"numberOfKids"];
|
||||
[coder encodeObject:self.netWorth forKey:@"netWorth"];
|
||||
}
|
||||
|
||||
- (nullable instancetype)initWithCoder:(nonnull NSCoder *)coder {
|
||||
self->_name = [coder decodeObjectForKey:@"name"];
|
||||
self->_age = [coder decodeIntegerForKey:@"age"];
|
||||
self->_height = [coder decodeDoubleForKey:@"height"];
|
||||
self->_numberOfKids = [coder decodeObjectForKey:@"numberOfKids"];
|
||||
self->_netWorth = [coder decodeObjectForKey:@"netWorth"];
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setNetWorth:(NSDecimalNumber *)netWorth {
|
||||
_netWorth = netWorth;
|
||||
}
|
||||
|
||||
- (NSUInteger)hash {
|
||||
return self.name.hash ^ @(self.age).hash ^ self.numberOfKids.hash ^ self.netWorth.hash;
|
||||
}
|
||||
|
||||
- (BOOL)isEqual:(id)object {
|
||||
if ([object isKindOfClass:[Person class]])
|
||||
return [self isEqualToPerson:object];
|
||||
|
||||
return [super isEqual:object];
|
||||
}
|
||||
|
||||
- (BOOL)isEqualToPerson:(Person *)person {
|
||||
return [self.name isEqualToString:person.name];
|
||||
}
|
||||
|
||||
+ (NSInteger)version {
|
||||
return 2;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -12,15 +12,6 @@ import UIKit
|
||||
class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
var repeatingLogExampleTimer: Timer!
|
||||
var window: UIWindow?
|
||||
|
||||
@available(iOS 13.0, *)
|
||||
func application(_ application: UIApplication,
|
||||
configurationForConnecting session: UISceneSession,
|
||||
options: UIScene.ConnectionOptions) -> UISceneConfiguration {
|
||||
// Called when a new scene session is being created.
|
||||
// Use this method to select a configuration to create the new scene with.
|
||||
return UISceneConfiguration(name: nil, sessionRole: session.role)
|
||||
}
|
||||
|
||||
func application(_ application: UIApplication,
|
||||
didFinishLaunchingWithOptions options: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
|
||||
@@ -30,53 +21,48 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
UserDefaults.standard.set("foo", forKey: "FLEXamplePrefFoo")
|
||||
|
||||
// To show off the system log viewer, send 10 example log messages at 3 second intervals
|
||||
self.repeatingLogExampleTimer = Timer.scheduledTimer(withTimeInterval: 3, repeats: true) { [weak self] (_) in
|
||||
if let self = self {
|
||||
NSLog("Example log \(self.exampleLogSent)")
|
||||
|
||||
self.exampleLogSent += 1
|
||||
if self.exampleLogSent > self.exampleLogLimit {
|
||||
self.repeatingLogExampleTimer.invalidate()
|
||||
}
|
||||
}
|
||||
}
|
||||
self.repeatingLogExampleTimer = Timer(
|
||||
timeInterval: 3, target: self,
|
||||
selector: #selector(sendExampleLogMessage),
|
||||
userInfo: nil, repeats: true
|
||||
)
|
||||
|
||||
// To show off the network logger, send several misc network requests
|
||||
MiscNetworkRequests.sendExampleRequests()
|
||||
|
||||
// For testing unarchiving of objects
|
||||
self.archiveBob()
|
||||
|
||||
// For < iOS 13, set up the window here
|
||||
self.setupWindow()
|
||||
if ProcessInfo.processInfo.operatingSystemVersion.majorVersion < 13 {
|
||||
let window = UIWindow(frame: UIScreen.main.bounds)
|
||||
window.rootViewController = FLEXNavigationController(
|
||||
rootViewController: CommitListViewController()
|
||||
)
|
||||
self.window = window
|
||||
window.makeKeyAndVisible()
|
||||
FLEXManager.shared.showExplorer()
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func setupWindow() {
|
||||
guard ProcessInfo.processInfo.operatingSystemVersion.majorVersion < 13 else {
|
||||
return
|
||||
}
|
||||
|
||||
let window = UIWindow(frame: UIScreen.main.bounds)
|
||||
window.rootViewController = FLEXNavigationController(
|
||||
rootViewController: CommitListViewController()
|
||||
)
|
||||
self.window = window
|
||||
window.makeKeyAndVisible()
|
||||
FLEXManager.shared.showExplorer()
|
||||
|
||||
@available(iOS 13.0, *)
|
||||
func application(_ application: UIApplication,
|
||||
configurationForConnecting session: UISceneSession,
|
||||
options: UIScene.ConnectionOptions) -> UISceneConfiguration {
|
||||
// Called when a new scene session is being created.
|
||||
// Use this method to select a configuration to create the new scene with.
|
||||
return UISceneConfiguration(name: nil, sessionRole: session.role)
|
||||
}
|
||||
|
||||
func archiveBob() {
|
||||
let documents = NSSearchPathForDirectoriesInDomains(
|
||||
.documentDirectory, .userDomainMask, true
|
||||
).first! as NSString
|
||||
let whereToSaveBob = documents.appendingPathComponent("Bob.plist")
|
||||
try! NSKeyedArchiver.archivedData(
|
||||
withRootObject: Person.bob(), requiringSecureCoding: false
|
||||
).write(to: URL(fileURLWithPath: whereToSaveBob), options: [])
|
||||
}
|
||||
|
||||
let exampleLogLimit = 10
|
||||
var exampleLogSent = 0
|
||||
|
||||
func sendExampleLogMessage() {
|
||||
NSLog("Example log \(self.exampleLogSent)")
|
||||
|
||||
self.exampleLogSent += 1
|
||||
if self.exampleLogSent > self.exampleLogLimit {
|
||||
self.repeatingLogExampleTimer.invalidate()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,4 +5,3 @@
|
||||
#import <FLEX.h>
|
||||
#import "MiscNetworkRequests.h"
|
||||
#import "CommitListViewController.h"
|
||||
#import "Person.h"
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
Pod::Spec.new do |spec|
|
||||
spec.name = "FLEX"
|
||||
spec.version = "4.4.0"
|
||||
spec.version = "4.2"
|
||||
spec.summary = "A set of in-app debugging and exploration tools for iOS"
|
||||
spec.description = <<-DESC
|
||||
- Inspect and modify views in the hierarchy.
|
||||
|
||||
@@ -204,7 +204,6 @@
|
||||
C36B096623E0D4A1008F5D47 /* UIMenu+FLEX.m in Sources */ = {isa = PBXBuildFile; fileRef = C36B096423E0D4A1008F5D47 /* UIMenu+FLEX.m */; };
|
||||
C36B097023E1EDCD008F5D47 /* FLEXTableViewSection.h in Headers */ = {isa = PBXBuildFile; fileRef = C36B096E23E1EDCD008F5D47 /* FLEXTableViewSection.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
C36B097123E1EDCD008F5D47 /* FLEXTableViewSection.m in Sources */ = {isa = PBXBuildFile; fileRef = C36B096F23E1EDCD008F5D47 /* FLEXTableViewSection.m */; };
|
||||
C36E1B26259D64CC00FEFEF6 /* FLEXNewRootClass.m in Sources */ = {isa = PBXBuildFile; fileRef = C36E1B25259D64CC00FEFEF6 /* FLEXNewRootClass.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; };
|
||||
C36FBFCB230F3B98008D95D5 /* FLEXMirror.m in Sources */ = {isa = PBXBuildFile; fileRef = C36FBFB9230F3B97008D95D5 /* FLEXMirror.m */; };
|
||||
C36FBFCC230F3B98008D95D5 /* FLEXProtocolBuilder.h in Headers */ = {isa = PBXBuildFile; fileRef = C36FBFBA230F3B97008D95D5 /* FLEXProtocolBuilder.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
C36FBFCD230F3B98008D95D5 /* FLEXMethod.m in Sources */ = {isa = PBXBuildFile; fileRef = C36FBFBB230F3B97008D95D5 /* FLEXMethod.m */; };
|
||||
@@ -568,8 +567,6 @@
|
||||
C36B096423E0D4A1008F5D47 /* UIMenu+FLEX.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "UIMenu+FLEX.m"; sourceTree = "<group>"; };
|
||||
C36B096E23E1EDCD008F5D47 /* FLEXTableViewSection.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FLEXTableViewSection.h; sourceTree = "<group>"; };
|
||||
C36B096F23E1EDCD008F5D47 /* FLEXTableViewSection.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FLEXTableViewSection.m; sourceTree = "<group>"; };
|
||||
C36E1B24259D64CC00FEFEF6 /* FLEXNewRootClass.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FLEXNewRootClass.h; sourceTree = "<group>"; };
|
||||
C36E1B25259D64CC00FEFEF6 /* FLEXNewRootClass.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FLEXNewRootClass.m; sourceTree = "<group>"; };
|
||||
C36FBFB9230F3B97008D95D5 /* FLEXMirror.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLEXMirror.m; sourceTree = "<group>"; };
|
||||
C36FBFBA230F3B97008D95D5 /* FLEXProtocolBuilder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FLEXProtocolBuilder.h; sourceTree = "<group>"; };
|
||||
C36FBFBB230F3B97008D95D5 /* FLEXMethod.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLEXMethod.m; sourceTree = "<group>"; };
|
||||
@@ -741,7 +738,6 @@
|
||||
1C27A8B81F0E5A0400F0D02D /* FLEXTestsMethodsList.m */,
|
||||
C3854DEF23F36C1700FCD1E2 /* FLEXTypeEncodingParserTests.m */,
|
||||
1C27A8BA1F0E5A0400F0D02D /* Info.plist */,
|
||||
C36E1B27259D64D300FEFEF6 /* Supporting Files */,
|
||||
);
|
||||
path = FLEXTests;
|
||||
sourceTree = "<group>";
|
||||
@@ -1091,23 +1087,6 @@
|
||||
path = Objc;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
C31E4E53259D4A4100712288 /* Private */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
C362AE7F23C7E9D1005A86AE /* NSMapTable+FLEX_Subscripting.h */,
|
||||
C362AE8023C7E9D1005A86AE /* NSMapTable+FLEX_Subscripting.m */,
|
||||
C387C88122E0D24A00750E58 /* UIView+FLEX_Layout.h */,
|
||||
C387C88222E0D24A00750E58 /* UIView+FLEX_Layout.m */,
|
||||
C3F9777F2311B38F0032776D /* NSDictionary+ObjcRuntime.h */,
|
||||
C3F9777E2311B38E0032776D /* NSDictionary+ObjcRuntime.m */,
|
||||
C3F9777D2311B38E0032776D /* NSString+ObjcRuntime.h */,
|
||||
C3F977802311B38F0032776D /* NSString+ObjcRuntime.m */,
|
||||
C398627423AD79B6007E6793 /* NSString+FLEX.h */,
|
||||
C398627523AD79B7007E6793 /* NSString+FLEX.m */,
|
||||
);
|
||||
path = Private;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
C33CF16B22D664E600F9C6C0 /* FileBrowser */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -1184,15 +1163,6 @@
|
||||
path = Cells;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
C36E1B27259D64D300FEFEF6 /* Supporting Files */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
C36E1B24259D64CC00FEFEF6 /* FLEXNewRootClass.h */,
|
||||
C36E1B25259D64CC00FEFEF6 /* FLEXNewRootClass.m */,
|
||||
);
|
||||
path = "Supporting Files";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
C36FBFB8230F3B52008D95D5 /* Runtime */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -1246,9 +1216,10 @@
|
||||
C387C88022E0D22600750E58 /* Categories */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
C31E4E53259D4A4100712288 /* Private */,
|
||||
C3DFCDB62418336D00BB7084 /* NSUserDefaults+FLEX.h */,
|
||||
C3DFCDB72418336D00BB7084 /* NSUserDefaults+FLEX.m */,
|
||||
C387C88122E0D24A00750E58 /* UIView+FLEX_Layout.h */,
|
||||
C387C88222E0D24A00750E58 /* UIView+FLEX_Layout.m */,
|
||||
C398627023AD7951007E6793 /* UIGestureRecognizer+Blocks.h */,
|
||||
C398627123AD7951007E6793 /* UIGestureRecognizer+Blocks.m */,
|
||||
C3F646BF239EAA8F00D4A011 /* UIPasteboard+FLEX.h */,
|
||||
@@ -1263,10 +1234,18 @@
|
||||
C3BFD06F233C23ED0015FB82 /* NSArray+FLEX.m */,
|
||||
C3F977812311B38F0032776D /* NSObject+FLEX_Reflection.h */,
|
||||
C3F977822311B38F0032776D /* NSObject+FLEX_Reflection.m */,
|
||||
C3F9777F2311B38F0032776D /* NSDictionary+ObjcRuntime.h */,
|
||||
C3F9777E2311B38E0032776D /* NSDictionary+ObjcRuntime.m */,
|
||||
C3F9777D2311B38E0032776D /* NSString+ObjcRuntime.h */,
|
||||
C3F977802311B38F0032776D /* NSString+ObjcRuntime.m */,
|
||||
C398627423AD79B6007E6793 /* NSString+FLEX.h */,
|
||||
C398627523AD79B7007E6793 /* NSString+FLEX.m */,
|
||||
C3E5D9FB2316E83700E655DB /* FLEXRuntime+Compare.h */,
|
||||
C3E5D9FC2316E83700E655DB /* FLEXRuntime+Compare.m */,
|
||||
C34C9BDB23A7F2740031CA3E /* FLEXRuntime+UIKitHelpers.h */,
|
||||
C34C9BDC23A7F2740031CA3E /* FLEXRuntime+UIKitHelpers.m */,
|
||||
C362AE7F23C7E9D1005A86AE /* NSMapTable+FLEX_Subscripting.h */,
|
||||
C362AE8023C7E9D1005A86AE /* NSMapTable+FLEX_Subscripting.m */,
|
||||
C36B096323E0D4A1008F5D47 /* UIMenu+FLEX.h */,
|
||||
C36B096423E0D4A1008F5D47 /* UIMenu+FLEX.m */,
|
||||
C3694DC023EA147F006625D7 /* UIBarButtonItem+FLEX.h */,
|
||||
@@ -1671,7 +1650,7 @@
|
||||
3A4C941E1B5B20570088C3F2 = {
|
||||
CreatedOnToolsVersion = 6.4;
|
||||
LastSwiftMigration = 1130;
|
||||
ProvisioningStyle = Automatic;
|
||||
ProvisioningStyle = Manual;
|
||||
};
|
||||
};
|
||||
};
|
||||
@@ -1717,7 +1696,6 @@
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
C36E1B26259D64CC00FEFEF6 /* FLEXNewRootClass.m in Sources */,
|
||||
C33C825B23159EAF00DD2451 /* FLEXTests.m in Sources */,
|
||||
1C27A8B91F0E5A0400F0D02D /* FLEXTestsMethodsList.m in Sources */,
|
||||
C3854DF023F36C1700FCD1E2 /* FLEXTypeEncodingParserTests.m in Sources */,
|
||||
@@ -2061,10 +2039,9 @@
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = NO;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = NO;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_IDENTITY = "";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
|
||||
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
DEFINES_MODULE = YES;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
@@ -2081,7 +2058,6 @@
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.flipboard.$(PRODUCT_NAME:rfc1034identifier)";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
"PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = "";
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 5.0;
|
||||
@@ -2096,10 +2072,9 @@
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = NO;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = NO;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_IDENTITY = "";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
|
||||
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
DEFINES_MODULE = YES;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
@@ -2116,7 +2091,6 @@
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.flipboard.$(PRODUCT_NAME:rfc1034identifier)";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
"PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = "";
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_VERSION = 5.0;
|
||||
WARNING_CFLAGS = "-Wno-unsupported-availability-guard";
|
||||
|
||||
+1
-20
@@ -13,10 +13,8 @@
|
||||
#import "FLEXPropertyAttributes.h"
|
||||
#import "FLEXProperty.h"
|
||||
#import "FLEXUtility.h"
|
||||
#import "FLEXRuntimeUtility.h"
|
||||
#import "FLEXMethod.h"
|
||||
#import "FLEXIvar.h"
|
||||
#import "FLEXNewRootClass.h"
|
||||
|
||||
@interface Subclass : NSObject {
|
||||
@public
|
||||
@@ -41,7 +39,6 @@
|
||||
- (void)testAssumptionsAboutClasses {
|
||||
Class cls = [self class];
|
||||
Class meta = objc_getMetaClass(NSStringFromClass(cls).UTF8String);
|
||||
Class rootMeta = object_getClass(meta);
|
||||
|
||||
// Subsequent `class` calls yield self
|
||||
XCTAssertEqual(cls, [cls class]);
|
||||
@@ -53,7 +50,7 @@
|
||||
|
||||
// Subsequent object_getClass calls yield metaclass
|
||||
XCTAssertEqual(object_getClass(cls), meta);
|
||||
XCTAssertEqual(object_getClass(object_getClass(meta)), rootMeta);
|
||||
XCTAssertEqual(object_getClass(meta), meta);
|
||||
|
||||
// Superclass of a root class is nil
|
||||
XCTAssertNil(NSObject.superclass);
|
||||
@@ -124,20 +121,4 @@
|
||||
XCTAssertEqual(pointerValue[0], 0xaa);
|
||||
}
|
||||
|
||||
- (void)testSafeRespondsToSelector {
|
||||
XCTAssertFalse([FLEXRuntimeUtility
|
||||
safeObject:[NSObject class] respondsToSelector:@selector(testSafeRespondsToSelector)
|
||||
]);
|
||||
|
||||
Class root = NSClassFromString(@"FLEXNewRootClass");
|
||||
XCTAssertTrue([FLEXRuntimeUtility safeObject:root respondsToSelector:@selector(theOnlyMethod)]);
|
||||
XCTAssertFalse([FLEXRuntimeUtility safeObject:root respondsToSelector:@selector(class)]);
|
||||
}
|
||||
|
||||
- (void)testSafeGetClassName {
|
||||
id instance = [NSClassFromString(@"FLEXNewRootClass") alloc];
|
||||
NSString *className = [FLEXRuntimeUtility safeClassNameForObject:instance];
|
||||
XCTAssertEqualObjects(@"FLEXNewRootClass", className);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
//
|
||||
// FLEXNewRootClass.h
|
||||
// FLEXTests
|
||||
//
|
||||
// Created by Tanner on 12/30/20.
|
||||
// Copyright © 2020 Flipboard. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
/// Root class with one method
|
||||
OBJC_ROOT_CLASS
|
||||
@interface FLEXNewRootClass {
|
||||
Class isa OBJC_ISA_AVAILABILITY;
|
||||
}
|
||||
|
||||
- (void)theOnlyMethod;
|
||||
|
||||
@end
|
||||
@@ -1,25 +0,0 @@
|
||||
//
|
||||
// FLEXNewRootClass.m
|
||||
// FLEXTests
|
||||
//
|
||||
// Created by Tanner on 12/30/20.
|
||||
// Copyright © 2020 Flipboard. All rights reserved.
|
||||
//
|
||||
|
||||
#import "FLEXNewRootClass.h"
|
||||
#import <objc/runtime.h>
|
||||
|
||||
@implementation FLEXNewRootClass
|
||||
|
||||
+ (id)alloc {
|
||||
FLEXNewRootClass *obj = (__bridge id)calloc(1, class_getInstanceSize(self));
|
||||
object_setClass(obj, self);
|
||||
return obj;
|
||||
}
|
||||
|
||||
- (void)theOnlyMethod { }
|
||||
|
||||
- (void)retain { }
|
||||
- (void)release { }
|
||||
|
||||
@end
|
||||
@@ -66,10 +66,6 @@ More complete version:
|
||||
}
|
||||
```
|
||||
|
||||
#### Aside: tvOS
|
||||
|
||||
FLEX itself does not support tvOS out of the box. However, others have taken it upon themselves to port FLEX to tvOS. If you need tvOS support, seek out one of these forks. [Here is one such fork.](https://github.com/lechium/FLEX/tree/tvos)
|
||||
|
||||
|
||||
## Feature Examples
|
||||
### Modify Views
|
||||
|
||||
Reference in New Issue
Block a user