Pārlūkot izejas kodu

Shortcuts and sections

These classes will be the direct data sources for explorer view controllers, and runtime metadata sections will pull their data from a `FLEXObjectExplorer` instance tied to an object explorer view controller
Tanner Bennett 6 gadi atpakaļ
vecāks
revīzija
3d05e4fb6a
31 mainītis faili ar 2503 papildinājumiem un 4 dzēšanām
  1. 56 0
      Classes/ObjectExplorers/Sections/FLEXCollectionContentSection.h
  2. 163 0
      Classes/ObjectExplorers/Sections/FLEXCollectionContentSection.m
  3. 15 0
      Classes/ObjectExplorers/Sections/FLEXColorPreviewSection.h
  4. 30 0
      Classes/ObjectExplorers/Sections/FLEXColorPreviewSection.m
  5. 26 0
      Classes/ObjectExplorers/Sections/FLEXDefaultsContentSection.h
  6. 94 0
      Classes/ObjectExplorers/Sections/FLEXDefaultsContentSection.m
  7. 101 0
      Classes/ObjectExplorers/Sections/FLEXExplorerSection.h
  8. 53 0
      Classes/ObjectExplorers/Sections/FLEXExplorerSection.m
  9. 34 0
      Classes/ObjectExplorers/Sections/FLEXMetadataSection.h
  10. 271 0
      Classes/ObjectExplorers/Sections/FLEXMetadataSection.m
  11. 28 0
      Classes/ObjectExplorers/Sections/FLEXSingleRowSection.h
  12. 75 0
      Classes/ObjectExplorers/Sections/FLEXSingleRowSection.m
  13. 17 0
      Classes/ObjectExplorers/Sections/Shortcuts/FLEXBundleShortcuts.h
  14. 27 0
      Classes/ObjectExplorers/Sections/Shortcuts/FLEXBundleShortcuts.m
  15. 17 0
      Classes/ObjectExplorers/Sections/Shortcuts/FLEXClassShortcuts.h
  16. 89 0
      Classes/ObjectExplorers/Sections/Shortcuts/FLEXClassShortcuts.m
  17. 16 0
      Classes/ObjectExplorers/Sections/Shortcuts/FLEXImageShortcuts.h
  18. 64 0
      Classes/ObjectExplorers/Sections/Shortcuts/FLEXImageShortcuts.m
  19. 15 0
      Classes/ObjectExplorers/Sections/Shortcuts/FLEXLayerShortcuts.h
  20. 54 0
      Classes/ObjectExplorers/Sections/Shortcuts/FLEXLayerShortcuts.m
  21. 43 0
      Classes/ObjectExplorers/Sections/Shortcuts/FLEXShortcut.h
  22. 174 0
      Classes/ObjectExplorers/Sections/Shortcuts/FLEXShortcut.m
  23. 19 0
      Classes/ObjectExplorers/Sections/Shortcuts/FLEXShortcutsFactory+Defaults.h
  24. 128 0
      Classes/ObjectExplorers/Sections/Shortcuts/FLEXShortcutsFactory+Defaults.m
  25. 122 0
      Classes/ObjectExplorers/Sections/Shortcuts/FLEXShortcutsSection.h
  26. 423 0
      Classes/ObjectExplorers/Sections/Shortcuts/FLEXShortcutsSection.m
  27. 15 0
      Classes/ObjectExplorers/Sections/Shortcuts/FLEXViewControllerShortcuts.h
  28. 76 0
      Classes/ObjectExplorers/Sections/Shortcuts/FLEXViewControllerShortcuts.m
  29. 13 0
      Classes/ObjectExplorers/Sections/Shortcuts/FLEXViewShortcuts.h
  30. 101 0
      Classes/ObjectExplorers/Sections/Shortcuts/FLEXViewShortcuts.m
  31. 144 4
      FLEX.xcodeproj/project.pbxproj

+ 56 - 0
Classes/ObjectExplorers/Sections/FLEXCollectionContentSection.h

@@ -0,0 +1,56 @@
+//
+//  FLEXCollectionContentSection.h
+//  FLEX
+//
+//  Created by Tanner Bennett on 8/28/19.
+//  Copyright © 2019 Flipboard. All rights reserved.
+//
+
+#import "FLEXExplorerSection.h"
+@class FLEXCollectionContentSection;
+@protocol FLEXCollection;
+
+typedef id<FLEXCollection>(^FLEXCollectionContentFuture)(__kindof FLEXCollectionContentSection *section);
+
+#pragma mark Collection
+/// A protocol that enables \c FLEXCollectionContentSection to operate on any arbitrary collection.
+/// \c NSArray, \c NSDictionary, \c NSSet, and \c NSOrderedSet all conform to this protocol.
+@protocol FLEXCollection <NSObject, NSFastEnumeration>
+
+@property (nonatomic, readonly) NSUInteger count;
+
+- (id)copy;
+- (id)mutableCopy;
+
+@optional
+
+/// Unordered, unkeyed collections must implement this
+@property (nonatomic, readonly) NSArray *allObjects;
+/// Keyed collections must implement this and \c objectForKeyedSubscript:
+@property (nonatomic, readonly) NSArray *allKeys;
+
+/// Ordered, indexed collections must implement this.
+- (id)objectAtIndexedSubscript:(NSUInteger)idx;
+/// Keyed, unordered collections must implement this and \c allKeys
+- (id)objectForKeyedSubscript:(id)idx;
+
+@end
+
+@interface NSArray (FLEXCollection) <FLEXCollection> @end
+@interface NSDictionary (FLEXCollection) <FLEXCollection> @end
+@interface NSSet (FLEXCollection) <FLEXCollection> @end
+@interface NSOrderedSet (FLEXCollection) <FLEXCollection> @end
+
+#pragma mark - FLEXCollectionContentSection
+/// A custom section for viewing collection elements.
+///
+/// Tapping on a row pushes an object explorer for that element.
+@interface FLEXCollectionContentSection : FLEXExplorerSection <FLEXObjectInfoSection>
+
++ (instancetype)forCollection:(id<FLEXCollection>)collection;
+/// The future given should be safe to call more than once.
+/// The result of calling this future multiple times may yield
+/// different results each time if the data is changing by nature.
++ (instancetype)forReusableFuture:(FLEXCollectionContentFuture)collectionFuture;
+
+@end

+ 163 - 0
Classes/ObjectExplorers/Sections/FLEXCollectionContentSection.m

@@ -0,0 +1,163 @@
+//
+//  FLEXCollectionContentSection.m
+//  FLEX
+//
+//  Created by Tanner Bennett on 8/28/19.
+//  Copyright © 2019 Flipboard. All rights reserved.
+//
+
+#import "FLEXCollectionContentSection.h"
+#import "FLEXUtility.h"
+#import "FLEXRuntimeUtility.h"
+#import "FLEXSubtitleTableViewCell.h"
+#import "FLEXTableView.h"
+#import "FLEXObjectExplorerFactory.h"
+
+typedef NS_ENUM(NSUInteger, FLEXCollectionType) {
+    FLEXUnsupportedCollection,
+    FLEXOrderedCollection,
+    FLEXUnorderedCollection,
+    FLEXKeyedCollection
+};
+
+@interface FLEXCollectionContentSection ()
+@property (nonatomic) id<FLEXCollection> cachedCollection;
+@property (nonatomic, readonly) id<FLEXCollection> collection;
+@property (nonatomic, readonly) FLEXCollectionContentFuture collectionFuture;
+@property (nonatomic, readonly) FLEXCollectionType collectionType;
+@end
+
+@implementation FLEXCollectionContentSection
+
+#pragma mark Initialization
+
++ (instancetype)forObject:(id)object {
+    return [self forCollection:object];
+}
+
++ (id)forCollection:(id<FLEXCollection>)collection {
+    FLEXCollectionContentSection *section = [self new];
+    section->_collectionType = [self typeForCollection:collection];
+    section->_collection = collection;
+    section.cachedCollection = collection.copy;
+    return section;
+}
+
++ (id)forReusableFuture:(FLEXCollectionContentFuture)collectionFuture {
+    FLEXCollectionContentSection *section = [self new];
+    section->_collectionFuture = collectionFuture;
+    section.cachedCollection = collectionFuture(section);
+    section->_collectionType = [self typeForCollection:section.cachedCollection];
+    return section;
+}
+
+#pragma mark - Misc
+
++ (FLEXCollectionType)typeForCollection:(id<FLEXCollection>)collection {
+    // Order matters here, as NSDictionary is keyed but it responds to allObjects
+    if ([collection respondsToSelector:@selector(objectAtIndexedSubscript:)]) {
+        return FLEXOrderedCollection;
+    }
+    if ([collection respondsToSelector:@selector(objectForKeyedSubscript:)]) {
+        return FLEXKeyedCollection;
+    }
+    if ([collection respondsToSelector:@selector(allObjects)]) {
+        return FLEXUnorderedCollection;
+    }
+
+    [NSException raise:NSInvalidArgumentException
+                format:@"Given collection does not properly conform to FLEXCollection"];
+    return FLEXUnsupportedCollection;
+}
+
+/// Row titles
+/// - Ordered: the index
+/// - Unordered: the object
+/// - Keyed: the key
+- (NSString *)titleForRow:(NSInteger)row {
+    switch (self.collectionType) {
+        case FLEXOrderedCollection:
+            return @(row).stringValue;
+        case FLEXUnorderedCollection:
+            return [self describe:[self objectForRow:row]];
+        case FLEXKeyedCollection:
+            return [self describe:self.collection.allKeys[row]];
+
+        case FLEXUnsupportedCollection:
+            return nil;
+    }
+}
+
+/// Row subtitles
+/// - Ordered: the object
+/// - Unordered: nothing
+/// - Keyed: the value
+- (NSString *)subtitleForRow:(NSInteger)row {
+    switch (self.collectionType) {
+        case FLEXOrderedCollection:
+        case FLEXKeyedCollection:
+            return [self describe:[self objectForRow:row]];
+        case FLEXUnorderedCollection:
+            return nil;
+
+        case FLEXUnsupportedCollection:
+            return nil;
+    }
+}
+
+- (NSString *)describe:(id)object {
+    return [FLEXRuntimeUtility summaryForObject:object];
+}
+
+- (id)objectForRow:(NSInteger)row {
+    switch (self.collectionType) {
+        case FLEXOrderedCollection:
+            return self.collection[row];
+        case FLEXUnorderedCollection:
+            return self.collection.allObjects[row];
+        case FLEXKeyedCollection:
+            return self.collection[self.collection.allKeys[row]];
+
+        case FLEXUnsupportedCollection:
+            return nil;
+    }
+}
+
+#pragma mark - Overrides
+
+- (NSString *)title {
+    return FLEXPluralString(self.cachedCollection.count, @"Entries", @"Entry");
+}
+
+- (NSInteger)numberOfRows {
+    return self.cachedCollection.count;
+}
+
+- (void)reloadData {
+    if (self.collectionFuture) {
+        self.cachedCollection = self.collectionFuture(self);
+    } else {
+        self.cachedCollection = self.collection.copy;
+    }
+}
+
+- (BOOL)canSelectRow:(NSInteger)row {
+    return YES;
+}
+
+- (UIViewController *)viewControllerToPushForRow:(NSInteger)row {
+    return [FLEXObjectExplorerFactory explorerViewControllerForObject:[self objectForRow:row]];
+}
+
+- (NSString *)reuseIdentifierForRow:(NSInteger)row {
+    // Default for unordered, subtitle for others
+    return self.collectionType == FLEXUnorderedCollection ? kFLEXDefaultCell : kFLEXDetailCell;
+}
+
+- (void)configureCell:(__kindof FLEXTableViewCell *)cell forRow:(NSInteger)row {
+    cell.titleLabel.text = [self titleForRow:row];
+    cell.subtitleLabel.text = [self subtitleForRow:row];
+    cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
+}
+
+@end

+ 15 - 0
Classes/ObjectExplorers/Sections/FLEXColorPreviewSection.h

@@ -0,0 +1,15 @@
+//
+//  FLEXColorPreviewSection.h
+//  FLEX
+//
+//  Created by Tanner Bennett on 12/12/19.
+//  Copyright © 2019 Flipboard. All rights reserved.
+//
+
+#import "FLEXSingleRowSection.h"
+
+@interface FLEXColorPreviewSection : FLEXSingleRowSection <FLEXObjectInfoSection>
+
++ (instancetype)forObject:(UIColor *)color;
+
+@end

+ 30 - 0
Classes/ObjectExplorers/Sections/FLEXColorPreviewSection.m

@@ -0,0 +1,30 @@
+//
+//  FLEXColorPreviewSection.m
+//  FLEX
+//
+//  Created by Tanner Bennett on 12/12/19.
+//  Copyright © 2019 Flipboard. All rights reserved.
+//
+
+#import "FLEXColorPreviewSection.h"
+
+@implementation FLEXColorPreviewSection
+
++ (instancetype)forObject:(UIColor *)color {
+    return [self title:@"Color" reuse:nil cell:^(__kindof UITableViewCell *cell) {
+        cell.backgroundColor = color;
+    }];
+}
+
+- (BOOL)canSelectRow:(NSInteger)row {
+    return NO;
+}
+
+- (BOOL (^)(NSString *))filterMatcher {
+    return ^BOOL(NSString *filterText) {
+        // Hide when searching
+        return !filterText.length;
+    };
+}
+
+@end

+ 26 - 0
Classes/ObjectExplorers/Sections/FLEXDefaultsContentSection.h

@@ -0,0 +1,26 @@
+//
+//  FLEXDefaultsContentSection.h
+//  FLEX
+//
+//  Created by Tanner Bennett on 8/28/19.
+//  Copyright © 2019 Flipboard. All rights reserved.
+//
+
+#import "FLEXCollectionContentSection.h"
+
+@interface FLEXDefaultsContentSection : FLEXCollectionContentSection <FLEXObjectInfoSection>
+
+/// Uses \c NSUserDefaults.standardUserDefaults
++ (instancetype)standard;
++ (instancetype)forDefaults:(NSUserDefaults *)userDefaults;
+
+/// Whether or not to filter out keys not present in the app's user defaults file.
+///
+/// This is useful for filtering out some useless keys that seem to appear
+/// in every app's defaults but are never actually used or touched by the app.
+/// Only applies to instances using \c NSUserDefaults.standardUserDefaults.
+/// This is the default for any instance using \c standardUserDefaults, so
+/// you must opt-out in those instances if you don't want this behavior.
+@property (nonatomic) BOOL onlyShowKeysForAppPrefs;
+
+@end

+ 94 - 0
Classes/ObjectExplorers/Sections/FLEXDefaultsContentSection.m

@@ -0,0 +1,94 @@
+//
+//  FLEXDefaultsContentSection.m
+//  FLEX
+//
+//  Created by Tanner Bennett on 8/28/19.
+//  Copyright © 2019 Flipboard. All rights reserved.
+//
+
+#import "FLEXDefaultsContentSection.h"
+
+@interface FLEXDefaultsContentSection ()
+@property (nonatomic) NSUserDefaults *defaults;
+@property (nonatomic) NSArray *keys;
+@property (nonatomic, readonly) NSDictionary *whitelistedDefaults;
+@end
+
+@implementation FLEXDefaultsContentSection
+@synthesize keys = _keys;
+
+#pragma mark Initialization
+
++ (instancetype)forObject:(id)object {
+    return [self forDefaults:object];
+}
+
++ (instancetype)standard {
+    return [self forDefaults:NSUserDefaults.standardUserDefaults];
+}
+
++ (instancetype)forDefaults:(NSUserDefaults *)userDefaults {
+    FLEXDefaultsContentSection *section = [self forReusableFuture:^id(FLEXDefaultsContentSection *section) {
+        return section.whitelistedDefaults;
+    }];
+
+    section.defaults = userDefaults;
+    return section;
+}
+
+#pragma mark - Overrides
+
+- (NSString *)title {
+    return @"Defaults";
+}
+
+#pragma mark - Private
+
+- (NSArray *)keys {
+    if (!_keys) {
+        if (self.onlyShowKeysForAppPrefs) {
+            // Read keys from preferences file
+            NSString *bundle = [NSBundle mainBundle].bundleIdentifier;
+            NSString *prefsPath = [NSHomeDirectory() stringByAppendingPathComponent:@"Library/Preferences"];
+            NSString *filePath = [NSString stringWithFormat:@"%@/%@.plist", prefsPath, bundle];
+            self.keys = [NSDictionary dictionaryWithContentsOfFile:filePath].allKeys;
+        } else {
+            self.keys = self.defaults.dictionaryRepresentation.allKeys;
+        }
+    }
+
+    return _keys;
+}
+
+- (void)setKeys:(NSArray *)keys {
+    _keys = [keys sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)];
+}
+
+- (NSDictionary *)whitelistedDefaults {
+    // Case: no whitelisting
+    if (!self.onlyShowKeysForAppPrefs) {
+        return self.defaults.dictionaryRepresentation;
+    }
+
+    // Always regenerate key whitelist when this method is called
+    _keys = nil;
+
+    // Generate new dictionary from whitelisted keys
+    NSArray *values = [self.defaults.dictionaryRepresentation
+        objectsForKeys:self.keys notFoundMarker:[NSNull null]
+    ];
+    return [NSDictionary dictionaryWithObjects:values forKeys:self.keys];
+}
+
+#pragma mark - Public
+
+- (void)setOnlyShowKeysForAppPrefs:(BOOL)onlyShowKeysForAppPrefs {
+    if (onlyShowKeysForAppPrefs) {
+        // This property only applies if we're using standardUserDefaults
+        if (self.defaults != NSUserDefaults.standardUserDefaults) return;
+    }
+
+    _onlyShowKeysForAppPrefs = onlyShowKeysForAppPrefs;
+}
+
+@end

+ 101 - 0
Classes/ObjectExplorers/Sections/FLEXExplorerSection.h

@@ -0,0 +1,101 @@
+//
+//  FLEXExplorerSection.h
+//  FLEX
+//
+//  Created by Tanner Bennett on 8/28/19.
+//  Copyright © 2019 Flipboard. All rights reserved.
+//
+
+#import <UIKit/UIKit.h>
+@class FLEXTableView;
+
+#pragma mark FLEXExplorerSection
+
+/// An abstract base class for custom object explorer sections.
+@interface FLEXExplorerSection : NSObject
+
+#pragma mark - Data
+
+/// A title to be displayed for the custom section. Subclasses must override.
+@property (nonatomic, readonly) NSString *title;
+/// The number of rows in this section.
+/// This should not change until \c filterText is changed or \c reloadData is called.
+@property (nonatomic, readonly) NSInteger numberOfRows;
+/// A map of reuse identifiers to \c UITableViewCell (sub)class objects.
+/// Subclasses \e may override this as necessary, but are not required to.
+/// See \c FLEXTableView.h for more information.
+/// @return nil by default.
+@property (nonatomic, readonly) NSDictionary<NSString *, Class> *cellRegistrationMapping;
+
+/// The section should filter itself based on the contents of this property
+/// as it is set. If it is set to nil or an empty string, it should not filter.
+/// Subclasses should override or observe this property and react to changes.
+@property (nonatomic) NSString *filterText;
+
+/// Provides an avenue for the section to change the number of rows.
+/// This is called before reloading the table view itself.
+- (void)reloadData;
+
+#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.
+/// Subclasses \e may override this as necessary, but are not required to.
+/// @return \c NO by default
+- (BOOL)canSelectRow:(NSInteger)row;
+
+/// An action "future" to be triggered when the row is selected, if the row
+/// supports being selected as indicated by \c canSelectRow:. Subclasses
+/// must implement this in accordance with how they implement \c canSelectRow:
+/// if they do not implement \c viewControllerToPushForRow:
+/// @return This returns \c nil if no view controller is provided by
+/// \c viewControllerToPushForRow: — otherwise it pushes that view controller
+/// onto \c host.navigationController
+- (void(^)(UIViewController *host))didSelectRowAction:(NSInteger)row;
+
+/// A view controller to display when the row is selected, if the row
+/// supports being selected as indicated by \c canSelectRow:. Subclasses
+/// must implement this in accordance with how they implement \c canSelectRow:
+/// if they do not implement \c didSelectRowAction:
+/// @return \c nil by default
+- (UIViewController *)viewControllerToPushForRow:(NSInteger)row;
+
+/// Called when the accessory view's detail button is pressed.
+/// @return \c nil by default.
+- (void(^)(UIViewController *host))didPressInfoButtonAction:(NSInteger)row;
+
+#pragma mark - Cell configuration
+
+/// Provide a reuse identifier for the given row. Subclasses should override.
+///
+/// Custom reuse identifiers should be specified in \c cellRegistrationMapping.
+/// You may return any of the identifiers in \c FLEXTableView.h
+/// without including them in the \c cellRegistrationMapping.
+/// @return \c kFLEXDefaultCell by default.
+- (NSString *)reuseIdentifierForRow:(NSInteger)row;
+/// Configure a cell for the given row. Subclasses must override.
+- (void)configureCell:(__kindof UITableViewCell *)cell forRow:(NSInteger)row;
+
+#pragma mark - External Convenience
+
+/// For use by whatever view controller uses your section. Not required.
+/// @return An optional title.
+- (NSString *)titleForRow:(NSInteger)row;
+/// For use by whatever view controller uses your section. Not required.
+/// @return An optional subtitle.
+- (NSString *)subtitleForRow:(NSInteger)row;
+
+@end
+
+
+#pragma mark - FLEXObjectInfoSection
+
+/// \c FLEXExplorerSection itself doesn't need to know about the object being explored.
+/// Subclasses might need this info to provide useful information about the object. Instead
+/// of adding an abstract class to the class hierarchy, subclasses can conform to this protocol
+/// to indicate that the only info they need to be initialized is the object being explored.
+@protocol FLEXObjectInfoSection <NSObject>
+
++ (instancetype)forObject:(id)object;
+
+@end

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

@@ -0,0 +1,53 @@
+//
+//  FLEXExplorerSection.m
+//  FLEX
+//
+//  Created by Tanner Bennett on 8/28/19.
+//  Copyright © 2019 Flipboard. All rights reserved.
+//
+
+#import "FLEXExplorerSection.h"
+#import "FLEXTableView.h"
+
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wincomplete-implementation"
+
+@implementation FLEXExplorerSection
+
+- (void)reloadData { }
+
+- (NSDictionary<NSString *,Class> *)cellRegistrationMapping {
+    return nil;
+}
+
+- (BOOL)canSelectRow:(NSInteger)row { return NO; }
+
+- (void (^)(UIViewController *))didSelectRowAction:(NSInteger)row {
+    UIViewController *toPush = [self viewControllerToPushForRow:row];
+    if (toPush) {
+        return ^(UIViewController *host) {
+            [host.navigationController pushViewController:toPush animated:YES];
+        };
+    }
+
+    return nil;
+}
+
+- (UIViewController *)viewControllerToPushForRow:(NSInteger)row {
+    return nil;
+}
+
+- (void (^)(UIViewController *))didPressInfoButtonAction:(NSInteger)row {
+    return nil;
+}
+
+- (NSString *)reuseIdentifierForRow:(NSInteger)row {
+    return kFLEXDefaultCell;
+}
+
+- (NSString *)titleForRow:(NSInteger)row { return nil; }
+- (NSString *)subtitleForRow:(NSInteger)row { return nil; }
+
+@end
+
+#pragma clang diagnostic pop

+ 34 - 0
Classes/ObjectExplorers/Sections/FLEXMetadataSection.h

@@ -0,0 +1,34 @@
+//
+//  FLEXMetadataSection.h
+//  FLEX
+//
+//  Created by Tanner Bennett on 9/19/19.
+//  Copyright © 2019 Flipboard. All rights reserved.
+//
+
+#import "FLEXExplorerSection.h"
+#import "FLEXObjectExplorer.h"
+
+#warning Missing ClassProperties
+typedef NS_ENUM(NSUInteger, FLEXMetadataKind) {
+    FLEXMetadataKindProperties = 1,
+    FLEXMetadataKindIvars,
+    FLEXMetadataKindMethods,
+    FLEXMetadataKindClassMethods
+};
+
+/// This section is used for displaying ObjC runtime metadata
+/// about a class or object, such as listing methods, properties, etc.
+@interface FLEXMetadataSection : FLEXExplorerSection
+
++ (instancetype)explorer:(FLEXObjectExplorer *)explorer kind:(FLEXMetadataKind)metadataKind;
+
+@property (nonatomic, readonly) FLEXMetadataKind metadataKind;
+
+/// The names of metadata to exclude. Useful if you wish to group specific
+/// properties or methods together in their own section outside of this one.
+///
+/// Setting this property calls \c reloadData on this section.
+@property (nonatomic) NSSet<NSString *> *excludedMetadata;
+
+@end

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

@@ -0,0 +1,271 @@
+//
+//  FLEXMetadataSection.m
+//  FLEX
+//
+//  Created by Tanner Bennett on 9/19/19.
+//  Copyright © 2019 Flipboard. All rights reserved.
+//
+
+#import "FLEXMetadataSection.h"
+#import "FLEXTableView.h"
+#import "FLEXTableViewCell.h"
+#import "FLEXObjectExplorerFactory.h"
+#import "FLEXFieldEditorViewController.h"
+#import "FLEXMethodCallingViewController.h"
+#import "FLEXIvar.h"
+#import "NSArray+Functional.h"
+#import "FLEXRuntime+UIKitHelpers.h"
+
+@interface FLEXMetadataSection ()
+@property (nonatomic, readonly) FLEXObjectExplorer *explorer;
+/// Filtered
+@property (nonatomic, copy) NSArray<id<FLEXRuntimeMetadata>> *metadata;
+/// Unfiltered
+@property (nonatomic, copy) NSArray<id<FLEXRuntimeMetadata>> *allMetadata;
+@end
+
+@implementation FLEXMetadataSection
+
+#pragma mark - Initialization
+
++ (instancetype)explorer:(FLEXObjectExplorer *)explorer kind:(FLEXMetadataKind)metadataKind {
+    return [[self alloc] initWithExplorer:explorer kind:metadataKind];
+}
+
+- (id)initWithExplorer:(FLEXObjectExplorer *)explorer kind:(FLEXMetadataKind)metadataKind {
+    self = [super init];
+    if (self) {
+        _explorer = explorer;
+        _metadataKind = metadataKind;
+
+        [self reloadData];
+    }
+
+    return self;
+}
+
+#pragma mark - Private
+
+- (NSString *)titleWithBaseName:(NSString *)baseName {
+    unsigned long totalCount = self.allMetadata.count;
+    unsigned long filteredCount = self.metadata.count;
+
+    if (totalCount == filteredCount) {
+        return [baseName stringByAppendingFormat:@" (%lu)", totalCount];
+    } else {
+        return [baseName stringByAppendingFormat:@" (%lu of %lu)", filteredCount, totalCount];
+    }
+}
+
+- (__kindof UIViewController *)detailScreenForMetadata:(id)metadata {
+    id target = self.explorer.object;
+
+    switch (self.metadataKind) {
+        case FLEXMetadataKindProperties:
+        case FLEXMetadataKindIvars: {
+            // This works for both properties and ivars, we just
+            // cast so the compiler knows what return type to expect since
+            // this method has the same name as -[NSValue getValue:] (void *)
+            id currentValue = [(FLEXIvar *)metadata getPotentiallyUnboxedValue:target];
+            return [FLEXObjectExplorerFactory explorerViewControllerForObject:currentValue];
+        }
+        case FLEXMetadataKindClassMethods:
+            // Make sure the target is a class
+            // for class methods, then fall through
+            if (self.explorer.objectIsInstance && ![metadata isInstanceMethod]) {
+                target = [target class];
+            }
+        case FLEXMetadataKindMethods: {
+            return [FLEXMethodCallingViewController target:target method:metadata];
+        }
+    }
+}
+
+// Only for properties or ivars
+- (id)valueForRow:(NSInteger)row {
+    id metadata = self.metadata[row];
+
+    // We use -[FLEXObjectExplorer valueFor...:] instead of getValue: below
+    // because we want to "preview" what object is being stored if this is
+    // a void * or something and we're given an NSValue back from getValue:
+    switch (self.metadataKind) {
+        case FLEXMetadataKindProperties:
+            return [self.explorer valueForProperty:metadata];
+        case FLEXMetadataKindIvars:
+            return [self.explorer valueForIvar:metadata];
+
+        // Methods: nil
+        case FLEXMetadataKindMethods:
+        case FLEXMetadataKindClassMethods:
+            return nil;
+    }
+}
+
+- (UITableViewCellAccessoryType)accessoryTypeForRow:(NSInteger)row {
+    return [self.metadata[row] suggestedAccessoryTypeWithTarget:self.explorer.object];
+}
+
+#pragma mark - Public
+
+- (void)setExcludedMetadata:(NSSet<NSString *> *)excludedMetadata {
+    _excludedMetadata = excludedMetadata;
+    [self reloadData];
+}
+
+#pragma mark - Overrides
+
+- (NSString *)titleForRow:(NSInteger)row {
+    return [self.metadata[row] description];
+}
+
+- (NSString *)subtitleForRow:(NSInteger)row {
+    id metadata = self.metadata[row];
+
+    // We use -[FLEXObjectExplorer valueFor...:] instead of getValue: below
+    // because we want to "preview" what object is being stored if this is
+    // a void * or something and we're given an NSValue back from getValue:
+    switch (self.metadataKind) {
+        case FLEXMetadataKindProperties:
+        case FLEXMetadataKindIvars:
+            #warning TODO: fix this logic for class properties
+            if (!self.explorer.objectIsInstance) {
+                if (self.metadataKind == FLEXMetadataKindProperties) {
+                    if (![metadata isClassProperty]) {
+                        return nil;
+                    }
+                } else {
+                    return nil;
+                }
+            }
+            return [FLEXRuntimeUtility summaryForObject:[self valueForRow:row]];
+        case FLEXMetadataKindMethods:
+        case FLEXMetadataKindClassMethods:
+            return [metadata selectorString];
+
+        default:
+            return nil;
+    }
+}
+
+- (NSString *)title {
+    switch (self.metadataKind) {
+        case FLEXMetadataKindProperties:
+            return [self titleWithBaseName:@"Properties"];
+        case FLEXMetadataKindIvars:
+            return [self titleWithBaseName:@"Ivars"];
+        case FLEXMetadataKindMethods:
+            return [self titleWithBaseName:@"Methods"];
+        case FLEXMetadataKindClassMethods:
+            return [self titleWithBaseName:@"Class methods"];
+
+        default: return nil;
+    }
+}
+
+- (NSInteger)numberOfRows {
+    return self.metadata.count;
+}
+
+- (void)setFilterText:(NSString *)filterText {
+    super.filterText = filterText;
+
+    if (!self.filterText.length) {
+        self.metadata = self.allMetadata;
+    } else {
+        self.metadata = [self.allMetadata flex_filtered:^BOOL(FLEXProperty *obj, NSUInteger idx) {
+            return [obj.description localizedCaseInsensitiveContainsString:self.filterText];
+        }];
+    }
+}
+
+- (void)reloadData {
+    switch (self.metadataKind) {
+        case FLEXMetadataKindProperties:
+            self.allMetadata = self.explorer.properties;
+            break;
+        case FLEXMetadataKindIvars:
+            self.allMetadata = self.explorer.ivars;
+            break;
+        case FLEXMetadataKindMethods:
+            self.allMetadata = self.explorer.methods;
+            break;
+        case FLEXMetadataKindClassMethods:
+            self.allMetadata = self.explorer.classMethods;
+            break;
+    }
+
+    // Remove excluded metadata
+    if (self.excludedMetadata.count) {
+        id filterBlock = ^BOOL(id obj, NSUInteger idx) {
+            return ![self.excludedMetadata containsObject:[obj name]];
+        };
+
+        // Filter exclusions and sort
+        self.allMetadata = [[self.allMetadata flex_filtered:filterBlock]
+            sortedArrayUsingSelector:@selector(compare:)
+        ];
+    }
+
+    // Re-filter data
+    self.filterText = self.filterText;
+}
+
+- (BOOL)canSelectRow:(NSInteger)row {
+    switch (self.metadataKind) {
+        case FLEXMetadataKindProperties:
+        case FLEXMetadataKindIvars:
+            if (![self valueForRow:row]) {
+                return NO;
+            }
+        case FLEXMetadataKindMethods:
+            return self.explorer.objectIsInstance;
+        case FLEXMetadataKindClassMethods:
+            return YES;
+
+        default: return NO;
+    }
+}
+
+- (NSString *)reuseIdentifierForRow:(NSInteger)row {
+    return kFLEXDetailCell;
+}
+
+- (UIViewController *)viewControllerToPushForRow:(NSInteger)row {
+    return [self detailScreenForMetadata:self.metadata[row]];
+}
+
+- (void (^)(UIViewController *))didPressInfoButtonAction:(NSInteger)row {
+    return ^(UIViewController *host) {
+        [host.navigationController pushViewController:[self editorForRow:row] animated:YES];
+    };
+}
+
+- (UIViewController *)editorForRow:(NSInteger)row {
+    NSAssert(
+        self.metadataKind == FLEXMetadataKindIvars ||
+        self.metadataKind == FLEXMetadataKindProperties, // ||,
+//        self.metadataKind == FLEXMetadataKindClassProperties,
+        @"Only ivars or properties can be edited"
+    );
+
+    id metadata = self.metadata[row];
+
+    // Nil editor means unsupported ivar or property type, or nil value
+    if (self.metadataKind == FLEXMetadataKindProperties) {
+        return [FLEXFieldEditorViewController target:self.explorer.object property:metadata];
+    } else if (self.metadataKind == FLEXMetadataKindIvars) {
+        return [FLEXFieldEditorViewController target:self.explorer.object ivar:metadata];
+    }
+
+    // TODO: support class properties
+    @throw NSInternalInconsistencyException;
+    return nil;
+}
+
+- (void)configureCell:(__kindof FLEXTableViewCell *)cell forRow:(NSInteger)row {
+    cell.titleLabel.text = [self titleForRow:row];
+    cell.subtitleLabel.text = [self subtitleForRow:row];
+    cell.accessoryType = [self accessoryTypeForRow:row];
+}
+
+@end

+ 28 - 0
Classes/ObjectExplorers/Sections/FLEXSingleRowSection.h

@@ -0,0 +1,28 @@
+//
+//  FLEXSingleRowSection.h
+//  FLEX
+//
+//  Created by Tanner Bennett on 9/25/19.
+//  Copyright © 2019 Flipboard. All rights reserved.
+//
+
+#import "FLEXExplorerSection.h"
+
+/// A section providing a specific single row.
+///
+/// You may optionally provide a view controller to push when the row
+/// is selected, or an action to perform when it is selected.
+/// Which one is used first is up to the table view data source.
+@interface FLEXSingleRowSection : FLEXExplorerSection
+
+/// @param reuseIdentifier if nil, kFLEXDefaultCell is used.
++ (instancetype)title:(NSString *)sectionTitle
+                reuse:(NSString *)reuseIdentifier
+                 cell:(void(^)(__kindof UITableViewCell *cell))cellConfiguration;
+
+@property (nonatomic) UIViewController *pushOnSelection;
+@property (nonatomic) void (^selectionAction)(UIViewController *host);
+/// Called to determine whether the single row should display itself or not.
+@property (nonatomic) BOOL (^filterMatcher)(NSString *filterText);
+
+@end

+ 75 - 0
Classes/ObjectExplorers/Sections/FLEXSingleRowSection.m

@@ -0,0 +1,75 @@
+//
+//  FLEXSingleRowSection.m
+//  FLEX
+//
+//  Created by Tanner Bennett on 9/25/19.
+//  Copyright © 2019 Flipboard. All rights reserved.
+//
+
+#import "FLEXSingleRowSection.h"
+#import "FLEXTableView.h"
+
+@interface FLEXSingleRowSection ()
+@property (nonatomic, readonly) NSString *reuseIdentifier;
+@property (nonatomic, readonly) void (^cellConfiguration)(__kindof UITableViewCell *cell);
+@end
+
+@implementation FLEXSingleRowSection
+@synthesize title = _title;
+
+#pragma mark - Public
+
++ (instancetype)title:(NSString *)title
+                reuse:(NSString *)reuse
+                 cell:(void (^)(__kindof UITableViewCell *))config {
+    return [[self alloc] initWithTitle:title reuse:reuse cell:config];
+}
+
+- (id)initWithTitle:(NSString *)sectionTitle
+              reuse:(NSString *)reuseIdentifier
+               cell:(void (^)(__kindof UITableViewCell *))cellConfiguration {
+    self = [super init];
+    if (self) {
+        _title = sectionTitle;
+        _reuseIdentifier = reuseIdentifier ?: kFLEXDefaultCell;
+        _cellConfiguration = cellConfiguration;
+    }
+
+    return self;
+}
+
+#pragma mark - Overrides
+
+- (NSInteger)numberOfRows {
+    if (self.filterMatcher && self.filterText.length) {
+        return self.filterMatcher(self.filterText) ? 1 : 0;
+    }
+    
+    return 1;
+}
+
+- (BOOL)canSelectRow:(NSInteger)row {
+    return self.pushOnSelection || self.selectionAction;
+}
+
+- (void (^)(UIViewController *))didSelectRowAction:(NSInteger)row {
+    return self.selectionAction;
+}
+
+- (UIViewController *)viewControllerToPushForRow:(NSInteger)row {
+    return self.pushOnSelection;
+}
+
+- (NSString *)reuseIdentifierForRow:(NSInteger)row {
+    return self.reuseIdentifier;
+}
+
+- (void)configureCell:(__kindof UITableViewCell *)cell forRow:(NSInteger)row {
+    cell.textLabel.text = nil;
+    cell.detailTextLabel.text = nil;
+    cell.accessoryType = UITableViewCellAccessoryNone;
+    
+    self.cellConfiguration(cell);
+}
+
+@end

+ 17 - 0
Classes/ObjectExplorers/Sections/Shortcuts/FLEXBundleShortcuts.h

@@ -0,0 +1,17 @@
+//
+//  FLEXBundleShortcuts.h
+//  FLEX
+//
+//  Created by Tanner Bennett on 12/12/19.
+//  Copyright © 2019 Flipboard. All rights reserved.
+//
+
+#import "FLEXShortcutsSection.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface FLEXBundleShortcuts : FLEXShortcutsSection
+
+@end
+
+NS_ASSUME_NONNULL_END

+ 27 - 0
Classes/ObjectExplorers/Sections/Shortcuts/FLEXBundleShortcuts.m

@@ -0,0 +1,27 @@
+//
+//  FLEXBundleShortcuts.m
+//  FLEX
+//
+//  Created by Tanner Bennett on 12/12/19.
+//  Copyright © 2019 Flipboard. All rights reserved.
+//
+
+#import "FLEXBundleShortcuts.h"
+#import "FLEXFileBrowserTableViewController.h"
+
+@implementation FLEXBundleShortcuts
+#pragma mark - Overrides
+
++ (instancetype)forObject:(NSBundle *)bundle {
+    return [self forObject:bundle additionalRows:@[@"Browse bundle directory"]];
+}
+
+- (UIViewController *)viewControllerToPushForRow:(NSInteger)row {
+    if (row == 0) {
+        return [FLEXFileBrowserTableViewController path:[self.object bundlePath]];
+    }
+
+    return [super viewControllerToPushForRow:row];
+}
+
+@end

+ 17 - 0
Classes/ObjectExplorers/Sections/Shortcuts/FLEXClassShortcuts.h

@@ -0,0 +1,17 @@
+//
+//  FLEXClassShortcuts.h
+//  FLEX
+//
+//  Created by Tanner Bennett on 11/22/19.
+//  Copyright © 2019 Flipboard. All rights reserved.
+//
+
+#import "FLEXShortcutsSection.h"
+
+/// Provides handy shortcuts for class objects.
+/// This is the default section used for all class objects.
+@interface FLEXClassShortcuts : FLEXShortcutsSection
+
++ (instancetype)forObject:(Class)cls;
+
+@end

+ 89 - 0
Classes/ObjectExplorers/Sections/Shortcuts/FLEXClassShortcuts.m

@@ -0,0 +1,89 @@
+//
+//  FLEXClassShortcuts.m
+//  FLEX
+//
+//  Created by Tanner Bennett on 11/22/19.
+//  Copyright © 2019 Flipboard. All rights reserved.
+//
+
+#import "FLEXClassShortcuts.h"
+#import "FLEXObjectExplorerFactory.h"
+#import "FLEXShortcut.h"
+#import "FLEXInstancesTableViewController.h"
+
+/// Pretty much only necessary because I want to provide
+/// a useful subtitle for the bundles of classes
+@interface FLEXBundleShortcut : NSObject <FLEXShortcut>
+@end
+@implementation FLEXBundleShortcut
+
+- (NSString *)titleWith:(id)object {
+    return @"Bundle";
+}
+
+- (NSString *)subtitleWith:(id)object {
+    return [self shortNameForBundlePath:[NSBundle bundleForClass:object].executablePath];
+}
+
+- (UIViewController *)viewerWith:(id)object {
+    NSBundle *bundle = [NSBundle bundleForClass:object];
+    return [FLEXObjectExplorerFactory explorerViewControllerForObject:bundle];
+}
+
+- (NSString *)shortNameForBundlePath:(NSString *)imageName {
+    NSArray<NSString *> *components = [imageName componentsSeparatedByString:@"/"];
+    if (components.count >= 2) {
+        return [NSString stringWithFormat:@"%@/%@",
+            components[components.count - 2],
+            components[components.count - 1]
+        ];
+    }
+
+    return imageName.lastPathComponent;
+}
+
+- (UITableViewCellAccessoryType)accessoryTypeWith:(id)object {
+    NSParameterAssert(object != nil);
+    return UITableViewCellAccessoryDisclosureIndicator;
+}
+
+
+@end
+
+@interface FLEXClassShortcuts ()
+@property (nonatomic, readonly) Class cls;
+@end
+
+@implementation FLEXClassShortcuts
+
+#pragma mark - Internal
+
+- (Class)cls {
+    return self.object;
+}
+
+
+#pragma mark - Overrides
+
++ (instancetype)forObject:(Class)cls {
+    // These additional rows will appear at the beginning of the shortcuts section.
+    // The methods below are written in such a way that they will not interfere
+    // with properties/etc being registered alongside these
+    return [self forObject:cls additionalRows:@[[FLEXBundleShortcut new], @"Live Instances"]];
+}
+
+- (UIViewController *)viewControllerToPushForRow:(NSInteger)row {
+    if (row == 1) {
+        return [FLEXInstancesTableViewController
+            instancesTableViewControllerForClassName:NSStringFromClass(self.cls)
+        ];
+    }
+
+    return [super viewControllerToPushForRow:row];
+}
+
+- (UITableViewCellAccessoryType)accessoryTypeForRow:(NSInteger)row {
+    return row == 1 ? UITableViewCellAccessoryDisclosureIndicator : [super accessoryTypeForRow:row];
+}
+
+@end

+ 16 - 0
Classes/ObjectExplorers/Sections/Shortcuts/FLEXImageShortcuts.h

@@ -0,0 +1,16 @@
+//
+//  FLEXImageShortcuts.h
+//  FLEX
+//
+//  Created by Tanner Bennett on 8/29/19.
+//  Copyright © 2019 Flipboard. All rights reserved.
+//
+
+#import "FLEXShortcutsSection.h"
+
+/// Provides "view image" and "save image" shortcuts for UIImage objects
+@interface FLEXImageShortcuts : FLEXShortcutsSection
+
++ (instancetype)forObject:(UIImage *)image;
+
+@end

+ 64 - 0
Classes/ObjectExplorers/Sections/Shortcuts/FLEXImageShortcuts.m

@@ -0,0 +1,64 @@
+//
+//  FLEXImageShortcuts.m
+//  FLEX
+//
+//  Created by Tanner Bennett on 8/29/19.
+//  Copyright © 2019 Flipboard. All rights reserved.
+//
+
+#import "FLEXImageShortcuts.h"
+#import "FLEXImagePreviewViewController.h"
+#import "FLEXAlert.h"
+
+@interface FLEXImageShortcuts ()
+@property (nonatomic, readonly) UIImage *image;
+@end
+
+@implementation FLEXImageShortcuts
+
+#pragma mark - Internal
+
+- (UIImage *)image {
+    return self.object;
+}
+
+
+#pragma mark - Overrides
+
++ (instancetype)forObject:(UIImage *)image {
+    // These additional rows will appear at the beginning of the shortcuts section.
+    // The methods below are written in such a way that they will not interfere
+    // with properties/etc being registered alongside these
+    return [self forObject:image additionalRows:@[@"View Image", @"Save Image"]];
+}
+
+/// View image
+- (UIViewController *)viewControllerToPushForRow:(NSInteger)row {
+    if (row == 0) {
+        return [[FLEXImagePreviewViewController alloc] initWithImage:self.image];
+    }
+
+    return [super viewControllerToPushForRow:row];
+}
+
+/// Save image
+- (void (^)(UIViewController *))didSelectRowAction:(NSInteger)row {
+    if (row == 1) {
+        return ^(UIViewController *host) {
+            UIImageWriteToSavedPhotosAlbum(self.image, nil, nil, nil);
+        };
+    }
+
+    return [super didSelectRowAction:row];
+}
+
+/// "Save Image" does not need a disclosure indicator
+- (UITableViewCellAccessoryType)accessoryTypeForRow:(NSInteger)row {
+    switch (row) {
+        case 0:  return UITableViewCellAccessoryDisclosureIndicator;
+        case 1:  return UITableViewCellAccessoryNone;
+        default: return [super accessoryTypeForRow:row];
+    }
+}
+
+@end

+ 15 - 0
Classes/ObjectExplorers/Sections/Shortcuts/FLEXLayerShortcuts.h

@@ -0,0 +1,15 @@
+//
+//  FLEXLayerShortcuts.h
+//  FLEX
+//
+//  Created by Tanner Bennett on 12/12/19.
+//  Copyright © 2019 Flipboard. All rights reserved.
+//
+
+#import "FLEXShortcutsSection.h"
+
+@interface FLEXLayerShortcuts : FLEXShortcutsSection
+
++ (instancetype)forObject:(CALayer *)layer;
+
+@end

+ 54 - 0
Classes/ObjectExplorers/Sections/Shortcuts/FLEXLayerShortcuts.m

@@ -0,0 +1,54 @@
+//
+//  FLEXLayerShortcuts.m
+//  FLEX
+//
+//  Created by Tanner Bennett on 12/12/19.
+//  Copyright © 2019 Flipboard. All rights reserved.
+//
+
+#import "FLEXLayerShortcuts.h"
+#import "FLEXImagePreviewViewController.h"
+
+@interface FLEXLayerShortcuts ()
+@property (nonatomic, readonly) CALayer *layer;
+@end
+
+@implementation FLEXLayerShortcuts
+
+#pragma mark - Internal
+
+- (CALayer *)layer {
+    return self.object;
+}
+
+#pragma mark - Internal
+
+- (UIViewController *)imagePreviewViewController {
+    if (!CGRectIsEmpty(self.layer.bounds)) {
+        UIGraphicsBeginImageContextWithOptions(self.layer.bounds.size, NO, 0.0);
+        CGContextRef imageContext = UIGraphicsGetCurrentContext();
+        [self.layer renderInContext:imageContext];
+        UIImage *previewImage = UIGraphicsGetImageFromCurrentImageContext();
+        UIGraphicsEndImageContext();
+        return [FLEXImagePreviewViewController forImage:previewImage];
+    }
+
+    return nil;
+}
+
+
+#pragma mark - Overrides
+
++ (instancetype)forObject:(CALayer *)layer {
+    return [self forObject:layer additionalRows:@[@"Preview"]];
+}
+
+- (UIViewController *)viewControllerToPushForRow:(NSInteger)row {
+    if (row == 0) {
+        return [self imagePreviewViewController];
+    }
+
+    return [super viewControllerToPushForRow:row];
+}
+
+@end

+ 43 - 0
Classes/ObjectExplorers/Sections/Shortcuts/FLEXShortcut.h

@@ -0,0 +1,43 @@
+//
+//  FLEXShortcut.h
+//  FLEX
+//
+//  Created by Tanner Bennett on 12/10/19.
+//  Copyright © 2019 Flipboard. All rights reserved.
+//
+
+#import <UIKit/UIKit.h>
+
+/// Represents a row in a shortcut section.
+///
+/// The purpsoe of this protocol is to allow delegating a small
+/// subset of the responsibilities of a \c FLEXShortcutsSection
+/// to another object, for a single arbitrary row.
+///
+/// It is useful to make your own shortcuts to append/prepend
+/// them to the existing list of shortcuts for a class.
+@protocol FLEXShortcut <NSObject>
+
+- (NSString *)titleWith:(id)object;
+- (NSString *)subtitleWith:(id)object;
+//- (void (^)(UIViewController *))didSelectAction:(id)object;
+/// Called when the row is selected
+- (UIViewController *)viewerWith:(id)object;
+/// Basically, whether or not to show a detail disclosure indicator
+- (UITableViewCellAccessoryType)accessoryTypeWith:(id)object;
+
+@optional
+/// Called when the (i) button is pressed
+- (UIViewController *)editorWith:(id)object;
+
+@end
+
+/// Provides default behavior for FLEX metadata objects.
+@interface FLEXShortcut : NSObject <FLEXShortcut>
+
+/// @param item An \c NSString or \c FLEX* metadata object.
+/// @note You may also pass a \c FLEXShortcut conforming object,
+/// and that object will be returned instead.
++ (id<FLEXShortcut>)shortcutFor:(id)item;
+
+@end

+ 174 - 0
Classes/ObjectExplorers/Sections/Shortcuts/FLEXShortcut.m

@@ -0,0 +1,174 @@
+//
+//  FLEXShortcut.m
+//  FLEX
+//
+//  Created by Tanner Bennett on 12/10/19.
+//  Copyright © 2019 Flipboard. All rights reserved.
+//
+
+#import "FLEXShortcut.h"
+#import "FLEXProperty.h"
+#import "FLEXPropertyAttributes.h"
+#import "FLEXIvar.h"
+#import "FLEXMethod.h"
+#import "FLEXRuntime+UIKitHelpers.h"
+#import "FLEXObjectExplorerFactory.h"
+#import "FLEXFieldEditorViewController.h"
+#import "FLEXMethodCallingViewController.h"
+#import "FLEXMetadataSection.h"
+
+@interface FLEXShortcut () {
+    id _item;
+}
+
+@property (nonatomic, readonly) FLEXMetadataKind metadataKind;
+@property (nonatomic, readonly) FLEXProperty *property;
+@property (nonatomic, readonly) FLEXMethod *method;
+@property (nonatomic, readonly) FLEXIvar *ivar;
+@property (nonatomic, readonly) id<FLEXRuntimeMetadata> metadata;
+@end
+
+@implementation FLEXShortcut
+
++ (id<FLEXShortcut>)shortcutFor:(id)item {
+    if ([item conformsToProtocol:@protocol(FLEXShortcut)]) {
+        return item;
+    }
+    
+    FLEXShortcut *shortcut = [self new];
+    shortcut->_item = item;
+
+    if ([item isKindOfClass:[FLEXProperty class]]) {
+        // We don't care if it's a class property or not
+        shortcut->_metadataKind = FLEXMetadataKindProperties;
+    }
+    if ([item isKindOfClass:[FLEXIvar class]]) {
+        shortcut->_metadataKind = FLEXMetadataKindIvars;
+    }
+    if ([item isKindOfClass:[FLEXMethod class]]) {
+        // We don't care if it's a class method or not
+        shortcut->_metadataKind = FLEXMetadataKindMethods;
+    }
+
+    return shortcut;
+}
+
+- (id)propertyOrIvarValue:(id)fromObject {
+    // We use -[FLEXObjectExplorer valueFor...:] instead of getValue: below
+    // because we want to "preview" what object is being stored if this is
+    // a void * or something and we're given an NSValue back from getValue:
+    switch (self.metadataKind) {
+        case FLEXMetadataKindProperties:
+            return [self.property getPotentiallyUnboxedValue:fromObject];
+        case FLEXMetadataKindIvars:
+            return [self.ivar getPotentiallyUnboxedValue:fromObject];
+
+        // Methods: nil
+        case FLEXMetadataKindMethods:
+        case FLEXMetadataKindClassMethods:
+            return nil;
+    }
+}
+
+- (NSString *)titleWith:(id)object {
+    switch (self.metadataKind) {
+        case FLEXMetadataKindProperties:
+            // Since we're outside of the "properties" section, prepend @property for clarity.
+            return [@"@property " stringByAppendingString:[_item description]];
+        case FLEXMetadataKindIvars:
+        case FLEXMetadataKindMethods:
+            return [_item description];
+
+        default:
+            break;
+    }
+
+    if ([_item isKindOfClass:[NSString class]]) {
+        return _item;
+    }
+
+    [NSException
+        raise:NSInvalidArgumentException
+        format:@"Unsupported shortcut '%@':\n%@",
+        [_item class], [_item description]
+    ];
+    return nil;
+}
+
+- (NSString *)subtitleWith:(id)fromObject {
+    switch (self.metadataKind) {
+        case FLEXMetadataKindProperties:
+        case FLEXMetadataKindIvars:
+            return [FLEXRuntimeUtility
+                summaryForObject:[self propertyOrIvarValue:fromObject]
+            ];
+        case FLEXMetadataKindMethods:
+        case FLEXMetadataKindClassMethods:
+            return [_item selectorString];
+
+        default:
+            break;
+    }
+
+    if ([_item isKindOfClass:[NSString class]]) {
+        // Must return empty string since these will be
+        // gathered into an array. If the object is a
+        // just a string, it doesn't get a subtitle.
+        return @"";
+    }
+
+    [NSException
+        raise:NSInvalidArgumentException
+        format:@"Unsupported shortcut '%@':\n%@",
+        [_item class], [_item description]
+    ];
+    return nil;
+}
+
+- (UIViewController *)viewerWith:(id)object {
+    // View or edit a property or ivar
+    switch (self.metadataKind) {
+        case FLEXMetadataKindProperties:
+        case FLEXMetadataKindIvars:
+            return [FLEXObjectExplorerFactory
+                explorerViewControllerForObject:[self propertyOrIvarValue:object]
+            ];
+        case FLEXMetadataKindMethods:
+        case FLEXMetadataKindClassMethods:
+            object = self.method.isInstanceMethod ? object : (object_isClass(object) ? object : [object class]);
+            return [FLEXMethodCallingViewController target:object method:self.method];
+
+        default:
+            return nil;
+    }
+
+    return nil;
+}
+
+- (UIViewController *)editorWith:(id)object {
+    // Nil editor means unsupported ivar or property type, or nil value
+    if (self.metadataKind == FLEXMetadataKindProperties) {
+        return [FLEXFieldEditorViewController target:object property:self.property];
+    } else if (self.metadataKind == FLEXMetadataKindIvars) {
+        return [FLEXFieldEditorViewController target:object ivar:self.ivar];
+    }
+
+    return nil;
+}
+
+- (UITableViewCellAccessoryType)accessoryTypeWith:(id)object {
+    if (self.metadataKind) {
+        return [self.metadata suggestedAccessoryTypeWithTarget:object];
+    }
+
+    return UITableViewCellAccessoryDisclosureIndicator;
+}
+
+#pragma mark - Helpers
+
+- (FLEXProperty *)property { return _item; }
+- (FLEXMethodBase *)method { return _item; }
+- (FLEXIvar *)ivar { return _item; }
+- (id<FLEXRuntimeMetadata>)metadata { return _item; }
+
+@end

+ 19 - 0
Classes/ObjectExplorers/Sections/Shortcuts/FLEXShortcutsFactory+Defaults.h

@@ -0,0 +1,19 @@
+//
+//  FLEXShortcutsFactory+Defaults.h
+//  FLEX
+//
+//  Created by Tanner Bennett on 8/29/19.
+//  Copyright © 2019 Flipboard. All rights reserved.
+//
+
+#import "FLEXShortcutsSection.h"
+
+@interface FLEXShortcutsFactory (Views) @end
+
+@interface FLEXShortcutsFactory (ViewControllers) @end
+
+@interface FLEXShortcutsFactory (UIImage) @end
+
+@interface FLEXShortcutsFactory (NSBundle) @end
+
+@interface FLEXShortcutsFactory (Classes) @end

+ 128 - 0
Classes/ObjectExplorers/Sections/Shortcuts/FLEXShortcutsFactory+Defaults.m

@@ -0,0 +1,128 @@
+//
+//  FLEXShortcutsFactory+Defaults.m
+//  FLEX
+//
+//  Created by Tanner Bennett on 8/29/19.
+//  Copyright © 2019 Flipboard. All rights reserved.
+//
+
+#import "FLEXShortcutsFactory+Defaults.h"
+#import "FLEXShortcut.h"
+#import <objc/runtime.h>
+
+#pragma mark - Views
+
+@implementation FLEXShortcutsFactory (Views)
+
++ (void)load {
+    // Only available since iOS 3.2, but we never supported iOS 3, so who cares
+    NSArray *ivars = @[@"_gestureRecognizers"];
+
+    // UIVIew
+    self.append.ivars(ivars).properties(@[
+        @"frame", @"bounds", @"center", @"transform",
+        @"backgroundColor", @"alpha", @"opaque", @"hidden",
+        @"clipsToBounds", @"userInteractionEnabled", @"layer",
+        @"superview", @"subviews"
+    ]).forClass(UIView.class);
+
+    // UILabel
+    self.append.ivars(ivars).properties(@[
+        @"text", @"attributedText", @"font", @"frame",
+        @"textColor", @"textAlignment", @"numberOfLines",
+        @"lineBreakMode", @"enabled", @"backgroundColor",
+        @"alpha", @"hidden", @"preferredMaxLayoutWidth",
+        @"superview", @"subviews"
+    ]).forClass(UILabel.class);
+
+    // UIWindow
+    self.append.ivars(ivars).properties(@[
+        @"rootViewController", @"windowLevel", @"keyWindow",
+        @"frame", @"bounds", @"center", @"transform",
+        @"backgroundColor", @"alpha", @"opaque", @"hidden",
+        @"clipsToBounds", @"userInteractionEnabled", @"layer",
+        @"subviews"
+    ]).forClass(UIWindow.class);
+
+    if (@available(iOS 13, *)) {
+        self.append.properties(@[@"windowScene"]).forClass(UIWindow.class);
+    }
+
+    ivars = @[@"_targetActions", @"_gestureRecognizers"];
+
+    // UIControl
+    self.append.ivars(ivars).properties(@[
+        @"enabled", @"allTargets", @"frame",
+        @"backgroundColor", @"hidden", @"clipsToBounds",
+        @"userInteractionEnabled", @"superview", @"subviews"
+    ]).forClass(UIControl.class);
+
+    // UIButton
+    self.append.ivars(ivars).properties(@[
+        @"titleLabel", @"font", @"imageView", @"tintColor",
+        @"currentTitle", @"currentImage", @"enabled", @"frame",
+        @"superview", @"subviews"
+    ]).forClass(UIButton.class);
+}
+
+@end
+
+
+#pragma mark - View Controllers
+
+@implementation FLEXShortcutsFactory (ViewControllers)
+
++ (void)load {
+    // UIViewController
+    self.append
+        .properties(@[@"view", @"title", @"navigationItem"])
+        .forClass(UIViewController.class);
+}
+
+@end
+
+
+#pragma mark - UIImage
+
+@implementation FLEXShortcutsFactory (UIImage)
+
++ (void)load {
+    self.append.methods(@[
+        @"CGImage", @"CIImage"
+    ]).properties(@[
+        @"scale", @"size", @"capInsets",
+        @"alignmentRectInsets", @"duration", @"images"
+    ]).forClass(UIImage.class);
+
+    if (@available(iOS 13, *)) {
+        self.append.properties(@[@"symbolImage"]);
+    }
+}
+
+@end
+
+
+#pragma mark - NSBundle
+
+@implementation FLEXShortcutsFactory (NSBundle)
+
++ (void)load {
+    self.append.properties(@[
+        @"bundleIdentifier", @"principalClass",
+        @"infoDictionary", @"bundlePath",
+        @"executablePath", @"loaded"
+    ]).forClass(NSBundle.class);
+}
+
+@end
+
+
+#pragma mark - Classes
+
+@implementation FLEXShortcutsFactory (Classes)
+
++ (void)load {
+    self.append.methods(@[@"new", @"alloc"]).forClass(objc_getMetaClass("NSObject"));
+}
+
+@end

+ 122 - 0
Classes/ObjectExplorers/Sections/Shortcuts/FLEXShortcutsSection.h

@@ -0,0 +1,122 @@
+//
+//  FLEXShortcutsSection.h
+//  FLEX
+//
+//  Created by Tanner Bennett on 8/29/19.
+//  Copyright © 2019 Flipboard. All rights reserved.
+//
+
+#import "FLEXExplorerSection.h"
+@class FLEXProperty, FLEXIvar, FLEXMethod;
+
+/// An abstract base class for custom object "shortcuts" where every
+/// row can possibly have some action. The section title is "Shortcuts".
+///
+/// You should only subclass this class if you need simple shortcuts
+/// with plain titles and/or subtitles. This class will automatically
+/// configure each cell appropriately. Since this is intended as a
+/// static section, subclasses should only need to implement the
+/// \c viewControllerToPushForRow: and/or \c didSelectRowAction: methods.
+///
+/// If you create the section using \c forObject:rows:numberOfLines:
+/// then it will provide a view controller from \c viewControllerToPushForRow:
+/// automatically for rows that are a property/ivar/method.
+@interface FLEXShortcutsSection : FLEXExplorerSection <FLEXObjectInfoSection>
+
+/// Uses \c kFLEXDefaultCell
++ (instancetype)forObject:(id)objectOrClass rowTitles:(NSArray<NSString *> *)titles;
+/// Uses \c kFLEXDetailCell for non-empty subtitles, otherwise uses \c kFLEXDefaultCell
++ (instancetype)forObject:(id)objectOrClass
+                rowTitles:(NSArray<NSString *> *)titles
+             rowSubtitles:(NSArray<NSString *> *)subtitles;
+
+/// Uses \c kFLEXDefaultCell for rows that are given a title, otherwise
+/// this uses \c kFLEXDetailCell for any other allowed object.
+///
+/// The section provide a view controller from \c viewControllerToPushForRow:
+/// automatically for rows that are a property/ivar/method.
+///
+/// @param rows A mixed array containing any of the following:
+/// - any \c FLEXShortcut conforming object
+/// - an \c NSString
+/// - a \c FLEXProperty
+/// - a \c FLEXIvar
+/// - a \c FLEXMethodBase (includes \c FLEXMethod of course)
+/// Passing one of the latter 3 will provide a shortcut to that property/ivar/method.
+/// @return \c nil if no rows are provided
++ (instancetype)forObject:(id)objectOrClass rows:(NSArray *)rows;
+
+/// Same as \c forObject:rows: but the given rows are prepended
+/// to the shortcuts already registered for the object's class.
+/// \c forObject:rows: does not use the registered shortcuts at all.
++ (instancetype)forObject:(id)objectOrClass additionalRows:(NSArray *)rows;
+
+/// Calls into \c forObject:rows: using the registered shortcuts for the object's class.
+/// @return \c nil if the object has no shortcuts registered at all
++ (instancetype)forObject:(id)objectOrClass;
+
+/// Subclasses \e may override this to hide the disclosure indicator
+/// for some rows. It is shown for all rows by default, unless
+/// you initialize it with \c forObject:rowTitles:rowSubtitles:
+- (UITableViewCellAccessoryType)accessoryTypeForRow:(NSInteger)row;
+
+/// The number of lines for the title and subtitle labels. Defaults to 1.
+@property (nonatomic, readonly) NSInteger numberOfLines;
+/// The object used to initialize this section.
+@property (nonatomic, readonly) id object;
+
+/// Whether dynamic subtitles should always be computed as a cell is configured.
+/// Defaults to NO. Has no effect on static subtitles that are passed explicitly.
+@property (nonatomic) BOOL cacheSubtitles;
+
+@end
+
+@class FLEXShortcutsFactory;
+typedef FLEXShortcutsFactory *(^FLEXShortcutsFactoryNames)(NSArray *names);
+typedef void (^FLEXShortcutsFactoryTarget)(Class targetClass);
+
+/// The block properties below are to be used like SnapKit or Masonry.
+/// \c FLEXShortcutsSection.append.properties(@[@"frame",@"bounds"]).forClass(UIView.class);
+///
+/// To safely register your own classes at launch, subclass this class,
+/// override \c +load, and call the appropriate methods on \c self
+@interface FLEXShortcutsFactory : NSObject
+
+/// Returns the list of all registered shortcuts for the given object in this order:
+/// Properties, ivars, methods.
+///
+/// This method traverses up the object's class hierarchy until it finds
+/// something registered. This allows you to show different shortcuts for
+/// the same object in different parts of the class hierarchy.
+///
+/// As an example, UIView may have a -layer shortcut registered. But if
+/// you're inspecting a UIControl, you may not care about the layer or other
+/// UIView-specific things; you might rather see the target-actions registered
+/// for this control, and so you would register that property or ivar to UIControl,
+/// And you would still be able to see the UIView-registered shorcuts by clicking
+/// on the UIView "lens" at the top the explorer view controller screen.
++ (NSArray *)shortcutsForObjectOrClass:(id)objectOrClass;
+
+@property (nonatomic, readonly, class) FLEXShortcutsFactory *append;
+@property (nonatomic, readonly, class) FLEXShortcutsFactory *prepend;
+@property (nonatomic, readonly, class) FLEXShortcutsFactory *replace;
+
+@property (nonatomic, readonly) FLEXShortcutsFactoryNames properties;
+/// Do not try to set \c classProperties at the same time as \c ivars or other instance things.
+@property (nonatomic, readonly) FLEXShortcutsFactoryNames classProperties;
+@property (nonatomic, readonly) FLEXShortcutsFactoryNames ivars;
+@property (nonatomic, readonly) FLEXShortcutsFactoryNames methods;
+/// Do not try to set \c classMethods at the same time as \c ivars or other instance things.
+@property (nonatomic, readonly) FLEXShortcutsFactoryNames classMethods;
+
+/// Accepts the target class. If you pass a regular class object,
+/// shortcuts will appear on instances. If you pass a metaclass object,
+/// shortcuts will appear when exploring a class object.
+///
+/// For example, some class method shortcuts are added to the NSObject meta
+/// class by default so that you can see +alloc and +new when exploring
+/// a class object. If you wanted these to show up when exploring
+/// instances you would pass them to the classMethods method above.
+@property (nonatomic, readonly) FLEXShortcutsFactoryTarget forClass;
+
+@end

+ 423 - 0
Classes/ObjectExplorers/Sections/Shortcuts/FLEXShortcutsSection.m

@@ -0,0 +1,423 @@
+//
+//  FLEXShortcutsSection.m
+//  FLEX
+//
+//  Created by Tanner Bennett on 8/29/19.
+//  Copyright © 2019 Flipboard. All rights reserved.
+//
+
+#import "FLEXShortcutsSection.h"
+#import "FLEXTableView.h"
+#import "FLEXTableViewCell.h"
+#import "FLEXUtility.h"
+#import "FLEXShortcut.h"
+#import "FLEXProperty.h"
+#import "FLEXPropertyAttributes.h"
+#import "FLEXIvar.h"
+#import "FLEXMethod.h"
+#import "FLEXRuntime+UIKitHelpers.h"
+
+#pragma mark Private
+
+@interface FLEXShortcutsSection ()
+@property (nonatomic, copy) NSArray<NSString *> *titles;
+@property (nonatomic, copy) NSArray<NSString *> *subtitles;
+
+@property (nonatomic, copy) NSArray<NSString *> *allTitles;
+@property (nonatomic, copy) NSArray<NSString *> *allSubtitles;
+
+// Shortcuts are not used if initialized with static titles and subtitles
+@property (nonatomic, copy) NSArray<id<FLEXShortcut>> *shortcuts;
+@property (nonatomic, readonly) NSArray<id<FLEXShortcut>> *allShortcuts;
+@end
+
+@implementation FLEXShortcutsSection
+
+#pragma mark Initialization
+
++ (instancetype)forObject:(id)objectOrClass rowTitles:(NSArray<NSString *> *)titles {
+    return [self forObject:objectOrClass rowTitles:titles rowSubtitles:nil];
+}
+
++ (instancetype)forObject:(id)objectOrClass
+                rowTitles:(NSArray<NSString *> *)titles
+             rowSubtitles:(NSArray<NSString *> *)subtitles {
+    return [[self alloc] initWithObject:objectOrClass titles:titles subtitles:subtitles];
+}
+
++ (instancetype)forObject:(id)objectOrClass rows:(NSArray *)rows {
+    return [[self alloc] initWithObject:objectOrClass rows:rows];
+}
+
++ (instancetype)forObject:(id)objectOrClass additionalRows:(NSArray *)toPrepend {
+    NSArray *rows = [FLEXShortcutsFactory shortcutsForObjectOrClass:objectOrClass];
+    NSArray *allRows = [toPrepend arrayByAddingObjectsFromArray:rows] ?: rows;
+    return [self forObject:objectOrClass rows:allRows];
+}
+
++ (instancetype)forObject:(id)objectOrClass {
+    return [self forObject:objectOrClass additionalRows:nil];
+}
+
+- (id)initWithObject:(id)object
+              titles:(NSArray<NSString *> *)titles
+           subtitles:(NSArray<NSString *> *)subtitles {
+
+    NSParameterAssert(titles.count == subtitles.count || !subtitles);
+    NSParameterAssert(titles.count);
+
+    self = [super init];
+    if (self) {
+        _object = object;
+        _allTitles = titles.copy;
+        _allSubtitles = subtitles.copy;
+        _numberOfLines = 1;
+    }
+
+    return self;
+}
+
+- (id)initWithObject:object rows:(NSArray *)rows {
+    self = [super init];
+    if (self) {
+        _object = object;
+        
+        _allShortcuts = [rows flex_mapped:^id(id obj, NSUInteger idx) {
+            return [FLEXShortcut shortcutFor:obj];
+        }];
+        _numberOfLines = 1;
+        // Populate titles and subtitles
+        [self reloadData];
+    }
+
+    return self;
+}
+
+
+#pragma mark - Public
+
+- (void)setCacheSubtitles:(BOOL)cacheSubtitles {
+    if (_cacheSubtitles == cacheSubtitles) return;
+
+    // cacheSubtitles only applies if we have shortcut objects
+    if (self.allShortcuts) {
+        _cacheSubtitles = cacheSubtitles;
+        [self reloadData];
+    } else {
+        NSLog(@"Warning: setting 'cacheSubtitles' on a shortcut section with static subtitles");
+    }
+}
+
+
+#pragma mark - Overrides
+
+- (UITableViewCellAccessoryType)accessoryTypeForRow:(NSInteger)row {
+    if (_allShortcuts) {
+        return [self.shortcuts[row] accessoryTypeWith:self.object];
+    }
+    
+    return UITableViewCellAccessoryNone;
+}
+
+- (void)setFilterText:(NSString *)filterText {
+    super.filterText = filterText;
+
+    if (filterText.length) {
+        // Tally up indexes of titles and subtitles matching the filter
+        NSMutableIndexSet *filterMatches = [NSMutableIndexSet new];
+        id filterBlock = ^BOOL(NSString *obj, NSUInteger idx) {
+            if ([obj localizedCaseInsensitiveContainsString:filterText]) {
+                [filterMatches addIndex:idx];
+                return YES;
+            }
+
+            return NO;
+        };
+
+        // Get all matching indexes, including subtitles
+        [self.allTitles flex_forEach:filterBlock];
+        [self.allSubtitles flex_forEach:filterBlock];
+        // Filter to matching indexes only
+        self.titles    = [self.allTitles objectsAtIndexes:filterMatches];
+        self.subtitles = [self.allSubtitles objectsAtIndexes:filterMatches];
+        self.shortcuts = [self.allShortcuts objectsAtIndexes:filterMatches];
+    } else {
+        self.titles    = self.allTitles;
+        self.subtitles = self.allSubtitles;
+        self.shortcuts = self.allShortcuts;
+    }
+}
+
+- (void)reloadData {
+    // Generate all (sub)titles from shortcuts
+    if (self.allShortcuts) {
+        self.allTitles = [self.allShortcuts flex_mapped:^id(FLEXShortcut *s, NSUInteger idx) {
+            return [s titleWith:self.object];
+        }];
+        self.allSubtitles = [self.allShortcuts flex_mapped:^id(FLEXShortcut *s, NSUInteger idx) {
+            return [s subtitleWith:self.object];
+        }];
+    }
+
+    // Re-generate filtered (sub)titles and shortcuts
+    self.filterText = self.filterText;
+}
+
+- (NSString *)title {
+    return @"Shortcuts";
+}
+
+- (NSInteger)numberOfRows {
+    return self.titles.count;
+}
+
+- (BOOL)canSelectRow:(NSInteger)row {
+    UITableViewCellAccessoryType type = [self.shortcuts[row] accessoryTypeWith:self.object];
+    BOOL hasDisclosure = NO;
+    hasDisclosure |= type == UITableViewCellAccessoryDisclosureIndicator;	
+    hasDisclosure |= type == UITableViewCellAccessoryDetailDisclosureButton;
+    return hasDisclosure;
+}
+
+- (UIViewController *)viewControllerToPushForRow:(NSInteger)row {
+    /// Nil if shortcuts is nil, i.e. if initialized with forObject:rowTitles:rowSubtitles:
+    return [self.shortcuts[row] viewerWith:self.object];
+}
+
+- (void (^)(UIViewController *))didPressInfoButtonAction:(NSInteger)row {
+    id<FLEXShortcut> shortcut = self.shortcuts[row];
+    if ([shortcut respondsToSelector:@selector(editorWith:)]) {
+        id object = self.object;
+        return ^(UIViewController *host) {
+            UIViewController *editor = [shortcut editorWith:object];
+            [host.navigationController pushViewController:editor animated:YES];
+        };
+    }
+
+    return nil;
+}
+
+- (NSString *)reuseIdentifierForRow:(NSInteger)row {
+    if (self.subtitles[row].length) {
+        // Title+subtitle: properties, ivars, methods, custom
+        return kFLEXMultilineDetailCell;
+    }
+
+    // Just a title string
+    return kFLEXMultilineCell;
+}
+
+- (void)configureCell:(__kindof FLEXTableViewCell *)cell forRow:(NSInteger)row {
+    cell.titleLabel.text = self.titles[row];
+    cell.titleLabel.numberOfLines = self.numberOfLines;
+    cell.subtitleLabel.text = self.subtitles[row];
+    cell.subtitleLabel.numberOfLines = self.numberOfLines;
+    cell.accessoryType = [self accessoryTypeForRow:row];
+}
+
+- (NSString *)titleForRow:(NSInteger)row {
+    return self.titles[row];
+}
+
+- (NSString *)subtitleForRow:(NSInteger)row {
+    // Case: dynamic, uncached subtitles
+    if (!self.cacheSubtitles) {
+        return [self.shortcuts[row] subtitleWith:self.object];
+    }
+
+    // Case: static subtitles, or cached subtitles
+    return self.subtitles[row];
+}
+
+// Not sure what this was for, maybe I added filterText after the fact?
+//- (BOOL)row:(NSInteger)row matchesFilter:(NSString *)query {
+//    if ([self.titles[row] localizedCaseInsensitiveContainsString:query]) {
+//        return YES;
+//    }
+//
+//    return [self.subtitles[row] localizedCaseInsensitiveContainsString:query];
+//}
+
+@end
+
+
+#pragma mark - Global shortcut registration
+
+@interface FLEXShortcutsFactory () {
+    BOOL _append, _prepend, _replace, _notInstance;
+    NSArray<NSString *> *_properties, *_ivars, *_methods;
+}
+@end
+
+#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;
+
++ (void)load {
+    cProperties = [NSMutableDictionary new];
+    cIvars = [NSMutableDictionary new];
+    cMethods = [NSMutableDictionary new];
+
+    mProperties = [NSMutableDictionary new];
+    mMethods = [NSMutableDictionary new];
+}
+
++ (NSArray<id<FLEXRuntimeMetadata>> *)shortcutsForObjectOrClass:(id)objectOrClass {
+    if (object_isClass(objectOrClass)) {
+        NSLog(@"+[FLEXShortcutsFactory shortcutsForObjectOrClass:] does not accept classes yet");
+        return nil;
+    }
+
+    NSMutableArray<id<FLEXRuntimeMetadata>> *shortcuts = [NSMutableArray new];
+    Class classKey = [objectOrClass class];
+
+    BOOL stop = NO;
+    while (!stop && classKey) {
+        NSArray *properties = cProperties[classKey];
+        NSArray *ivars = cIvars[classKey];
+        NSArray *methods = cMethods[classKey];
+
+        // Stop if we found anything
+        stop = properties || ivars || methods;
+        if (stop) {
+            // Add things we found to the list
+            [shortcuts addObjectsFromArray:properties];
+            [shortcuts addObjectsFromArray:ivars];
+            [shortcuts addObjectsFromArray:methods];
+        } else {
+            classKey = [classKey superclass];
+        }
+    }
+
+    // .tag is used to cache whether the value of .isEditable;
+    // This could change at runtime so it is important that
+    // it is cached every time shortcuts are requeted and not
+    // just once at as shortcuts are initially registered
+    for (id<FLEXRuntimeMetadata> metadata in shortcuts) {
+        metadata.tag = metadata.isEditable ? @YES : nil;
+    }
+
+    return shortcuts;
+}
+
++ (FLEXShortcutsFactory *)append {
+    return NewAndSet(_append);
+}
+
++ (FLEXShortcutsFactory *)prepend {
+    return NewAndSet(_prepend);
+}
+
++ (FLEXShortcutsFactory *)replace {
+    return NewAndSet(_replace);
+}
+
+- (void)_register:(NSArray<id<FLEXRuntimeMetadata>> *)items to:(RegistrationBuckets *)global class:(Class)key {
+    // 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];
+        }
+    }
+}
+
+- (FLEXShortcutsFactory *)class {
+    return SetIvar(_notInstance);
+}
+
+- (FLEXShortcutsFactoryNames)properties {
+    NSAssert(!_notInstance, @"Do not try to set properties+classProperties at the same time");
+    return SetParamBlock(_properties);
+}
+
+- (FLEXShortcutsFactoryNames)classProperties {
+    _notInstance = YES;
+    return SetParamBlock(_properties);
+}
+
+- (FLEXShortcutsFactoryNames)ivars {
+    return SetParamBlock(_ivars);
+}
+
+- (FLEXShortcutsFactoryNames)methods {
+    NSAssert(!_notInstance, @"Do not try to set methods+classMethods at the same time");
+    return SetParamBlock(_methods);
+}
+
+- (FLEXShortcutsFactoryNames)classMethods {
+    _notInstance = YES;
+    return SetParamBlock(_methods);
+}
+
+- (FLEXShortcutsFactoryTarget)forClass {
+    return ^(Class cls) {
+        NSAssert(
+            ( self->_append && !self->_prepend && !self->_replace) ||
+            (!self->_append &&  self->_prepend && !self->_replace) ||
+            (!self->_append && !self->_prepend &&  self->_replace),
+            @"You can only do one of [append, prepend, replace]"
+        );
+
+        BOOL onInstance = !self->_notInstance;
+
+        BOOL isMeta = class_isMetaClass(cls);
+        RegistrationBuckets *propertyBucket = isMeta ? mProperties : cProperties;
+        RegistrationBuckets *methodBucket = isMeta ? mMethods : cMethods;
+        RegistrationBuckets *ivarBucket = isMeta ? nil : cIvars;
+
+        // Pass the metaclass to the runtime wrappers if !onInstance
+        Class targetClass = onInstance ? cls : object_getClass(cls);
+
+        if (self->_properties) {
+            NSArray *items = [self->_properties flex_mapped:^id(NSString *name, NSUInteger idx) {
+                return [FLEXProperty named:name onClass:targetClass];
+            }];
+            [self _register:items to:propertyBucket class:cls];
+        }
+
+        if (self->_methods) {
+            NSArray *items = [self->_methods flex_mapped:^id(NSString *name, NSUInteger idx) {
+                return [FLEXMethod selector:NSSelectorFromString(name) class:targetClass];
+            }];
+            [self _register:items to:methodBucket class:cls];
+        }
+
+        if (self->_ivars) {
+            NSAssert(onInstance, @"Cannot register ivar shortcuts for classes (%@)", cls);
+            NSAssert(!isMeta, @"Cannot register ivar shortcuts for metaclasses (%@)", cls);
+            NSArray *items = [self->_ivars flex_mapped:^id(NSString *name, NSUInteger idx) {
+                return [FLEXIvar named:name onClass:cls];
+            }];
+            [self _register:items to:ivarBucket class:cls];
+        }
+    };
+}
+
+@end

+ 15 - 0
Classes/ObjectExplorers/Sections/Shortcuts/FLEXViewControllerShortcuts.h

@@ -0,0 +1,15 @@
+//
+//  FLEXViewControllerShortcuts.h
+//  FLEX
+//
+//  Created by Tanner Bennett on 12/12/19.
+//  Copyright © 2019 Flipboard. All rights reserved.
+//
+
+#import "FLEXShortcutsSection.h"
+
+@interface FLEXViewControllerShortcuts : FLEXShortcutsSection
+
++ (instancetype)forObject:(UIViewController *)viewController;
+
+@end

+ 76 - 0
Classes/ObjectExplorers/Sections/Shortcuts/FLEXViewControllerShortcuts.m

@@ -0,0 +1,76 @@
+//
+//  FLEXViewControllerShortcuts.m
+//  FLEX
+//
+//  Created by Tanner Bennett on 12/12/19.
+//  Copyright © 2019 Flipboard. All rights reserved.
+//
+
+#import "FLEXViewControllerShortcuts.h"
+#import "FLEXAlert.h"
+
+@interface FLEXViewControllerShortcuts ()
+@property (nonatomic, readonly) UIViewController *viewController;
+@property (nonatomic, readonly) BOOL viewControllerIsInUse;
+@end
+
+@implementation FLEXViewControllerShortcuts
+
+#pragma mark - Internal
+
+- (UIViewController *)viewController {
+    return self.object;
+}
+
+/// A view controller is "in use" if it's view is in a window,
+/// or if it belongs to a navigation stack which is in use.
+- (BOOL)viewControllerIsInUse {
+    if (self.viewController.view.window) {
+        return YES;
+    }
+
+    return self.viewController.navigationController != nil;
+}
+
+
+#pragma mark - Overrides
+
++ (instancetype)forObject:(UIViewController *)viewController {
+    // These additional rows will appear at the beginning of the shortcuts section.
+    // The methods below are written in such a way that they will not interfere
+    // with properties/etc being registered alongside these
+    return [self forObject:viewController additionalRows:@[@"Push View Controoller"]];
+}
+
+- (void (^)(UIViewController *))didSelectRowAction:(NSInteger)row {
+    if (row == 0) {
+        return ^(UIViewController *host) {
+            if (!self.viewControllerIsInUse) {
+                [host.navigationController pushViewController:self.viewController animated:YES];
+            } else {
+                [FLEXAlert
+                    showAlert:@"Cannot Push View Controller"
+                    message:@"This view controller's view is currently in use."
+                    from:host
+                ];
+            }
+        };
+    }
+
+    return [super didSelectRowAction:row];
+}
+
+- (UITableViewCellAccessoryType)accessoryTypeForRow:(NSInteger)row {
+    switch (row) {
+        case 0:
+            if (self.viewControllerIsInUse) {
+                return UITableViewCellAccessoryDisclosureIndicator;
+            } else {
+                return UITableViewCellAccessoryNone;
+            }
+        default:
+            return [super accessoryTypeForRow:row];
+    }
+}
+
+@end

+ 13 - 0
Classes/ObjectExplorers/Sections/Shortcuts/FLEXViewShortcuts.h

@@ -0,0 +1,13 @@
+//
+//  FLEXViewShortcuts.h
+//  FLEX
+//
+//  Created by Tanner Bennett on 12/11/19.
+//  Copyright © 2019 Flipboard. All rights reserved.
+//
+
+#import "FLEXShortcutsSection.h"
+
+@interface FLEXViewShortcuts : FLEXShortcutsSection
+
+@end

+ 101 - 0
Classes/ObjectExplorers/Sections/Shortcuts/FLEXViewShortcuts.m

@@ -0,0 +1,101 @@
+//
+//  FLEXViewShortcuts.m
+//  FLEX
+//
+//  Created by Tanner Bennett on 12/11/19.
+//  Copyright © 2019 Flipboard. All rights reserved.
+//
+
+#import "FLEXViewShortcuts.h"
+#import "FLEXObjectExplorerFactory.h"
+#import "FLEXImagePreviewViewController.h"
+
+@interface FLEXViewShortcuts ()
+@property (nonatomic, readonly) UIView *view;
+@property (nonatomic, readonly) BOOL showsViewControllerRow;
+@end
+
+@implementation FLEXViewShortcuts
+
+#pragma mark - Internal
+
+- (UIView *)view {
+    return self.object;
+}
+
++ (UIViewController *)viewControllerForView:(UIView *)view {
+    NSString *viewDelegate = @"viewDelegate";
+    if ([view respondsToSelector:NSSelectorFromString(viewDelegate)]) {
+        return [view valueForKey:viewDelegate];
+    }
+
+    return nil;
+}
+
++ (UIViewController *)viewControllerForAncestralView:(UIView *)view {
+    NSString *_viewControllerForAncestor = @"_viewControllerForAncestor";
+    if ([view respondsToSelector:NSSelectorFromString(_viewControllerForAncestor)]) {
+        return [view valueForKey:_viewControllerForAncestor];
+    }
+
+    return nil;
+}
+
+- (UIViewController *)viewControllerForView {
+    return [[self class] viewControllerForView:self.view] ?:
+        [[self class] viewControllerForAncestralView:self.view];
+}
+
+- (UIViewController *)imagePreviewViewController {
+    if (!CGRectIsEmpty(self.view.bounds)) {
+        CGSize viewSize = self.view.bounds.size;
+        UIGraphicsBeginImageContextWithOptions(viewSize, NO, 0.0);
+        [self.view drawViewHierarchyInRect:CGRectMake(0, 0, viewSize.width, viewSize.height) afterScreenUpdates:YES];
+        UIImage *previewImage = UIGraphicsGetImageFromCurrentImageContext();
+        UIGraphicsEndImageContext();
+        return [FLEXImagePreviewViewController forImage:previewImage];
+    }
+
+    return nil;
+}
+
+#pragma mark - Overrides
+
++ (instancetype)forObject:(UIView *)view {
+    // Views without a superview don't need the "View Controller for Ancestor" row
+    BOOL hasViewController = [self viewControllerForView:view] != nil;
+    BOOL hasAncestralVC = [self viewControllerForAncestralView:view] != nil;
+    NSString *vcRowTitle = hasViewController ? @"View Controller" :
+        hasAncestralVC ? @"View Controller for Ancestor" : nil;
+
+    // These additional rows will appear at the beginning of the shortcuts section.
+    // The methods below are written in such a way that they will not interfere
+    // with properties/etc being registered alongside these
+    FLEXViewShortcuts *shortcuts = [self forObject:view additionalRows:(
+        vcRowTitle ? @[vcRowTitle, @"Preview"] : @[@"Preview"]
+    )];
+    shortcuts->_showsViewControllerRow = hasViewController || hasAncestralVC;
+    return shortcuts;
+}
+
+- (UIViewController *)viewControllerToPushForRow:(NSInteger)row {
+    switch (row) {
+        case 0:
+            if (self.showsViewControllerRow) {
+                return [FLEXObjectExplorerFactory
+                    explorerViewControllerForObject:[self viewControllerForView]
+                ];
+            } else {
+                return [self imagePreviewViewController];
+            }
+        case 1:
+            if (self.showsViewControllerRow) {
+                return [self imagePreviewViewController];
+            }
+
+        default:
+            return [super viewControllerToPushForRow:row];
+    }
+}
+
+@end

+ 144 - 4
FLEX.xcodeproj/project.pbxproj

@@ -171,14 +171,28 @@
 		94AAF0391BAF2E1F00DE8760 /* FLEXKeyboardHelpViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 94AAF0371BAF2E1F00DE8760 /* FLEXKeyboardHelpViewController.m */; };
 		94AAF0391BAF2E1F00DE8760 /* FLEXKeyboardHelpViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 94AAF0371BAF2E1F00DE8760 /* FLEXKeyboardHelpViewController.m */; };
 		94AAF03A1BAF2F0300DE8760 /* FLEXKeyboardShortcutManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 942DCD821BAE0AD300DB5DC2 /* FLEXKeyboardShortcutManager.h */; settings = {ATTRIBUTES = (Private, ); }; };
 		94AAF03A1BAF2F0300DE8760 /* FLEXKeyboardShortcutManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 942DCD821BAE0AD300DB5DC2 /* FLEXKeyboardShortcutManager.h */; settings = {ATTRIBUTES = (Private, ); }; };
 		C309B82F223ED64400B228EC /* FLEXLogController.h in Headers */ = {isa = PBXBuildFile; fileRef = C309B82D223ED64400B228EC /* FLEXLogController.h */; };
 		C309B82F223ED64400B228EC /* FLEXLogController.h in Headers */ = {isa = PBXBuildFile; fileRef = C309B82D223ED64400B228EC /* FLEXLogController.h */; };
+		C31C4A6923342A2200C35F12 /* FLEXMetadataSection.h in Headers */ = {isa = PBXBuildFile; fileRef = C31C4A6723342A2200C35F12 /* FLEXMetadataSection.h */; };
+		C31C4A6A23342A2200C35F12 /* FLEXMetadataSection.m in Sources */ = {isa = PBXBuildFile; fileRef = C31C4A6823342A2200C35F12 /* FLEXMetadataSection.m */; };
+		C32A195E231732E800EB02AC /* FLEXCollectionContentSection.h in Headers */ = {isa = PBXBuildFile; fileRef = C32A195C231732E800EB02AC /* FLEXCollectionContentSection.h */; };
+		C32A195F231732E800EB02AC /* FLEXCollectionContentSection.m in Sources */ = {isa = PBXBuildFile; fileRef = C32A195D231732E800EB02AC /* FLEXCollectionContentSection.m */; };
+		C32A19622317378C00EB02AC /* FLEXDefaultsContentSection.h in Headers */ = {isa = PBXBuildFile; fileRef = C32A19602317378C00EB02AC /* FLEXDefaultsContentSection.h */; };
+		C32A19632317378C00EB02AC /* FLEXDefaultsContentSection.m in Sources */ = {isa = PBXBuildFile; fileRef = C32A19612317378C00EB02AC /* FLEXDefaultsContentSection.m */; };
 		C33C825E2316DC8600DD2451 /* FLEXObjectExplorer.h in Headers */ = {isa = PBXBuildFile; fileRef = C33C825C2316DC8600DD2451 /* FLEXObjectExplorer.h */; };
 		C33C825E2316DC8600DD2451 /* FLEXObjectExplorer.h in Headers */ = {isa = PBXBuildFile; fileRef = C33C825C2316DC8600DD2451 /* FLEXObjectExplorer.h */; };
 		C33C825F2316DC8600DD2451 /* FLEXObjectExplorer.m in Sources */ = {isa = PBXBuildFile; fileRef = C33C825D2316DC8600DD2451 /* FLEXObjectExplorer.m */; };
 		C33C825F2316DC8600DD2451 /* FLEXObjectExplorer.m in Sources */ = {isa = PBXBuildFile; fileRef = C33C825D2316DC8600DD2451 /* FLEXObjectExplorer.m */; };
 		C33E46AF223B02CD004BD0E6 /* FLEXASLLogController.h in Headers */ = {isa = PBXBuildFile; fileRef = C33E46AD223B02CD004BD0E6 /* FLEXASLLogController.h */; };
 		C33E46AF223B02CD004BD0E6 /* FLEXASLLogController.h in Headers */ = {isa = PBXBuildFile; fileRef = C33E46AD223B02CD004BD0E6 /* FLEXASLLogController.h */; };
 		C33E46B0223B02CD004BD0E6 /* FLEXASLLogController.m in Sources */ = {isa = PBXBuildFile; fileRef = C33E46AE223B02CD004BD0E6 /* FLEXASLLogController.m */; };
 		C33E46B0223B02CD004BD0E6 /* FLEXASLLogController.m in Sources */ = {isa = PBXBuildFile; fileRef = C33E46AE223B02CD004BD0E6 /* FLEXASLLogController.m */; };
+		C3490E1F233BDD73002AE200 /* FLEXSingleRowSection.h in Headers */ = {isa = PBXBuildFile; fileRef = C3490E1D233BDD73002AE200 /* FLEXSingleRowSection.h */; };
+		C3490E20233BDD73002AE200 /* FLEXSingleRowSection.m in Sources */ = {isa = PBXBuildFile; fileRef = C3490E1E233BDD73002AE200 /* FLEXSingleRowSection.m */; };
 		C34A70A022B2EC8D009C2C5F /* FLEXBundleExplorerViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = C34A709E22B2EC8D009C2C5F /* FLEXBundleExplorerViewController.h */; };
 		C34A70A022B2EC8D009C2C5F /* FLEXBundleExplorerViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = C34A709E22B2EC8D009C2C5F /* FLEXBundleExplorerViewController.h */; };
 		C34A70A122B2EC8D009C2C5F /* FLEXBundleExplorerViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C34A709F22B2EC8D009C2C5F /* FLEXBundleExplorerViewController.m */; };
 		C34A70A122B2EC8D009C2C5F /* FLEXBundleExplorerViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C34A709F22B2EC8D009C2C5F /* FLEXBundleExplorerViewController.m */; };
 		C34C9BDD23A7F2740031CA3E /* FLEXRuntime+UIKitHelpers.h in Headers */ = {isa = PBXBuildFile; fileRef = C34C9BDB23A7F2740031CA3E /* FLEXRuntime+UIKitHelpers.h */; };
 		C34C9BDD23A7F2740031CA3E /* FLEXRuntime+UIKitHelpers.h in Headers */ = {isa = PBXBuildFile; fileRef = C34C9BDB23A7F2740031CA3E /* FLEXRuntime+UIKitHelpers.h */; };
 		C34C9BDE23A7F2740031CA3E /* FLEXRuntime+UIKitHelpers.m in Sources */ = {isa = PBXBuildFile; fileRef = C34C9BDC23A7F2740031CA3E /* FLEXRuntime+UIKitHelpers.m */; };
 		C34C9BDE23A7F2740031CA3E /* FLEXRuntime+UIKitHelpers.m in Sources */ = {isa = PBXBuildFile; fileRef = C34C9BDC23A7F2740031CA3E /* FLEXRuntime+UIKitHelpers.m */; };
+		C34D4EB023A2ABD900C1F903 /* FLEXLayerShortcuts.h in Headers */ = {isa = PBXBuildFile; fileRef = C34D4EAE23A2ABD900C1F903 /* FLEXLayerShortcuts.h */; };
+		C34D4EB123A2ABD900C1F903 /* FLEXLayerShortcuts.m in Sources */ = {isa = PBXBuildFile; fileRef = C34D4EAF23A2ABD900C1F903 /* FLEXLayerShortcuts.m */; };
+		C34D4EB423A2AF2A00C1F903 /* FLEXColorPreviewSection.h in Headers */ = {isa = PBXBuildFile; fileRef = C34D4EB223A2AF2A00C1F903 /* FLEXColorPreviewSection.h */; };
+		C34D4EB523A2AF2A00C1F903 /* FLEXColorPreviewSection.m in Sources */ = {isa = PBXBuildFile; fileRef = C34D4EB323A2AF2A00C1F903 /* FLEXColorPreviewSection.m */; };
+		C34D4EB823A2B17900C1F903 /* FLEXBundleShortcuts.h in Headers */ = {isa = PBXBuildFile; fileRef = C34D4EB623A2B17900C1F903 /* FLEXBundleShortcuts.h */; };
+		C34D4EB923A2B17900C1F903 /* FLEXBundleShortcuts.m in Sources */ = {isa = PBXBuildFile; fileRef = C34D4EB723A2B17900C1F903 /* FLEXBundleShortcuts.m */; };
 		C34EE30821CB23CC00BD3A7C /* FLEXOSLogController.h in Headers */ = {isa = PBXBuildFile; fileRef = C34EE30621CB23CC00BD3A7C /* FLEXOSLogController.h */; };
 		C34EE30821CB23CC00BD3A7C /* FLEXOSLogController.h in Headers */ = {isa = PBXBuildFile; fileRef = C34EE30621CB23CC00BD3A7C /* FLEXOSLogController.h */; };
 		C3511B9122D7C99E0057BAB7 /* FLEXTableViewSection.h in Headers */ = {isa = PBXBuildFile; fileRef = C3511B8F22D7C99E0057BAB7 /* FLEXTableViewSection.h */; };
 		C3511B9122D7C99E0057BAB7 /* FLEXTableViewSection.h in Headers */ = {isa = PBXBuildFile; fileRef = C3511B8F22D7C99E0057BAB7 /* FLEXTableViewSection.h */; };
 		C3511B9222D7C99E0057BAB7 /* FLEXTableViewSection.m in Sources */ = {isa = PBXBuildFile; fileRef = C3511B9022D7C99E0057BAB7 /* FLEXTableViewSection.m */; };
 		C3511B9222D7C99E0057BAB7 /* FLEXTableViewSection.m in Sources */ = {isa = PBXBuildFile; fileRef = C3511B9022D7C99E0057BAB7 /* FLEXTableViewSection.m */; };
@@ -208,21 +222,27 @@
 		C387C88422E0D24A00750E58 /* UIView+FLEX_Layout.m in Sources */ = {isa = PBXBuildFile; fileRef = C387C88222E0D24A00750E58 /* UIView+FLEX_Layout.m */; };
 		C387C88422E0D24A00750E58 /* UIView+FLEX_Layout.m in Sources */ = {isa = PBXBuildFile; fileRef = C387C88222E0D24A00750E58 /* UIView+FLEX_Layout.m */; };
 		C38DF0EA22CFE4370077B4AD /* FLEXTableViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = C38DF0E822CFE4370077B4AD /* FLEXTableViewController.h */; };
 		C38DF0EA22CFE4370077B4AD /* FLEXTableViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = C38DF0E822CFE4370077B4AD /* FLEXTableViewController.h */; };
 		C38DF0EB22CFE4370077B4AD /* FLEXTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C38DF0E922CFE4370077B4AD /* FLEXTableViewController.m */; };
 		C38DF0EB22CFE4370077B4AD /* FLEXTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C38DF0E922CFE4370077B4AD /* FLEXTableViewController.m */; };
+		C38EF26223A2FCD20047A7EC /* FLEXViewControllerShortcuts.m in Sources */ = {isa = PBXBuildFile; fileRef = C38EF26023A2FCD20047A7EC /* FLEXViewControllerShortcuts.m */; };
+		C38EF26323A2FCD20047A7EC /* FLEXViewControllerShortcuts.h in Headers */ = {isa = PBXBuildFile; fileRef = C38EF26123A2FCD20047A7EC /* FLEXViewControllerShortcuts.h */; };
 		C38F3F31230C958F004E3731 /* FLEXAlert.h in Headers */ = {isa = PBXBuildFile; fileRef = C38F3F2F230C958F004E3731 /* FLEXAlert.h */; };
 		C38F3F31230C958F004E3731 /* FLEXAlert.h in Headers */ = {isa = PBXBuildFile; fileRef = C38F3F2F230C958F004E3731 /* FLEXAlert.h */; };
 		C38F3F32230C958F004E3731 /* FLEXAlert.m in Sources */ = {isa = PBXBuildFile; fileRef = C38F3F30230C958F004E3731 /* FLEXAlert.m */; };
 		C38F3F32230C958F004E3731 /* FLEXAlert.m in Sources */ = {isa = PBXBuildFile; fileRef = C38F3F30230C958F004E3731 /* FLEXAlert.m */; };
 		C395D6D921789BD800BEAD4D /* FLEXColorExplorerViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = C395D6D721789BD800BEAD4D /* FLEXColorExplorerViewController.h */; };
 		C395D6D921789BD800BEAD4D /* FLEXColorExplorerViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = C395D6D721789BD800BEAD4D /* FLEXColorExplorerViewController.h */; };
 		C395D6DA21789BD800BEAD4D /* FLEXColorExplorerViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C395D6D821789BD800BEAD4D /* FLEXColorExplorerViewController.m */; };
 		C395D6DA21789BD800BEAD4D /* FLEXColorExplorerViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C395D6D821789BD800BEAD4D /* FLEXColorExplorerViewController.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 */; };
 		C39ED92822D63F3200B5773A /* FLEXAddressExplorerCoordinator.h in Headers */ = {isa = PBXBuildFile; fileRef = C39ED92622D63F3200B5773A /* FLEXAddressExplorerCoordinator.h */; };
 		C39ED92822D63F3200B5773A /* FLEXAddressExplorerCoordinator.h in Headers */ = {isa = PBXBuildFile; fileRef = C39ED92622D63F3200B5773A /* FLEXAddressExplorerCoordinator.h */; };
 		C39ED92922D63F3200B5773A /* FLEXAddressExplorerCoordinator.m in Sources */ = {isa = PBXBuildFile; fileRef = C39ED92722D63F3200B5773A /* FLEXAddressExplorerCoordinator.m */; };
 		C39ED92922D63F3200B5773A /* FLEXAddressExplorerCoordinator.m in Sources */ = {isa = PBXBuildFile; fileRef = C39ED92722D63F3200B5773A /* FLEXAddressExplorerCoordinator.m */; };
-		C3DA55FE21A76406005DDA60 /* FLEXMutableFieldEditorViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = C3DA55FC21A76406005DDA60 /* FLEXMutableFieldEditorViewController.h */; };
-		C3DA55FF21A76406005DDA60 /* FLEXMutableFieldEditorViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C3DA55FD21A76406005DDA60 /* FLEXMutableFieldEditorViewController.m */; };
 		C3BFD070233C23ED0015FB82 /* NSArray+Functional.h in Headers */ = {isa = PBXBuildFile; fileRef = C3BFD06E233C23ED0015FB82 /* NSArray+Functional.h */; };
 		C3BFD070233C23ED0015FB82 /* NSArray+Functional.h in Headers */ = {isa = PBXBuildFile; fileRef = C3BFD06E233C23ED0015FB82 /* NSArray+Functional.h */; };
 		C3BFD071233C23ED0015FB82 /* NSArray+Functional.m in Sources */ = {isa = PBXBuildFile; fileRef = C3BFD06F233C23ED0015FB82 /* NSArray+Functional.m */; };
 		C3BFD071233C23ED0015FB82 /* NSArray+Functional.m in Sources */ = {isa = PBXBuildFile; fileRef = C3BFD06F233C23ED0015FB82 /* NSArray+Functional.m */; };
+		C3DA55FE21A76406005DDA60 /* FLEXMutableFieldEditorViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = C3DA55FC21A76406005DDA60 /* FLEXMutableFieldEditorViewController.h */; };
+		C3DA55FF21A76406005DDA60 /* FLEXMutableFieldEditorViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C3DA55FD21A76406005DDA60 /* FLEXMutableFieldEditorViewController.m */; };
 		C3DB9F642107FC9600B46809 /* FLEXObjectRef.h in Headers */ = {isa = PBXBuildFile; fileRef = C3DB9F622107FC9600B46809 /* FLEXObjectRef.h */; };
 		C3DB9F642107FC9600B46809 /* FLEXObjectRef.h in Headers */ = {isa = PBXBuildFile; fileRef = C3DB9F622107FC9600B46809 /* FLEXObjectRef.h */; };
 		C3DB9F652107FC9600B46809 /* FLEXObjectRef.m in Sources */ = {isa = PBXBuildFile; fileRef = C3DB9F632107FC9600B46809 /* FLEXObjectRef.m */; };
 		C3DB9F652107FC9600B46809 /* FLEXObjectRef.m in Sources */ = {isa = PBXBuildFile; fileRef = C3DB9F632107FC9600B46809 /* FLEXObjectRef.m */; };
 		C3DC287C223ED5F200F48AA6 /* FLEXOSLogController.m in Sources */ = {isa = PBXBuildFile; fileRef = C34EE30721CB23CC00BD3A7C /* FLEXOSLogController.m */; };
 		C3DC287C223ED5F200F48AA6 /* FLEXOSLogController.m in Sources */ = {isa = PBXBuildFile; fileRef = C34EE30721CB23CC00BD3A7C /* FLEXOSLogController.m */; };
 		C3E5D9FD2316E83700E655DB /* FLEXRuntime+Compare.h in Headers */ = {isa = PBXBuildFile; fileRef = C3E5D9FB2316E83700E655DB /* FLEXRuntime+Compare.h */; };
 		C3E5D9FD2316E83700E655DB /* FLEXRuntime+Compare.h in Headers */ = {isa = PBXBuildFile; fileRef = C3E5D9FB2316E83700E655DB /* FLEXRuntime+Compare.h */; };
 		C3E5D9FE2316E83700E655DB /* FLEXRuntime+Compare.m in Sources */ = {isa = PBXBuildFile; fileRef = C3E5D9FC2316E83700E655DB /* FLEXRuntime+Compare.m */; };
 		C3E5D9FE2316E83700E655DB /* FLEXRuntime+Compare.m in Sources */ = {isa = PBXBuildFile; fileRef = C3E5D9FC2316E83700E655DB /* FLEXRuntime+Compare.m */; };
+		C3E5DA02231700EE00E655DB /* FLEXExplorerSection.h in Headers */ = {isa = PBXBuildFile; fileRef = C3E5DA00231700EE00E655DB /* FLEXExplorerSection.h */; };
+		C3E5DA03231700EE00E655DB /* FLEXExplorerSection.m in Sources */ = {isa = PBXBuildFile; fileRef = C3E5DA01231700EE00E655DB /* FLEXExplorerSection.m */; };
 		C3EE76BF22DFC63600EC0AA0 /* FLEXScopeCarousel.h in Headers */ = {isa = PBXBuildFile; fileRef = C3EE76BD22DFC63600EC0AA0 /* FLEXScopeCarousel.h */; };
 		C3EE76BF22DFC63600EC0AA0 /* FLEXScopeCarousel.h in Headers */ = {isa = PBXBuildFile; fileRef = C3EE76BD22DFC63600EC0AA0 /* FLEXScopeCarousel.h */; };
 		C3EE76C022DFC63600EC0AA0 /* FLEXScopeCarousel.m in Sources */ = {isa = PBXBuildFile; fileRef = C3EE76BE22DFC63600EC0AA0 /* FLEXScopeCarousel.m */; };
 		C3EE76C022DFC63600EC0AA0 /* FLEXScopeCarousel.m in Sources */ = {isa = PBXBuildFile; fileRef = C3EE76BE22DFC63600EC0AA0 /* FLEXScopeCarousel.m */; };
 		C3F31D3D2267D883003C991A /* FLEXSubtitleTableViewCell.h in Headers */ = {isa = PBXBuildFile; fileRef = C3F31D342267D883003C991A /* FLEXSubtitleTableViewCell.h */; };
 		C3F31D3D2267D883003C991A /* FLEXSubtitleTableViewCell.h in Headers */ = {isa = PBXBuildFile; fileRef = C3F31D342267D883003C991A /* FLEXSubtitleTableViewCell.h */; };
@@ -233,8 +253,18 @@
 		C3F31D422267D883003C991A /* FLEXTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = C3F31D392267D883003C991A /* FLEXTableViewCell.m */; };
 		C3F31D422267D883003C991A /* FLEXTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = C3F31D392267D883003C991A /* FLEXTableViewCell.m */; };
 		C3F31D432267D883003C991A /* FLEXTableView.h in Headers */ = {isa = PBXBuildFile; fileRef = C3F31D3B2267D883003C991A /* FLEXTableView.h */; };
 		C3F31D432267D883003C991A /* FLEXTableView.h in Headers */ = {isa = PBXBuildFile; fileRef = C3F31D3B2267D883003C991A /* FLEXTableView.h */; };
 		C3F31D442267D883003C991A /* FLEXTableView.m in Sources */ = {isa = PBXBuildFile; fileRef = C3F31D3C2267D883003C991A /* FLEXTableView.m */; };
 		C3F31D442267D883003C991A /* FLEXTableView.m in Sources */ = {isa = PBXBuildFile; fileRef = C3F31D3C2267D883003C991A /* FLEXTableView.m */; };
+		C3F527BD2318603F009CBA07 /* FLEXShortcutsSection.h in Headers */ = {isa = PBXBuildFile; fileRef = C3F527BB2318603F009CBA07 /* FLEXShortcutsSection.h */; };
+		C3F527BE2318603F009CBA07 /* FLEXShortcutsSection.m in Sources */ = {isa = PBXBuildFile; fileRef = C3F527BC2318603F009CBA07 /* FLEXShortcutsSection.m */; };
+		C3F527C12318670F009CBA07 /* FLEXImageShortcuts.h in Headers */ = {isa = PBXBuildFile; fileRef = C3F527BF2318670F009CBA07 /* FLEXImageShortcuts.h */; };
+		C3F527C22318670F009CBA07 /* FLEXImageShortcuts.m in Sources */ = {isa = PBXBuildFile; fileRef = C3F527C02318670F009CBA07 /* FLEXImageShortcuts.m */; };
+		C3F527C5231891F6009CBA07 /* FLEXViewShortcuts.h in Headers */ = {isa = PBXBuildFile; fileRef = C3F527C3231891F6009CBA07 /* FLEXViewShortcuts.h */; };
+		C3F527C6231891F6009CBA07 /* FLEXViewShortcuts.m in Sources */ = {isa = PBXBuildFile; fileRef = C3F527C4231891F6009CBA07 /* FLEXViewShortcuts.m */; };
 		C3F646C1239EAA8F00D4A011 /* UIPasteboard+FLEX.h in Headers */ = {isa = PBXBuildFile; fileRef = C3F646BF239EAA8F00D4A011 /* UIPasteboard+FLEX.h */; };
 		C3F646C1239EAA8F00D4A011 /* UIPasteboard+FLEX.h in Headers */ = {isa = PBXBuildFile; fileRef = C3F646BF239EAA8F00D4A011 /* UIPasteboard+FLEX.h */; };
 		C3F646C2239EAA8F00D4A011 /* UIPasteboard+FLEX.m in Sources */ = {isa = PBXBuildFile; fileRef = C3F646C0239EAA8F00D4A011 /* UIPasteboard+FLEX.m */; };
 		C3F646C2239EAA8F00D4A011 /* UIPasteboard+FLEX.m in Sources */ = {isa = PBXBuildFile; fileRef = C3F646C0239EAA8F00D4A011 /* UIPasteboard+FLEX.m */; };
+		C3F646F223A045DB00D4A011 /* FLEXClassShortcuts.h in Headers */ = {isa = PBXBuildFile; fileRef = C3F646F023A045DB00D4A011 /* FLEXClassShortcuts.h */; };
+		C3F646F323A045DB00D4A011 /* FLEXClassShortcuts.m in Sources */ = {isa = PBXBuildFile; fileRef = C3F646F123A045DB00D4A011 /* FLEXClassShortcuts.m */; };
+		C3F646F623A04A7500D4A011 /* FLEXShortcut.h in Headers */ = {isa = PBXBuildFile; fileRef = C3F646F423A04A7500D4A011 /* FLEXShortcut.h */; };
+		C3F646F723A04A7500D4A011 /* FLEXShortcut.m in Sources */ = {isa = PBXBuildFile; fileRef = C3F646F523A04A7500D4A011 /* FLEXShortcut.m */; };
 		C3F977832311B38F0032776D /* NSString+ObjcRuntime.h in Headers */ = {isa = PBXBuildFile; fileRef = C3F9777D2311B38E0032776D /* NSString+ObjcRuntime.h */; };
 		C3F977832311B38F0032776D /* NSString+ObjcRuntime.h in Headers */ = {isa = PBXBuildFile; fileRef = C3F9777D2311B38E0032776D /* NSString+ObjcRuntime.h */; };
 		C3F977842311B38F0032776D /* NSDictionary+ObjcRuntime.m in Sources */ = {isa = PBXBuildFile; fileRef = C3F9777E2311B38E0032776D /* NSDictionary+ObjcRuntime.m */; };
 		C3F977842311B38F0032776D /* NSDictionary+ObjcRuntime.m in Sources */ = {isa = PBXBuildFile; fileRef = C3F9777E2311B38E0032776D /* NSDictionary+ObjcRuntime.m */; };
 		C3F977852311B38F0032776D /* NSDictionary+ObjcRuntime.h in Headers */ = {isa = PBXBuildFile; fileRef = C3F9777F2311B38F0032776D /* NSDictionary+ObjcRuntime.h */; };
 		C3F977852311B38F0032776D /* NSDictionary+ObjcRuntime.h in Headers */ = {isa = PBXBuildFile; fileRef = C3F9777F2311B38F0032776D /* NSDictionary+ObjcRuntime.h */; };
@@ -422,14 +452,28 @@
 		94AAF0361BAF2E1F00DE8760 /* FLEXKeyboardHelpViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FLEXKeyboardHelpViewController.h; sourceTree = "<group>"; };
 		94AAF0361BAF2E1F00DE8760 /* FLEXKeyboardHelpViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FLEXKeyboardHelpViewController.h; sourceTree = "<group>"; };
 		94AAF0371BAF2E1F00DE8760 /* FLEXKeyboardHelpViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLEXKeyboardHelpViewController.m; sourceTree = "<group>"; };
 		94AAF0371BAF2E1F00DE8760 /* FLEXKeyboardHelpViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLEXKeyboardHelpViewController.m; sourceTree = "<group>"; };
 		C309B82D223ED64400B228EC /* FLEXLogController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FLEXLogController.h; sourceTree = "<group>"; };
 		C309B82D223ED64400B228EC /* FLEXLogController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FLEXLogController.h; sourceTree = "<group>"; };
+		C31C4A6723342A2200C35F12 /* FLEXMetadataSection.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FLEXMetadataSection.h; sourceTree = "<group>"; };
+		C31C4A6823342A2200C35F12 /* FLEXMetadataSection.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FLEXMetadataSection.m; sourceTree = "<group>"; };
+		C32A195C231732E800EB02AC /* FLEXCollectionContentSection.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FLEXCollectionContentSection.h; sourceTree = "<group>"; };
+		C32A195D231732E800EB02AC /* FLEXCollectionContentSection.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FLEXCollectionContentSection.m; sourceTree = "<group>"; };
+		C32A19602317378C00EB02AC /* FLEXDefaultsContentSection.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FLEXDefaultsContentSection.h; sourceTree = "<group>"; };
+		C32A19612317378C00EB02AC /* FLEXDefaultsContentSection.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FLEXDefaultsContentSection.m; sourceTree = "<group>"; };
 		C33C825C2316DC8600DD2451 /* FLEXObjectExplorer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = FLEXObjectExplorer.h; path = Classes/ObjectExplorers/FLEXObjectExplorer.h; sourceTree = SOURCE_ROOT; };
 		C33C825C2316DC8600DD2451 /* FLEXObjectExplorer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = FLEXObjectExplorer.h; path = Classes/ObjectExplorers/FLEXObjectExplorer.h; sourceTree = SOURCE_ROOT; };
 		C33C825D2316DC8600DD2451 /* FLEXObjectExplorer.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = FLEXObjectExplorer.m; path = Classes/ObjectExplorers/FLEXObjectExplorer.m; sourceTree = SOURCE_ROOT; };
 		C33C825D2316DC8600DD2451 /* FLEXObjectExplorer.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = FLEXObjectExplorer.m; path = Classes/ObjectExplorers/FLEXObjectExplorer.m; sourceTree = SOURCE_ROOT; };
 		C33E46AD223B02CD004BD0E6 /* FLEXASLLogController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FLEXASLLogController.h; sourceTree = "<group>"; };
 		C33E46AD223B02CD004BD0E6 /* FLEXASLLogController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FLEXASLLogController.h; sourceTree = "<group>"; };
 		C33E46AE223B02CD004BD0E6 /* FLEXASLLogController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FLEXASLLogController.m; sourceTree = "<group>"; };
 		C33E46AE223B02CD004BD0E6 /* FLEXASLLogController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FLEXASLLogController.m; sourceTree = "<group>"; };
+		C3490E1D233BDD73002AE200 /* FLEXSingleRowSection.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FLEXSingleRowSection.h; sourceTree = "<group>"; };
+		C3490E1E233BDD73002AE200 /* FLEXSingleRowSection.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FLEXSingleRowSection.m; sourceTree = "<group>"; };
 		C34A709E22B2EC8D009C2C5F /* FLEXBundleExplorerViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FLEXBundleExplorerViewController.h; sourceTree = "<group>"; };
 		C34A709E22B2EC8D009C2C5F /* FLEXBundleExplorerViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FLEXBundleExplorerViewController.h; sourceTree = "<group>"; };
 		C34A709F22B2EC8D009C2C5F /* FLEXBundleExplorerViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FLEXBundleExplorerViewController.m; sourceTree = "<group>"; };
 		C34A709F22B2EC8D009C2C5F /* FLEXBundleExplorerViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FLEXBundleExplorerViewController.m; sourceTree = "<group>"; };
 		C34C9BDB23A7F2740031CA3E /* FLEXRuntime+UIKitHelpers.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "FLEXRuntime+UIKitHelpers.h"; sourceTree = "<group>"; };
 		C34C9BDB23A7F2740031CA3E /* FLEXRuntime+UIKitHelpers.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "FLEXRuntime+UIKitHelpers.h"; sourceTree = "<group>"; };
 		C34C9BDC23A7F2740031CA3E /* FLEXRuntime+UIKitHelpers.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "FLEXRuntime+UIKitHelpers.m"; sourceTree = "<group>"; };
 		C34C9BDC23A7F2740031CA3E /* FLEXRuntime+UIKitHelpers.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "FLEXRuntime+UIKitHelpers.m"; sourceTree = "<group>"; };
+		C34D4EAE23A2ABD900C1F903 /* FLEXLayerShortcuts.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FLEXLayerShortcuts.h; sourceTree = "<group>"; };
+		C34D4EAF23A2ABD900C1F903 /* FLEXLayerShortcuts.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FLEXLayerShortcuts.m; sourceTree = "<group>"; };
+		C34D4EB223A2AF2A00C1F903 /* FLEXColorPreviewSection.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FLEXColorPreviewSection.h; sourceTree = "<group>"; };
+		C34D4EB323A2AF2A00C1F903 /* FLEXColorPreviewSection.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FLEXColorPreviewSection.m; sourceTree = "<group>"; };
+		C34D4EB623A2B17900C1F903 /* FLEXBundleShortcuts.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FLEXBundleShortcuts.h; sourceTree = "<group>"; };
+		C34D4EB723A2B17900C1F903 /* FLEXBundleShortcuts.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FLEXBundleShortcuts.m; sourceTree = "<group>"; };
 		C34EE30621CB23CC00BD3A7C /* FLEXOSLogController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FLEXOSLogController.h; sourceTree = "<group>"; };
 		C34EE30621CB23CC00BD3A7C /* FLEXOSLogController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FLEXOSLogController.h; sourceTree = "<group>"; };
 		C34EE30721CB23CC00BD3A7C /* FLEXOSLogController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FLEXOSLogController.m; sourceTree = "<group>"; };
 		C34EE30721CB23CC00BD3A7C /* FLEXOSLogController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FLEXOSLogController.m; sourceTree = "<group>"; };
 		C34EE30A21CB249E00BD3A7C /* ActivityStreamAPI.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ActivityStreamAPI.h; sourceTree = "<group>"; };
 		C34EE30A21CB249E00BD3A7C /* ActivityStreamAPI.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ActivityStreamAPI.h; sourceTree = "<group>"; };
@@ -461,20 +505,26 @@
 		C387C88222E0D24A00750E58 /* UIView+FLEX_Layout.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "UIView+FLEX_Layout.m"; sourceTree = "<group>"; };
 		C387C88222E0D24A00750E58 /* UIView+FLEX_Layout.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "UIView+FLEX_Layout.m"; sourceTree = "<group>"; };
 		C38DF0E822CFE4370077B4AD /* FLEXTableViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FLEXTableViewController.h; sourceTree = "<group>"; };
 		C38DF0E822CFE4370077B4AD /* FLEXTableViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FLEXTableViewController.h; sourceTree = "<group>"; };
 		C38DF0E922CFE4370077B4AD /* FLEXTableViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FLEXTableViewController.m; sourceTree = "<group>"; };
 		C38DF0E922CFE4370077B4AD /* FLEXTableViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FLEXTableViewController.m; sourceTree = "<group>"; };
+		C38EF26023A2FCD20047A7EC /* FLEXViewControllerShortcuts.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLEXViewControllerShortcuts.m; sourceTree = "<group>"; };
+		C38EF26123A2FCD20047A7EC /* FLEXViewControllerShortcuts.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FLEXViewControllerShortcuts.h; sourceTree = "<group>"; };
 		C38F3F2F230C958F004E3731 /* FLEXAlert.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FLEXAlert.h; sourceTree = "<group>"; };
 		C38F3F2F230C958F004E3731 /* FLEXAlert.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FLEXAlert.h; sourceTree = "<group>"; };
 		C38F3F30230C958F004E3731 /* FLEXAlert.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FLEXAlert.m; sourceTree = "<group>"; };
 		C38F3F30230C958F004E3731 /* FLEXAlert.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FLEXAlert.m; sourceTree = "<group>"; };
 		C395D6D721789BD800BEAD4D /* FLEXColorExplorerViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FLEXColorExplorerViewController.h; sourceTree = "<group>"; };
 		C395D6D721789BD800BEAD4D /* FLEXColorExplorerViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FLEXColorExplorerViewController.h; sourceTree = "<group>"; };
 		C395D6D821789BD800BEAD4D /* FLEXColorExplorerViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FLEXColorExplorerViewController.m; sourceTree = "<group>"; };
 		C395D6D821789BD800BEAD4D /* FLEXColorExplorerViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FLEXColorExplorerViewController.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>"; };
 		C39ED92622D63F3200B5773A /* FLEXAddressExplorerCoordinator.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FLEXAddressExplorerCoordinator.h; sourceTree = "<group>"; };
 		C39ED92722D63F3200B5773A /* FLEXAddressExplorerCoordinator.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FLEXAddressExplorerCoordinator.m; sourceTree = "<group>"; };
 		C39ED92722D63F3200B5773A /* FLEXAddressExplorerCoordinator.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FLEXAddressExplorerCoordinator.m; sourceTree = "<group>"; };
-		C3DA55FC21A76406005DDA60 /* FLEXMutableFieldEditorViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FLEXMutableFieldEditorViewController.h; sourceTree = "<group>"; };
-		C3DA55FD21A76406005DDA60 /* FLEXMutableFieldEditorViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FLEXMutableFieldEditorViewController.m; sourceTree = "<group>"; };
 		C3BFD06E233C23ED0015FB82 /* NSArray+Functional.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSArray+Functional.h"; sourceTree = "<group>"; };
 		C3BFD06E233C23ED0015FB82 /* NSArray+Functional.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSArray+Functional.h"; sourceTree = "<group>"; };
 		C3BFD06F233C23ED0015FB82 /* NSArray+Functional.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSArray+Functional.m"; sourceTree = "<group>"; };
 		C3BFD06F233C23ED0015FB82 /* NSArray+Functional.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSArray+Functional.m"; sourceTree = "<group>"; };
+		C3DA55FC21A76406005DDA60 /* FLEXMutableFieldEditorViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FLEXMutableFieldEditorViewController.h; sourceTree = "<group>"; };
+		C3DA55FD21A76406005DDA60 /* FLEXMutableFieldEditorViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FLEXMutableFieldEditorViewController.m; sourceTree = "<group>"; };
 		C3DB9F622107FC9600B46809 /* FLEXObjectRef.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FLEXObjectRef.h; sourceTree = "<group>"; };
 		C3DB9F622107FC9600B46809 /* FLEXObjectRef.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FLEXObjectRef.h; sourceTree = "<group>"; };
 		C3DB9F632107FC9600B46809 /* FLEXObjectRef.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FLEXObjectRef.m; sourceTree = "<group>"; };
 		C3DB9F632107FC9600B46809 /* FLEXObjectRef.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FLEXObjectRef.m; sourceTree = "<group>"; };
 		C3E5D9FB2316E83700E655DB /* FLEXRuntime+Compare.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "FLEXRuntime+Compare.h"; sourceTree = "<group>"; };
 		C3E5D9FB2316E83700E655DB /* FLEXRuntime+Compare.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "FLEXRuntime+Compare.h"; sourceTree = "<group>"; };
 		C3E5D9FC2316E83700E655DB /* FLEXRuntime+Compare.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "FLEXRuntime+Compare.m"; sourceTree = "<group>"; };
 		C3E5D9FC2316E83700E655DB /* FLEXRuntime+Compare.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "FLEXRuntime+Compare.m"; sourceTree = "<group>"; };
+		C3E5DA00231700EE00E655DB /* FLEXExplorerSection.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FLEXExplorerSection.h; sourceTree = "<group>"; };
+		C3E5DA01231700EE00E655DB /* FLEXExplorerSection.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FLEXExplorerSection.m; sourceTree = "<group>"; };
 		C3EE76BD22DFC63600EC0AA0 /* FLEXScopeCarousel.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FLEXScopeCarousel.h; sourceTree = "<group>"; };
 		C3EE76BD22DFC63600EC0AA0 /* FLEXScopeCarousel.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FLEXScopeCarousel.h; sourceTree = "<group>"; };
 		C3EE76BE22DFC63600EC0AA0 /* FLEXScopeCarousel.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FLEXScopeCarousel.m; sourceTree = "<group>"; };
 		C3EE76BE22DFC63600EC0AA0 /* FLEXScopeCarousel.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FLEXScopeCarousel.m; sourceTree = "<group>"; };
 		C3F31D342267D883003C991A /* FLEXSubtitleTableViewCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FLEXSubtitleTableViewCell.h; sourceTree = "<group>"; };
 		C3F31D342267D883003C991A /* FLEXSubtitleTableViewCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FLEXSubtitleTableViewCell.h; sourceTree = "<group>"; };
@@ -485,8 +535,18 @@
 		C3F31D392267D883003C991A /* FLEXTableViewCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLEXTableViewCell.m; sourceTree = "<group>"; };
 		C3F31D392267D883003C991A /* FLEXTableViewCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLEXTableViewCell.m; sourceTree = "<group>"; };
 		C3F31D3B2267D883003C991A /* FLEXTableView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FLEXTableView.h; sourceTree = "<group>"; };
 		C3F31D3B2267D883003C991A /* FLEXTableView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FLEXTableView.h; sourceTree = "<group>"; };
 		C3F31D3C2267D883003C991A /* FLEXTableView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLEXTableView.m; sourceTree = "<group>"; };
 		C3F31D3C2267D883003C991A /* FLEXTableView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLEXTableView.m; sourceTree = "<group>"; };
+		C3F527BB2318603F009CBA07 /* FLEXShortcutsSection.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FLEXShortcutsSection.h; sourceTree = "<group>"; };
+		C3F527BC2318603F009CBA07 /* FLEXShortcutsSection.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FLEXShortcutsSection.m; sourceTree = "<group>"; };
+		C3F527BF2318670F009CBA07 /* FLEXImageShortcuts.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FLEXImageShortcuts.h; sourceTree = "<group>"; };
+		C3F527C02318670F009CBA07 /* FLEXImageShortcuts.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FLEXImageShortcuts.m; sourceTree = "<group>"; };
+		C3F527C3231891F6009CBA07 /* FLEXViewShortcuts.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FLEXViewShortcuts.h; sourceTree = "<group>"; };
+		C3F527C4231891F6009CBA07 /* FLEXViewShortcuts.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FLEXViewShortcuts.m; sourceTree = "<group>"; };
 		C3F646BF239EAA8F00D4A011 /* UIPasteboard+FLEX.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "UIPasteboard+FLEX.h"; sourceTree = "<group>"; };
 		C3F646BF239EAA8F00D4A011 /* UIPasteboard+FLEX.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "UIPasteboard+FLEX.h"; sourceTree = "<group>"; };
 		C3F646C0239EAA8F00D4A011 /* UIPasteboard+FLEX.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "UIPasteboard+FLEX.m"; sourceTree = "<group>"; };
 		C3F646C0239EAA8F00D4A011 /* UIPasteboard+FLEX.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "UIPasteboard+FLEX.m"; sourceTree = "<group>"; };
+		C3F646F023A045DB00D4A011 /* FLEXClassShortcuts.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FLEXClassShortcuts.h; sourceTree = "<group>"; };
+		C3F646F123A045DB00D4A011 /* FLEXClassShortcuts.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLEXClassShortcuts.m; sourceTree = "<group>"; };
+		C3F646F423A04A7500D4A011 /* FLEXShortcut.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FLEXShortcut.h; sourceTree = "<group>"; };
+		C3F646F523A04A7500D4A011 /* FLEXShortcut.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FLEXShortcut.m; sourceTree = "<group>"; };
 		C3F9777D2311B38E0032776D /* NSString+ObjcRuntime.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSString+ObjcRuntime.h"; sourceTree = "<group>"; };
 		C3F9777D2311B38E0032776D /* NSString+ObjcRuntime.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSString+ObjcRuntime.h"; sourceTree = "<group>"; };
 		C3F9777E2311B38E0032776D /* NSDictionary+ObjcRuntime.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSDictionary+ObjcRuntime.m"; sourceTree = "<group>"; };
 		C3F9777E2311B38E0032776D /* NSDictionary+ObjcRuntime.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSDictionary+ObjcRuntime.m"; sourceTree = "<group>"; };
 		C3F9777F2311B38F0032776D /* NSDictionary+ObjcRuntime.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSDictionary+ObjcRuntime.h"; sourceTree = "<group>"; };
 		C3F9777F2311B38F0032776D /* NSDictionary+ObjcRuntime.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSDictionary+ObjcRuntime.h"; sourceTree = "<group>"; };
@@ -578,6 +638,7 @@
 			children = (
 			children = (
 				C33C825C2316DC8600DD2451 /* FLEXObjectExplorer.h */,
 				C33C825C2316DC8600DD2451 /* FLEXObjectExplorer.h */,
 				C33C825D2316DC8600DD2451 /* FLEXObjectExplorer.m */,
 				C33C825D2316DC8600DD2451 /* FLEXObjectExplorer.m */,
+				C3E5D9FF2317007F00E655DB /* Sections */,
 				3A4C944A1B5B21410088C3F2 /* FLEXObjectExplorerFactory.h */,
 				3A4C944A1B5B21410088C3F2 /* FLEXObjectExplorerFactory.h */,
 				3A4C944B1B5B21410088C3F2 /* FLEXObjectExplorerFactory.m */,
 				3A4C944B1B5B21410088C3F2 /* FLEXObjectExplorerFactory.m */,
 				C3F31D3A2267D883003C991A /* Views */,
 				C3F31D3A2267D883003C991A /* Views */,
@@ -924,6 +985,51 @@
 			path = Core;
 			path = Core;
 			sourceTree = "<group>";
 			sourceTree = "<group>";
 		};
 		};
+		C3D43E9F231D79F60079F6A8 /* Shortcuts */ = {
+			isa = PBXGroup;
+			children = (
+				C398682423AC359600E9E391 /* FLEXShortcutsFactory+Defaults.h */,
+				C398682323AC359600E9E391 /* FLEXShortcutsFactory+Defaults.m */,
+				C3F646F423A04A7500D4A011 /* FLEXShortcut.h */,
+				C3F646F523A04A7500D4A011 /* FLEXShortcut.m */,
+				C3F527BB2318603F009CBA07 /* FLEXShortcutsSection.h */,
+				C3F527BC2318603F009CBA07 /* FLEXShortcutsSection.m */,
+				C3F527BF2318670F009CBA07 /* FLEXImageShortcuts.h */,
+				C3F527C02318670F009CBA07 /* FLEXImageShortcuts.m */,
+				C3F527C3231891F6009CBA07 /* FLEXViewShortcuts.h */,
+				C3F527C4231891F6009CBA07 /* FLEXViewShortcuts.m */,
+				C3F646F023A045DB00D4A011 /* FLEXClassShortcuts.h */,
+				C3F646F123A045DB00D4A011 /* FLEXClassShortcuts.m */,
+				C34D4EAE23A2ABD900C1F903 /* FLEXLayerShortcuts.h */,
+				C34D4EAF23A2ABD900C1F903 /* FLEXLayerShortcuts.m */,
+				C34D4EB623A2B17900C1F903 /* FLEXBundleShortcuts.h */,
+				C34D4EB723A2B17900C1F903 /* FLEXBundleShortcuts.m */,
+				C38EF26123A2FCD20047A7EC /* FLEXViewControllerShortcuts.h */,
+				C38EF26023A2FCD20047A7EC /* FLEXViewControllerShortcuts.m */,
+			);
+			path = Shortcuts;
+			sourceTree = "<group>";
+		};
+		C3E5D9FF2317007F00E655DB /* Sections */ = {
+			isa = PBXGroup;
+			children = (
+				C3E5DA00231700EE00E655DB /* FLEXExplorerSection.h */,
+				C3E5DA01231700EE00E655DB /* FLEXExplorerSection.m */,
+				C3490E1D233BDD73002AE200 /* FLEXSingleRowSection.h */,
+				C3490E1E233BDD73002AE200 /* FLEXSingleRowSection.m */,
+				C31C4A6723342A2200C35F12 /* FLEXMetadataSection.h */,
+				C31C4A6823342A2200C35F12 /* FLEXMetadataSection.m */,
+				C32A195C231732E800EB02AC /* FLEXCollectionContentSection.h */,
+				C32A195D231732E800EB02AC /* FLEXCollectionContentSection.m */,
+				C32A19602317378C00EB02AC /* FLEXDefaultsContentSection.h */,
+				C32A19612317378C00EB02AC /* FLEXDefaultsContentSection.m */,
+				C34D4EB223A2AF2A00C1F903 /* FLEXColorPreviewSection.h */,
+				C34D4EB323A2AF2A00C1F903 /* FLEXColorPreviewSection.m */,
+				C3D43E9F231D79F60079F6A8 /* Shortcuts */,
+			);
+			path = Sections;
+			sourceTree = "<group>";
+		};
 		C3F31D3A2267D883003C991A /* Views */ = {
 		C3F31D3A2267D883003C991A /* Views */ = {
 			isa = PBXGroup;
 			isa = PBXGroup;
 			children = (
 			children = (
@@ -981,9 +1087,11 @@
 				3A4C94ED1B5B21410088C3F2 /* FLEXArgumentInputColorView.h in Headers */,
 				3A4C94ED1B5B21410088C3F2 /* FLEXArgumentInputColorView.h in Headers */,
 				3A4C94EB1B5B21410088C3F2 /* FLEXImagePreviewViewController.h in Headers */,
 				3A4C94EB1B5B21410088C3F2 /* FLEXImagePreviewViewController.h in Headers */,
 				C3DA55FE21A76406005DDA60 /* FLEXMutableFieldEditorViewController.h in Headers */,
 				C3DA55FE21A76406005DDA60 /* FLEXMutableFieldEditorViewController.h in Headers */,
+				C34D4EB823A2B17900C1F903 /* FLEXBundleShortcuts.h in Headers */,
 				C38F3F31230C958F004E3731 /* FLEXAlert.h in Headers */,
 				C38F3F31230C958F004E3731 /* FLEXAlert.h in Headers */,
 				3A4C95381B5B21410088C3F2 /* FLEXNetworkRecorder.h in Headers */,
 				3A4C95381B5B21410088C3F2 /* FLEXNetworkRecorder.h in Headers */,
 				3A4C94251B5B20570088C3F2 /* FLEX.h in Headers */,
 				3A4C94251B5B20570088C3F2 /* FLEX.h in Headers */,
+				C3F527C12318670F009CBA07 /* FLEXImageShortcuts.h in Headers */,
 				3A4C95051B5B21410088C3F2 /* FLEXArgumentInputViewFactory.h in Headers */,
 				3A4C95051B5B21410088C3F2 /* FLEXArgumentInputViewFactory.h in Headers */,
 				222C88221C7339DC007CA15F /* FLEXRealmDefines.h in Headers */,
 				222C88221C7339DC007CA15F /* FLEXRealmDefines.h in Headers */,
 				3A4C951E1B5B21410088C3F2 /* FLEXClassesTableViewController.h in Headers */,
 				3A4C951E1B5B21410088C3F2 /* FLEXClassesTableViewController.h in Headers */,
@@ -994,12 +1102,17 @@
 				C37A0C93218BAC9600848CA7 /* FLEXObjcInternal.h in Headers */,
 				C37A0C93218BAC9600848CA7 /* FLEXObjcInternal.h in Headers */,
 				3A4C953E1B5B21410088C3F2 /* FLEXNetworkTransactionDetailTableViewController.h in Headers */,
 				3A4C953E1B5B21410088C3F2 /* FLEXNetworkTransactionDetailTableViewController.h in Headers */,
 				3A4C95301B5B21410088C3F2 /* FLEXSystemLogMessage.h in Headers */,
 				3A4C95301B5B21410088C3F2 /* FLEXSystemLogMessage.h in Headers */,
+				C3E5DA02231700EE00E655DB /* FLEXExplorerSection.h in Headers */,
+				C34D4EB023A2ABD900C1F903 /* FLEXLayerShortcuts.h in Headers */,
+				C3F646F623A04A7500D4A011 /* FLEXShortcut.h in Headers */,
 				3A4C95361B5B21410088C3F2 /* FLEXNetworkHistoryTableViewController.h in Headers */,
 				3A4C95361B5B21410088C3F2 /* FLEXNetworkHistoryTableViewController.h in Headers */,
 				3A4C94DD1B5B21410088C3F2 /* FLEXHeapEnumerator.h in Headers */,
 				3A4C94DD1B5B21410088C3F2 /* FLEXHeapEnumerator.h in Headers */,
+				C3F527BD2318603F009CBA07 /* FLEXShortcutsSection.h in Headers */,
 				3A4C94DB1B5B21410088C3F2 /* FLEXViewExplorerViewController.h in Headers */,
 				3A4C94DB1B5B21410088C3F2 /* FLEXViewExplorerViewController.h in Headers */,
 				3A4C95321B5B21410088C3F2 /* FLEXSystemLogTableViewCell.h in Headers */,
 				3A4C95321B5B21410088C3F2 /* FLEXSystemLogTableViewCell.h in Headers */,
 				C3F977852311B38F0032776D /* NSDictionary+ObjcRuntime.h in Headers */,
 				C3F977852311B38F0032776D /* NSDictionary+ObjcRuntime.h in Headers */,
 				3A4C94F91B5B21410088C3F2 /* FLEXArgumentInputNumberView.h in Headers */,
 				3A4C94F91B5B21410088C3F2 /* FLEXArgumentInputNumberView.h in Headers */,
+				C3F646F223A045DB00D4A011 /* FLEXClassShortcuts.h in Headers */,
 				C387C87A22DFCD6A00750E58 /* FLEXCarouselCell.h in Headers */,
 				C387C87A22DFCD6A00750E58 /* FLEXCarouselCell.h in Headers */,
 				C3511B9122D7C99E0057BAB7 /* FLEXTableViewSection.h in Headers */,
 				C3511B9122D7C99E0057BAB7 /* FLEXTableViewSection.h in Headers */,
 				3A4C953A1B5B21410088C3F2 /* FLEXNetworkSettingsTableViewController.h in Headers */,
 				3A4C953A1B5B21410088C3F2 /* FLEXNetworkSettingsTableViewController.h in Headers */,
@@ -1015,6 +1128,7 @@
 				3A4C95091B5B21410088C3F2 /* FLEXFieldEditorView.h in Headers */,
 				3A4C95091B5B21410088C3F2 /* FLEXFieldEditorView.h in Headers */,
 				3A4C950D1B5B21410088C3F2 /* FLEXIvarEditorViewController.h in Headers */,
 				3A4C950D1B5B21410088C3F2 /* FLEXIvarEditorViewController.h in Headers */,
 				C3E5D9FD2316E83700E655DB /* FLEXRuntime+Compare.h in Headers */,
 				C3E5D9FD2316E83700E655DB /* FLEXRuntime+Compare.h in Headers */,
+				C3490E1F233BDD73002AE200 /* FLEXSingleRowSection.h in Headers */,
 				3A4C95281B5B21410088C3F2 /* FLEXInstancesTableViewController.h in Headers */,
 				3A4C95281B5B21410088C3F2 /* FLEXInstancesTableViewController.h in Headers */,
 				3A4C950F1B5B21410088C3F2 /* FLEXMethodCallingViewController.h in Headers */,
 				3A4C950F1B5B21410088C3F2 /* FLEXMethodCallingViewController.h in Headers */,
 				3A4C94F51B5B21410088C3F2 /* FLEXArgumentInputObjectView.h in Headers */,
 				3A4C94F51B5B21410088C3F2 /* FLEXArgumentInputObjectView.h in Headers */,
@@ -1038,9 +1152,11 @@
 				3A4C94CB1B5B21410088C3F2 /* FLEXDictionaryExplorerViewController.h in Headers */,
 				3A4C94CB1B5B21410088C3F2 /* FLEXDictionaryExplorerViewController.h in Headers */,
 				3A4C95071B5B21410088C3F2 /* FLEXDefaultEditorViewController.h in Headers */,
 				3A4C95071B5B21410088C3F2 /* FLEXDefaultEditorViewController.h in Headers */,
 				C309B82F223ED64400B228EC /* FLEXLogController.h in Headers */,
 				C309B82F223ED64400B228EC /* FLEXLogController.h in Headers */,
+				C31C4A6923342A2200C35F12 /* FLEXMetadataSection.h in Headers */,
 				C3F977832311B38F0032776D /* NSString+ObjcRuntime.h in Headers */,
 				C3F977832311B38F0032776D /* NSString+ObjcRuntime.h in Headers */,
 				94A5151F1C4CA1F10063292F /* FLEXWindow.h in Headers */,
 				94A5151F1C4CA1F10063292F /* FLEXWindow.h in Headers */,
 				779B1ECE1C0C4D7C001F5E49 /* FLEXDatabaseManager.h in Headers */,
 				779B1ECE1C0C4D7C001F5E49 /* FLEXDatabaseManager.h in Headers */,
+				C32A19622317378C00EB02AC /* FLEXDefaultsContentSection.h in Headers */,
 				3A4C94D51B5B21410088C3F2 /* FLEXObjectExplorerViewController.h in Headers */,
 				3A4C94D51B5B21410088C3F2 /* FLEXObjectExplorerViewController.h in Headers */,
 				3A4C95011B5B21410088C3F2 /* FLEXArgumentInputTextView.h in Headers */,
 				3A4C95011B5B21410088C3F2 /* FLEXArgumentInputTextView.h in Headers */,
 				3A4C952A1B5B21410088C3F2 /* FLEXLibrariesTableViewController.h in Headers */,
 				3A4C952A1B5B21410088C3F2 /* FLEXLibrariesTableViewController.h in Headers */,
@@ -1072,12 +1188,17 @@
 				3A4C94F31B5B21410088C3F2 /* FLEXArgumentInputFontView.h in Headers */,
 				3A4C94F31B5B21410088C3F2 /* FLEXArgumentInputFontView.h in Headers */,
 				3A4C95261B5B21410088C3F2 /* FLEXGlobalsTableViewController.h in Headers */,
 				3A4C95261B5B21410088C3F2 /* FLEXGlobalsTableViewController.h in Headers */,
 				C33C825E2316DC8600DD2451 /* FLEXObjectExplorer.h in Headers */,
 				C33C825E2316DC8600DD2451 /* FLEXObjectExplorer.h in Headers */,
+				C34D4EB423A2AF2A00C1F903 /* FLEXColorPreviewSection.h in Headers */,
 				224D49A81C673AB5000EAB86 /* FLEXRealmDatabaseManager.h in Headers */,
 				224D49A81C673AB5000EAB86 /* FLEXRealmDatabaseManager.h in Headers */,
+				C3F527C5231891F6009CBA07 /* FLEXViewShortcuts.h in Headers */,
+				C398682623AC359600E9E391 /* FLEXShortcutsFactory+Defaults.h in Headers */,
+				C3F527C5231891F6009CBA07 /* FLEXViewShortcuts.h in Headers */,
 				7349FD6A22B93CDF00051810 /* FLEXColor.h in Headers */,
 				7349FD6A22B93CDF00051810 /* FLEXColor.h in Headers */,
 				C36FBFD3230F3B98008D95D5 /* FLEXMethod.h in Headers */,
 				C36FBFD3230F3B98008D95D5 /* FLEXMethod.h in Headers */,
 				C36FBFD8230F3B98008D95D5 /* FLEXPropertyAttributes.h in Headers */,
 				C36FBFD8230F3B98008D95D5 /* FLEXPropertyAttributes.h in Headers */,
 				3A4C94CD1B5B21410088C3F2 /* FLEXGlobalsEntry.h in Headers */,
 				3A4C94CD1B5B21410088C3F2 /* FLEXGlobalsEntry.h in Headers */,
 				3A4C94FB1B5B21410088C3F2 /* FLEXArgumentInputStringView.h in Headers */,
 				3A4C94FB1B5B21410088C3F2 /* FLEXArgumentInputStringView.h in Headers */,
+				C38EF26323A2FCD20047A7EC /* FLEXViewControllerShortcuts.h in Headers */,
 				3A4C95421B5B21410088C3F2 /* FLEXNetworkObserver.h in Headers */,
 				3A4C95421B5B21410088C3F2 /* FLEXNetworkObserver.h in Headers */,
 				C34A70A022B2EC8D009C2C5F /* FLEXBundleExplorerViewController.h in Headers */,
 				C34A70A022B2EC8D009C2C5F /* FLEXBundleExplorerViewController.h in Headers */,
 				679F64861BD53B7B00A8C94C /* FLEXCookiesTableViewController.h in Headers */,
 				679F64861BD53B7B00A8C94C /* FLEXCookiesTableViewController.h in Headers */,
@@ -1092,6 +1213,7 @@
 				C395D6D921789BD800BEAD4D /* FLEXColorExplorerViewController.h in Headers */,
 				C395D6D921789BD800BEAD4D /* FLEXColorExplorerViewController.h in Headers */,
 				94A515181C4CA1D70063292F /* FLEXManager+Private.h in Headers */,
 				94A515181C4CA1D70063292F /* FLEXManager+Private.h in Headers */,
 				C39ED92822D63F3200B5773A /* FLEXAddressExplorerCoordinator.h in Headers */,
 				C39ED92822D63F3200B5773A /* FLEXAddressExplorerCoordinator.h in Headers */,
+				C32A195E231732E800EB02AC /* FLEXCollectionContentSection.h in Headers */,
 				3A4C94E11B5B21410088C3F2 /* FLEXResources.h in Headers */,
 				3A4C94E11B5B21410088C3F2 /* FLEXResources.h in Headers */,
 				779B1ED81C0C4D7C001F5E49 /* FLEXTableLeftCell.h in Headers */,
 				779B1ED81C0C4D7C001F5E49 /* FLEXTableLeftCell.h in Headers */,
 			);
 			);
@@ -1207,14 +1329,20 @@
 			files = (
 			files = (
 				942DCD871BAE0CA300DB5DC2 /* FLEXKeyboardShortcutManager.m in Sources */,
 				942DCD871BAE0CA300DB5DC2 /* FLEXKeyboardShortcutManager.m in Sources */,
 				C3F977862311B38F0032776D /* NSString+ObjcRuntime.m in Sources */,
 				C3F977862311B38F0032776D /* NSString+ObjcRuntime.m in Sources */,
+				C31C4A6A23342A2200C35F12 /* FLEXMetadataSection.m in Sources */,
 				224D49A91C673AB5000EAB86 /* FLEXRealmDatabaseManager.m in Sources */,
 				224D49A91C673AB5000EAB86 /* FLEXRealmDatabaseManager.m in Sources */,
 				C39ED92922D63F3200B5773A /* FLEXAddressExplorerCoordinator.m in Sources */,
 				C39ED92922D63F3200B5773A /* FLEXAddressExplorerCoordinator.m in Sources */,
 				C34C9BDE23A7F2740031CA3E /* FLEXRuntime+UIKitHelpers.m in Sources */,
 				C34C9BDE23A7F2740031CA3E /* FLEXRuntime+UIKitHelpers.m in Sources */,
 				2EF6B04D1D494BE50006BDA5 /* FLEXNetworkCurlLogger.m in Sources */,
 				2EF6B04D1D494BE50006BDA5 /* FLEXNetworkCurlLogger.m in Sources */,
 				94A515201C4CA1F10063292F /* FLEXWindow.m in Sources */,
 				94A515201C4CA1F10063292F /* FLEXWindow.m in Sources */,
 				3A4C95121B5B21410088C3F2 /* FLEXPropertyEditorViewController.m in Sources */,
 				3A4C95121B5B21410088C3F2 /* FLEXPropertyEditorViewController.m in Sources */,
+				C3F527C6231891F6009CBA07 /* FLEXViewShortcuts.m in Sources */,
 				3A4C95391B5B21410088C3F2 /* FLEXNetworkRecorder.m in Sources */,
 				3A4C95391B5B21410088C3F2 /* FLEXNetworkRecorder.m in Sources */,
 				3A4C950E1B5B21410088C3F2 /* FLEXIvarEditorViewController.m in Sources */,
 				3A4C950E1B5B21410088C3F2 /* FLEXIvarEditorViewController.m in Sources */,
+				C3F527C6231891F6009CBA07 /* FLEXViewShortcuts.m in Sources */,
+				3A4C95391B5B21410088C3F2 /* FLEXNetworkRecorder.m in Sources */,
+				C38EF26223A2FCD20047A7EC /* FLEXViewControllerShortcuts.m in Sources */,
+				C398682523AC359600E9E391 /* FLEXShortcutsFactory+Defaults.m in Sources */,
 				C3DC287C223ED5F200F48AA6 /* FLEXOSLogController.m in Sources */,
 				C3DC287C223ED5F200F48AA6 /* FLEXOSLogController.m in Sources */,
 				3A4C95101B5B21410088C3F2 /* FLEXMethodCallingViewController.m in Sources */,
 				3A4C95101B5B21410088C3F2 /* FLEXMethodCallingViewController.m in Sources */,
 				C3F646C2239EAA8F00D4A011 /* UIPasteboard+FLEX.m in Sources */,
 				C3F646C2239EAA8F00D4A011 /* UIPasteboard+FLEX.m in Sources */,
@@ -1227,6 +1355,7 @@
 				3A4C951F1B5B21410088C3F2 /* FLEXClassesTableViewController.m in Sources */,
 				3A4C951F1B5B21410088C3F2 /* FLEXClassesTableViewController.m in Sources */,
 				3A4C94C61B5B21410088C3F2 /* FLEXArrayExplorerViewController.m in Sources */,
 				3A4C94C61B5B21410088C3F2 /* FLEXArrayExplorerViewController.m in Sources */,
 				C36FBFD4230F3B98008D95D5 /* FLEXProtocol.m in Sources */,
 				C36FBFD4230F3B98008D95D5 /* FLEXProtocol.m in Sources */,
+				C34D4EB123A2ABD900C1F903 /* FLEXLayerShortcuts.m in Sources */,
 				C38F3F32230C958F004E3731 /* FLEXAlert.m in Sources */,
 				C38F3F32230C958F004E3731 /* FLEXAlert.m in Sources */,
 				C3DA55FF21A76406005DDA60 /* FLEXMutableFieldEditorViewController.m in Sources */,
 				C3DA55FF21A76406005DDA60 /* FLEXMutableFieldEditorViewController.m in Sources */,
 				3A4C95081B5B21410088C3F2 /* FLEXDefaultEditorViewController.m in Sources */,
 				3A4C95081B5B21410088C3F2 /* FLEXDefaultEditorViewController.m in Sources */,
@@ -1239,11 +1368,14 @@
 				3A4C94F01B5B21410088C3F2 /* FLEXArgumentInputDateView.m in Sources */,
 				3A4C94F01B5B21410088C3F2 /* FLEXArgumentInputDateView.m in Sources */,
 				3A4C94CA1B5B21410088C3F2 /* FLEXDefaultsExplorerViewController.m in Sources */,
 				3A4C94CA1B5B21410088C3F2 /* FLEXDefaultsExplorerViewController.m in Sources */,
 				C36FBFDC230F3B98008D95D5 /* FLEXIvar.m in Sources */,
 				C36FBFDC230F3B98008D95D5 /* FLEXIvar.m in Sources */,
+				C3F527C22318670F009CBA07 /* FLEXImageShortcuts.m in Sources */,
 				3A4C94D01B5B21410088C3F2 /* FLEXImageExplorerViewController.m in Sources */,
 				3A4C94D01B5B21410088C3F2 /* FLEXImageExplorerViewController.m in Sources */,
 				679F64871BD53B7B00A8C94C /* FLEXCookiesTableViewController.m in Sources */,
 				679F64871BD53B7B00A8C94C /* FLEXCookiesTableViewController.m in Sources */,
 				3A4C94CE1B5B21410088C3F2 /* FLEXGlobalsEntry.m in Sources */,
 				3A4C94CE1B5B21410088C3F2 /* FLEXGlobalsEntry.m in Sources */,
 				C3E5D9FE2316E83700E655DB /* FLEXRuntime+Compare.m in Sources */,
 				C3E5D9FE2316E83700E655DB /* FLEXRuntime+Compare.m in Sources */,
 				71E1C2192307FBB800F5032A /* FLEXKeychainQuery.m in Sources */,
 				71E1C2192307FBB800F5032A /* FLEXKeychainQuery.m in Sources */,
+				C3E5DA03231700EE00E655DB /* FLEXExplorerSection.m in Sources */,
+				C3F646F723A04A7500D4A011 /* FLEXShortcut.m in Sources */,
 				3A4C94FE1B5B21410088C3F2 /* FLEXArgumentInputStructView.m in Sources */,
 				3A4C94FE1B5B21410088C3F2 /* FLEXArgumentInputStructView.m in Sources */,
 				C3F31D402267D883003C991A /* FLEXSubtitleTableViewCell.m in Sources */,
 				C3F31D402267D883003C991A /* FLEXSubtitleTableViewCell.m in Sources */,
 				C36FBFD9230F3B98008D95D5 /* FLEXProtocolBuilder.m in Sources */,
 				C36FBFD9230F3B98008D95D5 /* FLEXProtocolBuilder.m in Sources */,
@@ -1258,6 +1390,7 @@
 				3A4C95041B5B21410088C3F2 /* FLEXArgumentInputView.m in Sources */,
 				3A4C95041B5B21410088C3F2 /* FLEXArgumentInputView.m in Sources */,
 				C3F977842311B38F0032776D /* NSDictionary+ObjcRuntime.m in Sources */,
 				C3F977842311B38F0032776D /* NSDictionary+ObjcRuntime.m in Sources */,
 				3A4C95411B5B21410088C3F2 /* FLEXNetworkTransactionTableViewCell.m in Sources */,
 				3A4C95411B5B21410088C3F2 /* FLEXNetworkTransactionTableViewCell.m in Sources */,
+				C3F527BE2318603F009CBA07 /* FLEXShortcutsSection.m in Sources */,
 				3A4C94D61B5B21410088C3F2 /* FLEXObjectExplorerViewController.m in Sources */,
 				3A4C94D61B5B21410088C3F2 /* FLEXObjectExplorerViewController.m in Sources */,
 				3A4C94DE1B5B21410088C3F2 /* FLEXHeapEnumerator.m in Sources */,
 				3A4C94DE1B5B21410088C3F2 /* FLEXHeapEnumerator.m in Sources */,
 				3A4C95251B5B21410088C3F2 /* FLEXFileBrowserTableViewController.m in Sources */,
 				3A4C95251B5B21410088C3F2 /* FLEXFileBrowserTableViewController.m in Sources */,
@@ -1276,10 +1409,13 @@
 				3A4C95351B5B21410088C3F2 /* FLEXSystemLogTableViewController.m in Sources */,
 				3A4C95351B5B21410088C3F2 /* FLEXSystemLogTableViewController.m in Sources */,
 				3A4C95271B5B21410088C3F2 /* FLEXGlobalsTableViewController.m in Sources */,
 				3A4C95271B5B21410088C3F2 /* FLEXGlobalsTableViewController.m in Sources */,
 				71E1C2172307FBB800F5032A /* FLEXKeychain.m in Sources */,
 				71E1C2172307FBB800F5032A /* FLEXKeychain.m in Sources */,
+				C3F646F323A045DB00D4A011 /* FLEXClassShortcuts.m in Sources */,
 				C387C88422E0D24A00750E58 /* UIView+FLEX_Layout.m in Sources */,
 				C387C88422E0D24A00750E58 /* UIView+FLEX_Layout.m in Sources */,
+				C32A195F231732E800EB02AC /* FLEXCollectionContentSection.m in Sources */,
 				C36FBFCD230F3B98008D95D5 /* FLEXMethod.m in Sources */,
 				C36FBFCD230F3B98008D95D5 /* FLEXMethod.m in Sources */,
 				779B1ED31C0C4D7C001F5E49 /* FLEXTableColumnHeader.m in Sources */,
 				779B1ED31C0C4D7C001F5E49 /* FLEXTableColumnHeader.m in Sources */,
 				C37A0C94218BAC9600848CA7 /* FLEXObjcInternal.mm in Sources */,
 				C37A0C94218BAC9600848CA7 /* FLEXObjcInternal.mm in Sources */,
+				C34D4EB523A2AF2A00C1F903 /* FLEXColorPreviewSection.m in Sources */,
 				3A4C94EA1B5B21410088C3F2 /* FLEXHierarchyTableViewController.m in Sources */,
 				3A4C94EA1B5B21410088C3F2 /* FLEXHierarchyTableViewController.m in Sources */,
 				3A4C95331B5B21410088C3F2 /* FLEXSystemLogTableViewCell.m in Sources */,
 				3A4C95331B5B21410088C3F2 /* FLEXSystemLogTableViewCell.m in Sources */,
 				C3DB9F652107FC9600B46809 /* FLEXObjectRef.m in Sources */,
 				C3DB9F652107FC9600B46809 /* FLEXObjectRef.m in Sources */,
@@ -1303,6 +1439,9 @@
 				3A4C94E61B5B21410088C3F2 /* FLEXUtility.m in Sources */,
 				3A4C94E61B5B21410088C3F2 /* FLEXUtility.m in Sources */,
 				3A4C95231B5B21410088C3F2 /* FLEXFileBrowserSearchOperation.m in Sources */,
 				3A4C95231B5B21410088C3F2 /* FLEXFileBrowserSearchOperation.m in Sources */,
 				3A4C950C1B5B21410088C3F2 /* FLEXFieldEditorViewController.m in Sources */,
 				3A4C950C1B5B21410088C3F2 /* FLEXFieldEditorViewController.m in Sources */,
+				C3490E20233BDD73002AE200 /* FLEXSingleRowSection.m in Sources */,
+				3A4C950C1B5B21410088C3F2 /* FLEXFieldEditorViewController.m in Sources */,
+				C34D4EB923A2B17900C1F903 /* FLEXBundleShortcuts.m in Sources */,
 				3A4C952D1B5B21410088C3F2 /* FLEXLiveObjectsTableViewController.m in Sources */,
 				3A4C952D1B5B21410088C3F2 /* FLEXLiveObjectsTableViewController.m in Sources */,
 				C33E46B0223B02CD004BD0E6 /* FLEXASLLogController.m in Sources */,
 				C33E46B0223B02CD004BD0E6 /* FLEXASLLogController.m in Sources */,
 				3A4C953D1B5B21410088C3F2 /* FLEXNetworkTransaction.m in Sources */,
 				3A4C953D1B5B21410088C3F2 /* FLEXNetworkTransaction.m in Sources */,
@@ -1316,6 +1455,7 @@
 				94A5151E1C4CA1F10063292F /* FLEXExplorerViewController.m in Sources */,
 				94A5151E1C4CA1F10063292F /* FLEXExplorerViewController.m in Sources */,
 				3A4C95371B5B21410088C3F2 /* FLEXNetworkHistoryTableViewController.m in Sources */,
 				3A4C95371B5B21410088C3F2 /* FLEXNetworkHistoryTableViewController.m in Sources */,
 				C3511B9222D7C99E0057BAB7 /* FLEXTableViewSection.m in Sources */,
 				C3511B9222D7C99E0057BAB7 /* FLEXTableViewSection.m in Sources */,
+				C32A19632317378C00EB02AC /* FLEXDefaultsContentSection.m in Sources */,
 				C3EE76C022DFC63600EC0AA0 /* FLEXScopeCarousel.m in Sources */,
 				C3EE76C022DFC63600EC0AA0 /* FLEXScopeCarousel.m in Sources */,
 			);
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 			runOnlyForDeploymentPostprocessing = 0;