Compare commits
25 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8367342b25 | |||
| 2df073a792 | |||
| 8236fc97cc | |||
| 0364de36bd | |||
| 12195eb879 | |||
| acdc46c43f | |||
| 52eed1b6f9 | |||
| a91d1de9ad | |||
| 492d2e49fe | |||
| 49bc439000 | |||
| 8c919cc26c | |||
| 8e86ffccd6 | |||
| 228de102e7 | |||
| 37b5d1be2a | |||
| ef8f866330 | |||
| e862b81734 | |||
| a3f66b3f87 | |||
| 3e12ad9887 | |||
| fe36b59b4c | |||
| b735a69c1b | |||
| 7b1b6f9e24 | |||
| c047fbc581 | |||
| 40239524d1 | |||
| 2f93050e2e | |||
| dc8ac6c195 |
@@ -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;
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
+2
-2
@@ -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;
|
||||
+3
-3
@@ -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;
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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 |
@@ -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.
|
||||
|
||||

|
||||
<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.
|
||||
|
||||

|
||||
<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.
|
||||
|
||||

|
||||
<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.
|
||||
|
||||

|
||||
<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]`
|
||||
|
||||

|
||||
<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.
|
||||
|
||||

|
||||
<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.
|
||||
|
||||

|
||||
<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.
|
||||
|
||||

|
||||
<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.
|
||||
|
||||

|
||||
<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.
|
||||
|
||||

|
||||
<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:
|
||||
|
||||
 
|
||||
<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:
|
||||
|
||||

|
||||
|
||||
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.
|
||||
|
||||

|
||||
<img width=75% height=75% src=https://user-images.githubusercontent.com/8371943/70281926-e21d1c00-1781-11ea-92eb-aee340791da8.png>
|
||||
|
||||
## Additional Notes
|
||||
|
||||
|
||||
Reference in New Issue
Block a user