Compare commits

...

25 Commits

Author SHA1 Message Date
Tanner Bennett 8367342b25 Bump podspec version 2019-12-26 17:30:03 -06:00
Tanner Bennett 2df073a792 Keep search bar active between screens
Not sure why I ever added this code. Possibly to ignore a glitch on older versions of iOS? It works fine now on iOS 13, though.
2019-12-26 17:16:46 -06:00
Tanner Bennett 8236fc97cc Clean up cell reuse identifiers 2019-12-20 14:08:28 -06:00
Tanner Bennett 0364de36bd Add FLEXPluralString in FLEXUtility 2019-12-19 18:46:07 -06:00
Tanner Bennett 12195eb879 NSArray+Functional 2019-12-19 18:46:07 -06:00
Tanner Bennett acdc46c43f Add -subviews and -superview @properties to UIView 2019-12-19 18:46:07 -06:00
Tanner Bennett 52eed1b6f9 Add convenience init to some view controllers
Clean up libraries view controller
2019-12-19 18:46:07 -06:00
Tanner Bennett a91d1de9ad Change some private API accessors to use KVC
`performSelector:` can leak. The `KVC` methods are much safer and more reliable. If you pass it the exact method name, that will be called first, assuming there isn't a method with the same name but prefixed with `get`.
2019-12-19 18:46:07 -06:00
Tanner Bennett 492d2e49fe Fix bug in potentiallyUnwrapBoxedPointer: 2019-12-19 18:46:07 -06:00
Tanner Bennett 49bc439000 Delete unused class 2019-12-15 22:14:27 -06:00
Tanner Bennett 8c919cc26c Fix #359 a little better 2019-12-10 13:56:01 -06:00
Tanner Bennett 8e86ffccd6 Bump podspec version, add warning flag to podspec
Our podspec needs -Wno-unsupported-availability-guard for now
2019-12-09 15:34:20 -06:00
Tanner Bennett 228de102e7 Fix #359
Custom UIMenu callouts were not not appearing because the UITextEffectsWindow has a lower level than the FLEX window by default. Until a text field is activated, it would sit below the FLEX window, and custom callouts (copy, copy address) would not appear.
2019-12-09 14:54:41 -06:00
Tanner Bennett 37b5d1be2a Swipe to delete keychain items 2019-12-09 10:48:28 -06:00
renwei.chen ef8f866330 Bug fixes
- Fix root view controller global not working
- Fix crash in keychain viewer when viewing NSData
2019-12-09 10:48:28 -06:00
Tanner Bennett e862b81734 Update README.md 2019-12-05 17:26:10 -06:00
Tanner Bennett a3f66b3f87 Fix scope carousel not working properly on iOS 13 2019-12-02 23:13:38 -06:00
Tanner Bennett 3e12ad9887 Add explicit header titles to several screens
Some screens used the ugly "plain" table view or used a group table view with no title. Both look awful. This commit updates the cookies, keychain, libraries, and live objects screens to use grouped table views with a descriptive section header, like "5 cookies" or "123 of 456 objects, 30 MB"

Some screens, like live objects and keychain, would display the number of items in the navigation bar title. That has been removed; they now used a fixed title.

Also, rename keyChainItems → keychainItems
2019-11-27 17:00:35 -06:00
Tanner Bennett fe36b59b4c Restore search bar appearing initially
In c047fbc5 I made it so that the search bar would not appear initially—except of of course for the globals screen in iOS 13, where it shows by default no matter what. We did this by toggling navigationItem.hidesSearchBarWhenScrolling in `viewWillAppear:` and `viewDidAppear:`.

The method we were using to reveal the search bar initially while letting it hide as you scroll would cause a weird visual glitch where the search bar would stay pinned to the top of the screen as you scroll, but the navigation bar would be transparent.

I've managed to work around that bug by calling `setNeedsLayout` and `layoutIfNeeded` on `self.navigationController.view`
2019-11-27 16:48:32 -06:00
Tanner Bennett b735a69c1b Fix #248
Fixes view controllers and other objects not appearing in references
2019-11-26 18:08:47 -06:00
Tanner Bennett 7b1b6f9e24 iOS 13: Fix long-press copy menu in network pages 2019-11-26 12:09:30 -06:00
Tanner Bennett c047fbc581 Misc fixes
- Typo
- Remove <FLEX/...> import syntax added by #353 which causes compilation errors when building FLEX as a part of a different target (as opposed to building it as its own target)
- Fix iOS 13 glitch
2019-11-25 15:08:06 -06:00
Tanner Bennett 40239524d1 Fix #351 2019-11-25 12:13:10 -06:00
Dominik Pich 2f93050e2e Add Searchbar to Database Table Viewer Tables List #352
This Commit adds a Searchbar to Database Table Viewer Tables List.

For that it turns FLEXTableListViewController into a FLEXTableViewController sublcass, calls showSearchbar=YES and implements updateSearchResults
2019-11-19 13:33:09 -08:00
Tanner Bennett dc8ac6c195 UIView+Layout → UIVIew+FLEX_Layout 2019-11-13 20:23:20 -06:00
47 changed files with 776 additions and 329 deletions
+1 -1
View File
@@ -8,7 +8,7 @@
#import "FLEXCarouselCell.h"
#import "FLEXColor.h"
#import "UIView+Layout.h"
#import "UIView+FLEX_Layout.h"
@interface FLEXCarouselCell ()
@property (nonatomic, readonly) UILabel *titleLabel;
+1 -1
View File
@@ -9,7 +9,7 @@
#import "FLEXScopeCarousel.h"
#import "FLEXCarouselCell.h"
#import "FLEXColor.h"
#import "UIView+Layout.h"
#import "UIView+FLEX_Layout.h"
const CGFloat kCarouselItemSpacing = 0;
NSString * const kCarouselCellReuseIdentifier = @"kCarouselCellReuseIdentifier";
+4 -2
View File
@@ -43,7 +43,7 @@ extern CGFloat const kFLEXDebounceForExpensiveIO;
///
/// Setting this to YES will make the search bar appear whenever the view appears.
/// Otherwise, iOS will only show the search bar when you scroll up.
@property (nonatomic) BOOL hideSearchBarInitially;
@property (nonatomic) BOOL showSearchBarInitially;
/// nil unless showsSearchBar is set to YES.
///
@@ -71,7 +71,9 @@ extern CGFloat const kFLEXDebounceForExpensiveIO;
/// search becomes active and hide it when search is dismissed.
///
/// Do not set the showsCancelButton property on the searchController's
/// searchBar manually.
/// searchBar manually. Set this property after turning on showsSearchBar.
///
/// Does nothing pre-iOS 13, safe to call on any version.
@property (nonatomic) BOOL automaticallyShowsSearchBarCancelButton;
/// If using the scope bar, self.searchController.searchBar.selectedScopeButtonIndex.
+82 -26
View File
@@ -9,6 +9,7 @@
#import "FLEXTableViewController.h"
#import "FLEXScopeCarousel.h"
#import "FLEXTableView.h"
#import "FLEXUtility.h"
#import <objc/runtime.h>
@interface Block : NSObject
@@ -22,10 +23,11 @@ CGFloat const kFLEXDebounceForExpensiveIO = 0.5;
@interface FLEXTableViewController ()
@property (nonatomic) NSTimer *debounceTimer;
@property (nonatomic) BOOL didInitiallyRevealSearchBar;
@end
@implementation FLEXTableViewController
@synthesize automaticallyShowsSearchBarCancelButton = _automaticallyShowsSearchBarCancelButton;
#pragma mark - Public
@@ -46,7 +48,8 @@ CGFloat const kFLEXDebounceForExpensiveIO = 0.5;
self = [super initWithStyle:style];
if (self) {
self.searchBarDebounceInterval = kFLEXDebounceFast;
_searchBarDebounceInterval = kFLEXDebounceFast;
_showSearchBarInitially = YES;
}
return self;
@@ -63,8 +66,16 @@ CGFloat const kFLEXDebounceForExpensiveIO = 0.5;
self.searchController.delegate = (id)self;
self.searchController.dimsBackgroundDuringPresentation = NO;
self.searchController.hidesNavigationBarDuringPresentation = NO;
/// Not necessary in iOS 13; remove this when iOS 13 is the deployment target
/// Not necessary in iOS 13; remove this when iOS 13 is the minimum deployment target
self.searchController.searchBar.delegate = self;
self.automaticallyShowsSearchBarCancelButton = YES;
#if FLEX_AT_LEAST_IOS13_SDK
if (@available(iOS 13, *)) {
self.searchController.automaticallyShowsScopeBar = NO;
}
#endif
if (@available(iOS 11.0, *)) {
self.navigationItem.searchController = self.searchController;
@@ -113,16 +124,24 @@ CGFloat const kFLEXDebounceForExpensiveIO = 0.5;
return self.searchController.searchBar.text;
}
- (void)setAutomaticallyShowsSearchBarCancelButton:(BOOL)autoShowCancel {
- (BOOL)automaticallyShowsSearchBarCancelButton {
#if FLEX_AT_LEAST_IOS13_SDK
if (@available(iOS 13.0, *)) {
self.searchController.automaticallyShowsCancelButton = autoShowCancel;
} else {
_automaticallyShowsSearchBarCancelButton = autoShowCancel;
if (@available(iOS 13, *)) {
return self.searchController.automaticallyShowsCancelButton;
}
#else
_automaticallyShowsSearchBarCancelButton = autoShowCancel;
#endif
return _automaticallyShowsSearchBarCancelButton;
}
- (void)setAutomaticallyShowsSearchBarCancelButton:(BOOL)value {
#if FLEX_AT_LEAST_IOS13_SDK
if (@available(iOS 13, *)) {
self.searchController.automaticallyShowsCancelButton = value;
}
#endif
_automaticallyShowsSearchBarCancelButton = value;
}
- (void)updateSearchResults:(NSString *)newText { }
@@ -142,33 +161,57 @@ CGFloat const kFLEXDebounceForExpensiveIO = 0.5;
[super viewDidLoad];
self.tableView.keyboardDismissMode = UIScrollViewKeyboardDismissModeOnDrag;
// On iOS 13, the root view controller shows it's search bar no matter what.
// Turning this off avoids some weird flash the navigation bar does when we
// toggle navigationItem.hidesSearchBarWhenScrolling on and off. The flash
// will still happen on subsequent view controllers, but we can at least
// avoid it for the root view controller
if (@available(iOS 13, *)) {
if (self.navigationController.viewControllers.firstObject == self) {
_showSearchBarInitially = NO;
}
}
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
// Make the search bar re-appear instead of hiding
if (@available(iOS 11.0, *)) if (!self.hideSearchBarInitially) {
self.navigationItem.hidesSearchBarWhenScrolling = NO;
// When going back, make the search bar reappear instead of hiding
if (@available(iOS 11.0, *)) {
if ((self.pinSearchBar || self.showSearchBarInitially) && !self.didInitiallyRevealSearchBar) {
self.navigationItem.hidesSearchBarWhenScrolling = NO;
}
}
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
// Allow scrolling to collapse the search bar,
// only if we don't want it pinned
if (@available(iOS 11.0, *)) if (!self.hideSearchBarInitially) {
self.navigationItem.hidesSearchBarWhenScrolling = !self.pinSearchBar;
// Allow scrolling to collapse the search bar, only if we don't want it pinned
if (@available(iOS 11.0, *)) {
if (self.showSearchBarInitially && !self.pinSearchBar && !self.didInitiallyRevealSearchBar) {
// All this mumbo jumbo is necessary to work around a bug in iOS 13 up to 13.2
// wherein quickly toggling navigationItem.hidesSearchBarWhenScrolling to make
// the search bar appear initially results in a bugged search bar that
// becomes transparent and floats over the screen as you scroll
[UIView animateWithDuration:0.2 animations:^{
self.navigationItem.hidesSearchBarWhenScrolling = YES;
[self.navigationController.view setNeedsLayout];
[self.navigationController.view layoutIfNeeded];
}];
}
}
// We only want to reveal the search bar when the view controller first appears.
self.didInitiallyRevealSearchBar = YES;
}
- (void)viewDidDisappear:(BOOL)animated {
[super viewDidDisappear:animated];
if (self.searchController.active) {
self.searchController.active = NO;
}
- (void)willMoveToParentViewController:(UIViewController *)parent {
[super willMoveToParentViewController:parent];
// Reset this since we are re-appearing under a new
// parent view controller and need to show it again
self.didInitiallyRevealSearchBar = NO;
}
#pragma mark - Private
@@ -208,13 +251,15 @@ CGFloat const kFLEXDebounceForExpensiveIO = 0.5;
#pragma mark UISearchControllerDelegate
- (void)willPresentSearchController:(UISearchController *)searchController {
if (self.automaticallyShowsSearchBarCancelButton) {
// Manually show cancel button for < iOS 13
if (!@available(iOS 13, *) && self.automaticallyShowsSearchBarCancelButton) {
[searchController.searchBar setShowsCancelButton:YES animated:YES];
}
}
- (void)willDismissSearchController:(UISearchController *)searchController {
if (self.automaticallyShowsSearchBarCancelButton) {
// Manually hide cancel button for < iOS 13
if (!@available(iOS 13, *) && self.automaticallyShowsSearchBarCancelButton) {
[searchController.searchBar setShowsCancelButton:NO animated:YES];
}
}
@@ -226,4 +271,15 @@ CGFloat const kFLEXDebounceForExpensiveIO = 0.5;
[self updateSearchResultsForSearchController:self.searchController];
}
#pragma mark Table view
/// Not having a title in the first section looks weird with a rounded-corner table view style
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
if (@available(iOS 13, *)) {
return @" "; // For inset grouped style
}
return nil; // For plain/gropued style
}
@end
@@ -131,11 +131,9 @@ typedef NS_ENUM(NSUInteger, FLEXExplorerMode) {
NSString *viewControllerSelectorString = [@[@"_vie", @"wContro", @"llerFor", @"Supported", @"Interface", @"Orientations"] componentsJoinedByString:@""];
SEL viewControllerSelector = NSSelectorFromString(viewControllerSelectorString);
if ([viewController respondsToSelector:viewControllerSelector]) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
viewController = [viewController performSelector:viewControllerSelector];
#pragma clang diagnostic pop
viewController = [viewController valueForKey:viewControllerSelectorString];
}
return viewController;
}
@@ -148,7 +146,8 @@ typedef NS_ENUM(NSUInteger, FLEXExplorerMode) {
}
// The UIViewController docs state that this method must not return zero.
// If we weren't able to get a valid value for the supported interface orientations, default to all supported.
// If we weren't able to get a valid value for the supported interface
// orientations, default to all supported.
if (supportedOrientations == 0) {
supportedOrientations = UIInterfaceOrientationMaskAll;
}
@@ -785,11 +784,25 @@ typedef NS_ENUM(NSUInteger, FLEXExplorerMode) {
- (void)makeKeyAndPresentViewController:(UIViewController *)viewController animated:(BOOL)animated completion:(void (^)(void))completion
{
// Save the current key window so we can restore it following dismissal.
self.previousKeyWindow = [UIApplication.sharedApplication keyWindow];
self.previousKeyWindow = UIApplication.sharedApplication.keyWindow;
// Make our window key to correctly handle input.
[self.view.window makeKeyWindow];
// Fix for iOS 13, regarding custom UIMenu callouts not appearing because
// the UITextEffectsWindow has a lower level than the FLEX window by default
// until a text field is activated, bringing it above the FLEX window.
if (@available(iOS 13, *)) {
for (UIWindow *window in UIApplication.sharedApplication.windows) {
if ([window isKindOfClass:NSClassFromString(@"UITextEffectsWindow")]) {
if (window.windowLevel <= self.view.window.windowLevel) {
window.windowLevel = self.view.window.windowLevel + 1;
break;
}
}
}
}
// Move the status bar on top of FLEX so we can get scroll to top behavior for taps.
[[self statusWindow] setWindowLevel:self.view.window.windowLevel + 1.0];
@@ -6,9 +6,9 @@
// Copyright © 2015年 Peng Tao. All rights reserved.
//
#import <UIKit/UIKit.h>
#import "FLEXTableViewController.h"
@interface FLEXTableListViewController : UITableViewController
@interface FLEXTableListViewController : FLEXTableViewController
+ (BOOL)supportsExtension:(NSString *)extension;
- (instancetype)initWithPath:(NSString *)path;
@@ -21,6 +21,7 @@
}
@property (nonatomic) NSArray<NSString *> *tables;
@property (nonatomic) NSArray<NSString *> *filteredTables;
+ (NSArray<NSString *> *)supportedSQLiteExtensions;
+ (NSArray<NSString *> *)supportedRealmExtensions;
@@ -67,11 +68,35 @@
[array addObject:columnName];
}
self.tables = array;
self.filteredTables = array;
}
- (void)viewDidLoad
{
[super viewDidLoad];
self.showsSearchBar = YES;
}
#pragma mark - Search bar
- (void)updateSearchResults:(NSString *)searchText
{
if (searchText.length > 0) {
NSPredicate *searchPredicate = [NSPredicate predicateWithFormat:@"SELF CONTAINS[cd] %@", searchText];
self.filteredTables = [self.tables filteredArrayUsingPredicate:searchPredicate];
} else {
self.filteredTables = self.tables;
}
[self.tableView reloadData];
}
#pragma mark - Table View Data Source
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return self.tables.count;
return self.filteredTables.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
@@ -81,7 +106,7 @@
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:@"FLEXTableListViewControllerCell"];
}
cell.textLabel.text = self.tables[indexPath.row];
cell.textLabel.text = self.filteredTables[indexPath.row];
return cell;
}
@@ -90,19 +115,20 @@
{
FLEXTableContentViewController *contentViewController = [FLEXTableContentViewController new];
contentViewController.contentsArray = [_dbm queryAllDataWithTableName:self.tables[indexPath.row]];
contentViewController.columnsArray = [_dbm queryAllColumnsWithTableName:self.tables[indexPath.row]];
contentViewController.contentsArray = [_dbm queryAllDataWithTableName:self.filteredTables[indexPath.row]];
contentViewController.columnsArray = [_dbm queryAllColumnsWithTableName:self.filteredTables[indexPath.row]];
contentViewController.title = self.tables[indexPath.row];
contentViewController.title = self.filteredTables[indexPath.row];
[self.navigationController pushViewController:contentViewController animated:YES];
}
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
{
return [NSString stringWithFormat:@"%lu tables", (unsigned long)self.tables.count];
return [NSString stringWithFormat:@"Tables (%lu)", (unsigned long)self.filteredTables.count];
}
#pragma mark - FLEXTableListViewController
+ (BOOL)supportsExtension:(NSString *)extension
{
extension = extension.lowercaseString;
@@ -11,6 +11,6 @@
@interface FLEXClassesTableViewController : FLEXTableViewController <FLEXGlobalsEntry>
@property (nonatomic, copy) NSString *binaryImageName;
+ (instancetype)binaryImageName:(NSString *)binaryImageName;
@end
@@ -14,33 +14,56 @@
@interface FLEXClassesTableViewController ()
@property (nonatomic) NSArray<NSString *> *classNames;
@property (nonatomic) NSArray<NSString *> *filteredClassNames;
@property (nonatomic, copy) NSArray<NSString *> *classNames;
@property (nonatomic, copy) NSArray<NSString *> *filteredClassNames;
@property (nonatomic, copy) NSString *binaryImageName;
@end
@implementation FLEXClassesTableViewController
#pragma mark - Initialization
+ (instancetype)binaryImageName:(NSString *)binaryImageName
{
return [[self alloc] initWithBinaryImageName:binaryImageName];
}
- (id)initWithBinaryImageName:(NSString *)binaryImageName
{
NSParameterAssert(binaryImageName);
self = [super init];
if (self) {
self.binaryImageName = binaryImageName;
[self loadClassNames];
}
return self;
}
#pragma mark - Internal
- (void)viewDidLoad
{
[super viewDidLoad];
self.showsSearchBar = YES;
[self updateTitle];
}
- (void)setBinaryImageName:(NSString *)binaryImageName
- (void)updateTitle
{
if (![_binaryImageName isEqual:binaryImageName]) {
_binaryImageName = binaryImageName;
[self loadClassNames];
[self updateTitle];
}
NSString *shortImageName = self.binaryImageName.lastPathComponent;
self.title = [NSString stringWithFormat:@"%@ Classes (%lu)",
shortImageName, (unsigned long)self.filteredClassNames.count
];
}
- (void)setClassNames:(NSArray<NSString *> *)classNames
{
_classNames = classNames;
self.filteredClassNames = classNames;
_classNames = self.filteredClassNames = classNames.copy;
}
- (void)loadClassNames
@@ -61,12 +84,6 @@
}
}
- (void)updateTitle
{
NSString *shortImageName = self.binaryImageName.lastPathComponent;
self.title = [NSString stringWithFormat:@"%@ Classes (%lu)", shortImageName, (unsigned long)self.filteredClassNames.count];
}
#pragma mark - FLEXGlobalsEntry
@@ -75,10 +92,7 @@
}
+ (UIViewController *)globalsEntryViewController:(FLEXGlobalsRow)row {
FLEXClassesTableViewController *classesViewController = [self new];
classesViewController.binaryImageName = [FLEXUtility applicationImageName];
return classesViewController;
return [self binaryImageName:[FLEXUtility applicationImageName]];
}
@@ -88,7 +102,7 @@
{
if (searchText.length > 0) {
NSPredicate *searchPredicate = [NSPredicate predicateWithFormat:@"SELF CONTAINS[cd] %@", searchText];
self.filteredClassNames = [self.classNames filteredArrayUsingPredicate:searchPredicate];
self.filteredClassNames = [self.classNames filteredArrayUsingPredicate:searchPredicate].reverseObjectEnumerator.allObjects;
} else {
self.filteredClassNames = self.classNames;
}
@@ -7,7 +7,8 @@
//
#import "FLEXGlobalsEntry.h"
#import "FLEXTableViewController.h"
@interface FLEXCookiesTableViewController : UITableViewController <FLEXGlobalsEntry>
@interface FLEXCookiesTableViewController : FLEXTableViewController <FLEXGlobalsEntry>
@end
@@ -11,33 +11,37 @@
#import "FLEXUtility.h"
@interface FLEXCookiesTableViewController ()
@property (nonatomic) NSArray<NSHTTPCookie *> *cookies;
@property (nonatomic, readonly) NSArray<NSHTTPCookie *> *cookies;
@property (nonatomic) NSString *headerTitle;
@end
@implementation FLEXCookiesTableViewController
- (id)initWithStyle:(UITableViewStyle)style {
self = [super initWithStyle:style];
if (self) {
self.title = @"Cookies";
- (void)viewDidLoad {
[super viewDidLoad];
NSSortDescriptor *nameSortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"name" ascending:YES selector:@selector(caseInsensitiveCompare:)];
_cookies = [NSHTTPCookieStorage.sharedHTTPCookieStorage.cookies sortedArrayUsingDescriptors:@[nameSortDescriptor]];
}
return self;
NSSortDescriptor *nameSortDescriptor = [[NSSortDescriptor alloc]
initWithKey:@"name" ascending:YES selector:@selector(caseInsensitiveCompare:)
];
_cookies = [NSHTTPCookieStorage.sharedHTTPCookieStorage.cookies
sortedArrayUsingDescriptors:@[nameSortDescriptor]
];
self.title = @"Cookies";
[self updateHeaderTitle];
}
- (void)updateHeaderTitle {
self.headerTitle = [NSString stringWithFormat:@"%@ cookies", @(self.cookies.count)];
// TODO update header title here when we can search cookies
}
- (NSHTTPCookie *)cookieForRowAtIndexPath:(NSIndexPath *)indexPath {
return self.cookies[indexPath.row];
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
#pragma mark - Table View Data Source
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return self.cookies.count;
@@ -61,6 +65,13 @@
return cell;
}
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
return self.headerTitle;
}
#pragma mark - Table View Delegate
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
NSHTTPCookie *cookie = [self cookieForRowAtIndexPath:indexPath];
UIViewController *cookieViewController = (UIViewController *)[FLEXObjectExplorerFactory explorerViewControllerForObject:cookie];
@@ -68,6 +79,7 @@
[self.navigationController pushViewController:cookieViewController animated:YES];
}
#pragma mark - FLEXGlobalsEntry
+ (NSString *)globalsEntryTitle:(FLEXGlobalsRow)row {
@@ -9,13 +9,14 @@
#import "FLEXLibrariesTableViewController.h"
#import "FLEXUtility.h"
#import "FLEXClassesTableViewController.h"
#import "FLEXClassExplorerViewController.h"
#import "FLEXObjectExplorerFactory.h"
#import <objc/runtime.h>
@interface FLEXLibrariesTableViewController ()
@property (nonatomic) NSArray<NSString *> *imageNames;
@property (nonatomic) NSArray<NSString *> *filteredImageNames;
@property (nonatomic) NSString *headerTitle;
@property (nonatomic) Class foundClass;
@@ -37,6 +38,22 @@
[super viewDidLoad];
self.showsSearchBar = YES;
[self updateHeaderTitle];
}
- (void)updateHeaderTitle
{
if (self.foundClass) {
self.headerTitle = @"Looking for this?";
} else if (self.imageNames.count == self.filteredImageNames.count) {
// Unfiltered
self.headerTitle = [NSString stringWithFormat:@"%@ libraries", @(self.imageNames.count)];
} else {
self.headerTitle = [NSString
stringWithFormat:@"%@ of %@ libraries",
@(self.filteredImageNames.count), @(self.imageNames.count)
];
}
}
@@ -120,17 +137,13 @@
}
self.foundClass = NSClassFromString(searchText);
[self updateHeaderTitle];
[self.tableView reloadData];
}
#pragma mark - Table View Data Source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return self.filteredImageNames.count + (self.foundClass ? 1 : 0);
@@ -162,19 +175,24 @@
return cell;
}
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
{
return self.headerTitle;
}
#pragma mark - Table View Delegate
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if (indexPath.row == 0 && self.foundClass) {
FLEXClassExplorerViewController *objectExplorer = [FLEXClassExplorerViewController new];
objectExplorer.object = self.foundClass;
[self.navigationController pushViewController:objectExplorer animated:YES];
[self.navigationController pushViewController:[FLEXObjectExplorerFactory
explorerViewControllerForObject:self.foundClass
] animated:YES];
} else {
FLEXClassesTableViewController *classesViewController = [FLEXClassesTableViewController new];
classesViewController.binaryImageName = self.filteredImageNames[self.foundClass ? indexPath.row-1 : indexPath.row];
[self.navigationController pushViewController:classesViewController animated:YES];
[self.navigationController pushViewController:[FLEXClassesTableViewController
binaryImageName:self.filteredImageNames[self.foundClass ? 0 : indexPath.row]
] animated:YES];
}
}
@@ -11,6 +11,7 @@
#import "FLEXInstancesTableViewController.h"
#import "FLEXUtility.h"
#import "FLEXScopeCarousel.h"
#import "FLEXTableView.h"
#import <objc/runtime.h>
static const NSInteger kFLEXLiveObjectsSortAlphabeticallyIndex = 0;
@@ -23,15 +24,22 @@ static const NSInteger kFLEXLiveObjectsSortBySizeIndex = 2;
@property (nonatomic) NSDictionary<NSString *, NSNumber *> *instanceSizesForClassNames;
@property (nonatomic, readonly) NSArray<NSString *> *allClassNames;
@property (nonatomic) NSArray<NSString *> *filteredClassNames;
@property (nonatomic) NSString *headerTitle;
@end
@implementation FLEXLiveObjectsTableViewController
- (void)loadView
{
self.tableView = [FLEXTableView flexDefaultTableView];
}
- (void)viewDidLoad
{
[super viewDidLoad];
// self.title = @"Live Objects";
self.showsSearchBar = YES;
self.searchBarDebounceInterval = kFLEXDebounceInstant;
self.showsCarousel = YES;
@@ -96,35 +104,42 @@ static const NSInteger kFLEXLiveObjectsSortBySizeIndex = 2;
[self.refreshControl endRefreshing];
}
- (void)updateTitle
- (void)updateHeaderTitle
{
NSString *title = @"Live Objects";
NSUInteger totalCount = 0;
NSUInteger totalSize = 0;
for (NSString *className in self.allClassNames) {
NSUInteger count = [self.instanceCountsForClassNames[className] unsignedIntegerValue];
NSUInteger count = self.instanceCountsForClassNames[className].unsignedIntegerValue;
totalCount += count;
totalSize += count * [self.instanceSizesForClassNames[className] unsignedIntegerValue];
totalSize += count * self.instanceSizesForClassNames[className].unsignedIntegerValue;
}
NSUInteger filteredCount = 0;
NSUInteger filteredSize = 0;
for (NSString *className in self.filteredClassNames) {
NSUInteger count = [self.instanceCountsForClassNames[className] unsignedIntegerValue];
NSUInteger count = self.instanceCountsForClassNames[className].unsignedIntegerValue;
filteredCount += count;
filteredSize += count * [self.instanceSizesForClassNames[className] unsignedIntegerValue];
filteredSize += count * self.instanceSizesForClassNames[className].unsignedIntegerValue;
}
if (filteredCount == totalCount) {
// Unfiltered
title = [title stringByAppendingFormat:@" (%lu, %@)", (unsigned long)totalCount,
[NSByteCountFormatter stringFromByteCount:totalSize countStyle:NSByteCountFormatterCountStyleFile]];
self.headerTitle = [NSString
stringWithFormat:@"%@ objects, %@",
@(totalCount), [NSByteCountFormatter
stringFromByteCount:totalSize
countStyle:NSByteCountFormatterCountStyleFile
]
];
} else {
title = [title stringByAppendingFormat:@" (filtered, %lu, %@)", (unsigned long)filteredCount,
[NSByteCountFormatter stringFromByteCount:filteredSize countStyle:NSByteCountFormatterCountStyleFile]];
self.headerTitle = [NSString
stringWithFormat:@"%@ of %@ objects, %@",
@(filteredCount), @(totalCount), [NSByteCountFormatter
stringFromByteCount:filteredSize
countStyle:NSByteCountFormatterCountStyleFile
]
];
}
self.title = title;
}
@@ -135,7 +150,10 @@ static const NSInteger kFLEXLiveObjectsSortBySizeIndex = 2;
}
+ (UIViewController *)globalsEntryViewController:(FLEXGlobalsRow)row {
return [self new];
FLEXLiveObjectsTableViewController *liveObjectsViewController = [self new];
liveObjectsViewController.title = [self globalsEntryTitle:row];
return liveObjectsViewController;
}
@@ -172,7 +190,7 @@ static const NSInteger kFLEXLiveObjectsSortBySizeIndex = 2;
}];
}
[self updateTitle];
[self updateHeaderTitle];
[self.tableView reloadData];
}
@@ -189,26 +207,34 @@ static const NSInteger kFLEXLiveObjectsSortBySizeIndex = 2;
return self.filteredClassNames.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
- (UITableViewCell *)tableView:(__kindof UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = @"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (!cell) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
cell.textLabel.font = [FLEXUtility defaultTableViewCellLabelFont];
}
UITableViewCell *cell = [tableView
dequeueReusableCellWithIdentifier:kFLEXDefaultCell
forIndexPath:indexPath
];
NSString *className = self.filteredClassNames[indexPath.row];
NSNumber *count = self.instanceCountsForClassNames[className];
NSNumber *size = self.instanceSizesForClassNames[className];
unsigned long totalSize = count.unsignedIntegerValue * size.unsignedIntegerValue;
cell.textLabel.text = [NSString stringWithFormat:@"%@ (%ld, %@)", className, (long)[count integerValue],
[NSByteCountFormatter stringFromByteCount:totalSize countStyle:NSByteCountFormatterCountStyleFile]];
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
cell.textLabel.text = [NSString stringWithFormat:@"%@ (%ld, %@)",
className, (long)[count integerValue],
[NSByteCountFormatter
stringFromByteCount:totalSize
countStyle:NSByteCountFormatterCountStyleFile
]
];
return cell;
}
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
{
return self.headerTitle;
}
#pragma mark - Table view delegate
@@ -12,6 +12,7 @@
@interface FLEXFileBrowserTableViewController : FLEXTableViewController <FLEXGlobalsEntry>
+ (instancetype)path:(NSString *)path;
- (id)initWithPath:(NSString *)path;
@end
@@ -31,6 +31,11 @@
@implementation FLEXFileBrowserTableViewController
+ (instancetype)path:(NSString *)path
{
return [[self alloc] initWithPath:path];
}
- (id)init
{
return [self initWithPath:NSHomeDirectory()];
@@ -310,7 +315,7 @@
- (UIContextMenuConfiguration *)tableView:(UITableView *)tableView contextMenuConfigurationForRowAtIndexPath:(NSIndexPath *)indexPath point:(CGPoint)point __IOS_AVAILABLE(13.0)
{
__weak typeof(self) weakSelf = self;
return [UIContextMenuConfiguration configurationWithIdentifier:NSUUID.UUID.UUIDString
return [UIContextMenuConfiguration configurationWithIdentifier:nil
previewProvider:nil
actionProvider:^UIMenu * _Nullable(NSArray<UIMenuElement *> * _Nonnull suggestedActions) {
UITableViewCell * const cell = [tableView cellForRowAtIndexPath:indexPath];
@@ -162,7 +162,6 @@ static __weak UIWindow *s_applicationWindow = nil;
self.title = @"💪 FLEX";
self.showsSearchBar = YES;
self.hideSearchBarInitially = YES;
self.searchBarDebounceInterval = kFLEXDebounceInstant;
// Table view data
@@ -179,7 +178,11 @@ static __weak UIWindow *s_applicationWindow = nil;
}
// Done button
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone target:self action:@selector(donePressed:)];
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc]
initWithBarButtonSystemItem:UIBarButtonSystemItemDone
target:self
action:@selector(donePressed:)
];
}
#pragma mark - Search Bar
@@ -10,10 +10,12 @@
#import "FLEXKeychainQuery.h"
#import "FLEXKeychainTableViewController.h"
#import "FLEXUtility.h"
#import "UIPasteboard+FLEX.h"
@interface FLEXKeychainTableViewController ()
@property (nonatomic) NSArray<NSDictionary *> *keyChainItems;
@property (nonatomic) NSMutableArray<NSDictionary *> *keychainItems;
@property (nonatomic) NSString *headerTitle;
@end
@@ -32,15 +34,50 @@
],
];
[self refreshKeychainItems];
[self refreshkeychainItems];
[self updateHeaderTitle];
}
- (void)refreshKeychainItems
- (void)refreshkeychainItems
{
self.keyChainItems = [FLEXKeychain allAccounts];
self.title = [NSString stringWithFormat:@"🔑 Keychain Items (%lu)", (unsigned long)self.keyChainItems.count];
self.keychainItems = [FLEXKeychain allAccounts].mutableCopy;
}
- (void)updateHeaderTitle
{
self.headerTitle = [NSString stringWithFormat:@"%@ items", @(self.keychainItems.count)];
}
- (FLEXKeychainQuery *)queryForItemAtIndex:(NSInteger)idx
{
NSDictionary *item = self.keychainItems[idx];
FLEXKeychainQuery *query = [FLEXKeychainQuery new];
query.service = item[kFLEXKeychainWhereKey];
query.account = item[kFLEXKeychainAccountKey];
[query fetch:nil];
return query;
}
- (void)deleteItem:(NSDictionary *)item
{
NSError *error = nil;
BOOL success = [FLEXKeychain
deletePasswordForService:item[kFLEXKeychainWhereKey]
account:item[kFLEXKeychainAccountKey]
error:&error
];
if (!success) {
[FLEXAlert makeAlert:^(FLEXAlert *make) {
make.title(@"Error Deleting Item");
make.message(error.localizedDescription);
} showFrom:self];
}
}
#pragma mark Buttons
- (void)trashPressed
@@ -50,22 +87,11 @@
make.message(@"This will remove all keychain items for this app.\n");
make.message(@"This action cannot be undone. Are you sure?");
make.button(@"Yes, clear the keychain").destructiveStyle().handler(^(NSArray *strings) {
for (id account in self.keyChainItems) {
FLEXKeychainQuery *query = [FLEXKeychainQuery new];
query.service = account[kFLEXKeychainWhereKey];
query.account = account[kFLEXKeychainAccountKey];
// Delete item or display error
NSError *error = nil;
if (![query deleteItem:&error]) {
[FLEXAlert makeAlert:^(FLEXAlert *make) {
make.title(@"Error Deleting Item");
make.message(error.localizedDescription);
} showFrom:self];
}
for (id account in self.keychainItems) {
[self deleteItem:account];
}
[self refreshKeychainItems];
[self refreshkeychainItems];
[self.tableView reloadData];
});
make.button(@"Cancel").cancelStyle();
@@ -87,14 +113,13 @@
[FLEXAlert showAlert:@"Error" message:error.localizedDescription from:self];
}
[self refreshKeychainItems];
[self refreshkeychainItems];
[self.tableView reloadData];
});
} showFrom:self];
}
#pragma mark - FLEXGlobalsEntry
+ (NSString *)globalsEntryTitle:(FLEXGlobalsRow)row
@@ -102,9 +127,11 @@
return @"🔑 Keychain";
}
+ (UIViewController *)globalsEntryViewController:(FLEXGlobalsRow)row
{
return [self new];
+ (UIViewController *)globalsEntryViewController:(FLEXGlobalsRow)row {
FLEXKeychainTableViewController *viewController = [self new];
viewController.title = [self globalsEntryTitle:row];
return viewController;
}
@@ -112,7 +139,7 @@
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return self.keyChainItems.count;
return self.keychainItems.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
@@ -125,24 +152,42 @@
cell.textLabel.font = [FLEXUtility defaultTableViewCellLabelFont];
}
NSDictionary *item = self.keyChainItems[indexPath.row];
cell.textLabel.text = item[kFLEXKeychainAccountKey];
NSDictionary *item = self.keychainItems[indexPath.row];
id account = item[kFLEXKeychainAccountKey];
if ([account isKindOfClass:[NSString class]]) {
cell.textLabel.text = account;
} else {
cell.textLabel.text = [NSString stringWithFormat:
@"[%@]\n\n%@",
NSStringFromClass([account class]),
[account description]
];
}
return cell;
}
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
{
return self.headerTitle;
}
- (void)tableView:(UITableView *)tv commitEditingStyle:(UITableViewCellEditingStyle)style forRowAtIndexPath:(NSIndexPath *)ip
{
if (style == UITableViewCellEditingStyleDelete) {
[self deleteItem:self.keychainItems[ip.row]];
[self.keychainItems removeObjectAtIndex:ip.row];
[tv deleteRowsAtIndexPaths:@[ip] withRowAnimation:UITableViewRowAnimationAutomatic];
}
}
#pragma mark - Table View Delegate
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
NSDictionary *item = self.keyChainItems[indexPath.row];
FLEXKeychainQuery *query = [self queryForItemAtIndex:indexPath.row];
FLEXKeychainQuery *query = [FLEXKeychainQuery new];
query.service = item[kFLEXKeychainWhereKey];
query.account = item[kFLEXKeychainAccountKey];
[query fetch:nil];
[FLEXAlert makeAlert:^(FLEXAlert *make) {
make.title(query.service);
make.message(@"Service: ").message(query.service);
@@ -150,16 +195,19 @@
make.message(@"\nPassword: ").message(query.password);
make.button(@"Copy Service").handler(^(NSArray<NSString *> *strings) {
UIPasteboard.generalPasteboard.string = query.service;
[UIPasteboard.generalPasteboard flex_copy:query.service];
});
make.button(@"Copy Account").handler(^(NSArray<NSString *> *strings) {
UIPasteboard.generalPasteboard.string = query.account;
[UIPasteboard.generalPasteboard flex_copy:query.account];
});
make.button(@"Copy Password").handler(^(NSArray<NSString *> *strings) {
UIPasteboard.generalPasteboard.string = query.password;
[UIPasteboard.generalPasteboard flex_copy:query.password];
});
make.button(@"Dismiss").cancelStyle();
} showFrom:self];
[tableView deselectRowAtIndexPath:indexPath animated:YES];
}
@end
@@ -7,6 +7,7 @@
//
#import "FLEXColor.h"
#import "FLEXUtility.h"
#import "FLEXNetworkHistoryTableViewController.h"
#import "FLEXNetworkTransaction.h"
#import "FLEXNetworkTransactionTableViewCell.h"
@@ -252,11 +253,6 @@
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return self.searchController.isActive ? self.filteredNetworkTransactions.count : self.networkTransactions.count;
@@ -278,7 +274,7 @@
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
FLEXNetworkTransactionTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:kFLEXNetworkTransactionCellIdentifier forIndexPath:indexPath];
cell.transaction = [self transactionAtIndexPath:indexPath inTableView:tableView];
cell.transaction = [self transactionAtIndexPath:indexPath];
// Since we insert from the top, assign background colors bottom up to keep them consistent for each transaction.
NSInteger totalRows = [tableView numberOfRowsInSection:indexPath.section];
@@ -294,7 +290,7 @@
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
FLEXNetworkTransactionDetailTableViewController *detailViewController = [FLEXNetworkTransactionDetailTableViewController new];
detailViewController.transaction = [self transactionAtIndexPath:indexPath inTableView:tableView];
detailViewController.transaction = [self transactionAtIndexPath:indexPath];
[self.navigationController pushViewController:detailViewController animated:YES];
}
@@ -313,13 +309,40 @@
- (void)tableView:(UITableView *)tableView performAction:(SEL)action forRowAtIndexPath:(NSIndexPath *)indexPath withSender:(id)sender
{
if (action == @selector(copy:)) {
FLEXNetworkTransaction *transaction = [self transactionAtIndexPath:indexPath inTableView:tableView];
NSString *requestURLString = transaction.request.URL.absoluteString ?: @"";
[UIPasteboard.generalPasteboard setString:requestURLString];
NSURLRequest *request = [self transactionAtIndexPath:indexPath].request;
UIPasteboard.generalPasteboard.string = request.URL.absoluteString ?: @"";
}
}
- (FLEXNetworkTransaction *)transactionAtIndexPath:(NSIndexPath *)indexPath inTableView:(UITableView *)tableView
#if FLEX_AT_LEAST_IOS13_SDK
- (UIContextMenuConfiguration *)tableView:(UITableView *)tableView contextMenuConfigurationForRowAtIndexPath:(NSIndexPath *)indexPath point:(CGPoint)point __IOS_AVAILABLE(13.0)
{
return [UIContextMenuConfiguration
configurationWithIdentifier:nil
previewProvider:nil
actionProvider:^UIMenu *(NSArray<UIMenuElement *> *suggestedActions) {
UIAction *copy = [UIAction
actionWithTitle:@"Copy"
image:nil
identifier:nil
handler:^(__kindof UIAction *action) {
NSURLRequest *request = [self transactionAtIndexPath:indexPath].request;
UIPasteboard.generalPasteboard.string = request.URL.absoluteString ?: @"";
}
];
return [UIMenu
menuWithTitle:@"" image:nil identifier:nil
options:UIMenuOptionsDisplayInline
children:@[copy]
];
}
];
}
#endif
- (FLEXNetworkTransaction *)transactionAtIndexPath:(NSIndexPath *)indexPath
{
return self.searchController.isActive ? self.filteredNetworkTransactions[indexPath.row] : self.networkTransactions[indexPath.row];
}
@@ -16,6 +16,7 @@
#import "FLEXMultilineTableViewCell.h"
#import "FLEXUtility.h"
#import "FLEXManager+Private.h"
#import "FLEXTableView.h"
typedef UIViewController *(^FLEXNetworkDetailRowSelectionFuture)(void);
@@ -65,7 +66,7 @@ typedef UIViewController *(^FLEXNetworkDetailRowSelectionFuture)(void);
{
[super viewDidLoad];
[self.tableView registerClass:[FLEXMultilineTableViewCell class] forCellReuseIdentifier:kFLEXMultilineTableViewCellIdentifier];
[self.tableView registerClass:[FLEXMultilineTableViewCell class] forCellReuseIdentifier:kFLEXMultilineCell];
}
- (void)setTransaction:(FLEXNetworkTransaction *)transaction
@@ -147,7 +148,7 @@ typedef UIViewController *(^FLEXNetworkDetailRowSelectionFuture)(void);
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
FLEXMultilineTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:kFLEXMultilineTableViewCellIdentifier forIndexPath:indexPath];
FLEXMultilineTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:kFLEXMultilineCell forIndexPath:indexPath];
FLEXNetworkDetailRow *rowModel = [self rowModelAtIndexPath:indexPath];
@@ -206,10 +207,38 @@ typedef UIViewController *(^FLEXNetworkDetailRowSelectionFuture)(void);
{
if (action == @selector(copy:)) {
FLEXNetworkDetailRow *row = [self rowModelAtIndexPath:indexPath];
[UIPasteboard.generalPasteboard setString:row.detailText];
UIPasteboard.generalPasteboard.string = row.detailText;
}
}
#if FLEX_AT_LEAST_IOS13_SDK
- (UIContextMenuConfiguration *)tableView:(UITableView *)tableView contextMenuConfigurationForRowAtIndexPath:(NSIndexPath *)indexPath point:(CGPoint)point __IOS_AVAILABLE(13.0)
{
return [UIContextMenuConfiguration
configurationWithIdentifier:nil
previewProvider:nil
actionProvider:^UIMenu *(NSArray<UIMenuElement *> *suggestedActions) {
UIAction *copy = [UIAction
actionWithTitle:@"Copy"
image:nil
identifier:nil
handler:^(__kindof UIAction *action) {
FLEXNetworkDetailRow *row = [self rowModelAtIndexPath:indexPath];
UIPasteboard.generalPasteboard.string = row.detailText;
}
];
return [UIMenu
menuWithTitle:@"" image:nil identifier:nil
options:UIMenuOptionsDisplayInline
children:@[copy]
];
}
];
}
#endif
#pragma mark - View Configuration
+ (NSAttributedString *)attributedTextForRow:(FLEXNetworkDetailRow *)row
@@ -1,17 +0,0 @@
//
// 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
@@ -1,22 +0,0 @@
//
// 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
@@ -6,6 +6,10 @@
// Copyright (c) 2014 Flipboard. All rights reserved.
//
#ifndef _FLEXObjectExplorerViewController_h
#define _FLEXObjectExplorerViewController_h
#endif
#import "FLEXTableViewController.h"
typedef NS_ENUM(NSUInteger, FLEXObjectExplorerSection) {
@@ -810,7 +810,7 @@ typedef NS_ENUM(NSUInteger, FLEXMetadataKind) {
BOOL isCustomSection = explorerSection == FLEXObjectExplorerSectionCustom;
BOOL useDescriptionCell = explorerSection == FLEXObjectExplorerSectionDescription;
NSString *cellIdentifier = useDescriptionCell ? kFLEXMultilineTableViewCellIdentifier : @"cell";
NSString *cellIdentifier = useDescriptionCell ? kFLEXMultilineCell : @"cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (!cell) {
if (useDescriptionCell) {
@@ -168,6 +168,7 @@ typedef NS_ENUM(NSUInteger, FLEXViewExplorerRow) {
#define PropertyKeyGetter(getter) kFLEXUtilityAttributeCustomGetter : NSStringFromSelector(@selector(getter))
#define PropertyKeySetter(setter) kFLEXUtilityAttributeCustomSetter : NSStringFromSelector(@selector(setter))
/// Takes: min iOS version, property name, target class, property type, and a list of attributes
#define FLEXRuntimeUtilityTryAddProperty(iOS_atLeast, name, cls, type, ...) ({ \
if (@available(iOS iOS_atLeast, *)) { \
NSMutableDictionary *attrs = [NSMutableDictionary dictionaryWithDictionary:@{ \
@@ -181,8 +182,11 @@ typedef NS_ENUM(NSUInteger, FLEXViewExplorerRow) {
]; \
} \
})
/// Takes: min iOS version, property name, target class, property type, and a list of attributes
#define FLEXRuntimeUtilityTryAddNonatomicProperty(iOS_atLeast, name, cls, type, ...) \
FLEXRuntimeUtilityTryAddProperty(iOS_atLeast, name, cls, @encode(type), PropertyKey(NonAtomic), __VA_ARGS__);
/// Takes: min iOS version, property name, target class, property type (class name), and a list of attributes
#define FLEXRuntimeUtilityTryAddObjectProperty(iOS_atLeast, name, cls, type, ...) \
FLEXRuntimeUtilityTryAddProperty(iOS_atLeast, name, cls, FLEXEncodeClass(type), PropertyKey(NonAtomic), __VA_ARGS__);
@@ -195,7 +199,7 @@ typedef NS_ENUM(NSUInteger, FLEXViewExplorerRow) {
// This way, we can use our property editor to access and change them.
// The property attributes match the declared attributes in their headers.
// UIView
// UIView, public
FLEXRuntimeUtilityTryAddNonatomicProperty(2, frame, UIView, CGRect);
FLEXRuntimeUtilityTryAddNonatomicProperty(2, alpha, UIView, CGFloat);
FLEXRuntimeUtilityTryAddNonatomicProperty(2, clipsToBounds, UIView, BOOL);
@@ -203,6 +207,8 @@ typedef NS_ENUM(NSUInteger, FLEXViewExplorerRow) {
FLEXRuntimeUtilityTryAddNonatomicProperty(2, hidden, UIView, BOOL, PropertyKeyGetter(isHidden));
FLEXRuntimeUtilityTryAddObjectProperty(2, backgroundColor, UIView, UIColor, PropertyKey(Copy));
FLEXRuntimeUtilityTryAddObjectProperty(6, constraints, UIView, NSArray, PropertyKey(ReadOnly));
FLEXRuntimeUtilityTryAddObjectProperty(2, subviews, UIView, NSArray, PropertyKey(ReadOnly));
FLEXRuntimeUtilityTryAddObjectProperty(2, superview, UIView, UIView, PropertyKey(ReadOnly));
}
@end
@@ -8,7 +8,11 @@
#import "FLEXGlobalsEntry.h"
#ifndef _FLEXObjectExplorerViewController_h
#import "FLEXObjectExplorerViewController.h"
#else
@class FLEXObjectExplorerViewController;
#endif
@interface FLEXObjectExplorerFactory : NSObject <FLEXGlobalsEntry>
@@ -89,7 +89,7 @@
case FLEXGlobalsRowCurrentDevice:
return @"📱 UIDevice.currentDevice";
case FLEXGlobalsRowPasteboard:
return @"📋 UIPasteboard.generalPastboard";
return @"📋 UIPasteboard.generalPasteboard";
default: return nil;
}
}
@@ -115,6 +115,8 @@
return [self explorerViewControllerForObject:UIDevice.currentDevice];
case FLEXGlobalsRowPasteboard:
return [self explorerViewControllerForObject:UIPasteboard.generalPasteboard];
case FLEXGlobalsRowRootViewController:
return [self explorerViewControllerForObject:UIApplication.sharedApplication.delegate.window.rootViewController];
default: return nil;
}
}
@@ -8,10 +8,14 @@
#import "FLEXTableViewCell.h"
extern NSString *const kFLEXMultilineTableViewCellIdentifier;
/// A cell with both labels set to be multi-line capable.
@interface FLEXMultilineTableViewCell : FLEXTableViewCell
+ (CGFloat)preferredHeightWithAttributedText:(NSAttributedString *)attributedText inTableViewWidth:(CGFloat)tableViewWidth style:(UITableViewStyle)style showsAccessory:(BOOL)showsAccessory;
@end
/// A \c FLEXMultilineTableViewCell initialized with \c UITableViewCellStyleSubtitle
@interface FLEXMultilineDetailTableViewCell : FLEXMultilineTableViewCell
@end
@@ -8,8 +8,6 @@
#import "FLEXMultilineTableViewCell.h"
NSString *const kFLEXMultilineTableViewCellIdentifier = @"kFLEXMultilineTableViewCellIdentifier";
@implementation FLEXMultilineTableViewCell
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
@@ -17,6 +15,7 @@ NSString *const kFLEXMultilineTableViewCellIdentifier = @"kFLEXMultilineTableVie
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
self.textLabel.numberOfLines = 0;
self.detailTextLabel.numberOfLines = 0;
}
return self;
}
@@ -53,3 +52,12 @@ NSString *const kFLEXMultilineTableViewCellIdentifier = @"kFLEXMultilineTableVie
}
@end
@implementation FLEXMultilineDetailTableViewCell
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
return [super initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:reuseIdentifier];
}
@end
@@ -8,10 +8,7 @@
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
/// A cell initialized with \c UITableViewCellStyleSubtitle
@interface FLEXSubtitleTableViewCell : UITableViewCell
@end
NS_ASSUME_NONNULL_END
+25 -3
View File
@@ -8,10 +8,32 @@
#import <UIKit/UIKit.h>
#pragma mark Reuse identifiers
typedef NSString * FLEXTableViewCellReuseIdentifier;
/// A regular \c UITableViewCell initialized with \c UITableViewCellStyleDefault
extern FLEXTableViewCellReuseIdentifier const kFLEXDefaultCell;
/// A \c FLEXSubtitleTableViewCell initialized with \c UITableViewCellStyleSubtitle
extern FLEXTableViewCellReuseIdentifier const kFLEXDetailCell;
/// A \c FLEXMultilineTableViewCell initialized with \c UITableViewCellStyleDefault
extern FLEXTableViewCellReuseIdentifier const kFLEXMultilineCell;
/// A \c FLEXMultilineTableViewCell initialized with \c UITableViewCellStyleSubtitle
extern FLEXTableViewCellReuseIdentifier const kFLEXMultilineDetailCell;
#pragma mark - FLEXTableView
@interface FLEXTableView : UITableView
@property (nonatomic, readonly) NSString *defaultReuseIdentifier;
@property (nonatomic, readonly) NSString *subtitleReuseIdentifier;
@property (nonatomic, readonly) NSString *multilineReuseIdentifier;
+ (instancetype)flexDefaultTableView;
+ (instancetype)groupedTableView;
+ (instancetype)plainTableView;
/// You do not need to register classes for any of the default reuse identifiers above
/// (annotated as \c FLEXTableViewCellReuseIdentifier types) unless you wish to provide
/// a custom cell for any of those reuse identifiers. By default, \c FLEXTableViewCell,
/// \c FLEXSubtitleTableViewCell, and \c FLEXMultilineTableViewCell are used, respectively.
///
/// @param registrationMapping A map of reuse identifiers to \c UITableViewCell (sub)class objects.
- (void)registerCells:(NSDictionary<NSString *, Class> *)registrationMapping;
@end
+54 -24
View File
@@ -7,58 +7,88 @@
//
#import "FLEXTableView.h"
#import "FLEXUtility.h"
#import "FLEXSubtitleTableViewCell.h"
#import "FLEXMultilineTableViewCell.h"
FLEXTableViewCellReuseIdentifier const kFLEXDefaultCell = @"kFLEXDefaultCell";
FLEXTableViewCellReuseIdentifier const kFLEXDetailCell = @"kFLEXDetailCell";
FLEXTableViewCellReuseIdentifier const kFLEXMultilineCell = @"kFLEXMultilineCell";
FLEXTableViewCellReuseIdentifier const kFLEXMultilineDetailCell = @"kFLEXMultilineDetailCell";
#pragma mark Private
@interface UITableView (Private)
- (CGFloat)_heightForHeaderInSection:(NSInteger)section;
- (NSString *)_titleForHeaderInSection:(NSInteger)section;
@end
@implementation FLEXTableView
+ (instancetype)flexDefaultTableView {
#if FLEX_AT_LEAST_IOS13_SDK
if (@available(iOS 13.0, *)) {
return [[self alloc] initWithFrame:CGRectZero style:UITableViewStyleInsetGrouped];
} else {
return [[self alloc] initWithFrame:CGRectZero style:UITableViewStyleGrouped];
}
#else
return [[self alloc] initWithFrame:CGRectZero style:UITableViewStyleGrouped];
#endif
}
- (CGFloat)_heightForHeaderInSection:(NSInteger)section {
CGFloat height = [super _heightForHeaderInSection:section];
if (section == 0 && self.tableHeaderView && !@available(iOS 13.0, *)) {
return height - self.tableHeaderView.frame.size.height + 8;
if (section == 0 && self.tableHeaderView) {
NSString *title = [self _titleForHeaderInSection:section];
if (!@available(iOS 13, *)) {
return height - self.tableHeaderView.frame.size.height + 8;
} else if ([title isEqualToString:@" "]) {
return height - self.tableHeaderView.frame.size.height + 5;
}
}
return height;
}
- (id)initWithFrame:(CGRect)frame style:(UITableViewStyle)style
{
#pragma mark - Initialization
+ (id)groupedTableView {
#if FLEX_AT_LEAST_IOS13_SDK
if (@available(iOS 13.0, *)) {
return [[self alloc] initWithFrame:CGRectZero style:UITableViewStyleInsetGrouped];
} else {
return [[self alloc] initWithFrame:CGRectZero style:UITableViewStyleGrouped];
}
#else
return [[self alloc] initWithFrame:CGRectZero style:UITableViewStyleGrouped];
#endif
}
+ (id)plainTableView {
return [[self alloc] initWithFrame:CGRectZero style:UITableViewStylePlain];
}
- (id)initWithFrame:(CGRect)frame style:(UITableViewStyle)style {
self = [super initWithFrame:frame style:style];
if (self) {
[self registerCells:@{
self.defaultReuseIdentifier : [FLEXTableViewCell class],
self.subtitleReuseIdentifier : [FLEXSubtitleTableViewCell class],
self.multilineReuseIdentifier : [FLEXMultilineTableViewCell class],
kFLEXDefaultCell : [FLEXTableViewCell class],
kFLEXDetailCell : [FLEXSubtitleTableViewCell class],
kFLEXMultilineCell : [FLEXMultilineTableViewCell class],
kFLEXMultilineDetailCell : [FLEXMultilineDetailTableViewCell class]
}];
}
return self;
}
- (void)registerCells:(NSDictionary<NSString*,Class> *)registrationMapping
{
#pragma mark - Public
- (void)registerCells:(NSDictionary<NSString*, Class> *)registrationMapping {
[registrationMapping enumerateKeysAndObjectsUsingBlock:^(NSString *identifier, Class cellClass, BOOL *stop) {
[self registerClass:cellClass forCellReuseIdentifier:identifier];
}];
}
- (NSString *)defaultReuseIdentifier
{
return @"kFLEXTableViewCellIdentifier";
}
- (NSString *)subtitleReuseIdentifier
{
return @"kFLEXSubtitleTableViewCellIdentifier";
}
- (NSString *)multilineReuseIdentifier
{
return kFLEXMultilineTableViewCellIdentifier;
}
@end
@@ -0,0 +1,19 @@
//
// NSArray+Functional.h
// FLEX
//
// Created by Tanner Bennett on 9/25/19.
// Copyright © 2019 Flipboard. All rights reserved.
//
#import <Foundation/Foundation.h>
@interface NSArray<T> (Functional)
/// 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 *)flex_mapped:(id(^)(T obj, NSUInteger idx))mapFunc;
- (NSArray<T> *)flex_filtered:(BOOL(^)(T obj, NSUInteger idx))filterFunc;
- (void)flex_forEach:(id(^)(T obj, NSUInteger idx))block;
@end
@@ -0,0 +1,37 @@
//
// NSArray+Functional.m
// FLEX
//
// Created by Tanner Bennett on 9/25/19.
// Copyright © 2019 Flipboard. All rights reserved.
//
#import "NSArray+Functional.h"
@implementation NSArray (Functional)
- (instancetype)flex_mapped:(id (^)(id, NSUInteger))mapFunc {
NSMutableArray *map = [NSMutableArray new];
[self enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
id ret = mapFunc(obj, idx);
if (ret) {
[map addObject:ret];
}
}];
return map.copy;
}
- (NSArray *)flex_filtered:(BOOL (^)(id, NSUInteger))filterFunc {
return [self flex_mapped:^id(id obj, NSUInteger idx) {
return filterFunc(obj, idx) ? obj : nil;
}];
}
- (void)flex_forEach:(id (^)(id, NSUInteger))block {
[self enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
block(obj, idx);
}];
}
@end
@@ -0,0 +1,16 @@
//
// UIPasteboard+FLEX.h
// FLEX
//
// Created by Tanner Bennett on 12/9/19.
//Copyright © 2019 Flipboard. All rights reserved.
//
#import <UIKit/UIKit.h>
@interface UIPasteboard (FLEX)
/// For copying an object which could be a string, data, or number
- (void)flex_copy:(id)unknownType;
@end
@@ -0,0 +1,26 @@
//
// UIPasteboard+FLEX.m
// FLEX
//
// Created by Tanner Bennett on 12/9/19.
//Copyright © 2019 Flipboard. All rights reserved.
//
#import "UIPasteboard+FLEX.h"
@implementation UIPasteboard (FLEX)
- (void)flex_copy:(id)object {
if ([object isKindOfClass:[NSString class]]) {
UIPasteboard.generalPasteboard.string = object;
} else if([object isKindOfClass:[NSData class]]) {
[UIPasteboard.generalPasteboard setData:object forPasteboardType:@"public.data"];
} else if ([object isKindOfClass:[NSNumber class]]) {
UIPasteboard.generalPasteboard.string = [object stringValue];
}
[NSException raise:NSInternalInconsistencyException
format:@"Tried to copy unsupported type: %@", [object class]];
}
@end
@@ -1,5 +1,5 @@
//
// UIView+Layout.h
// UIView+FLEX_Layout.h
// FLEX
//
// Created by Tanner Bennett on 7/18/19.
@@ -10,7 +10,7 @@
#define Padding(p) UIEdgeInsetsMake(p, p, p, p)
@interface UIView (Layout)
@interface UIView (FLEX_Layout)
- (void)centerInView:(UIView *)view;
- (void)pinEdgesTo:(UIView *)view;
@@ -1,14 +1,14 @@
//
// UIView+Layout.m
// UIView+FLEX_Layout.m
// FLEX
//
// Created by Tanner Bennett on 7/18/19.
//Copyright © 2019 Flipboard. All rights reserved.
//
#import "UIView+Layout.h"
#import "UIView+FLEX_Layout.h"
@implementation UIView (Layout)
@implementation UIView (FLEX_Layout)
- (void)centerInView:(UIView *)view {
[self.centerXAnchor constraintEqualToAnchor:view.centerXAnchor].active = YES;
+1 -6
View File
@@ -71,12 +71,10 @@ static kern_return_t reader(__unused task_t remote_task, vm_address_t remote_add
for (unsigned int i = 0; i < zoneCount; i++) {
malloc_zone_t *zone = (malloc_zone_t *)zones[i];
malloc_introspection_t *introspection = zone->introspect;
NSString *zoneName = @(zone->zone_name);
// We only need to look at the default malloc zone.
// This may explain why some zone functions are
// sometimes invalid; perhaps not all zones support them?
if (![zoneName isEqualToString:@"DefaultMallocZone"] || !introspection) {
if (!introspection) {
continue;
}
@@ -114,9 +112,6 @@ static kern_return_t reader(__unused task_t remote_task, vm_address_t remote_add
introspection->enumerator(TASK_NULL, (void *)&callback, MALLOC_PTR_IN_USE_RANGE_TYPE, (vm_address_t)zone, reader, &range_callback);
unlock_zone(zone);
}
// Only one zone to enumerate
break;
}
}
}
+9
View File
@@ -65,6 +65,11 @@ const unsigned int kFLEXNumberOfImplicitArgs = 2;
// we check to see if the pointer is of a valid object. If not,
// we just display the NSValue.
if (!returnsObjectOrClass) {
// Skip NSNumber instances
if ([returnedObjectOrNil isKindOfClass:[NSNumber class]]) {
return returnedObjectOrNil;
}
// Can only be NSValue since return type is not an object,
// so we bail if this doesn't add up
if (![returnedObjectOrNil isKindOfClass:[NSValue class]]) {
@@ -743,6 +748,10 @@ const unsigned int kFLEXNumberOfImplicitArgs = 2;
+ (NSString *)appendName:(NSString *)name toType:(NSString *)type
{
if (!type.length) {
type = @"(?)";
}
NSString *combined = nil;
if ([type characterAtIndex:type.length - 1] == FLEXTypeEncodingCString) {
combined = [type stringByAppendingString:name];
+5 -1
View File
@@ -15,12 +15,16 @@
#define FLEXFloor(x) (floor(UIScreen.mainScreen.scale * (x)) / UIScreen.mainScreen.scale)
#if defined(__IPHONE_13_0)
#ifdef __IPHONE_13_0
#define FLEX_AT_LEAST_IOS13_SDK (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0)
#else
#define FLEX_AT_LEAST_IOS13_SDK NO
#endif
#define FLEXPluralString(count, plural, singular) [NSString \
stringWithFormat:@"%@ %@", @(count), (count == 1 ? singular : plural) \
]
@interface FLEXUtility : NSObject
+ (UIColor *)consistentRandomColorForObject:(id)object;
+10 -16
View File
@@ -48,28 +48,22 @@
+ (UIViewController *)viewControllerForView:(UIView *)view
{
UIViewController *viewController = nil;
SEL viewDelSel = NSSelectorFromString(@"_viewDelegate");
if ([view respondsToSelector:viewDelSel]) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
viewController = [view performSelector:viewDelSel];
#pragma clang diagnostic pop
NSString *viewDelegate = @"viewDelegate";
if ([view respondsToSelector:NSSelectorFromString(viewDelegate)]) {
return [view valueForKey:viewDelegate];
}
return viewController;
return nil;
}
+ (UIViewController *)viewControllerForAncestralView:(UIView *)view
{
UIViewController *viewController = nil;
SEL viewDelSel = NSSelectorFromString([NSString stringWithFormat:@"%@ewControllerForAncestor", @"_vi"]);
if ([view respondsToSelector:viewDelSel]) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
viewController = [view performSelector:viewDelSel];
#pragma clang diagnostic pop
NSString *_viewControllerForAncestor = @"_viewControllerForAncestor";
if ([view respondsToSelector:NSSelectorFromString(_viewControllerForAncestor)]) {
return [view valueForKey:_viewControllerForAncestor];
}
return viewController;
return nil;
}
+ (NSString *)detailDescriptionForView:(UIView *)view
@@ -10,6 +10,7 @@
@interface FLEXImagePreviewViewController : UIViewController
+ (instancetype)forImage:(UIImage *)image;
- (id)initWithImage:(UIImage *)image;
@end
@@ -21,6 +21,11 @@
@implementation FLEXImagePreviewViewController
+ (instancetype)forImage:(UIImage *)image
{
return [[self alloc] initWithImage:image];
}
- (id)initWithImage:(UIImage *)image
{
self = [super initWithNibName:nil bundle:nil];
+2 -1
View File
@@ -1,6 +1,6 @@
Pod::Spec.new do |spec|
spec.name = "FLEX"
spec.version = "3.1.0"
spec.version = "3.1.2"
spec.summary = "A set of in-app debugging and exploration tools for iOS"
spec.description = <<-DESC
- Inspect and modify views in the hierarchy.
@@ -35,5 +35,6 @@ Pod::Spec.new do |spec|
spec.frameworks = [ "Foundation", "UIKit", "CoreGraphics", "ImageIO", "QuartzCore", "WebKit", "Security" ]
spec.libraries = [ "z", "sqlite3" ]
spec.requires_arc = true
spec.compiler_flags = "-Wno-unsupported-availability-guard"
spec.public_header_files = [ "Classes/**/FLEXManager.h", "Classes/FLEX.h" ]
end
+24 -16
View File
@@ -182,10 +182,8 @@
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 */; };
C387C88322E0D24A00750E58 /* UIView+FLEX_Layout.h in Headers */ = {isa = PBXBuildFile; fileRef = C387C88122E0D24A00750E58 /* UIView+FLEX_Layout.h */; };
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 */; };
C38DF0EB22CFE4370077B4AD /* FLEXTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C38DF0E922CFE4370077B4AD /* FLEXTableViewController.m */; };
C38F3F31230C958F004E3731 /* FLEXAlert.h in Headers */ = {isa = PBXBuildFile; fileRef = C38F3F2F230C958F004E3731 /* FLEXAlert.h */; };
@@ -196,6 +194,8 @@
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 */; };
C3BFD071233C23ED0015FB82 /* NSArray+Functional.m in Sources */ = {isa = PBXBuildFile; fileRef = C3BFD06F233C23ED0015FB82 /* NSArray+Functional.m */; };
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 */; };
@@ -209,6 +209,8 @@
C3F31D422267D883003C991A /* FLEXTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = C3F31D392267D883003C991A /* FLEXTableViewCell.m */; };
C3F31D432267D883003C991A /* FLEXTableView.h in Headers */ = {isa = PBXBuildFile; fileRef = C3F31D3B2267D883003C991A /* FLEXTableView.h */; };
C3F31D442267D883003C991A /* FLEXTableView.m in Sources */ = {isa = PBXBuildFile; fileRef = C3F31D3C2267D883003C991A /* FLEXTableView.m */; };
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 */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@@ -403,10 +405,8 @@
C37A0C92218BAC9600848CA7 /* FLEXObjcInternal.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = FLEXObjcInternal.mm; sourceTree = "<group>"; };
C387C87822DFCD6A00750E58 /* FLEXCarouselCell.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FLEXCarouselCell.h; sourceTree = "<group>"; };
C387C87922DFCD6A00750E58 /* FLEXCarouselCell.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FLEXCarouselCell.m; sourceTree = "<group>"; };
C387C87C22DFD71A00750E58 /* FLEXClassTreeViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FLEXClassTreeViewController.h; sourceTree = "<group>"; };
C387C87D22DFD71A00750E58 /* FLEXClassTreeViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FLEXClassTreeViewController.m; sourceTree = "<group>"; };
C387C88122E0D24A00750E58 /* UIView+Layout.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "UIView+Layout.h"; sourceTree = "<group>"; };
C387C88222E0D24A00750E58 /* UIView+Layout.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "UIView+Layout.m"; sourceTree = "<group>"; };
C387C88122E0D24A00750E58 /* UIView+FLEX_Layout.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "UIView+FLEX_Layout.h"; 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>"; };
C38DF0E922CFE4370077B4AD /* FLEXTableViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FLEXTableViewController.m; sourceTree = "<group>"; };
C38F3F2F230C958F004E3731 /* FLEXAlert.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FLEXAlert.h; sourceTree = "<group>"; };
@@ -417,6 +417,8 @@
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>"; };
C3BFD06F233C23ED0015FB82 /* NSArray+Functional.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSArray+Functional.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; path = FLEXScopeCarousel.h; sourceTree = "<group>"; };
@@ -429,6 +431,8 @@
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>"; };
C3F31D3C2267D883003C991A /* FLEXTableView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLEXTableView.m; 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>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@@ -797,8 +801,12 @@
C387C88022E0D22600750E58 /* Categories */ = {
isa = PBXGroup;
children = (
C387C88122E0D24A00750E58 /* UIView+Layout.h */,
C387C88222E0D24A00750E58 /* UIView+Layout.m */,
C387C88122E0D24A00750E58 /* UIView+FLEX_Layout.h */,
C387C88222E0D24A00750E58 /* UIView+FLEX_Layout.m */,
C3F646BF239EAA8F00D4A011 /* UIPasteboard+FLEX.h */,
C3F646C0239EAA8F00D4A011 /* UIPasteboard+FLEX.m */,
C3BFD06E233C23ED0015FB82 /* NSArray+Functional.h */,
C3BFD06F233C23ED0015FB82 /* NSArray+Functional.m */,
);
path = Categories;
sourceTree = "<group>";
@@ -838,8 +846,6 @@
children = (
3A4C944C1B5B21410088C3F2 /* FLEXObjectExplorerViewController.h */,
3A4C944D1B5B21410088C3F2 /* FLEXObjectExplorerViewController.m */,
C387C87C22DFD71A00750E58 /* FLEXClassTreeViewController.h */,
C387C87D22DFD71A00750E58 /* FLEXClassTreeViewController.m */,
3A4C943C1B5B21410088C3F2 /* FLEXArrayExplorerViewController.h */,
3A4C943D1B5B21410088C3F2 /* FLEXArrayExplorerViewController.m */,
3A4C943E1B5B21410088C3F2 /* FLEXClassExplorerViewController.h */,
@@ -885,6 +891,7 @@
3A4C951E1B5B21410088C3F2 /* FLEXClassesTableViewController.h in Headers */,
779B1ED21C0C4D7C001F5E49 /* FLEXTableColumnHeader.h in Headers */,
3A4C94FD1B5B21410088C3F2 /* FLEXArgumentInputStructView.h in Headers */,
C3F646C1239EAA8F00D4A011 /* UIPasteboard+FLEX.h in Headers */,
94A515141C4CA1C00063292F /* FLEXManager.h in Headers */,
C37A0C93218BAC9600848CA7 /* FLEXObjcInternal.h in Headers */,
3A4C953E1B5B21410088C3F2 /* FLEXNetworkTransactionDetailTableViewController.h in Headers */,
@@ -910,9 +917,9 @@
3A4C950F1B5B21410088C3F2 /* FLEXMethodCallingViewController.h in Headers */,
3A4C94F51B5B21410088C3F2 /* FLEXArgumentInputObjectView.h in Headers */,
3A4C94F11B5B21410088C3F2 /* FLEXArgumentInputFontsPickerView.h in Headers */,
C3BFD070233C23ED0015FB82 /* NSArray+Functional.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 */,
@@ -946,7 +953,7 @@
3A4C950B1B5B21410088C3F2 /* FLEXFieldEditorViewController.h in Headers */,
C3F31D3D2267D883003C991A /* FLEXSubtitleTableViewCell.h in Headers */,
94A515251C4CA2080063292F /* FLEXExplorerToolbar.h in Headers */,
C387C88322E0D24A00750E58 /* UIView+Layout.h in Headers */,
C387C88322E0D24A00750E58 /* UIView+FLEX_Layout.h in Headers */,
3A4C953C1B5B21410088C3F2 /* FLEXNetworkTransaction.h in Headers */,
3A4C94D71B5B21410088C3F2 /* FLEXSetExplorerViewController.h in Headers */,
3A4C94D91B5B21410088C3F2 /* FLEXViewControllerExplorerViewController.h in Headers */,
@@ -1089,16 +1096,17 @@
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 */,
3A4C950E1B5B21410088C3F2 /* FLEXIvarEditorViewController.m in Sources */,
C3DC287C223ED5F200F48AA6 /* FLEXOSLogController.m in Sources */,
3A4C95101B5B21410088C3F2 /* FLEXMethodCallingViewController.m in Sources */,
C3F646C2239EAA8F00D4A011 /* UIPasteboard+FLEX.m in Sources */,
3A4C94F61B5B21410088C3F2 /* FLEXArgumentInputObjectView.m in Sources */,
3A4C94EC1B5B21410088C3F2 /* FLEXImagePreviewViewController.m in Sources */,
3A4C94F21B5B21410088C3F2 /* FLEXArgumentInputFontsPickerView.m in Sources */,
C3BFD071233C23ED0015FB82 /* NSArray+Functional.m in Sources */,
7349FD6B22B93CDF00051810 /* FLEXColor.m in Sources */,
94AAF0391BAF2E1F00DE8760 /* FLEXKeyboardHelpViewController.m in Sources */,
3A4C951F1B5B21410088C3F2 /* FLEXClassesTableViewController.m in Sources */,
@@ -1146,7 +1154,7 @@
3A4C95351B5B21410088C3F2 /* FLEXSystemLogTableViewController.m in Sources */,
3A4C95271B5B21410088C3F2 /* FLEXGlobalsTableViewController.m in Sources */,
71E1C2172307FBB800F5032A /* FLEXKeychain.m in Sources */,
C387C88422E0D24A00750E58 /* UIView+Layout.m in Sources */,
C387C88422E0D24A00750E58 /* UIView+FLEX_Layout.m in Sources */,
779B1ED31C0C4D7C001F5E49 /* FLEXTableColumnHeader.m in Sources */,
C37A0C94218BAC9600848CA7 /* FLEXObjcInternal.mm in Sources */,
3A4C94EA1B5B21410088C3F2 /* FLEXHierarchyTableViewController.m in Sources */,
Binary file not shown.

Before

Width:  |  Height:  |  Size: 291 KiB

+46 -29
View File
@@ -8,7 +8,7 @@
FLEX (Flipboard Explorer) is a set of in-app debugging and exploration tools for iOS development. When presented, FLEX shows a toolbar that lives in a window above your application. From this toolbar, you can view and modify nearly every piece of state in your running application.
![View Hierarchy Exploration](https://engineering.flipboard.com/assets/flex/basic-view-exploration.gif)
<img alt="Demo" width=36% height=36% src=https://user-images.githubusercontent.com/8371943/70185687-e842c800-16af-11ea-8ef9-9e071380a462.gif>
## Give Yourself Debugging Superpowers
@@ -71,52 +71,61 @@ More complete version:
### Modify Views
Once a view is selected, you can tap on the info bar below the toolbar to present more details about the view. From there, you can modify properties and call methods.
![View Modification](https://engineering.flipboard.com/assets/flex/advanced-view-editing.gif)
<img alt="Modify Views" width=36% height=36% src=https://user-images.githubusercontent.com/8371943/70271816-c5c2b480-176c-11ea-8bf4-2c5a755bc392.gif>
### Network History
When enabled, network debugging allows you to view all requests made using NSURLConnection or NSURLSession. Settings allow you to adjust what kind of response bodies get cached and the maximum size limit of the response cache. You can choose to have network debugging enabled automatically on app launch. This setting is persisted across launches.
![Network History](https://engineering.flipboard.com/assets/flex/network-history.gif)
<img alt="Network History" width=36% height=36% src=https://user-images.githubusercontent.com/8371943/70271876-e5f27380-176c-11ea-98ef-24170205b706.gif>
### All Objects on the Heap
FLEX queries malloc for all the live allocated memory blocks and searches for ones that look like objects. You can see everything from here.
![Heap Exploration](https://engineering.flipboard.com/assets/flex/heap-browser.gif)
<img alt="Heap/Live Objects Explorer" width=36% height=36% src=https://user-images.githubusercontent.com/8371943/70271850-d83cee00-176c-11ea-9750-ee3a479c6769.gif>
### Explore-at-address
If you get your hands on an arbitrary address, you can try explore the object at that address, and FLEX will open it if it can verify the address points to a valid object. If FLEX isn't sure, it'll warn you and refuse to dereference the pointer. If you know better, however, you can choose to explore it anyway by choosing "Unsafe Explore"
<img alt="Address Explorer" width=36% height=36% src=https://user-images.githubusercontent.com/8371943/70271798-bb081f80-176c-11ea-806d-9d74ac293641.gif>
### Simulator Keyboard Shortcuts
Default keyboard shortcuts allow you to activate the FLEX tools, scroll with the arrow keys, and close modals using the escape key. You can also add custom keyboard shortcuts via `-[FLEXManager registerSimulatorShortcutWithKey:modifiers:action:description]`
![Simulator Shortcuts](https://cloud.githubusercontent.com/assets/1422245/10002927/1106fd32-6067-11e5-8e21-57a357c259b6.png)
<img alt="Simulator Keyboard Shortcuts" width=40% height=40% src="https://user-images.githubusercontent.com/8371943/70272984-d3793980-176e-11ea-89a2-66d187d71b4c.png">
### File Browser
View the file system within your app's sandbox. FLEX shows file sizes, image previews, and pretty prints `.json` and `.plist` files. You can copy text and image files to the pasteboard if you want to inspect them outside of your app.
View the file system within your app's bundle or sandbox container. FLEX shows file sizes, image previews, and pretty prints `.json` and `.plist` files. You can rename and delete files and folders. You can "share" any file if you want to inspect them outside of your app.
![File Browser](https://engineering.flipboard.com/assets/flex/file-browser.gif)
<img alt="File Browser" width=36% height=36% src=https://user-images.githubusercontent.com/8371943/70271831-d115e000-176c-11ea-8078-ada291f980f3.gif>
### SQLite Browser
SQLite database files (with either `.db` or `.sqlite` extensions), or [Realm](https://realm.io) database files can be explored using FLEX. The database browser lets you view all tables, and individual tables can be sorted by tapping column headers.
![Database Browser](https://cloud.githubusercontent.com/assets/1422245/11786700/d0ab95dc-a23c-11e5-80ce-0e1b4dba2b6b.png)
<img alt="SQLite Browser" width=36% height=36% src=https://user-images.githubusercontent.com/8371943/70271881-ea1e9100-176c-11ea-9a42-01618311c869.gif>
### 3D Touch in the Simulator
Using a combination of the command, control, and shift keys, you can simulate different levels of 3D touch pressure in the simulator. Each key contributes 1/3 of maximum possible force. Note that you need to move the touch slightly to get pressure updates.
![Simulator 3D Touch](https://cloud.githubusercontent.com/assets/1422245/11786615/5d4ef96c-a23c-11e5-975e-67275341e439.gif)
<img alt="Simulator 3D Touch" width=36% height=36% src=https://cloud.githubusercontent.com/assets/1422245/11786615/5d4ef96c-a23c-11e5-975e-67275341e439.gif>
### System Library Exploration
Go digging for all things public and private. To learn more about a class, you can create an instance of it and explore its default state.
### Explore Loaded Libraries
Go digging for all things public and private. To learn more about a class, you can create an instance of it and explore its default state. You can also type in a class name to jump to that class directly if you know which class you're looking for.
![System Libraries Browser](https://engineering.flipboard.com/assets/flex/system-libraries-browser.gif)
<img alt="Loaded Libraries Exploration" width=36% height=36% src=https://user-images.githubusercontent.com/8371943/70271868-dffc9280-176c-11ea-8704-a0c05b75cc5f.gif>
### NSUserDefaults Editing
FLEX allows you to edit defaults that are any combination of strings, numbers, arrays, and dictionaries. The input is parsed as `JSON`. If other kinds of objects are set for a defaults key (i.e. `NSDate`), you can view them but not edit them.
![NSUserDefaults Editor](https://engineering.flipboard.com/assets/flex/nsuserdefaults-editor.gif)
<img alt="NSUserDefaults Editing" width=36% height=36% src=https://user-images.githubusercontent.com/8371943/70271889-edb21800-176c-11ea-92b4-71e07d2b6ce7.gif>
### Learning from Other Apps
The code injection is left as an exercise for the reader. :innocent:
![Springboard Lock Screen](https://engineering.flipboard.com/assets/flex/flex-readme-reverse-1.png) ![Springboard Home Screen](https://engineering.flipboard.com/assets/flex/flex-readme-reverse-2.png)
<p float="left">
<img alt="Springboard Lock Screen" width=25% height=25% src= https://engineering.flipboard.com/assets/flex/flex-readme-reverse-1.png>
<img alt="Springboard Home Screen" width=25% height=25% src= https://engineering.flipboard.com/assets/flex/flex-readme-reverse-2.png>
</p>
## Installation
@@ -128,7 +137,7 @@ FLEX requires an app that targets iOS 9 or higher.
FLEX is available on [CocoaPods](https://cocoapods.org/pods/FLEX). Simply add the following line to your podfile:
```ruby
pod 'FLEX', '~> 3.0', :configurations => ['Debug']
pod 'FLEX', :configurations => ['Debug']
```
### Carthage
@@ -136,27 +145,39 @@ pod 'FLEX', '~> 3.0', :configurations => ['Debug']
Add the following to your Cartfile:
```
github "flipboard/FLEX" ~> 3.0
github "flipboard/FLEX"
```
### Buck
If you're using Buck, you may want to silence some of the warnings emitted by FLEX. You will need to build FLEX as an `apple_library` and pass the `-Wno-unsupported-availability-guard` flag, as well as the flags to disable any other warnings FLEX may have.
### Manual
Manually add the files in `Classes/` to your Xcode project.
Manually add the files in `Classes/` to your Xcode project, or just drag in the entire `FLEX/` folder. Be sure to exclude FLEX from `Release` builds or your app will be rejected.
##### Silencing warnings
Add the following flags to to **Other Warnings Flags** in **Build Settings:**
- `-Wno-deprecated-declarations`
- `-Wno-unsupported-availability-guard`
## Excluding FLEX from Release (App Store) Builds
FLEX makes it easy to explore the internals of your app, so it is not something you should expose to your users. Fortunately, it is easy to exclude FLEX files from Release builds. The strategies differ depending on how you integrated FLEX in your project, and are described below.
At the places in your code where you integrate FLEX, do a `#if DEBUG` check to ensure the tool is only accessible in your `Debug` builds and to avoid errors in your `Release` builds. For more help with integrating FLEX, see the example project.
Wrap the places in your code where you integrate FLEX with an `#if DEBUG` statement to ensure the tool is only accessible in your `Debug` builds and to avoid errors in your `Release` builds. For more help with integrating FLEX, see the example project.
### FLEX added with CocoaPods
### CocoaPods
CocoaPods automatically excludes FLEX from release builds if you only specify the Debug configuration for FLEX in your Podfile.
CocoaPods automatically excludes FLEX from release builds if you only specify the Debug configuration for FLEX in your Podfile:
### FLEX added with Carthage
```ruby
pod 'FLEX', :configurations => ['Debug']
```
If you are using Carthage, only including the `FLEX.framework` in debug builds is easy:
### Carthage
1. Do NOT add `FLEX.framework` to the embedded binaries of your target, as it would otherwise be included in all builds (therefore also in release ones).
1. Instead, add `$(PROJECT_DIR)/Carthage/Build/iOS` to your target _Framework Search Paths_ (this setting might already be present if you already included other frameworks with Carthage). This makes it possible to import the FLEX framework from your source files. It does not harm if this setting is added for all configurations, but it should at least be added for the debug one.
@@ -170,17 +191,13 @@ If you are using Carthage, only including the `FLEX.framework` in debug builds i
Finally, add `$(SRCROOT)/Carthage/Build/iOS/FLEX.framework` as input file of this script phase.
<p align="center"><img src="README-images/flex-exclusion-carthage.jpg"/></p>
<img width=75% height=75% src=https://user-images.githubusercontent.com/8371943/70274062-0d4b3f80-1771-11ea-94ea-ca7e7b5ca244.jpg>
### FLEX files added manually to a project
In Xcode, navigate to the "Build Settings" tab of your project. Click the plus and select `Add User-Defined Setting`.
In Xcode, navigate to `Build Settings > Build Options > Excluded Source File Names`. For your `Release` configuration, set it to `FLEX*` like this to exclude all files with the `FLEX` prefix:
![Add User-Defined Setting](https://engineering.flipboard.com/assets/flex/flex-readme-exclude-1.png)
Name the setting `EXCLUDED_SOURCE_FILE_NAMES`. For your `Release` configuration, set the value to `FLEX*`. This will exclude all files with the prefix FLEX from compilation. Leave the value blank for your `Debug` configuration.
![EXCLUDED_SOURCE_FILE_NAMES](https://engineering.flipboard.com/assets/flex/flex-readme-exclude-2.png)
<img width=75% height=75% src=https://user-images.githubusercontent.com/8371943/70281926-e21d1c00-1781-11ea-92eb-aee340791da8.png>
## Additional Notes