Просмотр исходного кода

Add context menus to metadata in the object explorer

Tanner Bennett лет назад: 6
Родитель
Сommit
bf649ff1f6

+ 1 - 1
Classes/GlobalStateExplorers/RuntimeBrowser/TBKeyPathSearchController.m

@@ -9,7 +9,7 @@
 #import "TBKeyPathSearchController.h"
 #import "TBKeyPathTokenizer.h"
 #import "TBRuntimeController.h"
-#import "NSString+KeyPaths.h"
+#import "NSString+FLEX.h"
 #import "NSArray+Functional.h"
 #import "UITextField+Range.h"
 #import "NSTimer+Blocks.h"

+ 21 - 0
Classes/ObjectExplorers/FLEXObjectExplorerViewController.m

@@ -415,6 +415,27 @@
     [self.sections[indexPath.section] didPressInfoButtonAction:indexPath.row](self);
 }
 
+#if FLEX_AT_LEAST_IOS13_SDK
+
+- (UIContextMenuConfiguration *)tableView:(UITableView *)tableView contextMenuConfigurationForRowAtIndexPath:(NSIndexPath *)indexPath point:(CGPoint)point __IOS_AVAILABLE(13.0) {
+    FLEXExplorerSection *section = self.sections[indexPath.section];
+    NSString *title = [section menuTitleForRow:indexPath.row];
+    NSArray<UIMenuElement *> *menuItems = [section menuItemsForRow:indexPath.row sender:self];
+    
+    if (menuItems.count) {
+        return [UIContextMenuConfiguration
+            configurationWithIdentifier:nil
+            previewProvider:nil
+            actionProvider:^UIMenu *(NSArray<UIMenuElement *> *suggestedActions) {
+                return [UIMenu menuWithTitle:title children:menuItems];
+            }
+        ];
+    }
+    
+    return nil;
+}
+
+#endif
 
 #pragma mark - UIMenuController
 

+ 26 - 2
Classes/ObjectExplorers/Sections/FLEXExplorerSection.h

@@ -7,6 +7,7 @@
 //
 
 #import <UIKit/UIKit.h>
+#import "FLEXUtility.h"
 @class FLEXTableView;
 
 #pragma mark FLEXExplorerSection
@@ -36,7 +37,7 @@
 /// This is called before reloading the table view itself.
 - (void)reloadData;
 
-#pragma mark - Row selection
+#pragma mark - Row Selection
 
 /// Whether the given row should be selectable, such as if tapping the cell
 /// should take the user to a new screen or trigger an action.
@@ -64,7 +65,30 @@
 /// @return \c nil by default.
 - (void(^)(UIViewController *host))didPressInfoButtonAction:(NSInteger)row;
 
-#pragma mark - Cell configuration
+#pragma mark - Context Menus
+#if FLEX_AT_LEAST_IOS13_SDK
+
+/// By default, this is the title of the row.
+/// @return The title of the context menu, if any.
+- (NSString *)menuTitleForRow:(NSInteger)row API_AVAILABLE(ios(13.0));
+/// Protected, not intended for public use. \c menuTitleForRow:
+/// already includes the value returned from this method.
+/// 
+/// By default, this returns \c @"". Subclasses may override to
+/// provide a detailed description of the target of the context menu.
+- (NSString *)menuSubtitleForRow:(NSInteger)row API_AVAILABLE(ios(13.0));
+/// The context menu items, if any. Subclasses may override.
+/// By default, only inludes items for \c copyMenuItemsForRow:.
+- (NSArray<UIMenuElement *> *)menuItemsForRow:(NSInteger)row sender:(UIViewController *)sender API_AVAILABLE(ios(13.0));
+/// Subclasses may override to return a list of copiable items.
+/// 
+/// Every two elements in the list compose a key-value pair, where the key
+/// should be a description of what will be copied, and the values should be
+/// the strings to copy. Return an empty string as a value to show a disabled action.
+- (NSArray<NSString *> *)copyMenuItemsForRow:(NSInteger)row API_AVAILABLE(ios(13.0));
+#endif
+
+#pragma mark - Cell Configuration
 
 /// Provide a reuse identifier for the given row. Subclasses should override.
 ///

+ 70 - 0
Classes/ObjectExplorers/Sections/FLEXExplorerSection.m

@@ -8,6 +8,7 @@
 
 #import "FLEXExplorerSection.h"
 #import "FLEXTableView.h"
+#import "UIMenu+FLEX.h"
 
 #pragma clang diagnostic push
 #pragma clang diagnostic ignored "-Wincomplete-implementation"
@@ -45,6 +46,75 @@
     return kFLEXDefaultCell;
 }
 
+#if FLEX_AT_LEAST_IOS13_SDK
+
+- (NSString *)menuTitleForRow:(NSInteger)row {
+    NSString *title = [self titleForRow:row];
+    NSString *subtitle = [self menuSubtitleForRow:row];
+    
+    if (subtitle.length) {
+        return [NSString stringWithFormat:@"%@\n\n%@", title, subtitle];
+    }
+    
+    return title;
+}
+
+- (NSString *)menuSubtitleForRow:(NSInteger)row {
+    return @"";
+}
+
+- (NSArray<UIMenuElement *> *)menuItemsForRow:(NSInteger)row sender:(UIViewController *)sender {
+    NSArray<NSString *> *copyItems = [self copyMenuItemsForRow:row];
+    NSAssert(copyItems.count % 2 == 0, @"copyMenuItemsForRow: should return an even list");
+    
+    if (copyItems.count) {
+        NSInteger numberOfActions = copyItems.count / 2;
+        BOOL collapseMenu = numberOfActions > 4;
+        UIImage *copyIcon = [UIImage systemImageNamed:@"doc.on.doc"];
+        
+        NSMutableArray *actions = [NSMutableArray new];
+        
+        for (NSInteger i = 0; i < copyItems.count; i += 2) {
+            NSString *key = copyItems[i], *value = copyItems[i+1];
+            NSString *title = collapseMenu ? key : [@"Copy " stringByAppendingString:key];
+            
+            UIAction *copy = [UIAction
+                actionWithTitle:title
+                image:copyIcon
+                identifier:nil
+                handler:^(__kindof UIAction *action) {
+                    UIPasteboard.generalPasteboard.string = value;
+                }
+            ];
+            if (!value.length) {
+                copy.attributes = UIMenuElementAttributesDisabled;
+            }
+            
+            [actions addObject:copy];
+        }
+        
+        UIMenu *copyMenu = [UIMenu
+            inlineMenuWithTitle:@"Copy…" 
+            image:copyIcon
+            children:actions
+        ];
+        
+        if (collapseMenu) {
+            return @[[copyMenu collapsed]];
+        } else {
+            return @[copyMenu];
+        }
+    }
+    
+    return @[];
+}
+
+#endif
+
+- (NSArray<NSString *> *)copyMenuItemsForRow:(NSInteger)row {
+    return nil;
+}
+
 - (NSString *)titleForRow:(NSInteger)row { return nil; }
 - (NSString *)subtitleForRow:(NSInteger)row { return nil; }
 

+ 40 - 0
Classes/ObjectExplorers/Sections/FLEXMetadataSection.m

@@ -189,4 +189,44 @@
     cell.accessoryType = [self accessoryTypeForRow:row];
 }
 
+#if FLEX_AT_LEAST_IOS13_SDK
+
+- (NSString *)menuSubtitleForRow:(NSInteger)row {
+    return [self.metadata[row] contextualSubtitleWithTarget:self.explorer.object];
+}
+
+- (NSArray<UIMenuElement *> *)menuItemsForRow:(NSInteger)row sender:(UIViewController *)sender {
+    NSArray<UIMenuElement *> *existingItems = [super menuItemsForRow:row sender:sender];
+    
+    // These two metadata kinds don't need an "Explore Metadata…" option
+    switch (self.metadataKind) {
+        case FLEXMetadataKindClassHierarchy:
+        case FLEXMetadataKindOther:
+            return existingItems;
+            
+        default: break;
+    }
+    
+    id metadata = self.metadata[row];
+    
+    UIAction *explore = [UIAction
+        actionWithTitle:@"Explore Metadata…"
+        image:nil
+        identifier:nil
+        handler:^(__kindof UIAction *action) {
+            [sender.navigationController pushViewController:[FLEXObjectExplorerFactory
+                explorerViewControllerForObject:metadata
+            ] animated:YES];
+        }
+    ];
+    
+    return [@[explore] arrayByAddingObjectsFromArray:existingItems];
+}
+
+- (NSArray<NSString *> *)copyMenuItemsForRow:(NSInteger)row {
+    return [self.metadata[row] copiableMetadataWithTarget:self.explorer.object];
+}
+
+#endif
+
 @end

+ 5 - 0
Classes/Utility/Categories/FLEXRuntime+UIKitHelpers.h

@@ -37,6 +37,11 @@
 - (UITableViewCellAccessoryType)suggestedAccessoryTypeWithTarget:(id)object;
 /// Return nil to use the default reuse identifier
 - (NSString *)reuseIdentifierWithTarget:(id)object;
+/// An array where every 2 elements are a key-value pair. The key is a description
+/// of what to copy like "Name" and the values are what will be copied.
+- (NSArray<NSString *> *)copiableMetadataWithTarget:(id)object;
+/// Properties and ivars return the address of an object, if they hold one.
+- (NSString *)contextualSubtitleWithTarget:(id)object;
 
 @end
 

+ 122 - 9
Classes/Utility/Categories/FLEXRuntime+UIKitHelpers.m

@@ -14,22 +14,24 @@
 #import "FLEXFieldEditorViewController.h"
 #import "FLEXMethodCallingViewController.h"
 #import "FLEXTableView.h"
+#import "FLEXUtility.h"
 #import "NSArray+Functional.h"
+#import "NSString+FLEX.h"
 
 #pragma mark FLEXProperty
 @implementation FLEXProperty (UIKitHelpers)
 
-/// Decide whether to use object or [object class] to get or set property
-- (id)targetForPropertyTypeGivenObject:(id)object {
-    if (!object_isClass(object)) {
+/// Decide whether to use potentialTarget or [potentialTarget class] to get or set property
+- (id)appropriateTargetForPropertyType:(id)potentialTarget {
+    if (!object_isClass(potentialTarget)) {
         if (self.isClassProperty) {
-            return [object class];
+            return [potentialTarget class];
         } else {
-            return object;
+            return potentialTarget;
         }
     } else {
         if (self.isClassProperty) {
-            return object;
+            return potentialTarget;
         } else {
             // Instance property with a class object
             return nil;
@@ -52,7 +54,13 @@
 
 - (id)currentValueWithTarget:(id)object {
     return [self getPotentiallyUnboxedValue:
-        [self targetForPropertyTypeGivenObject:object]
+        [self appropriateTargetForPropertyType:object]
+    ];
+}
+
+- (id)currentValueBeforeUnboxingWithTarget:(id)object {
+    return [self getValue:
+        [self appropriateTargetForPropertyType:object]
     ];
 }
 
@@ -72,12 +80,12 @@
 }
 
 - (UIViewController *)editorWithTarget:(id)object {
-    id target = [self targetForPropertyTypeGivenObject:object];
+    id target = [self appropriateTargetForPropertyType:object];
     return [FLEXFieldEditorViewController target:target property:self];
 }
 
 - (UITableViewCellAccessoryType)suggestedAccessoryTypeWithTarget:(id)object {
-    id targetForValueCheck = [self targetForPropertyTypeGivenObject:object];
+    id targetForValueCheck = [self appropriateTargetForPropertyType:object];
     if (!targetForValueCheck) {
         // Instance property with a class object
         return UITableViewCellAccessoryNone;
@@ -106,6 +114,33 @@
 
 - (NSString *)reuseIdentifierWithTarget:(id)object { return nil; }
 
+- (NSArray<NSString *> *)copiableMetadataWithTarget:(id)object {
+    BOOL returnsObject = self.attributes.typeEncoding.typeIsObjectOrClass;
+    id value = [self currentValueBeforeUnboxingWithTarget:object];
+    return @[
+        @"Name",                        self.name ?: @"",
+        @"Type",                        self.attributes.typeEncoding ?: @"",
+        @"Declaration",                 self.fullDescription ?: @"",
+        @"Value Preview",               [self previewWithTarget:object],
+        @"Value Address",               returnsObject ? [FLEXUtility addressOfObject:value] : @"",
+        @"Getter",                      NSStringFromSelector(self.likelyGetter) ?: @"",
+        @"Setter",                      self.likelySetterExists ? NSStringFromSelector(self.likelySetter) : @"",
+        @"Image Name",                  self.imageName ?: @"",
+        @"Attributes",                  self.attributes.string ?: @"",
+        @"objc_property",             [FLEXUtility pointerToString:self.objc_property],
+        @"objc_property_attribute_t", [FLEXUtility pointerToString:self.attributes.list],
+    ];
+}
+
+- (NSString *)contextualSubtitleWithTarget:(id)object {
+    id target = [self appropriateTargetForPropertyType:object];
+    if (target && self.attributes.typeEncoding.typeIsObjectOrClass) {
+        return [FLEXUtility addressOfObject:[self currentValueBeforeUnboxingWithTarget:target]];
+    }
+    
+    return nil;
+}
+
 @end
 
 
@@ -176,6 +211,29 @@
 
 - (NSString *)reuseIdentifierWithTarget:(id)object { return nil; }
 
+- (NSArray<NSString *> *)copiableMetadataWithTarget:(id)object {
+    BOOL returnsObject = self.typeEncoding.typeIsObjectOrClass;
+    id value = [self getValue:object];
+    return @[
+        @"Name",          self.name ?: @"",
+        @"Type",          self.typeEncoding ?: @"",
+        @"Declaration",   self.description ?: @"",
+        @"Value Preview", [self previewWithTarget:object],
+        @"Value Address", returnsObject ? [FLEXUtility addressOfObject:value] : @"",
+        @"Size",          @(self.size).stringValue,
+        @"Offset",        @(self.offset).stringValue,
+        @"objc_ivar",   [FLEXUtility pointerToString:self.objc_ivar],
+    ];
+}
+
+- (NSString *)contextualSubtitleWithTarget:(id)object {
+    if (!object_isClass(object) && self.typeEncoding.typeIsObjectOrClass) {
+        return [FLEXUtility addressOfObject:[self getValue:object]];
+    }
+    
+    return nil;
+}
+
 @end
 
 
@@ -219,6 +277,18 @@
 
 - (NSString *)reuseIdentifierWithTarget:(id)object { return nil; }
 
+- (NSArray<NSString *> *)copiableMetadataWithTarget:(id)object {
+    return @[
+        @"Selector",      self.name ?: @"",
+        @"Type Encoding", self.typeEncoding ?: @"",
+        @"Declaration",   self.description ?: @"",
+    ];
+}
+
+- (NSString *)contextualSubtitleWithTarget:(id)object {
+    return nil;
+}
+
 @end
 
 @implementation FLEXMethod (UIKitHelpers)
@@ -246,6 +316,17 @@
     }
 }
 
+- (NSArray<NSString *> *)copiableMetadataWithTarget:(id)object {
+    return [[super copiableMetadataWithTarget:object] arrayByAddingObjectsFromArray:@[
+        @"NSMethodSignature *", [FLEXUtility addressOfObject:self.signature],
+        @"Signature String",    self.signatureString ?: @"",
+        @"Number of Arguments", @(self.numberOfArguments).stringValue,
+        @"Return Type",         @(self.returnType ?: ""),
+        @"Return Size",         @(self.returnSize).stringValue,
+        @"objc_method",       [FLEXUtility pointerToString:self.objc_method],
+    ]];
+}
+
 @end
 
 
@@ -282,6 +363,19 @@
 
 - (NSString *)reuseIdentifierWithTarget:(id)object { return nil; }
 
+- (NSArray<NSString *> *)copiableMetadataWithTarget:(id)object {
+    NSArray<NSString *> *conformanceNames = [self.protocols valueForKeyPath:@"name"];
+    NSString *conformances = [conformanceNames componentsJoinedByString:@"\n"];
+    return @[
+        @"Name",         self.name ?: @"",
+        @"Conformances", conformances,
+    ];
+}
+
+- (NSString *)contextualSubtitleWithTarget:(id)object {
+    return nil;
+}
+
 @end
 
 
@@ -369,6 +463,14 @@
     return UITableViewCellAccessoryNone;
 }
 
+- (NSArray<NSString *> *)copiableMetadataWithTarget:(id)object {
+    return @[self.name, self.subtitle];
+}
+
+- (NSString *)contextualSubtitleWithTarget:(id)object {
+    return nil;
+}
+
 @end
 
 
@@ -398,4 +500,15 @@
     return UITableViewCellAccessoryDisclosureIndicator;
 }
 
+- (NSArray<NSString *> *)copiableMetadataWithTarget:(id)object {
+    return @[
+        @"Class Name", self.name,
+        @"Class", [FLEXUtility addressOfObject:self.metadata]
+    ];
+}
+
+- (NSString *)contextualSubtitleWithTarget:(id)object {
+    return [FLEXUtility addressOfObject:self.metadata];
+}
+
 @end

+ 30 - 0
Classes/Utility/Categories/NSString+FLEX.h

@@ -0,0 +1,30 @@
+//
+//  NSString+FLEX.h
+//  FLEX
+//
+//  Created by Tanner on 3/26/17.
+//  Copyright © 2017 Tanner Bennett. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+#import "FLEXRuntimeUtility.h"
+
+@interface NSString (FLEXTypeEncoding)
+
+///@return whether this type starts with the const specifier
+@property (nonatomic, readonly) BOOL typeIsConst;
+/// @return the first char in the type encoding that is not the const specifier
+@property (nonatomic, readonly) FLEXTypeEncoding firstNonConstType;
+/// @return whether this type is an objc object of any kind, even if it's const
+@property (nonatomic, readonly) BOOL typeIsObjectOrClass;
+/// Includes C strings and selectors as well as regular pointers
+@property (nonatomic, readonly) BOOL typeIsNonObjcPointer;
+
+@end
+
+@interface NSString (KeyPaths)
+
+- (NSString *)stringByRemovingLastKeyPathComponent;
+- (NSString *)stringByReplacingLastKeyPathComponent:(NSString *)replacement;
+
+@end

+ 26 - 2
Classes/Utility/Categories/NSString+KeyPaths.m

@@ -1,12 +1,12 @@
 //
-//  NSString+KeyPaths.m
+//  NSString+FLEX.m
 //  FLEX
 //
 //  Created by Tanner on 3/26/17.
 //  Copyright © 2017 Tanner Bennett. All rights reserved.
 //
 
-#import "NSString+KeyPaths.h"
+#import "NSString+FLEX.h"
 
 @interface NSMutableString (Replacement)
 - (void)replaceOccurencesOfString:(NSString *)string with:(NSString *)replacement;
@@ -51,6 +51,30 @@
 
 @end
 
+@implementation NSString (FLEXTypeEncoding)
+
+- (BOOL)typeIsConst {
+    return [self characterAtIndex:0] == FLEXTypeEncodingConst;
+}
+
+- (FLEXTypeEncoding)firstNonConstType {
+    return [self characterAtIndex:(self.typeIsConst ? 1 : 0)];
+}
+
+- (BOOL)typeIsObjectOrClass {
+    FLEXTypeEncoding type = self.firstNonConstType;
+    return type == FLEXTypeEncodingObjcObject || type == FLEXTypeEncodingObjcClass;
+}
+
+- (BOOL)typeIsNonObjcPointer {
+    FLEXTypeEncoding type = self.firstNonConstType;
+    return type == FLEXTypeEncodingPointer ||
+           type == FLEXTypeEncodingCString ||
+           type == FLEXTypeEncodingSelector;
+}
+
+@end
+
 @implementation NSString (KeyPaths)
 
 - (NSString *)stringByRemovingLastKeyPathComponent {

+ 0 - 16
Classes/Utility/Categories/NSString+KeyPaths.h

@@ -1,16 +0,0 @@
-//
-//  NSString+KeyPaths.h
-//  FLEX
-//
-//  Created by Tanner on 3/26/17.
-//  Copyright © 2017 Tanner Bennett. All rights reserved.
-//
-
-#import <Foundation/Foundation.h>
-
-@interface NSString (KeyPaths)
-
-- (NSString *)stringByRemovingLastKeyPathComponent;
-- (NSString *)stringByReplacingLastKeyPathComponent:(NSString *)replacement;
-
-@end

+ 19 - 0
Classes/Utility/Categories/UIMenu+FLEX.h

@@ -0,0 +1,19 @@
+//
+//  UIMenu+FLEX.h
+//  FLEX
+//
+//  Created by Tanner on 1/28/20.
+//Copyright © 2020 Flipboard. All rights reserved.
+//
+
+#import <UIKit/UIKit.h>
+
+@interface UIMenu (FLEX)
+
++ (instancetype)inlineMenuWithTitle:(NSString *)title
+                              image:(UIImage *)image
+                           children:(NSArray<UIMenuElement *> *)children;
+
+- (instancetype)collapsed;
+
+@end

+ 39 - 0
Classes/Utility/Categories/UIMenu+FLEX.m

@@ -0,0 +1,39 @@
+//
+//  UIMenu+FLEX.m
+//  FLEX
+//
+//  Created by Tanner on 1/28/20.
+//Copyright © 2020 Flipboard. All rights reserved.
+//
+
+#import "UIMenu+FLEX.h"
+
+@implementation UIMenu (FLEX)
+
++ (instancetype)inlineMenuWithTitle:(NSString *)title image:(UIImage *)image children:(NSArray<UIMenuElement *> *)children {
+    return [UIMenu
+        menuWithTitle:title
+        image:image
+        identifier:nil
+        options:UIMenuOptionsDisplayInline
+        children:children
+    ];
+}
+
+- (instancetype)collapsed {
+    return [UIMenu
+        menuWithTitle:@""
+        image:nil
+        identifier:nil
+        options:UIMenuOptionsDisplayInline
+        children:@[[UIMenu
+            menuWithTitle:self.title
+            image:self.image
+            identifier:self.identifier
+            options:0
+            children:self.children
+        ]]
+    ];
+}
+
+@end

+ 1 - 0
Classes/Utility/FLEXUtility.h

@@ -48,6 +48,7 @@ NS_INLINE CGRect FLEXRectMake(CGFloat x, CGFloat y, CGFloat width, CGFloat heigh
 + (UIColor *)hierarchyIndentPatternColor;
 + (NSString *)applicationImageName;
 + (NSString *)applicationName;
++ (NSString *)pointerToString:(void *)ptr;
 + (NSString *)addressOfObject:(id)object;
 + (NSString *)stringByEscapingHTMLEntitiesInString:(NSString *)originalString;
 + (UIInterfaceOrientationMask)infoPlistSupportedInterfaceOrientationsMask;

+ 5 - 0
Classes/Utility/FLEXUtility.m

@@ -123,6 +123,11 @@
     return [FLEXUtility applicationImageName].lastPathComponent;
 }
 
++ (NSString *)pointerToString:(void *)ptr
+{
+    return [NSString stringWithFormat:@"%p", ptr];
+}
+
 + (NSString *)addressOfObject:(id)object
 {
     return [NSString stringWithFormat:@"%p", object];

+ 2 - 0
Classes/Utility/Runtime/FLEXIvar.h

@@ -28,6 +28,8 @@
 @property (nonatomic, readonly) NSString         *typeEncoding;
 /// The offset of the instance variable.
 @property (nonatomic, readonly) NSInteger        offset;
+/// The size of the instance variable. 0 if unknown.
+@property (nonatomic, readonly) NSUInteger       size;
 /// Describes the type encoding, size, offset, and objc_ivar
 @property (nonatomic, readonly) NSString        *details;
 

+ 2 - 3
Classes/Utility/Runtime/FLEXIvar.m

@@ -71,13 +71,12 @@
     _type         = (FLEXTypeEncoding)[_typeEncoding characterAtIndex:0];
     _offset       = ivar_getOffset(self.objc_ivar);
 
-    NSUInteger size = 0;
     @try {
-        NSGetSizeAndAlignment(_typeEncoding.UTF8String, &size, nil);
+        NSGetSizeAndAlignment(_typeEncoding.UTF8String, &_size, nil);
     } @catch (NSException *exception) { }
     _details = [NSString stringWithFormat:
         @"%@ bytes, offset %@  —  %@",
-        (size ? @(size) : @"?"), @(_offset), _typeEncoding
+        (_size ? @(_size) : @"?"), @(_offset), _typeEncoding
     ];
 }
 

+ 5 - 0
Classes/Utility/Runtime/FLEXProperty.m

@@ -200,6 +200,11 @@
     } else {
         [attributesStrings addObject:@"readwrite"];
     }
+    
+    // Class or not
+    if (self.isClassProperty) {
+        [attributesStrings addObject:@"class"];
+    }
 
     // Custom getter/setter
     SEL customGetter = attributes.customGetter;

+ 16 - 8
FLEX.xcodeproj/project.pbxproj

@@ -170,6 +170,8 @@
 		C3511B9222D7C99E0057BAB7 /* FLEXTableViewSection.m in Sources */ = {isa = PBXBuildFile; fileRef = C3511B9022D7C99E0057BAB7 /* FLEXTableViewSection.m */; };
 		C362AE8123C7E9D1005A86AE /* NSMapTable+FLEX_Subscripting.h in Headers */ = {isa = PBXBuildFile; fileRef = C362AE7F23C7E9D1005A86AE /* NSMapTable+FLEX_Subscripting.h */; };
 		C362AE8223C7E9D1005A86AE /* NSMapTable+FLEX_Subscripting.m in Sources */ = {isa = PBXBuildFile; fileRef = C362AE8023C7E9D1005A86AE /* NSMapTable+FLEX_Subscripting.m */; };
+		C36B096523E0D4A1008F5D47 /* UIMenu+FLEX.h in Headers */ = {isa = PBXBuildFile; fileRef = C36B096323E0D4A1008F5D47 /* UIMenu+FLEX.h */; };
+		C36B096623E0D4A1008F5D47 /* UIMenu+FLEX.m in Sources */ = {isa = PBXBuildFile; fileRef = C36B096423E0D4A1008F5D47 /* UIMenu+FLEX.m */; };
 		C36FBFCB230F3B98008D95D5 /* FLEXMirror.m in Sources */ = {isa = PBXBuildFile; fileRef = C36FBFB9230F3B97008D95D5 /* FLEXMirror.m */; };
 		C36FBFCC230F3B98008D95D5 /* FLEXProtocolBuilder.h in Headers */ = {isa = PBXBuildFile; fileRef = C36FBFBA230F3B97008D95D5 /* FLEXProtocolBuilder.h */; };
 		C36FBFCD230F3B98008D95D5 /* FLEXMethod.m in Sources */ = {isa = PBXBuildFile; fileRef = C36FBFBB230F3B97008D95D5 /* FLEXMethod.m */; };
@@ -236,8 +238,8 @@
 		C398626E23AD71C1007E6793 /* TBRuntime.m in Sources */ = {isa = PBXBuildFile; fileRef = C398626A23AD71C1007E6793 /* TBRuntime.m */; };
 		C398627223AD7951007E6793 /* UIGestureRecognizer+Blocks.h in Headers */ = {isa = PBXBuildFile; fileRef = C398627023AD7951007E6793 /* UIGestureRecognizer+Blocks.h */; };
 		C398627323AD7951007E6793 /* UIGestureRecognizer+Blocks.m in Sources */ = {isa = PBXBuildFile; fileRef = C398627123AD7951007E6793 /* UIGestureRecognizer+Blocks.m */; };
-		C398627623AD79B7007E6793 /* NSString+KeyPaths.h in Headers */ = {isa = PBXBuildFile; fileRef = C398627423AD79B6007E6793 /* NSString+KeyPaths.h */; };
-		C398627723AD79B7007E6793 /* NSString+KeyPaths.m in Sources */ = {isa = PBXBuildFile; fileRef = C398627523AD79B7007E6793 /* NSString+KeyPaths.m */; };
+		C398627623AD79B7007E6793 /* NSString+FLEX.h in Headers */ = {isa = PBXBuildFile; fileRef = C398627423AD79B6007E6793 /* NSString+FLEX.h */; };
+		C398627723AD79B7007E6793 /* NSString+FLEX.m in Sources */ = {isa = PBXBuildFile; fileRef = C398627523AD79B7007E6793 /* NSString+FLEX.m */; };
 		C398682523AC359600E9E391 /* FLEXShortcutsFactory+Defaults.m in Sources */ = {isa = PBXBuildFile; fileRef = C398682323AC359600E9E391 /* FLEXShortcutsFactory+Defaults.m */; };
 		C398682623AC359600E9E391 /* FLEXShortcutsFactory+Defaults.h in Headers */ = {isa = PBXBuildFile; fileRef = C398682423AC359600E9E391 /* FLEXShortcutsFactory+Defaults.h */; };
 		C398682823AC36EC00E9E391 /* FLEXViewShortcuts.m in Sources */ = {isa = PBXBuildFile; fileRef = C3F527C4231891F6009CBA07 /* FLEXViewShortcuts.m */; };
@@ -480,6 +482,8 @@
 		C3511B9022D7C99E0057BAB7 /* FLEXTableViewSection.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FLEXTableViewSection.m; sourceTree = "<group>"; };
 		C362AE7F23C7E9D1005A86AE /* NSMapTable+FLEX_Subscripting.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSMapTable+FLEX_Subscripting.h"; sourceTree = "<group>"; };
 		C362AE8023C7E9D1005A86AE /* NSMapTable+FLEX_Subscripting.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSMapTable+FLEX_Subscripting.m"; sourceTree = "<group>"; };
+		C36B096323E0D4A1008F5D47 /* UIMenu+FLEX.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "UIMenu+FLEX.h"; sourceTree = "<group>"; };
+		C36B096423E0D4A1008F5D47 /* UIMenu+FLEX.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "UIMenu+FLEX.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>"; };
@@ -544,8 +548,8 @@
 		C398626A23AD71C1007E6793 /* TBRuntime.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TBRuntime.m; sourceTree = "<group>"; };
 		C398627023AD7951007E6793 /* UIGestureRecognizer+Blocks.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "UIGestureRecognizer+Blocks.h"; sourceTree = "<group>"; };
 		C398627123AD7951007E6793 /* UIGestureRecognizer+Blocks.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "UIGestureRecognizer+Blocks.m"; sourceTree = "<group>"; };
-		C398627423AD79B6007E6793 /* NSString+KeyPaths.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSString+KeyPaths.h"; sourceTree = "<group>"; };
-		C398627523AD79B7007E6793 /* NSString+KeyPaths.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSString+KeyPaths.m"; sourceTree = "<group>"; };
+		C398627423AD79B6007E6793 /* NSString+FLEX.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSString+FLEX.h"; sourceTree = "<group>"; };
+		C398627523AD79B7007E6793 /* NSString+FLEX.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSString+FLEX.m"; sourceTree = "<group>"; };
 		C398682323AC359600E9E391 /* FLEXShortcutsFactory+Defaults.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "FLEXShortcutsFactory+Defaults.m"; sourceTree = "<group>"; };
 		C398682423AC359600E9E391 /* FLEXShortcutsFactory+Defaults.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "FLEXShortcutsFactory+Defaults.h"; sourceTree = "<group>"; };
 		C39ED92622D63F3200B5773A /* FLEXAddressExplorerCoordinator.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FLEXAddressExplorerCoordinator.h; sourceTree = "<group>"; };
@@ -1016,14 +1020,16 @@
 				C3F9777E2311B38E0032776D /* NSDictionary+ObjcRuntime.m */,
 				C3F9777D2311B38E0032776D /* NSString+ObjcRuntime.h */,
 				C3F977802311B38F0032776D /* NSString+ObjcRuntime.m */,
-				C398627423AD79B6007E6793 /* NSString+KeyPaths.h */,
-				C398627523AD79B7007E6793 /* NSString+KeyPaths.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 */,
 			);
 			path = Categories;
 			sourceTree = "<group>";
@@ -1261,7 +1267,7 @@
 				3A4C94E71B5B21410088C3F2 /* FLEXHierarchyTableViewCell.h in Headers */,
 				224D49AA1C673AB5000EAB86 /* FLEXSQLiteDatabaseManager.h in Headers */,
 				3A4C95031B5B21410088C3F2 /* FLEXArgumentInputView.h in Headers */,
-				C398627623AD79B7007E6793 /* NSString+KeyPaths.h in Headers */,
+				C398627623AD79B7007E6793 /* NSString+FLEX.h in Headers */,
 				94A5151D1C4CA1F10063292F /* FLEXExplorerViewController.h in Headers */,
 				C3F31D3F2267D883003C991A /* FLEXMultilineTableViewCell.h in Headers */,
 				71E1C2132307FBB800F5032A /* FLEXKeychain.h in Headers */,
@@ -1321,6 +1327,7 @@
 				3A4C95241B5B21410088C3F2 /* FLEXFileBrowserTableViewController.h in Headers */,
 				94AAF0381BAF2E1F00DE8760 /* FLEXKeyboardHelpViewController.h in Headers */,
 				71E1C2152307FBB800F5032A /* FLEXKeychainQuery.h in Headers */,
+				C36B096523E0D4A1008F5D47 /* UIMenu+FLEX.h in Headers */,
 				C36FBFD6230F3B98008D95D5 /* FLEXIvar.h in Headers */,
 				C3A9423823C51B8D006871A3 /* FHSRangeSlider.h in Headers */,
 				C398626123AD70DF007E6793 /* TBKeyboardToolbar.h in Headers */,
@@ -1487,7 +1494,7 @@
 				C3A9423D23C54010006871A3 /* FHSSnapshotView.m in Sources */,
 				C387C87B22DFCD6A00750E58 /* FLEXCarouselCell.m in Sources */,
 				3A4C94F01B5B21410088C3F2 /* FLEXArgumentInputDateView.m in Sources */,
-				C398627723AD79B7007E6793 /* NSString+KeyPaths.m in Sources */,
+				C398627723AD79B7007E6793 /* NSString+FLEX.m in Sources */,
 				C36FBFDC230F3B98008D95D5 /* FLEXIvar.m in Sources */,
 				C3F527C22318670F009CBA07 /* FLEXImageShortcuts.m in Sources */,
 				679F64871BD53B7B00A8C94C /* FLEXCookiesTableViewController.m in Sources */,
@@ -1549,6 +1556,7 @@
 				C3DB9F652107FC9600B46809 /* FLEXObjectRef.m in Sources */,
 				3A4C95021B5B21410088C3F2 /* FLEXArgumentInputTextView.m in Sources */,
 				C3F977882311B38F0032776D /* NSObject+Reflection.m in Sources */,
+				C36B096623E0D4A1008F5D47 /* UIMenu+FLEX.m in Sources */,
 				94A515261C4CA2080063292F /* FLEXExplorerToolbar.m in Sources */,
 				C33C825F2316DC8600DD2451 /* FLEXObjectExplorer.m in Sources */,
 				C383C3C123B6B429007A321B /* NSTimer+Blocks.m in Sources */,