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

Custom scope bar

Adds a custom "segmented control" for use on the object explorers as well as the heap explorer, due to bugs with the default implementation of the UISearchBar scope bar, and the limited size of the scope bar.

The new scope bar will scroll and display the entire class hierarchy for any object. As for the heap explorer, we can switch back to the native scope bar for iOS 13 since iOS 13 has fixed the biggest bugs.
Tanner Bennett лет назад: 7
Родитель
Сommit
d3ae20bebe

+ 15 - 0
Classes/Core/FLEXCarouselCell.h

@@ -0,0 +1,15 @@
+//
+//  FLEXCarouselCell.h
+//  FLEX
+//
+//  Created by Tanner Bennett on 7/17/19.
+//  Copyright © 2019 Flipboard. All rights reserved.
+//
+
+#import <UIKit/UIKit.h>
+
+@interface FLEXCarouselCell : UICollectionViewCell
+
+@property (nonatomic, copy) NSString *title;
+
+@end

+ 91 - 0
Classes/Core/FLEXCarouselCell.m

@@ -0,0 +1,91 @@
+//
+//  FLEXCarouselCell.m
+//  FLEX
+//
+//  Created by Tanner Bennett on 7/17/19.
+//  Copyright © 2019 Flipboard. All rights reserved.
+//
+
+#import "FLEXCarouselCell.h"
+#import "FLEXColor.h"
+#import "UIView+Layout.h"
+
+@interface FLEXCarouselCell ()
+@property (nonatomic, readonly) UILabel *titleLabel;
+@property (nonatomic, readonly) UIView *selectionIndicatorStripe;
+@property (nonatomic) BOOL constraintsInstalled;
+@end
+
+@implementation FLEXCarouselCell
+
+- (instancetype)initWithFrame:(CGRect)frame {
+    self = [super initWithFrame:frame];
+    if (self) {
+        _titleLabel = [UILabel new];
+        _selectionIndicatorStripe = [UIView new];
+
+        self.titleLabel.font = [UIFont preferredFontForTextStyle:UIFontTextStyleBody];
+        self.titleLabel.adjustsFontForContentSizeCategory = YES;
+        self.selectionIndicatorStripe.backgroundColor = self.tintColor;
+
+        [self.contentView addSubview:self.titleLabel];
+        [self.contentView addSubview:self.selectionIndicatorStripe];
+
+        [self installConstraints];
+
+        [self updateAppearance];
+    }
+
+    return self;
+}
+
+- (void)updateAppearance {
+    self.selectionIndicatorStripe.hidden = !self.selected;
+
+    if (self.selected) {
+        self.titleLabel.textColor = self.tintColor;
+    } else {
+        self.titleLabel.textColor = [FLEXColor deemphasizedTextColor];
+    }
+}
+
+#pragma mark Public
+
+- (NSString *)title {
+    return self.titleLabel.text;
+}
+
+- (void)setTitle:(NSString *)title {
+    self.titleLabel.text = title;
+    [self.titleLabel sizeToFit];
+    [self setNeedsLayout];
+}
+
+#pragma mark Overrides
+
+- (void)prepareForReuse {
+    [super prepareForReuse];
+    [self updateAppearance];
+}
+
+- (void)installConstraints {
+    CGFloat stripeHeight = 2;
+
+    self.titleLabel.translatesAutoresizingMaskIntoConstraints = NO;
+    self.selectionIndicatorStripe.translatesAutoresizingMaskIntoConstraints = NO;
+
+    UIView *superview = self.contentView;
+    [self.titleLabel pinEdgesToSuperviewWithInsets:UIEdgeInsetsMake(10, 15, 8 + stripeHeight, 15)];
+
+    [self.selectionIndicatorStripe.leadingAnchor constraintEqualToAnchor:superview.leadingAnchor].active = YES;
+    [self.selectionIndicatorStripe.bottomAnchor constraintEqualToAnchor:superview.bottomAnchor].active = YES;
+    [self.selectionIndicatorStripe.trailingAnchor constraintEqualToAnchor:superview.trailingAnchor].active = YES;
+    [self.selectionIndicatorStripe.heightAnchor constraintEqualToConstant:stripeHeight].active = YES;
+}
+
+- (void)setSelected:(BOOL)selected {
+    super.selected = selected;
+    [self updateAppearance];
+}
+
+@end

+ 20 - 0
Classes/Core/FLEXScopeCarousel.h

@@ -0,0 +1,20 @@
+//
+//  FLEXScopeCarousel.h
+//  FLEX
+//
+//  Created by Tanner Bennett on 7/17/19.
+//  Copyright © 2019 Flipboard. All rights reserved.
+//
+
+#import <UIKit/UIKit.h>
+
+/// Only use on iOS 10 and up. Requires iOS 10 APIs for calculating row sizes.
+@interface FLEXScopeCarousel : UIControl
+
+@property (nonatomic, copy) NSArray<NSString *> *items;
+@property (nonatomic) NSInteger selectedIndex;
+@property (nonatomic) void(^selectedIndexChangedAction)(NSInteger idx);
+
+- (void)registerBlockForDynamicTypeChanges:(void(^)(FLEXScopeCarousel *))handler;
+
+@end

+ 198 - 0
Classes/Core/FLEXScopeCarousel.m

@@ -0,0 +1,198 @@
+//
+//  FLEXScopeCarousel.m
+//  FLEX
+//
+//  Created by Tanner Bennett on 7/17/19.
+//  Copyright © 2019 Flipboard. All rights reserved.
+//
+
+#import "FLEXScopeCarousel.h"
+#import "FLEXCarouselCell.h"
+#import "FLEXColor.h"
+#import "UIView+Layout.h"
+
+const CGFloat kCarouselItemSpacing = 0;
+NSString * const kCarouselCellReuseIdentifier = @"kCarouselCellReuseIdentifier";
+
+@interface FLEXScopeCarousel () <UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout>
+@property (nonatomic, readonly) UICollectionView *collectionView;
+@property (nonatomic, readonly) FLEXCarouselCell *sizingCell;
+@property (nonatomic, readonly) NSLayoutConstraint *heightConstraint;
+
+@property (nonatomic, readonly) id dynamicTypeObserver;
+@property (nonatomic, readonly) NSMutableArray *dynamicTypeHandlers;
+
+@property (nonatomic) BOOL constraintsInstalled;
+@end
+
+@implementation FLEXScopeCarousel
+
+- (id)initWithFrame:(CGRect)frame {
+    self = [super initWithFrame:frame];
+    if (self) {
+        self.backgroundColor = [FLEXColor primaryBackgroundColor];
+        self.autoresizingMask = UIViewAutoresizingFlexibleWidth;
+        _dynamicTypeHandlers = [NSMutableArray new];
+
+        // Collection view layout
+        UICollectionViewFlowLayout *layout = ({
+            UICollectionViewFlowLayout *layout = [UICollectionViewFlowLayout new];
+            layout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
+            layout.sectionInset = UIEdgeInsetsZero;
+            layout.minimumLineSpacing = kCarouselItemSpacing;
+            layout.itemSize = UICollectionViewFlowLayoutAutomaticSize;
+            layout.estimatedItemSize = UICollectionViewFlowLayoutAutomaticSize;
+            layout;
+        });
+
+        // Collection view
+        _collectionView = ({
+            UICollectionView *cv = [[UICollectionView alloc]
+                initWithFrame:CGRectZero
+                collectionViewLayout:layout
+            ];
+            cv.showsHorizontalScrollIndicator = NO;
+            cv.backgroundColor = [UIColor clearColor];
+            cv.delegate = self;
+            cv.dataSource = self;
+            [cv registerClass:[FLEXCarouselCell class] forCellWithReuseIdentifier:kCarouselCellReuseIdentifier];
+
+            [self addSubview:cv];
+            cv;
+        });
+
+
+        // Sizing cell
+        _sizingCell = [FLEXCarouselCell new];
+        self.sizingCell.title = @"NSObject";
+
+        // Dynamic type
+        __weak __typeof(self) weakSelf = self;
+        _dynamicTypeObserver = [[NSNotificationCenter defaultCenter]
+            addObserverForName:UIContentSizeCategoryDidChangeNotification
+            object:nil queue:nil usingBlock:^(NSNotification *note) {
+                [self.collectionView setNeedsLayout];
+                [self setNeedsUpdateConstraints];
+
+                // Notify observers
+                __typeof(self) self = weakSelf;
+                for (void (^block)() in self.dynamicTypeHandlers) {
+                    block(self);
+                }
+            }
+        ];
+    }
+
+    return self;
+}
+
+- (void)dealloc {
+    [[NSNotificationCenter defaultCenter] removeObserver:self.dynamicTypeObserver];
+}
+
+#pragma mark - Overrides
+
+- (void)drawRect:(CGRect)rect {
+    [super drawRect:rect];
+
+    CGFloat width = 1.f / [UIScreen mainScreen].scale;
+
+    // Draw hairline
+    CGContextRef context = UIGraphicsGetCurrentContext();
+    CGContextSetStrokeColorWithColor(context, [FLEXColor hairlineColor].CGColor);
+    CGContextSetLineWidth(context, width);
+    CGContextMoveToPoint(context, 0, rect.size.height - width);
+    CGContextAddLineToPoint(context, rect.size.width, rect.size.height - width);
+    CGContextStrokePath(context);
+}
+
++ (BOOL)requiresConstraintBasedLayout {
+    return YES;
+}
+
+- (void)updateConstraints {
+    if (!self.constraintsInstalled) {
+        self.translatesAutoresizingMaskIntoConstraints = NO;
+        self.collectionView.translatesAutoresizingMaskIntoConstraints = NO;
+
+        [self.centerXAnchor constraintEqualToAnchor:self.superview.centerXAnchor].active = YES;
+        [self.widthAnchor constraintEqualToAnchor:self.superview.widthAnchor].active = YES;
+        [self.topAnchor constraintEqualToAnchor:self.superview.topAnchor].active = YES;
+
+        [self.collectionView pinEdgesToSuperview];
+        _heightConstraint = [self.heightAnchor constraintEqualToConstant:100];
+        self.heightConstraint.active = YES;
+
+        self.constraintsInstalled = YES;
+    }
+
+    self.heightConstraint.constant = [self.sizingCell systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;
+    
+    [super updateConstraints];
+}
+
+#pragma mark - Public
+
+- (void)setItems:(NSArray<NSString *> *)items {
+    NSParameterAssert(items.count);
+
+    _items = items.copy;
+
+    // Refresh list, select first item initially
+    [self.collectionView reloadData];
+    self.selectedIndex = 0;
+}
+
+- (void)setSelectedIndex:(NSInteger)idx {
+    NSParameterAssert(idx < self.items.count);
+
+    _selectedIndex = idx;
+    NSIndexPath *path = [NSIndexPath indexPathForItem:idx inSection:0];
+    [self.collectionView selectItemAtIndexPath:path
+                                      animated:YES
+                                scrollPosition:UICollectionViewScrollPositionCenteredHorizontally];
+    [self collectionView:self.collectionView didSelectItemAtIndexPath:path];
+}
+
+- (void)registerBlockForDynamicTypeChanges:(void (^)(FLEXScopeCarousel *))handler {
+    [self.dynamicTypeHandlers addObject:handler];
+}
+
+#pragma mark - UICollectionView
+
+- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
+    self.sizingCell.title = self.items[indexPath.item];
+    return [self.sizingCell systemLayoutSizeFittingSize:UILayoutFittingCompressedSize];
+}
+
+- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
+    return self.items.count;
+}
+
+- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView
+                  cellForItemAtIndexPath:(NSIndexPath *)indexPath {
+    FLEXCarouselCell *cell = (id)[collectionView dequeueReusableCellWithReuseIdentifier:kCarouselCellReuseIdentifier
+                                                                           forIndexPath:indexPath];
+    cell.title = self.items[indexPath.row];
+    return cell;
+}
+
+- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
+    _selectedIndex = indexPath.item; // In case self.selectedIndex didn't trigger this call
+
+    if (self.selectedIndexChangedAction) {
+        self.selectedIndexChangedAction(indexPath.row);
+    }
+
+    // TODO: dynamically choose a scroll position. Very wide items should
+    // get "Left" while smaller items should not scroll at all, unless
+    // they are only partially on the screen, in which case they
+    // should get "HorizontallyCentered" to bring them onto the screen.
+    // For now, everything goes to the left, as this has a similar effect.
+    [collectionView scrollToItemAtIndexPath:indexPath
+                           atScrollPosition:UICollectionViewScrollPositionLeft
+                                   animated:YES];
+    [self sendActionsForControlEvents:UIControlEventValueChanged];
+}
+
+@end

+ 13 - 2
Classes/Core/FLEXTableViewController.h

@@ -7,6 +7,7 @@
 //
 
 #import <UIKit/UIKit.h>
+@class FLEXScopeCarousel;
 
 typedef CGFloat FLEXDebounceInterval;
 /// No delay, all events delivered
@@ -25,9 +26,18 @@ extern CGFloat const kFLEXDebounceForExpensiveIO;
 /// Simply calls into initWithStyle:
 - (id)init;
 
+/// Defaults to NO.
+///
+/// Setting this to YES will initialize the carousel and the view.
+@property (nonatomic) BOOL showsCarousel;
+/// A horizontally scrolling list with functionality similar to
+/// that of a search bar's scope bar. You'd want to use this when
+/// you have potentially more than 4 scope options.
+@property (nonatomic) FLEXScopeCarousel *carousel;
+
 /// Defaults to NO.
 /// 
-/// Setting this to YES will initialize searchController.
+/// Setting this to YES will initialize searchController and the view.
 @property (nonatomic) BOOL showsSearchBar;
 /// Defaults to NO.
 ///
@@ -64,7 +74,8 @@ extern CGFloat const kFLEXDebounceForExpensiveIO;
 /// searchBar manually.
 @property (nonatomic) BOOL automaticallyShowsSearchBarCancelButton;
 
-/// self.searchController.searchBar.selectedScopeButtonIndex
+/// If using the scope bar, self.searchController.searchBar.selectedScopeButtonIndex.
+/// Otherwise, this is the selected index of the carousel, or NSNotFound if using neither.
 @property (nonatomic, readonly) NSInteger selectedScope;
 /// self.searchController.searchBar.text
 @property (nonatomic, readonly) NSString *searchText;

+ 37 - 1
Classes/Core/FLEXTableViewController.m

@@ -7,6 +7,9 @@
 //
 
 #import "FLEXTableViewController.h"
+#import "FLEXScopeCarousel.h"
+#import "FLEXTableView.h"
+#import <objc/runtime.h>
 
 @interface Block : NSObject
 - (void)invoke;
@@ -19,6 +22,7 @@ CGFloat const kFLEXDebounceForExpensiveIO = 0.5;
 
 @interface FLEXTableViewController ()
 @property (nonatomic) NSTimer *debounceTimer;
+
 @end
 
 @implementation FLEXTableViewController
@@ -69,8 +73,40 @@ CGFloat const kFLEXDebounceForExpensiveIO = 0.5;
     }
 }
 
+- (void)setShowsCarousel:(BOOL)showsCarousel {
+    if (_showsCarousel == showsCarousel) return;
+    _showsCarousel = showsCarousel;
+
+    _carousel = ({
+        __weak __typeof(self) weakSelf = self;
+
+        FLEXScopeCarousel *carousel = [FLEXScopeCarousel new];
+        carousel.selectedIndexChangedAction = ^(NSInteger idx) {
+            __typeof(self) self = weakSelf;
+            [self updateSearchResults:self.searchText];
+        };
+
+        self.tableView.tableHeaderView = carousel;
+        [self.tableView layoutIfNeeded];
+        // UITableView won't update the header size unless you reset the header view
+        [carousel registerBlockForDynamicTypeChanges:^(FLEXScopeCarousel *carousel) {
+            __typeof(self) self = weakSelf;
+            self.tableView.tableHeaderView = carousel;
+            [self.tableView layoutIfNeeded];
+        }];
+
+        carousel;
+    });
+}
+
 - (NSInteger)selectedScope {
-    return self.searchController.searchBar.selectedScopeButtonIndex;
+    if (self.searchController.searchBar.showsScopeBar) {
+        return self.searchController.searchBar.selectedScopeButtonIndex;
+    } else if (self.showsCarousel) {
+        return self.carousel.selectedIndex;
+    } else {
+        return NSNotFound;
+    }
 }
 
 - (NSString *)searchText {

+ 6 - 10
Classes/GlobalStateExplorers/FLEXLiveObjectsTableViewController.m

@@ -10,6 +10,7 @@
 #import "FLEXHeapEnumerator.h"
 #import "FLEXInstancesTableViewController.h"
 #import "FLEXUtility.h"
+#import "FLEXScopeCarousel.h"
 #import <objc/runtime.h>
 
 static const NSInteger kFLEXLiveObjectsSortAlphabeticallyIndex = 0;
@@ -33,8 +34,8 @@ static const NSInteger kFLEXLiveObjectsSortBySizeIndex = 2;
     
     self.showsSearchBar = YES;
     self.searchBarDebounceInterval = kFLEXDebounceInstant;
-    self.searchController.searchBar.showsScopeBar = YES;
-    self.searchController.searchBar.scopeButtonTitles = @[@"Sort Alphabetically", @"Sort by Count", @"Sort by Size"];
+    self.showsCarousel = YES;
+    self.carousel.items = @[@"A→Z", @"Count", @"Size"];
     
     self.refreshControl = [[UIRefreshControl alloc] init];
     [self.refreshControl addTarget:self action:@selector(refreshControlDidRefresh:) forControlEvents:UIControlEventValueChanged];
@@ -86,7 +87,7 @@ static const NSInteger kFLEXLiveObjectsSortBySizeIndex = 2;
     self.instanceCountsForClassNames = mutableCountsForClassNames;
     self.instanceSizesForClassNames = mutableSizesForClassNames;
     
-    [self updateTableDataForSearchFilter:nil];
+    [self updateSearchResults:nil];
 }
 
 - (void)refreshControlDidRefresh:(id)sender
@@ -140,14 +141,9 @@ static const NSInteger kFLEXLiveObjectsSortBySizeIndex = 2;
 
 #pragma mark - Search bar
 
-- (void)updateSearchResults:(NSString *)newText
+- (void)updateSearchResults:(NSString *)filter
 {
-    [self updateTableDataForSearchFilter:newText];
-}
-
-- (void)updateTableDataForSearchFilter:(NSString *)filter
-{
-    NSInteger selectedScope = self.searchController.searchBar.selectedScopeButtonIndex;
+    NSInteger selectedScope = self.selectedScope;
     
     if (filter.length) {
         NSPredicate *searchPredicate = [NSPredicate predicateWithFormat:@"SELF CONTAINS[cd] %@", filter];

+ 17 - 0
Classes/ObjectExplorers/Controllers/FLEXClassTreeViewController.h

@@ -0,0 +1,17 @@
+//
+//  FLEXClassTreeViewController.h
+//  FLEX
+//
+//  Created by Tanner Bennett on 7/17/19.
+//  Copyright © 2019 Flipboard. All rights reserved.
+//
+
+#import <UIKit/UIKit.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface FLEXClassTreeViewController : UIPageViewController
+
+@end
+
+NS_ASSUME_NONNULL_END

+ 22 - 0
Classes/ObjectExplorers/Controllers/FLEXClassTreeViewController.m

@@ -0,0 +1,22 @@
+//
+//  FLEXClassTreeViewController.m
+//  FLEX
+//
+//  Created by Tanner Bennett on 7/17/19.
+//  Copyright © 2019 Flipboard. All rights reserved.
+//
+
+#import "FLEXClassTreeViewController.h"
+
+@interface FLEXClassTreeViewController ()
+
+@end
+
+@implementation FLEXClassTreeViewController
+
+- (void)viewDidLoad {
+    [super viewDidLoad];
+    
+}
+
+@end

+ 76 - 180
Classes/ObjectExplorers/Controllers/FLEXObjectExplorerViewController.m

@@ -16,15 +16,9 @@
 #import "FLEXMethodCallingViewController.h"
 #import "FLEXInstancesTableViewController.h"
 #import "FLEXTableView.h"
+#import "FLEXScopeCarousel.h"
 #import <objc/runtime.h>
 
-typedef NS_ENUM(NSUInteger, FLEXObjectExplorerScope) {
-    FLEXObjectExplorerScopeNoInheritance,
-    FLEXObjectExplorerScopeWithParent,
-    FLEXObjectExplorerScopeAllButNSObject,
-    FLEXObjectExplorerScopeNSObjectOnly
-};
-
 typedef NS_ENUM(NSUInteger, FLEXMetadataKind) {
     FLEXMetadataKindProperties,
     FLEXMetadataKindIvars,
@@ -53,38 +47,27 @@ typedef NS_ENUM(NSUInteger, FLEXMetadataKind) {
 
 @interface FLEXObjectExplorerViewController ()
 
-@property (nonatomic, strong) NSArray<FLEXPropertyBox *> *properties;
-@property (nonatomic, strong) NSArray<FLEXPropertyBox *> *propertiesWithParent;
-@property (nonatomic, strong) NSArray<FLEXPropertyBox *> *inheritedProperties;
-@property (nonatomic, strong) NSArray<FLEXPropertyBox *> *NSObjectProperties;
+@property (nonatomic, strong) NSMutableArray<NSArray<FLEXPropertyBox *> *> *properties;
 @property (nonatomic, strong) NSArray<FLEXPropertyBox *> *filteredProperties;
 
-@property (nonatomic, strong) NSArray<FLEXIvarBox *> *ivars;
-@property (nonatomic, strong) NSArray<FLEXIvarBox *> *ivarsWithParent;
-@property (nonatomic, strong) NSArray<FLEXIvarBox *> *inheritedIvars;
-@property (nonatomic, strong) NSArray<FLEXIvarBox *> *NSObjectIvars;
+@property (nonatomic, strong) NSMutableArray<NSArray<FLEXIvarBox *> *> *ivars;
 @property (nonatomic, strong) NSArray<FLEXIvarBox *> *filteredIvars;
 
-@property (nonatomic, strong) NSArray<FLEXMethodBox *> *methods;
-@property (nonatomic, strong) NSArray<FLEXMethodBox *> *methodsWithParent;
-@property (nonatomic, strong) NSArray<FLEXMethodBox *> *inheritedMethods;
-@property (nonatomic, strong) NSArray<FLEXMethodBox *> *NSObjectMethods;
+@property (nonatomic, strong) NSMutableArray<NSArray<FLEXMethodBox *> *> *methods;
 @property (nonatomic, strong) NSArray<FLEXMethodBox *> *filteredMethods;
 
-@property (nonatomic, strong) NSArray<FLEXMethodBox *> *classMethods;
-@property (nonatomic, strong) NSArray<FLEXMethodBox *> *classMethodsWithParent;
-@property (nonatomic, strong) NSArray<FLEXMethodBox *> *inheritedClassMethods;
-@property (nonatomic, strong) NSArray<FLEXMethodBox *> *NSObjectClassMethods;
+@property (nonatomic, strong) NSMutableArray<NSArray<FLEXMethodBox *> *> *classMethods;
 @property (nonatomic, strong) NSArray<FLEXMethodBox *> *filteredClassMethods;
 
-@property (nonatomic, strong) NSArray<Class> *superclasses;
-@property (nonatomic, strong) NSArray<Class> *filteredSuperclasses;
+@property (nonatomic, copy) NSArray<Class> *classHierarchy;
+@property (nonatomic, copy) NSArray<Class> *filteredSuperclasses;
 
 @property (nonatomic, strong) NSArray *cachedCustomSectionRowCookies;
 @property (nonatomic, strong) NSIndexSet *customSectionVisibleIndexes;
 
 @property (nonatomic, strong) NSString *filterText;
-@property (nonatomic, assign) FLEXObjectExplorerScope scope;
+/// An index into the `classHierarchy` array
+@property (nonatomic) NSInteger classScope;
 
 @end
 
@@ -111,7 +94,7 @@ typedef NS_ENUM(NSUInteger, FLEXMetadataKind) {
     
     self.showsSearchBar = YES;
     self.searchBarDebounceInterval = kFLEXDebounceInstant;
-    self.searchController.searchBar.showsScopeBar = YES;
+    self.showsCarousel = YES;
     [self refreshScopeTitles];
     
     self.refreshControl = [[UIRefreshControl alloc] init];
@@ -127,107 +110,72 @@ typedef NS_ENUM(NSUInteger, FLEXMetadataKind) {
     [self updateTableData];
 }
 
-- (void)scrollViewDidScroll:(UIScrollView *)scrollView
-{
-    [self.searchController.searchBar endEditing:YES];
-}
-
 - (void)refreshControlDidRefresh:(id)sender
 {
     [self updateTableData];
     [self.refreshControl endRefreshing];
 }
 
+- (BOOL)shouldShowDescription
+{
+    // Not if we have filter text that doesn't match the desctiption.
+    if (self.filterText.length) {
+        NSString *description = [self displayedObjectDescription];
+        return [description rangeOfString:self.filterText options:NSCaseInsensitiveSearch].length > 0;
+    }
+
+    return YES;
+}
+
+- (NSString *)displayedObjectDescription
+{
+    NSString *desc = [FLEXUtility safeDescriptionForObject:self.object];
+
+    if (!desc.length) {
+        NSString *address = [FLEXUtility addressOfObject:self.object];
+        desc = [NSString stringWithFormat:@"Object at %@ returned empty description", address];
+    }
+
+    return desc;
+}
+
 
 #pragma mark - Search
 
 - (void)refreshScopeTitles
 {
-    if (!self.searchController.searchBar) return;
-
-    Class parent = [self.object superclass];
-    Class parentSuper = [parent superclass];
+    [self updateSuperclasses];
 
-    NSMutableArray *scopes = [NSMutableArray arrayWithObject:@"Base"];
-    if (parent) {
-        [scopes addObject:@"+ Parent"];
-    }
-    if (parentSuper && parentSuper != [NSObject class]) {
-        [scopes addObject:@"+ Inherited"];
-    }
-    if ([self.object isKindOfClass:[NSObject class]]) {
-        [scopes addObject:@"NSObject"];
-    }
+    self.carousel.items = [FLEXUtility map:self.classHierarchy block:^id(Class cls, NSUInteger idx) {
+        return NSStringFromClass(cls);
+    }];
 
-    self.searchController.searchBar.scopeButtonTitles = scopes;
     [self updateTableData];
 }
 
 - (void)updateSearchResults:(NSString *)newText;
 {
-    NSInteger newScope = self.searchController.searchBar.selectedScopeButtonIndex;
-    BOOL delta = self.scope != newScope || ![self.filterText isEqualToString:newText];
-    
-    if (delta) {
-        self.scope = newScope;
-        self.filterText = newText;
-        [self updateDisplayedData];
-    }
+    self.filterText = newText;
+    [self updateDisplayedData];
 }
 
-- (NSArray *)metadata:(FLEXMetadataKind)metadataKind forScope:(FLEXObjectExplorerScope)scope
+- (NSArray *)metadata:(FLEXMetadataKind)metadataKind forClassAtIndex:(NSUInteger)idx
 {
     switch (metadataKind) {
         case FLEXMetadataKindProperties:
-            switch (self.scope) {
-                case FLEXObjectExplorerScopeNoInheritance:
-                    return self.properties;
-                case FLEXObjectExplorerScopeWithParent:
-                    return self.propertiesWithParent;
-                case FLEXObjectExplorerScopeAllButNSObject:
-                    return self.inheritedProperties;
-                case FLEXObjectExplorerScopeNSObjectOnly:
-                    return self.NSObjectProperties;
-            }
+            return self.properties[idx];
         case FLEXMetadataKindIvars:
-            switch (self.scope) {
-                case FLEXObjectExplorerScopeNoInheritance:
-                    return self.ivars;
-                case FLEXObjectExplorerScopeWithParent:
-                    return self.ivarsWithParent;
-                case FLEXObjectExplorerScopeAllButNSObject:
-                    return self.inheritedIvars;
-                case FLEXObjectExplorerScopeNSObjectOnly:
-                    return self.NSObjectIvars;
-            }
+            return self.ivars[idx];
         case FLEXMetadataKindMethods:
-            switch (self.scope) {
-                case FLEXObjectExplorerScopeNoInheritance:
-                    return self.methods;
-                case FLEXObjectExplorerScopeWithParent:
-                    return self.methodsWithParent;
-                case FLEXObjectExplorerScopeAllButNSObject:
-                    return self.inheritedMethods;
-                case FLEXObjectExplorerScopeNSObjectOnly:
-                    return self.NSObjectMethods;
-            }
+            return self.methods[idx];
         case FLEXMetadataKindClassMethods:
-            switch (self.scope) {
-                case FLEXObjectExplorerScopeNoInheritance:
-                    return self.classMethods;
-                case FLEXObjectExplorerScopeWithParent:
-                    return self.classMethodsWithParent;
-                case FLEXObjectExplorerScopeAllButNSObject:
-                    return self.inheritedClassMethods;
-                case FLEXObjectExplorerScopeNSObjectOnly:
-                    return self.NSObjectClassMethods;
-            }
+            return self.classMethods[idx];
     }
 }
 
-- (NSInteger)totalCountOfMetadata:(FLEXMetadataKind)metadataKind forScope:(FLEXObjectExplorerScope)scope
+- (NSInteger)totalCountOfMetadata:(FLEXMetadataKind)metadataKind forClassAtIndex:(NSUInteger)idx
 {
-    return [self metadata:metadataKind forScope:scope].count;
+    return [self metadata:metadataKind forClassAtIndex:idx].count;
 }
 
 #pragma mark - Setter overrides
@@ -237,7 +185,12 @@ typedef NS_ENUM(NSUInteger, FLEXMetadataKind) {
     _object = object;
     // Use [object class] here rather than object_getClass because we don't want to show the KVO prefix for observed objects.
     self.title = [[object class] description];
-    [self refreshScopeTitles];
+
+    // Only refresh if the view has appeared
+    #warning TODO make .object readonly so we don't have to deal with this...
+    if (self.showsCarousel) {
+        [self refreshScopeTitles];
+    }
 }
 
 #pragma mark - Reloading
@@ -245,11 +198,7 @@ typedef NS_ENUM(NSUInteger, FLEXMetadataKind) {
 - (void)updateTableData
 {
     [self updateCustomData];
-    [self updateProperties];
-    [self updateIvars];
-    [self updateMethods];
-    [self updateClassMethods];
-    [self updateSuperclasses];
+    [self updateMetadata];
     [self updateDisplayedData];
 }
 
@@ -267,40 +216,24 @@ typedef NS_ENUM(NSUInteger, FLEXMetadataKind) {
     }
 }
 
-- (BOOL)shouldShowDescription
+- (void)updateMetadata
 {
-    // Not if we have filter text that doesn't match the desctiption.
-    if (self.filterText.length) {
-        NSString *description = [self displayedObjectDescription];
-        return [description rangeOfString:self.filterText options:NSCaseInsensitiveSearch].length > 0;
-    }
-    
-    return YES;
-}
+    self.properties = [NSMutableArray new];
+    self.ivars = [NSMutableArray new];
+    self.methods = [NSMutableArray new];
+    self.classMethods = [NSMutableArray new];
 
-- (NSString *)displayedObjectDescription {
-    NSString *desc = [FLEXUtility safeDescriptionForObject:self.object];
-
-    if (!desc.length) {
-        NSString *address = [FLEXUtility addressOfObject:self.object];
-        desc = [NSString stringWithFormat:@"Object at %@ returned empty description", address];
+    for (Class cls in self.classHierarchy) {
+        [self.properties addObject:[[self class] propertiesForClass:cls]];
+        [self.ivars addObject:[[self class] ivarsForClass:cls]];
+        [self.methods addObject:[[self class] methodsForClass:cls]];
+        [self.classMethods addObject:[[self class] methodsForClass:object_getClass(cls)]];
     }
-
-    return desc;
 }
 
 
 #pragma mark - Properties
 
-- (void)updateProperties
-{
-    Class class = [self.object class];
-    self.properties = [[self class] propertiesForClass:class];
-    self.propertiesWithParent = [self.properties arrayByAddingObjectsFromArray:[[self class] propertiesForClass:[class superclass]]];
-    self.inheritedProperties = [self.properties arrayByAddingObjectsFromArray:[[self class] inheritedPropertiesForClass:class]];
-    self.NSObjectProperties = [[self class] propertiesForClass:[NSObject class]];
-}
-
 + (NSArray<FLEXPropertyBox *> *)propertiesForClass:(Class)class
 {
     if (!class) {
@@ -333,7 +266,7 @@ typedef NS_ENUM(NSUInteger, FLEXMetadataKind) {
 
 - (void)updateFilteredProperties
 {
-    NSArray<FLEXPropertyBox *> *candidateProperties = [self metadata:FLEXMetadataKindProperties forScope:self.scope];
+    NSArray<FLEXPropertyBox *> *candidateProperties = [self metadata:FLEXMetadataKindProperties forClassAtIndex:self.selectedScope];
     
     NSArray<FLEXPropertyBox *> *unsortedFilteredProperties = nil;
     if ([self.filterText length] > 0) {
@@ -378,15 +311,6 @@ typedef NS_ENUM(NSUInteger, FLEXMetadataKind) {
 
 #pragma mark - Ivars
 
-- (void)updateIvars
-{
-    Class class = [self.object class];
-    self.ivars = [[self class] ivarsForClass:class];
-    self.ivarsWithParent = [self.ivars arrayByAddingObjectsFromArray:[[self class] ivarsForClass:[class superclass]]];
-    self.inheritedIvars = [self.ivars arrayByAddingObjectsFromArray:[[self class] inheritedIvarsForClass:class]];
-    self.NSObjectIvars = [[self class] ivarsForClass:[NSObject class]];
-}
-
 + (NSArray<FLEXIvarBox *> *)ivarsForClass:(Class)class
 {
     if (!class) {
@@ -418,7 +342,7 @@ typedef NS_ENUM(NSUInteger, FLEXMetadataKind) {
 
 - (void)updateFilteredIvars
 {
-    NSArray<FLEXIvarBox *> *candidateIvars = [self metadata:FLEXMetadataKindIvars forScope:self.scope];
+    NSArray<FLEXIvarBox *> *candidateIvars = [self metadata:FLEXMetadataKindIvars forClassAtIndex:self.selectedScope];
     
     NSArray<FLEXIvarBox *> *unsortedFilteredIvars = nil;
     if ([self.filterText length] > 0) {
@@ -462,34 +386,15 @@ typedef NS_ENUM(NSUInteger, FLEXMetadataKind) {
 
 #pragma mark - Methods
 
-- (void)updateMethods
-{
-    Class class = [self.object class];
-    self.methods = [[self class] methodsForClass:class];
-    self.methodsWithParent = [self.methods arrayByAddingObjectsFromArray:[[self class] methodsForClass:[class superclass]]];
-    self.inheritedMethods = [self.methods arrayByAddingObjectsFromArray:[[self class] inheritedMethodsForClass:class]];
-    self.NSObjectMethods = [[self class] methodsForClass:[NSObject class]];
-}
-
 - (void)updateFilteredMethods
 {
-    NSArray<FLEXMethodBox *> *candidateMethods = [self metadata:FLEXMetadataKindMethods forScope:self.scope];
+    NSArray<FLEXMethodBox *> *candidateMethods = [self metadata:FLEXMetadataKindMethods forClassAtIndex:self.selectedScope];
     self.filteredMethods = [self filteredMethodsFromMethods:candidateMethods areClassMethods:NO];
 }
 
-- (void)updateClassMethods
-{
-    const char *className = [NSStringFromClass([self.object class]) UTF8String];
-    Class metaClass = objc_getMetaClass(className);
-    self.classMethods = [[self class] methodsForClass:metaClass];
-    self.classMethodsWithParent = [self.classMethods arrayByAddingObjectsFromArray:[[self class] methodsForClass:[metaClass superclass]]];
-    self.inheritedClassMethods = [self.classMethods arrayByAddingObjectsFromArray:[[self class] inheritedMethodsForClass:metaClass]];
-    self.NSObjectClassMethods = [[self class] methodsForClass:[NSObject class]];
-}
-
 - (void)updateFilteredClassMethods
 {
-    NSArray<FLEXMethodBox *> *candidateMethods = [self metadata:FLEXMetadataKindClassMethods forScope:self.scope];
+    NSArray<FLEXMethodBox *> *candidateMethods = [self metadata:FLEXMetadataKindClassMethods forClassAtIndex:self.selectedScope];
     self.filteredClassMethods = [self filteredMethodsFromMethods:candidateMethods areClassMethods:YES];
 }
 
@@ -569,32 +474,23 @@ typedef NS_ENUM(NSUInteger, FLEXMetadataKind) {
 
 #pragma mark - Superclasses
 
-+ (NSArray<Class> *)superclassesForClass:(Class)class
-{
-    NSMutableArray<Class> *superClasses = [NSMutableArray array];
-    while ((class = [class superclass])) {
-        [superClasses addObject:class];
-    }
-    return superClasses;
-}
-
 - (void)updateSuperclasses
 {
-    self.superclasses = [[self class] superclassesForClass:[self.object class]];
+    self.classHierarchy = [FLEXRuntimeUtility classHierarchyOfObject:self.object];
 }
 
 - (void)updateFilteredSuperclasses
 {
     if ([self.filterText length] > 0) {
         NSMutableArray<Class> *filteredSuperclasses = [NSMutableArray array];
-        for (Class superclass in self.superclasses) {
-            if ([NSStringFromClass(superclass) rangeOfString:self.filterText options:NSCaseInsensitiveSearch].length > 0) {
+        for (Class superclass in self.classHierarchy) {
+            if ([NSStringFromClass(superclass) localizedCaseInsensitiveContainsString:self.filterText]) {
                 [filteredSuperclasses addObject:superclass];
             }
         }
         self.filteredSuperclasses = filteredSuperclasses;
     } else {
-        self.filteredSuperclasses = self.superclasses;
+        self.filteredSuperclasses = self.classHierarchy;
     }
 }
 
@@ -831,27 +727,27 @@ typedef NS_ENUM(NSUInteger, FLEXMetadataKind) {
         } break;
             
         case FLEXObjectExplorerSectionProperties: {
-            NSUInteger totalCount = [self totalCountOfMetadata:FLEXMetadataKindProperties forScope:self.scope];
+            NSUInteger totalCount = [self totalCountOfMetadata:FLEXMetadataKindProperties forClassAtIndex:self.selectedScope];
             title = [self sectionTitleWithBaseName:@"Properties" totalCount:totalCount filteredCount:[self.filteredProperties count]];
         } break;
             
         case FLEXObjectExplorerSectionIvars: {
-            NSUInteger totalCount = [self totalCountOfMetadata:FLEXMetadataKindIvars forScope:self.scope];
+            NSUInteger totalCount = [self totalCountOfMetadata:FLEXMetadataKindIvars forClassAtIndex:self.selectedScope];
             title = [self sectionTitleWithBaseName:@"Ivars" totalCount:totalCount filteredCount:[self.filteredIvars count]];
         } break;
             
         case FLEXObjectExplorerSectionMethods: {
-            NSUInteger totalCount = [self totalCountOfMetadata:FLEXMetadataKindMethods forScope:self.scope];
+            NSUInteger totalCount = [self totalCountOfMetadata:FLEXMetadataKindMethods forClassAtIndex:self.selectedScope];
             title = [self sectionTitleWithBaseName:@"Methods" totalCount:totalCount filteredCount:[self.filteredMethods count]];
         } break;
             
         case FLEXObjectExplorerSectionClassMethods: {
-            NSUInteger totalCount = [self totalCountOfMetadata:FLEXMetadataKindClassMethods forScope:self.scope];
+            NSUInteger totalCount = [self totalCountOfMetadata:FLEXMetadataKindClassMethods forClassAtIndex:self.selectedScope];
             title = [self sectionTitleWithBaseName:@"Class Methods" totalCount:totalCount filteredCount:[self.filteredClassMethods count]];
         } break;
             
         case FLEXObjectExplorerSectionSuperclasses: {
-            title = [self sectionTitleWithBaseName:@"Superclasses" totalCount:[self.superclasses count] filteredCount:[self.filteredSuperclasses count]];
+            title = [self sectionTitleWithBaseName:@"Superclasses" totalCount:[self.classHierarchy count] filteredCount:[self.filteredSuperclasses count]];
         } break;
             
         case FLEXObjectExplorerSectionReferencingInstances: {

+ 21 - 4
Classes/ObjectExplorers/Views/FLEXTableView.m

@@ -10,10 +10,24 @@
 #import "FLEXSubtitleTableViewCell.h"
 #import "FLEXMultilineTableViewCell.h"
 
+@interface UITableView (Private)
+- (CGFloat)_heightForHeaderInSection:(NSInteger)section;
+@end
+
 @implementation FLEXTableView
 
+- (CGFloat)_heightForHeaderInSection:(NSInteger)section {
+    CGFloat height = [super _heightForHeaderInSection:section];
+    if (section == 0 && self.tableHeaderView) {
+        return height - self.tableHeaderView.frame.size.height + 8;
+    }
+
+    return height;
+}
+
 - (id)initWithFrame:(CGRect)frame style:(UITableViewStyle)style
-{    self = [super initWithFrame:frame style:style];
+{
+    self = [super initWithFrame:frame style:style];
     if (self) {
         [self registerCells:@{
             self.defaultReuseIdentifier: [FLEXTableViewCell class],
@@ -26,17 +40,20 @@
 }
 
 - (void)registerCells:(NSDictionary<NSString*,Class> *)registrationMapping
-{    [registrationMapping enumerateKeysAndObjectsUsingBlock:^(NSString *identifier, Class cellClass, BOOL *stop) {
+{
+    [registrationMapping enumerateKeysAndObjectsUsingBlock:^(NSString *identifier, Class cellClass, BOOL *stop) {
         [self registerClass:cellClass forCellReuseIdentifier:identifier];
     }];
 }
 
 - (NSString *)defaultReuseIdentifier
-{    return @"kFLEXTableViewCellIdentifier";
+{
+    return @"kFLEXTableViewCellIdentifier";
 }
 
 - (NSString *)subtitleReuseIdentifier
-{    return @"kFLEXSubtitleTableViewCellIdentifier";
+{
+    return @"kFLEXSubtitleTableViewCellIdentifier";
 }
 
 - (NSString *)multilineReuseIdentifier

+ 21 - 0
Classes/Utility/Categories/UIView+Layout.h

@@ -0,0 +1,21 @@
+//
+//  UIView+Layout.h
+//  FLEX
+//
+//  Created by Tanner Bennett on 7/18/19.
+//Copyright © 2019 Flipboard. All rights reserved.
+//
+
+#import <UIKit/UIKit.h>
+
+#define Padding(p) UIEdgeInsetsMake(p, p, p, p)
+
+@interface UIView (Layout)
+
+- (void)centerInView:(UIView *)view;
+- (void)pinEdgesTo:(UIView *)view;
+- (void)pinEdgesTo:(UIView *)view withInsets:(UIEdgeInsets)insets;
+- (void)pinEdgesToSuperview;
+- (void)pinEdgesToSuperviewWithInsets:(UIEdgeInsets)insets;
+
+@end

+ 40 - 0
Classes/Utility/Categories/UIView+Layout.m

@@ -0,0 +1,40 @@
+//
+//  UIView+Layout.m
+//  FLEX
+//
+//  Created by Tanner Bennett on 7/18/19.
+//Copyright © 2019 Flipboard. All rights reserved.
+//
+
+#import "UIView+Layout.h"
+
+@implementation UIView (Layout)
+
+- (void)centerInView:(UIView *)view {
+    [self.centerXAnchor constraintEqualToAnchor:view.centerXAnchor].active = YES;
+    [self.centerYAnchor constraintEqualToAnchor:view.centerYAnchor].active = YES;
+}
+
+- (void)pinEdgesTo:(UIView *)view {
+    [self.topAnchor constraintEqualToAnchor:view.topAnchor].active = YES;
+    [self.leftAnchor constraintEqualToAnchor:view.leftAnchor].active = YES;
+    [self.bottomAnchor constraintEqualToAnchor:view.bottomAnchor].active = YES;
+    [self.rightAnchor constraintEqualToAnchor:view.rightAnchor].active = YES;
+}
+
+- (void)pinEdgesTo:(UIView *)view withInsets:(UIEdgeInsets)i {
+    [self.topAnchor constraintEqualToAnchor:view.topAnchor constant:i.top].active = YES;
+    [self.leftAnchor constraintEqualToAnchor:view.leftAnchor constant:i.left].active = YES;
+    [self.bottomAnchor constraintEqualToAnchor:view.bottomAnchor constant:-i.bottom].active = YES;
+    [self.rightAnchor constraintEqualToAnchor:view.rightAnchor constant:-i.right].active = YES;
+}
+
+- (void)pinEdgesToSuperview {
+    [self pinEdgesTo:self.superview];
+}
+
+- (void)pinEdgesToSuperviewWithInsets:(UIEdgeInsets)insets {
+    [self pinEdgesTo:self.superview withInsets:insets];
+}
+
+@end

+ 2 - 0
Classes/Utility/FLEXColor.h

@@ -28,11 +28,13 @@ NS_ASSUME_NONNULL_BEGIN
 + (UIColor *)deemphasizedTextColor;
 
 // UI element colors
++ (UIColor *)tintColor;
 + (UIColor *)scrollViewBackgroundColor;
 + (UIColor *)iconColor;
 + (UIColor *)borderColor;
 + (UIColor *)toolbarItemHighlightedColor;
 + (UIColor *)toolbarItemSelectedColor;
++ (UIColor *)hairlineColor;
 
 @end
 

+ 16 - 0
Classes/Utility/FLEXColor.m

@@ -69,6 +69,18 @@
 
 #pragma mark - UI Element Colors
 
++ (UIColor *)tintColor {
+    #if FLEX_AT_LEAST_IOS13_SDK
+    if (@available(iOS 13, *)) {
+        return [UIColor systemBlueColor];
+    } else {
+        return [UIApplication sharedApplication].keyWindow.tintColor;
+    }
+    #else
+    return [UIApplication sharedApplication].keyWindow.tintColor;
+    #endif
+}
+
 + (UIColor *)scrollViewBackgroundColor {
     return FLEXDynamicColor(
         systemGroupedBackgroundColor,
@@ -98,4 +110,8 @@
     );
 }
 
++ (UIColor *)hairlineColor {
+    return FLEXDynamicColor(systemGrayColor, grayColor);
+}
+
 @end

+ 4 - 0
Classes/Utility/FLEXRuntimeUtility.h

@@ -63,6 +63,10 @@ typedef NS_ENUM(char, FLEXTypeEncoding)
 /// Unwraps raw pointers to objects stored in NSValue, and re-boxes C strings into NSStrings.
 + (id)potentiallyUnwrapBoxedPointer:(id)returnedObjectOrNil type:(const FLEXTypeEncoding *)returnType;
 
+/// @return The class hierarchy for the given object or class,
+/// from the current class to the root-most class.
++ (NSArray<Class> *)classHierarchyOfObject:(id)objectOrClass;
+
 // Property Helpers
 + (NSString *)prettyNameForProperty:(objc_property_t)property;
 + (NSString *)typeEncodingForProperty:(objc_property_t)property;

+ 11 - 0
Classes/Utility/FLEXRuntimeUtility.m

@@ -88,6 +88,17 @@ const unsigned int kFLEXNumberOfImplicitArgs = 2;
     return returnedObjectOrNil;
 }
 
++ (NSArray<Class> *)classHierarchyOfObject:(id)objectOrClass
+{
+    NSMutableArray<Class> *superClasses = [NSMutableArray new];
+    id cls = [objectOrClass class];
+    do {
+        [superClasses addObject:cls];
+    } while ((cls = [cls superclass]));
+
+    return superClasses;
+}
+
 #pragma mark - Property Helpers (Public)
 
 + (NSString *)prettyNameForProperty:(objc_property_t)property

+ 4 - 0
Classes/Utility/FLEXUtility.h

@@ -54,6 +54,10 @@
 + (BOOL)isValidJSONData:(NSData *)data;
 + (NSData *)inflatedDataFromCompressedData:(NSData *)compressedData;
 
+/// Actually more like flatmap, but it seems like the objc way to allow returning nil to omit objects.
+/// So, return nil from the block to omit objects, and return an object to include it in the new array.
++ (NSArray *)map:(NSArray *)array block:(id(^)(id obj, NSUInteger idx))mapFunc;
+
 + (NSArray<UIWindow *> *)allWindows;
 
 + (void)alert:(NSString *)title message:(NSString *)message from:(UIViewController *)viewController;

+ 13 - 0
Classes/Utility/FLEXUtility.m

@@ -375,6 +375,19 @@
     return inflatedData;
 }
 
++ (NSArray *)map:(NSArray *)array block:(id(^)(id obj, NSUInteger idx))mapFunc
+{
+    NSMutableArray *map = [NSMutableArray new];
+    [array enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
+        id ret = mapFunc(obj, idx);
+        if (ret) {
+            [map addObject:ret];
+        }
+    }];
+
+    return map;
+}
+
 + (NSArray<UIWindow *> *)allWindows
 {
     BOOL includeInternalWindows = YES;

+ 12 - 5
Example/UICatalog.xcodeproj/project.pbxproj

@@ -366,7 +366,8 @@
 				ORGANIZATIONNAME = f;
 				TargetAttributes = {
 					5356823918F3656900BAAD62 = {
-						DevelopmentTeam = S6N2F22V2Z;
+						DevelopmentTeam = 7JHAJ3JSPW;
+						ProvisioningStyle = Automatic;
 					};
 				};
 			};
@@ -582,7 +583,9 @@
 			isa = XCBuildConfiguration;
 			buildSettings = {
 				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
-				DEVELOPMENT_TEAM = S6N2F22V2Z;
+				CODE_SIGN_IDENTITY = "iPhone Developer";
+				CODE_SIGN_STYLE = Automatic;
+				DEVELOPMENT_TEAM = 7JHAJ3JSPW;
 				EXCLUDED_SOURCE_FILE_NAMES = "";
 				FRAMEWORK_SEARCH_PATHS = (
 					"$(inherited)",
@@ -592,8 +595,9 @@
 				GCC_PREFIX_HEADER = "UICatalog/UICatalog-Prefix.pch";
 				INFOPLIST_FILE = "UICatalog/UICatalog-Info.plist";
 				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
-				PRODUCT_BUNDLE_IDENTIFIER = com.flipboard.FLEX.example;
+				PRODUCT_BUNDLE_IDENTIFIER = com.flipboard.FLEX.example2;
 				PRODUCT_NAME = "$(TARGET_NAME)";
+				PROVISIONING_PROFILE_SPECIFIER = "";
 				WRAPPER_EXTENSION = app;
 			};
 			name = Debug;
@@ -602,7 +606,9 @@
 			isa = XCBuildConfiguration;
 			buildSettings = {
 				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
-				DEVELOPMENT_TEAM = S6N2F22V2Z;
+				CODE_SIGN_IDENTITY = "iPhone Developer";
+				CODE_SIGN_STYLE = Automatic;
+				DEVELOPMENT_TEAM = 7JHAJ3JSPW;
 				EXCLUDED_SOURCE_FILE_NAMES = "FLEX*";
 				FRAMEWORK_SEARCH_PATHS = (
 					"$(inherited)",
@@ -612,8 +618,9 @@
 				GCC_PREFIX_HEADER = "UICatalog/UICatalog-Prefix.pch";
 				INFOPLIST_FILE = "UICatalog/UICatalog-Info.plist";
 				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
-				PRODUCT_BUNDLE_IDENTIFIER = com.flipboard.FLEX.example;
+				PRODUCT_BUNDLE_IDENTIFIER = com.flipboard.FLEX.example2;
 				PRODUCT_NAME = "$(TARGET_NAME)";
+				PROVISIONING_PROFILE_SPECIFIER = "";
 				WRAPPER_EXTENSION = app;
 			};
 			name = Release;

+ 49 - 0
FLEX.xcodeproj/project.pbxproj

@@ -176,6 +176,12 @@
 		C3511B9222D7C99E0057BAB7 /* FLEXTableViewSection.m in Sources */ = {isa = PBXBuildFile; fileRef = C3511B9022D7C99E0057BAB7 /* FLEXTableViewSection.m */; };
 		C37A0C93218BAC9600848CA7 /* FLEXObjcInternal.h in Headers */ = {isa = PBXBuildFile; fileRef = C37A0C91218BAC9600848CA7 /* FLEXObjcInternal.h */; };
 		C37A0C94218BAC9600848CA7 /* FLEXObjcInternal.mm in Sources */ = {isa = PBXBuildFile; fileRef = C37A0C92218BAC9600848CA7 /* FLEXObjcInternal.mm */; };
+		C387C87A22DFCD6A00750E58 /* FLEXCarouselCell.h in Headers */ = {isa = PBXBuildFile; fileRef = C387C87822DFCD6A00750E58 /* FLEXCarouselCell.h */; };
+		C387C87B22DFCD6A00750E58 /* FLEXCarouselCell.m in Sources */ = {isa = PBXBuildFile; fileRef = C387C87922DFCD6A00750E58 /* FLEXCarouselCell.m */; };
+		C387C87E22DFD71A00750E58 /* FLEXClassTreeViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = C387C87C22DFD71A00750E58 /* FLEXClassTreeViewController.h */; };
+		C387C87F22DFD71A00750E58 /* FLEXClassTreeViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C387C87D22DFD71A00750E58 /* FLEXClassTreeViewController.m */; };
+		C387C88322E0D24A00750E58 /* UIView+Layout.h in Headers */ = {isa = PBXBuildFile; fileRef = C387C88122E0D24A00750E58 /* UIView+Layout.h */; };
+		C387C88422E0D24A00750E58 /* UIView+Layout.m in Sources */ = {isa = PBXBuildFile; fileRef = C387C88222E0D24A00750E58 /* UIView+Layout.m */; };
 		C38DF0EA22CFE4370077B4AD /* FLEXTableViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = C38DF0E822CFE4370077B4AD /* FLEXTableViewController.h */; };
 		C38DF0EB22CFE4370077B4AD /* FLEXTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C38DF0E922CFE4370077B4AD /* FLEXTableViewController.m */; };
 		C395D6D921789BD800BEAD4D /* FLEXColorExplorerViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = C395D6D721789BD800BEAD4D /* FLEXColorExplorerViewController.h */; };
@@ -187,6 +193,8 @@
 		C3DB9F642107FC9600B46809 /* FLEXObjectRef.h in Headers */ = {isa = PBXBuildFile; fileRef = C3DB9F622107FC9600B46809 /* FLEXObjectRef.h */; };
 		C3DB9F652107FC9600B46809 /* FLEXObjectRef.m in Sources */ = {isa = PBXBuildFile; fileRef = C3DB9F632107FC9600B46809 /* FLEXObjectRef.m */; };
 		C3DC287C223ED5F200F48AA6 /* FLEXOSLogController.m in Sources */ = {isa = PBXBuildFile; fileRef = C34EE30721CB23CC00BD3A7C /* FLEXOSLogController.m */; };
+		C3EE76BF22DFC63600EC0AA0 /* FLEXScopeCarousel.h in Headers */ = {isa = PBXBuildFile; fileRef = C3EE76BD22DFC63600EC0AA0 /* FLEXScopeCarousel.h */; };
+		C3EE76C022DFC63600EC0AA0 /* FLEXScopeCarousel.m in Sources */ = {isa = PBXBuildFile; fileRef = C3EE76BE22DFC63600EC0AA0 /* FLEXScopeCarousel.m */; };
 		C3F31D3D2267D883003C991A /* FLEXSubtitleTableViewCell.h in Headers */ = {isa = PBXBuildFile; fileRef = C3F31D342267D883003C991A /* FLEXSubtitleTableViewCell.h */; };
 		C3F31D3E2267D883003C991A /* FLEXTableViewCell.h in Headers */ = {isa = PBXBuildFile; fileRef = C3F31D352267D883003C991A /* FLEXTableViewCell.h */; };
 		C3F31D3F2267D883003C991A /* FLEXMultilineTableViewCell.h in Headers */ = {isa = PBXBuildFile; fileRef = C3F31D362267D883003C991A /* FLEXMultilineTableViewCell.h */; };
@@ -383,6 +391,12 @@
 		C3511B9022D7C99E0057BAB7 /* FLEXTableViewSection.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FLEXTableViewSection.m; sourceTree = "<group>"; };
 		C37A0C91218BAC9600848CA7 /* FLEXObjcInternal.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FLEXObjcInternal.h; sourceTree = "<group>"; };
 		C37A0C92218BAC9600848CA7 /* FLEXObjcInternal.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = FLEXObjcInternal.mm; sourceTree = "<group>"; };
+		C387C87822DFCD6A00750E58 /* FLEXCarouselCell.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = FLEXCarouselCell.h; path = ../../../../../../System/Volumes/Data/Users/tanner/Repos/FLEX/Classes/Core/FLEXCarouselCell.h; sourceTree = "<group>"; };
+		C387C87922DFCD6A00750E58 /* FLEXCarouselCell.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = FLEXCarouselCell.m; path = ../../../../../../System/Volumes/Data/Users/tanner/Repos/FLEX/Classes/Core/FLEXCarouselCell.m; sourceTree = "<group>"; };
+		C387C87C22DFD71A00750E58 /* FLEXClassTreeViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = FLEXClassTreeViewController.h; path = ../../../../../../../System/Volumes/Data/Users/tanner/Repos/FLEX/Classes/ObjectExplorers/Controllers/FLEXClassTreeViewController.h; sourceTree = "<group>"; };
+		C387C87D22DFD71A00750E58 /* FLEXClassTreeViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = FLEXClassTreeViewController.m; path = ../../../../../../../System/Volumes/Data/Users/tanner/Repos/FLEX/Classes/ObjectExplorers/Controllers/FLEXClassTreeViewController.m; sourceTree = "<group>"; };
+		C387C88122E0D24A00750E58 /* UIView+Layout.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "UIView+Layout.h"; path = "../../../../../../../System/Volumes/Data/Users/tanner/Repos/FLEX/Classes/Utility/Categories/UIView+Layout.h"; sourceTree = "<group>"; };
+		C387C88222E0D24A00750E58 /* UIView+Layout.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = "UIView+Layout.m"; path = "../../../../../../../System/Volumes/Data/Users/tanner/Repos/FLEX/Classes/Utility/Categories/UIView+Layout.m"; 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>"; };
 		C395D6D721789BD800BEAD4D /* FLEXColorExplorerViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FLEXColorExplorerViewController.h; sourceTree = "<group>"; };
@@ -393,6 +407,8 @@
 		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>"; };
 		C3DB9F632107FC9600B46809 /* FLEXObjectRef.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FLEXObjectRef.m; sourceTree = "<group>"; };
+		C3EE76BD22DFC63600EC0AA0 /* FLEXScopeCarousel.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = FLEXScopeCarousel.h; path = ../../../../../../System/Volumes/Data/Users/tanner/Repos/FLEX/Classes/Core/FLEXScopeCarousel.h; sourceTree = "<group>"; };
+		C3EE76BE22DFC63600EC0AA0 /* FLEXScopeCarousel.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = FLEXScopeCarousel.m; path = ../../../../../../System/Volumes/Data/Users/tanner/Repos/FLEX/Classes/Core/FLEXScopeCarousel.m; sourceTree = "<group>"; };
 		C3F31D342267D883003C991A /* FLEXSubtitleTableViewCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FLEXSubtitleTableViewCell.h; sourceTree = "<group>"; };
 		C3F31D352267D883003C991A /* FLEXTableViewCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FLEXTableViewCell.h; sourceTree = "<group>"; };
 		C3F31D362267D883003C991A /* FLEXMultilineTableViewCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FLEXMultilineTableViewCell.h; sourceTree = "<group>"; };
@@ -495,6 +511,7 @@
 		3A4C94541B5B21410088C3F2 /* Utility */ = {
 			isa = PBXGroup;
 			children = (
+				C387C88022E0D22600750E58 /* Categories */,
 				3A4C94551B5B21410088C3F2 /* FLEXHeapEnumerator.h */,
 				3A4C94561B5B21410088C3F2 /* FLEXHeapEnumerator.m */,
 				3A4C94591B5B21410088C3F2 /* FLEXResources.h */,
@@ -751,6 +768,15 @@
 			path = Globals;
 			sourceTree = "<group>";
 		};
+		C387C88022E0D22600750E58 /* Categories */ = {
+			isa = PBXGroup;
+			children = (
+				C387C88122E0D24A00750E58 /* UIView+Layout.h */,
+				C387C88222E0D24A00750E58 /* UIView+Layout.m */,
+			);
+			path = Categories;
+			sourceTree = "<group>";
+		};
 		C38DF0E722CFE4140077B4AD /* Core */ = {
 			isa = PBXGroup;
 			children = (
@@ -758,6 +784,10 @@
 				C38DF0E922CFE4370077B4AD /* FLEXTableViewController.m */,
 				C3511B8F22D7C99E0057BAB7 /* FLEXTableViewSection.h */,
 				C3511B9022D7C99E0057BAB7 /* FLEXTableViewSection.m */,
+				C3EE76BD22DFC63600EC0AA0 /* FLEXScopeCarousel.h */,
+				C3EE76BE22DFC63600EC0AA0 /* FLEXScopeCarousel.m */,
+				C387C87822DFCD6A00750E58 /* FLEXCarouselCell.h */,
+				C387C87922DFCD6A00750E58 /* FLEXCarouselCell.m */,
 			);
 			path = Core;
 			sourceTree = "<group>";
@@ -782,6 +812,8 @@
 			children = (
 				3A4C944C1B5B21410088C3F2 /* FLEXObjectExplorerViewController.h */,
 				3A4C944D1B5B21410088C3F2 /* FLEXObjectExplorerViewController.m */,
+				C387C87C22DFD71A00750E58 /* FLEXClassTreeViewController.h */,
+				C387C87D22DFD71A00750E58 /* FLEXClassTreeViewController.m */,
 				3A4C943C1B5B21410088C3F2 /* FLEXArrayExplorerViewController.h */,
 				3A4C943D1B5B21410088C3F2 /* FLEXArrayExplorerViewController.m */,
 				3A4C943E1B5B21410088C3F2 /* FLEXClassExplorerViewController.h */,
@@ -836,6 +868,7 @@
 				3A4C94DB1B5B21410088C3F2 /* FLEXViewExplorerViewController.h in Headers */,
 				3A4C95321B5B21410088C3F2 /* FLEXSystemLogTableViewCell.h in Headers */,
 				3A4C94F91B5B21410088C3F2 /* FLEXArgumentInputNumberView.h in Headers */,
+				C387C87A22DFCD6A00750E58 /* FLEXCarouselCell.h in Headers */,
 				C3511B9122D7C99E0057BAB7 /* FLEXTableViewSection.h in Headers */,
 				3A4C953A1B5B21410088C3F2 /* FLEXNetworkSettingsTableViewController.h in Headers */,
 				3A4C94D11B5B21410088C3F2 /* FLEXLayerExplorerViewController.h in Headers */,
@@ -853,6 +886,7 @@
 				3A4C94F11B5B21410088C3F2 /* FLEXArgumentInputFontsPickerView.h in Headers */,
 				779B1ED61C0C4D7C001F5E49 /* FLEXTableContentViewController.h in Headers */,
 				3A4C94C91B5B21410088C3F2 /* FLEXDefaultsExplorerViewController.h in Headers */,
+				C387C87E22DFD71A00750E58 /* FLEXClassTreeViewController.h in Headers */,
 				3A4C95221B5B21410088C3F2 /* FLEXFileBrowserSearchOperation.h in Headers */,
 				C33E46AF223B02CD004BD0E6 /* FLEXASLLogController.h in Headers */,
 				C34EE30821CB23CC00BD3A7C /* FLEXOSLogController.h in Headers */,
@@ -884,11 +918,13 @@
 				3A4C950B1B5B21410088C3F2 /* FLEXFieldEditorViewController.h in Headers */,
 				C3F31D3D2267D883003C991A /* FLEXSubtitleTableViewCell.h in Headers */,
 				94A515251C4CA2080063292F /* FLEXExplorerToolbar.h in Headers */,
+				C387C88322E0D24A00750E58 /* UIView+Layout.h in Headers */,
 				3A4C953C1B5B21410088C3F2 /* FLEXNetworkTransaction.h in Headers */,
 				3A4C94D71B5B21410088C3F2 /* FLEXSetExplorerViewController.h in Headers */,
 				3A4C94D91B5B21410088C3F2 /* FLEXViewControllerExplorerViewController.h in Headers */,
 				3A4C952E1B5B21410088C3F2 /* FLEXWebViewController.h in Headers */,
 				3A4C95111B5B21410088C3F2 /* FLEXPropertyEditorViewController.h in Headers */,
+				C3EE76BF22DFC63600EC0AA0 /* FLEXScopeCarousel.h in Headers */,
 				3A4C94E91B5B21410088C3F2 /* FLEXHierarchyTableViewController.h in Headers */,
 				3A4C94F31B5B21410088C3F2 /* FLEXArgumentInputFontView.h in Headers */,
 				3A4C95261B5B21410088C3F2 /* FLEXGlobalsTableViewController.h in Headers */,
@@ -966,6 +1002,7 @@
 					};
 					3A4C941E1B5B20570088C3F2 = {
 						CreatedOnToolsVersion = 6.4;
+						ProvisioningStyle = Manual;
 					};
 				};
 			};
@@ -1023,6 +1060,7 @@
 				224D49A91C673AB5000EAB86 /* FLEXRealmDatabaseManager.m in Sources */,
 				C39ED92922D63F3200B5773A /* FLEXAddressExplorerCoordinator.m in Sources */,
 				2EF6B04D1D494BE50006BDA5 /* FLEXNetworkCurlLogger.m in Sources */,
+				C387C87F22DFD71A00750E58 /* FLEXClassTreeViewController.m in Sources */,
 				94A515201C4CA1F10063292F /* FLEXWindow.m in Sources */,
 				3A4C95121B5B21410088C3F2 /* FLEXPropertyEditorViewController.m in Sources */,
 				3A4C95391B5B21410088C3F2 /* FLEXNetworkRecorder.m in Sources */,
@@ -1042,6 +1080,7 @@
 				C3F31D412267D883003C991A /* FLEXMultilineTableViewCell.m in Sources */,
 				3A4C94EE1B5B21410088C3F2 /* FLEXArgumentInputColorView.m in Sources */,
 				C38DF0EB22CFE4370077B4AD /* FLEXTableViewController.m in Sources */,
+				C387C87B22DFCD6A00750E58 /* FLEXCarouselCell.m in Sources */,
 				3A4C94F01B5B21410088C3F2 /* FLEXArgumentInputDateView.m in Sources */,
 				3A4C94CA1B5B21410088C3F2 /* FLEXDefaultsExplorerViewController.m in Sources */,
 				3A4C94D01B5B21410088C3F2 /* FLEXImageExplorerViewController.m in Sources */,
@@ -1076,6 +1115,7 @@
 				3A4C94F81B5B21410088C3F2 /* FLEXArgumentInputNotSupportedView.m in Sources */,
 				3A4C95351B5B21410088C3F2 /* FLEXSystemLogTableViewController.m in Sources */,
 				3A4C95271B5B21410088C3F2 /* FLEXGlobalsTableViewController.m in Sources */,
+				C387C88422E0D24A00750E58 /* UIView+Layout.m in Sources */,
 				779B1ED31C0C4D7C001F5E49 /* FLEXTableColumnHeader.m in Sources */,
 				C37A0C94218BAC9600848CA7 /* FLEXObjcInternal.mm in Sources */,
 				3A4C94EA1B5B21410088C3F2 /* FLEXHierarchyTableViewController.m in Sources */,
@@ -1108,6 +1148,7 @@
 				94A5151E1C4CA1F10063292F /* FLEXExplorerViewController.m in Sources */,
 				3A4C95371B5B21410088C3F2 /* FLEXNetworkHistoryTableViewController.m in Sources */,
 				C3511B9222D7C99E0057BAB7 /* FLEXTableViewSection.m in Sources */,
+				C3EE76C022DFC63600EC0AA0 /* FLEXScopeCarousel.m in Sources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -1270,8 +1311,11 @@
 			isa = XCBuildConfiguration;
 			buildSettings = {
 				CLANG_WARN_STRICT_PROTOTYPES = NO;
+				CODE_SIGN_IDENTITY = "iPhone Developer";
 				"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
+				CODE_SIGN_STYLE = Manual;
 				DEFINES_MODULE = YES;
+				DEVELOPMENT_TEAM = "";
 				DYLIB_COMPATIBILITY_VERSION = 1;
 				DYLIB_CURRENT_VERSION = 1;
 				DYLIB_INSTALL_NAME_BASE = "@rpath";
@@ -1284,6 +1328,7 @@
 				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
 				PRODUCT_BUNDLE_IDENTIFIER = "com.flipboard.$(PRODUCT_NAME:rfc1034identifier)";
 				PRODUCT_NAME = "$(TARGET_NAME)";
+				PROVISIONING_PROFILE_SPECIFIER = "";
 				SKIP_INSTALL = YES;
 			};
 			name = Debug;
@@ -1292,8 +1337,11 @@
 			isa = XCBuildConfiguration;
 			buildSettings = {
 				CLANG_WARN_STRICT_PROTOTYPES = NO;
+				CODE_SIGN_IDENTITY = "iPhone Developer";
 				"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
+				CODE_SIGN_STYLE = Manual;
 				DEFINES_MODULE = YES;
+				DEVELOPMENT_TEAM = "";
 				DYLIB_COMPATIBILITY_VERSION = 1;
 				DYLIB_CURRENT_VERSION = 1;
 				DYLIB_INSTALL_NAME_BASE = "@rpath";
@@ -1306,6 +1354,7 @@
 				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
 				PRODUCT_BUNDLE_IDENTIFIER = "com.flipboard.$(PRODUCT_NAME:rfc1034identifier)";
 				PRODUCT_NAME = "$(TARGET_NAME)";
+				PROVISIONING_PROFILE_SPECIFIER = "";
 				SKIP_INSTALL = YES;
 			};
 			name = Release;