Compare commits
45 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 67097982ea | |||
| 1342d3029c | |||
| 62dcef4644 | |||
| 7ee296143e | |||
| c6bac54597 | |||
| 5f7bce64ed | |||
| d2dde55bb1 | |||
| f1a0a5c5e5 | |||
| 0db073459e | |||
| 7c7ed9286f | |||
| aac88dd6c8 | |||
| d7376b75cd | |||
| 5b39b3ed03 | |||
| bbaa85bdbf | |||
| 34e27bc5d9 | |||
| 714307273e | |||
| 5242d3c5a1 | |||
| cf2e94a1d2 | |||
| 800acb4cad | |||
| 368ce64121 | |||
| 05f03090a9 | |||
| a8803781e8 | |||
| 170f74b297 | |||
| 0d0f2a3073 | |||
| d6bddf5199 | |||
| 258ec8697b | |||
| 8f9a6e88ec | |||
| c37270e6ac | |||
| ce10d45c29 | |||
| b2716c4b2e | |||
| ab135ba94e | |||
| bcc04f4113 | |||
| 470b3fa3b3 | |||
| e84dfeae5c | |||
| 44e86e59b7 | |||
| ac6d9cfa3f | |||
| 1a59711760 | |||
| b60ce7a057 | |||
| b4ac210bef | |||
| 9360c58975 | |||
| 43d9a460ce | |||
| 15fee5a8a5 | |||
| fad038392b | |||
| 558d65a0b0 | |||
| 077fca36c0 |
@@ -0,0 +1,3 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: [NSExceptional]
|
||||
@@ -9,6 +9,7 @@
|
||||
#import "FLEXFilteringTableViewController.h"
|
||||
#import "FLEXTableViewSection.h"
|
||||
#import "NSArray+FLEX.h"
|
||||
#import "FLEXMacros.h"
|
||||
|
||||
@interface FLEXFilteringTableViewController ()
|
||||
|
||||
@@ -187,8 +188,6 @@
|
||||
[self.filterDelegate.sections[indexPath.section] didPressInfoButtonAction:indexPath.row](self);
|
||||
}
|
||||
|
||||
#if FLEX_AT_LEAST_IOS13_SDK
|
||||
|
||||
- (UIContextMenuConfiguration *)tableView:(UITableView *)tableView contextMenuConfigurationForRowAtIndexPath:(NSIndexPath *)indexPath point:(CGPoint)point __IOS_AVAILABLE(13.0) {
|
||||
FLEXTableViewSection *section = self.filterDelegate.sections[indexPath.section];
|
||||
NSString *title = [section menuTitleForRow:indexPath.row];
|
||||
@@ -207,6 +206,4 @@
|
||||
return nil;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@end
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
@interface FLEXNavigationController ()
|
||||
@property (nonatomic, readonly) BOOL toolbarWasHidden;
|
||||
@property (nonatomic) BOOL waitingToAddTab;
|
||||
@property (nonatomic, readonly) BOOL canShowToolbar;
|
||||
@property (nonatomic) BOOL didSetupPendingDismissButtons;
|
||||
@property (nonatomic) UISwipeGestureRecognizer *navigationBarSwipeGesture;
|
||||
@end
|
||||
@@ -99,6 +100,10 @@
|
||||
[self.presentingViewController dismissViewControllerAnimated:YES completion:nil];
|
||||
}
|
||||
|
||||
- (BOOL)canShowToolbar {
|
||||
return self.topViewController.toolbarItems.count;
|
||||
}
|
||||
|
||||
- (void)addNavigationBarItemsToViewController:(UINavigationItem *)navigationItem {
|
||||
if (!self.presentingViewController) {
|
||||
return;
|
||||
@@ -148,8 +153,15 @@
|
||||
}
|
||||
|
||||
- (void)handleNavigationBarTap:(UIGestureRecognizer *)sender {
|
||||
// Don't reveal the toolbar if we were just tapping a button
|
||||
CGPoint location = [sender locationInView:self.navigationBar];
|
||||
UIView *hitView = [self.navigationBar hitTest:location withEvent:nil];
|
||||
if ([hitView isKindOfClass:[UIControl class]]) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (sender.state == UIGestureRecognizerStateRecognized) {
|
||||
if (self.toolbarHidden) {
|
||||
if (self.toolbarHidden && self.canShowToolbar) {
|
||||
[self setToolbarHidden:NO animated:YES];
|
||||
}
|
||||
}
|
||||
@@ -165,7 +177,7 @@
|
||||
|
||||
- (void)_gestureRecognizedInteractiveHide:(UIPanGestureRecognizer *)sender {
|
||||
if (sender.state == UIGestureRecognizerStateRecognized) {
|
||||
BOOL show = self.topViewController.toolbarItems.count;
|
||||
BOOL show = self.canShowToolbar;
|
||||
CGFloat yTranslation = [sender translationInView:self.view].y;
|
||||
CGFloat yVelocity = [sender velocityInView:self.view].y;
|
||||
if (yVelocity > 2000) {
|
||||
|
||||
@@ -67,6 +67,10 @@ 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 showSearchBarInitially;
|
||||
/// Defaults to NO.
|
||||
///
|
||||
/// Setting this to YES will make the search bar activate whenever the view appears.
|
||||
@property (nonatomic) BOOL activatesSearchBarAutomatically;
|
||||
|
||||
/// nil unless showsSearchBar is set to YES.
|
||||
///
|
||||
|
||||
@@ -50,15 +50,12 @@ CGFloat const kFLEXDebounceForExpensiveIO = 0.5;
|
||||
#pragma mark - Initialization
|
||||
|
||||
- (id)init {
|
||||
#if FLEX_AT_LEAST_IOS13_SDK
|
||||
if (@available(iOS 13.0, *)) {
|
||||
self = [self initWithStyle:UITableViewStyleInsetGrouped];
|
||||
} else {
|
||||
self = [self initWithStyle:UITableViewStyleGrouped];
|
||||
}
|
||||
#else
|
||||
self = [self initWithStyle:UITableViewStyleGrouped];
|
||||
#endif
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
@@ -106,11 +103,9 @@ CGFloat const kFLEXDebounceForExpensiveIO = 0.5;
|
||||
|
||||
self.automaticallyShowsSearchBarCancelButton = YES;
|
||||
|
||||
#if FLEX_AT_LEAST_IOS13_SDK
|
||||
if (@available(iOS 13, *)) {
|
||||
self.searchController.automaticallyShowsScopeBar = NO;
|
||||
}
|
||||
#endif
|
||||
|
||||
[self addSearchController:self.searchController];
|
||||
} else {
|
||||
@@ -170,21 +165,17 @@ CGFloat const kFLEXDebounceForExpensiveIO = 0.5;
|
||||
}
|
||||
|
||||
- (BOOL)automaticallyShowsSearchBarCancelButton {
|
||||
#if FLEX_AT_LEAST_IOS13_SDK
|
||||
if (@available(iOS 13, *)) {
|
||||
return self.searchController.automaticallyShowsCancelButton;
|
||||
}
|
||||
#endif
|
||||
|
||||
return _automaticallyShowsSearchBarCancelButton;
|
||||
}
|
||||
|
||||
- (void)setAutomaticallyShowsSearchBarCancelButton:(BOOL)value {
|
||||
#if FLEX_AT_LEAST_IOS13_SDK
|
||||
if (@available(iOS 13, *)) {
|
||||
self.searchController.automaticallyShowsCancelButton = value;
|
||||
}
|
||||
#endif
|
||||
|
||||
_automaticallyShowsSearchBarCancelButton = value;
|
||||
}
|
||||
@@ -238,7 +229,7 @@ CGFloat const kFLEXDebounceForExpensiveIO = 0.5;
|
||||
self.tableView.keyboardDismissMode = UIScrollViewKeyboardDismissModeOnDrag;
|
||||
|
||||
// Toolbar
|
||||
self.navigationController.toolbarHidden = NO;
|
||||
self.navigationController.toolbarHidden = self.toolbarItems.count > 0;
|
||||
self.navigationController.hidesBarsOnSwipe = YES;
|
||||
|
||||
// On iOS 13, the root view controller shows it's search bar no matter what.
|
||||
@@ -256,12 +247,17 @@ CGFloat const kFLEXDebounceForExpensiveIO = 0.5;
|
||||
- (void)viewWillAppear:(BOOL)animated {
|
||||
[super viewWillAppear:animated];
|
||||
|
||||
// When going back, make the search bar reappear instead of hiding
|
||||
if (@available(iOS 11.0, *)) {
|
||||
// When going back, make the search bar reappear instead of hiding
|
||||
if ((self.pinSearchBar || self.showSearchBarInitially) && !self.didInitiallyRevealSearchBar) {
|
||||
self.navigationItem.hidesSearchBarWhenScrolling = NO;
|
||||
}
|
||||
}
|
||||
|
||||
// Make the keyboard seem to appear faster
|
||||
if (self.activatesSearchBarAutomatically) {
|
||||
[self makeKeyboardAppearNow];
|
||||
}
|
||||
|
||||
[self setupToolbarItems];
|
||||
}
|
||||
@@ -283,6 +279,17 @@ CGFloat const kFLEXDebounceForExpensiveIO = 0.5;
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
if (self.activatesSearchBarAutomatically) {
|
||||
// Keyboard has appeared, now we call this as we soon present our search bar
|
||||
[self removeDummyTextField];
|
||||
|
||||
// Activate the search bar
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
// This doesn't work unless it's wrapped in this dispatch_async call
|
||||
[self.searchController.searchBar becomeFirstResponder];
|
||||
});
|
||||
}
|
||||
|
||||
// We only want to reveal the search bar when the view controller first appears.
|
||||
self.didInitiallyRevealSearchBar = YES;
|
||||
@@ -522,6 +529,30 @@ CGFloat const kFLEXDebounceForExpensiveIO = 0.5;
|
||||
|
||||
#pragma mark - Search Bar
|
||||
|
||||
#pragma mark Faster keyboard
|
||||
|
||||
static UITextField *kDummyTextField = nil;
|
||||
|
||||
/// Make the keyboard appear instantly. We use this to make the
|
||||
/// keyboard appear faster when the search bar is set to appear initially.
|
||||
/// You must call \c -removeDummyTextField before your search bar is to appear.
|
||||
- (void)makeKeyboardAppearNow {
|
||||
if (!kDummyTextField) {
|
||||
kDummyTextField = [UITextField new];
|
||||
kDummyTextField.autocorrectionType = UITextAutocorrectionTypeNo;
|
||||
}
|
||||
|
||||
kDummyTextField.inputAccessoryView = self.searchController.searchBar.inputAccessoryView;
|
||||
[UIApplication.sharedApplication.keyWindow addSubview:kDummyTextField];
|
||||
[kDummyTextField becomeFirstResponder];
|
||||
}
|
||||
|
||||
- (void)removeDummyTextField {
|
||||
if (kDummyTextField.superview) {
|
||||
[kDummyTextField removeFromSuperview];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark UISearchResultsUpdating
|
||||
|
||||
- (void)updateSearchResultsForSearchController:(UISearchController *)searchController {
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import "FLEXMacros.h"
|
||||
#import "NSArray+FLEX.h"
|
||||
@class FLEXTableView;
|
||||
|
||||
@@ -101,7 +100,6 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
- (nullable void(^)(__kindof UIViewController *host))didPressInfoButtonAction:(NSInteger)row;
|
||||
|
||||
#pragma mark - Context Menus
|
||||
#if FLEX_AT_LEAST_IOS13_SDK
|
||||
|
||||
/// By default, this is the title of the row.
|
||||
/// @return The title of the context menu, if any.
|
||||
@@ -121,7 +119,6 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
/// should be a description of what will be copied, and the values should be
|
||||
/// the strings to copy. Return an empty string as a value to show a disabled action.
|
||||
- (nullable NSArray<NSString *> *)copyMenuItemsForRow:(NSInteger)row API_AVAILABLE(ios(13.0));
|
||||
#endif
|
||||
|
||||
#pragma mark - Cell Configuration
|
||||
|
||||
|
||||
@@ -64,8 +64,6 @@
|
||||
return kFLEXDefaultCell;
|
||||
}
|
||||
|
||||
#if FLEX_AT_LEAST_IOS13_SDK
|
||||
|
||||
- (NSString *)menuTitleForRow:(NSInteger)row {
|
||||
NSString *title = [self titleForRow:row];
|
||||
NSString *subtitle = [self menuSubtitleForRow:row];
|
||||
@@ -127,8 +125,6 @@
|
||||
return @[];
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
- (NSArray<NSString *> *)copyMenuItemsForRow:(NSInteger)row {
|
||||
return nil;
|
||||
}
|
||||
|
||||
@@ -30,29 +30,21 @@ FLEXTableViewCellReuseIdentifier const kFLEXCodeFontCell = @"kFLEXCodeFontCell";
|
||||
@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
|
||||
}
|
||||
|
||||
#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 {
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
self.inputTextView.delegate = self;
|
||||
self.inputTextView.inputAccessoryView = [self createToolBar];
|
||||
if (@available(iOS 11, *)) {
|
||||
self.inputTextView.smartQuotesType = UITextSmartQuotesTypeNo;
|
||||
[self.inputTextView.layer setValue:@YES forKey:@"continuousCorners"];
|
||||
} else {
|
||||
self.inputTextView.layer.borderWidth = 1.f;
|
||||
|
||||
@@ -21,13 +21,22 @@
|
||||
|
||||
- (BOOL)shouldReceiveTouchAtWindowPoint:(CGPoint)pointInWindowCoordinates;
|
||||
|
||||
/// @brief Used to present (or dismiss) a modal view controller ("tool"), typically triggered by pressing a button in the toolbar.
|
||||
/// @brief Used to present (or dismiss) a modal view controller ("tool"),
|
||||
/// typically triggered by pressing a button in the toolbar.
|
||||
///
|
||||
/// If a tool is already presented, this method simply dismisses it and calls the completion block.
|
||||
/// If no tool is presented, @code future() @endcode is presented and the completion block is called.
|
||||
- (void)toggleToolWithViewControllerProvider:(UINavigationController *(^)(void))future
|
||||
completion:(void (^)(void))completion;
|
||||
|
||||
/// @brief Used to present (or dismiss) a modal view controller ("tool"),
|
||||
/// typically triggered by pressing a button in the toolbar.
|
||||
///
|
||||
/// If a tool is already presented, this method dismisses it and presents the given tool.
|
||||
/// The completion block is called once the tool has been presented.
|
||||
- (void)presentTool:(UINavigationController *(^)(void))future
|
||||
completion:(void (^)(void))completion;
|
||||
|
||||
// Keyboard shortcut helpers
|
||||
|
||||
- (void)toggleSelectTool;
|
||||
|
||||
@@ -925,12 +925,26 @@ typedef NS_ENUM(NSUInteger, FLEXExplorerMode) {
|
||||
- (void)toggleToolWithViewControllerProvider:(UINavigationController *(^)(void))future
|
||||
completion:(void (^)(void))completion {
|
||||
if (self.presentedViewController) {
|
||||
// We do NOT want to present the future; this is
|
||||
// a convenience method for toggling the SAME TOOL
|
||||
[self dismissViewControllerAnimated:YES completion:completion];
|
||||
} else if (future) {
|
||||
[self presentViewController:future() animated:YES completion:completion];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)presentTool:(UINavigationController *(^)(void))future
|
||||
completion:(void (^)(void))completion {
|
||||
if (self.presentedViewController) {
|
||||
// If a tool is already presented, dismiss it first
|
||||
[self dismissViewControllerAnimated:YES completion:^{
|
||||
[self presentViewController:future() animated:YES completion:completion];
|
||||
}];
|
||||
} else if (future) {
|
||||
[self presentViewController:future() animated:YES completion:completion];
|
||||
}
|
||||
}
|
||||
|
||||
- (FLEXWindow *)window {
|
||||
return (id)self.view.window;
|
||||
}
|
||||
|
||||
@@ -20,6 +20,5 @@
|
||||
#import <FLEX/FLEX-Categories.h>
|
||||
#import <FLEX/FLEX-ObjectExploring.h>
|
||||
|
||||
#import <FLEX/FLEXMacros.h>
|
||||
#import <FLEX/FLEXAlert.h>
|
||||
#import <FLEX/FLEXResources.h>
|
||||
|
||||
@@ -8,12 +8,21 @@
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@class FLEXDBQueryRowCell;
|
||||
|
||||
extern NSString * const kFLEXDBQueryRowCellReuse;
|
||||
|
||||
@protocol FLEXDBQueryRowCellLayoutSource <NSObject>
|
||||
|
||||
- (CGFloat)dbQueryRowCell:(FLEXDBQueryRowCell *)dbQueryRowCell minXForColumn:(NSUInteger)column;
|
||||
- (CGFloat)dbQueryRowCell:(FLEXDBQueryRowCell *)dbQueryRowCell widthForColumn:(NSUInteger)column;
|
||||
|
||||
@end
|
||||
|
||||
@interface FLEXDBQueryRowCell : UITableViewCell
|
||||
|
||||
/// An array of NSString, NSNumber, or NSData objects
|
||||
@property (nonatomic) NSArray *data;
|
||||
@property (nonatomic, weak) id<FLEXDBQueryRowCellLayoutSource> layoutSource;
|
||||
|
||||
@end
|
||||
|
||||
@@ -63,11 +63,12 @@ NSString * const kFLEXDBQueryRowCellReuse = @"kFLEXDBQueryRowCellReuse";
|
||||
- (void)layoutSubviews {
|
||||
[super layoutSubviews];
|
||||
|
||||
CGFloat width = self.contentView.frame.size.width / self.labels.count;
|
||||
CGFloat height = self.contentView.frame.size.height;
|
||||
|
||||
[self.labels flex_forEach:^(UILabel *label, NSUInteger i) {
|
||||
label.frame = CGRectMake(width * i + 5, 0, (width - 10), height);
|
||||
CGFloat width = [self.layoutSource dbQueryRowCell:self widthForColumn:i];
|
||||
CGFloat minX = [self.layoutSource dbQueryRowCell:self minXForColumn:i];
|
||||
label.frame = CGRectMake(minX + 5, 0, (width - 10), height);
|
||||
}];
|
||||
}
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
|
||||
@optional
|
||||
|
||||
- (NSArray<NSString *> *)queryRowIDsInTable:(NSString *)tableName;
|
||||
- (FLEXSQLResult *)executeStatement:(NSString *)SQLStatement;
|
||||
|
||||
@end
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
- (NSString *)rowTitle:(NSInteger)row;
|
||||
- (NSArray<NSString *> *)contentForRow:(NSInteger)row;
|
||||
|
||||
- (CGFloat)multiColumnTableView:(FLEXMultiColumnTableView *)tableView widthForContentCellInColumn:(NSInteger)column;
|
||||
- (CGFloat)multiColumnTableView:(FLEXMultiColumnTableView *)tableView minWidthForContentCellInColumn:(NSInteger)column;
|
||||
- (CGFloat)multiColumnTableView:(FLEXMultiColumnTableView *)tableView heightForContentCellInRow:(NSInteger)row;
|
||||
- (CGFloat)heightForTopHeaderInTableView:(FLEXMultiColumnTableView *)tableView;
|
||||
- (CGFloat)widthForLeftHeaderInTableView:(FLEXMultiColumnTableView *)tableView;
|
||||
|
||||
@@ -9,10 +9,12 @@
|
||||
#import "FLEXMultiColumnTableView.h"
|
||||
#import "FLEXDBQueryRowCell.h"
|
||||
#import "FLEXTableLeftCell.h"
|
||||
#import "NSArray+FLEX.h"
|
||||
#import "FLEXColor.h"
|
||||
|
||||
@interface FLEXMultiColumnTableView () <
|
||||
UITableViewDataSource, UITableViewDelegate, UIScrollViewDelegate
|
||||
UITableViewDataSource, UITableViewDelegate,
|
||||
UIScrollViewDelegate, FLEXDBQueryRowCellLayoutSource
|
||||
>
|
||||
|
||||
@property (nonatomic) UIScrollView *contentScrollView;
|
||||
@@ -21,12 +23,12 @@
|
||||
@property (nonatomic) UITableView *contentTableView;
|
||||
@property (nonatomic) UIView *leftHeader;
|
||||
|
||||
@property (nonatomic) NSArray<UIView *> *headerViews;
|
||||
|
||||
/// \c NSNotFound if no column selected
|
||||
@property (nonatomic) NSInteger sortColumn;
|
||||
@property (nonatomic) FLEXTableColumnHeaderSortType sortType;
|
||||
|
||||
@property (nonatomic) NSArray *rowData;
|
||||
|
||||
@property (nonatomic, readonly) NSInteger numberOfColumns;
|
||||
@property (nonatomic, readonly) NSInteger numberOfRows;
|
||||
@property (nonatomic, readonly) CGFloat topHeaderHeight;
|
||||
@@ -71,9 +73,9 @@ static const CGFloat kColumnMargin = 1;
|
||||
}
|
||||
|
||||
CGFloat contentWidth = 0.0;
|
||||
NSInteger rowsCount = self.numberOfColumns;
|
||||
for (int i = 0; i < rowsCount; i++) {
|
||||
contentWidth += [self contentWidthForColumn:i];
|
||||
NSInteger columnsCount = self.numberOfColumns;
|
||||
for (int i = 0; i < columnsCount; i++) {
|
||||
contentWidth += CGRectGetWidth(self.headerViews[i].bounds);
|
||||
}
|
||||
|
||||
CGFloat contentHeight = height - topheaderHeight - topInsets;
|
||||
@@ -147,26 +149,30 @@ static const CGFloat kColumnMargin = 1;
|
||||
#pragma mark - Data
|
||||
|
||||
- (void)reloadData {
|
||||
[self loadHeaderData];
|
||||
[self loadLeftViewData];
|
||||
[self loadContentData];
|
||||
[self loadHeaderData];
|
||||
}
|
||||
|
||||
- (void)loadHeaderData {
|
||||
// Remove existing headers, if any
|
||||
for (UIView *subview in self.headerScrollView.subviews) {
|
||||
for (UIView *subview in self.headerViews) {
|
||||
[subview removeFromSuperview];
|
||||
}
|
||||
|
||||
CGFloat xOffset = 0.0;
|
||||
for (NSInteger column = 0; column < self.numberOfColumns; column++) {
|
||||
CGFloat width = [self contentWidthForColumn:column] + self.columnMargin;
|
||||
|
||||
FLEXTableColumnHeader *header = [[FLEXTableColumnHeader alloc]
|
||||
initWithFrame:CGRectMake(xOffset, 0, width, self.topHeaderHeight - 1)
|
||||
];
|
||||
__block CGFloat xOffset = 0;
|
||||
|
||||
self.headerViews = [NSArray flex_forEachUpTo:self.numberOfColumns map:^id(NSUInteger column) {
|
||||
FLEXTableColumnHeader *header = [FLEXTableColumnHeader new];
|
||||
header.titleLabel.text = [self columnTitle:column];
|
||||
|
||||
CGSize fittingSize = CGSizeMake(CGFLOAT_MAX, self.topHeaderHeight - 1);
|
||||
CGFloat width = self.columnMargin + MAX(
|
||||
[self minContentWidthForColumn:column],
|
||||
[header sizeThatFits:fittingSize].width
|
||||
);
|
||||
header.frame = CGRectMake(xOffset, 0, width, self.topHeaderHeight - 1);
|
||||
|
||||
if (column == self.sortColumn) {
|
||||
header.sortType = self.sortType;
|
||||
}
|
||||
@@ -178,21 +184,22 @@ static const CGFloat kColumnMargin = 1;
|
||||
[header addGestureRecognizer:gesture];
|
||||
header.userInteractionEnabled = YES;
|
||||
|
||||
[self.headerScrollView addSubview:header];
|
||||
xOffset += width;
|
||||
}
|
||||
[self.headerScrollView addSubview:header];
|
||||
return header;
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)contentHeaderTap:(UIGestureRecognizer *)gesture {
|
||||
NSInteger newSortColumn = [self.headerScrollView.subviews indexOfObject:gesture.view];
|
||||
NSInteger newSortColumn = [self.headerViews indexOfObject:gesture.view];
|
||||
FLEXTableColumnHeaderSortType newType = FLEXNextTableColumnHeaderSortType(self.sortType);
|
||||
|
||||
// Reset old header
|
||||
FLEXTableColumnHeader *oldHeader = (id)self.headerScrollView.subviews[self.sortColumn];
|
||||
FLEXTableColumnHeader *oldHeader = (id)self.headerViews[self.sortColumn];
|
||||
oldHeader.sortType = FLEXTableColumnHeaderSortTypeNone;
|
||||
|
||||
// Update new header
|
||||
FLEXTableColumnHeader *newHeader = (id)self.headerScrollView.subviews[newSortColumn];
|
||||
FLEXTableColumnHeader *newHeader = (id)self.headerViews[newSortColumn];
|
||||
newHeader.sortType = newType;
|
||||
|
||||
// Update self
|
||||
@@ -227,13 +234,13 @@ static const CGFloat kColumnMargin = 1;
|
||||
}
|
||||
// Right side table view for data
|
||||
else {
|
||||
self.rowData = [self.dataSource contentForRow:indexPath.row];
|
||||
FLEXDBQueryRowCell *cell = [tableView
|
||||
dequeueReusableCellWithIdentifier:kFLEXDBQueryRowCellReuse forIndexPath:indexPath
|
||||
];
|
||||
|
||||
cell.contentView.backgroundColor = backgroundColor;
|
||||
cell.data = [self.dataSource contentForRow:indexPath.row];
|
||||
cell.layoutSource = self;
|
||||
NSAssert(cell.data.count == self.numberOfColumns, @"Count of data provided was incorrect");
|
||||
return cell;
|
||||
}
|
||||
@@ -280,6 +287,17 @@ static const CGFloat kColumnMargin = 1;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark FLEXDBQueryRowCellLayoutSource
|
||||
|
||||
- (CGFloat)dbQueryRowCell:(FLEXDBQueryRowCell *)dbQueryRowCell minXForColumn:(NSUInteger)column {
|
||||
return CGRectGetMinX(self.headerViews[column].frame);
|
||||
}
|
||||
|
||||
- (CGFloat)dbQueryRowCell:(FLEXDBQueryRowCell *)dbQueryRowCell widthForColumn:(NSUInteger)column {
|
||||
return CGRectGetWidth(self.headerViews[column].bounds);
|
||||
}
|
||||
|
||||
|
||||
#pragma mark DataSource Accessor
|
||||
|
||||
- (NSInteger)numberOfRows {
|
||||
@@ -298,8 +316,8 @@ static const CGFloat kColumnMargin = 1;
|
||||
return [self.dataSource rowTitle:row];
|
||||
}
|
||||
|
||||
- (CGFloat)contentWidthForColumn:(NSInteger)column {
|
||||
return [self.dataSource multiColumnTableView:self widthForContentCellInColumn:column];
|
||||
- (CGFloat)minContentWidthForColumn:(NSInteger)column {
|
||||
return [self.dataSource multiColumnTableView:self minWidthForContentCellInColumn:column];
|
||||
}
|
||||
|
||||
- (CGFloat)contentHeightForRow:(NSInteger)row {
|
||||
|
||||
@@ -30,7 +30,7 @@ static NSString * const QUERY_TABLENAMES = @"SELECT name FROM sqlite_master WHER
|
||||
- (instancetype)initWithPath:(NSString *)path {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
self.path = path;;
|
||||
self.path = path;
|
||||
}
|
||||
|
||||
return self;
|
||||
@@ -114,9 +114,17 @@ static NSString * const QUERY_TABLENAMES = @"SELECT name FROM sqlite_master WHER
|
||||
}
|
||||
|
||||
- (NSArray<NSArray *> *)queryAllDataInTable:(NSString *)tableName {
|
||||
return [self executeStatement:[@"SELECT * FROM "
|
||||
stringByAppendingString:tableName
|
||||
]].rows ?: @[];
|
||||
NSString *command = [NSString stringWithFormat:@"SELECT * FROM \"%@\"", tableName];
|
||||
return [self executeStatement:command].rows ?: @[];
|
||||
}
|
||||
|
||||
- (NSArray<NSString *> *)queryRowIDsInTable:(NSString *)tableName {
|
||||
NSString *command = [NSString stringWithFormat:@"SELECT rowid FROM \"%@\"", tableName];
|
||||
NSArray<NSArray<NSString *> *> *data = [self executeStatement:command].rows ?: @[];
|
||||
|
||||
return [data flex_mapped:^id(NSArray<NSString *> *obj, NSUInteger idx) {
|
||||
return obj.firstObject;
|
||||
}];
|
||||
}
|
||||
|
||||
- (FLEXSQLResult *)executeStatement:(NSString *)sql {
|
||||
@@ -257,7 +265,7 @@ static NSString * const QUERY_TABLENAMES = @"SELECT name FROM sqlite_master WHER
|
||||
- (FLEXSQLResult *)errorResult:(NSString *)description {
|
||||
const char *error = sqlite3_errmsg(_db);
|
||||
NSString *message = error ? @(error) : [NSString
|
||||
stringWithFormat:@"(%@: empty error", description
|
||||
stringWithFormat:@"(%@: empty error)", description
|
||||
];
|
||||
|
||||
return [FLEXSQLResult error:message];
|
||||
|
||||
@@ -11,6 +11,9 @@
|
||||
#import "UIFont+FLEX.h"
|
||||
#import "FLEXUtility.h"
|
||||
|
||||
static const CGFloat kMargin = 5;
|
||||
static const CGFloat kArrowWidth = 20;
|
||||
|
||||
@interface FLEXTableColumnHeader ()
|
||||
@property (nonatomic, readonly) UILabel *arrowLabel;
|
||||
@property (nonatomic, readonly) UIView *lineView;
|
||||
@@ -60,9 +63,16 @@
|
||||
|
||||
CGSize size = self.frame.size;
|
||||
|
||||
self.titleLabel.frame = CGRectMake(5, 0, size.width - 25, size.height);
|
||||
self.arrowLabel.frame = CGRectMake(size.width - 20, 0, 20, size.height);
|
||||
self.titleLabel.frame = CGRectMake(kMargin, 0, size.width - kArrowWidth - kMargin, size.height);
|
||||
self.arrowLabel.frame = CGRectMake(size.width - kArrowWidth, 0, kArrowWidth, size.height);
|
||||
self.lineView.frame = CGRectMake(size.width - 1, 2, FLEXPointsToPixels(1), size.height - 4);
|
||||
}
|
||||
|
||||
- (CGSize)sizeThatFits:(CGSize)size {
|
||||
CGFloat margins = kArrowWidth - 2 * kMargin;
|
||||
size = CGSizeMake(size.width - margins, size.height);
|
||||
CGFloat width = [_titleLabel sizeThatFits:size].width + margins;
|
||||
return CGSizeMake(width, size.height);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -7,10 +7,20 @@
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import "FLEXDatabaseManager.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface FLEXTableContentViewController : UIViewController
|
||||
|
||||
/// Display a table with the given columns, rows, and name.
|
||||
/// @param databaseManager an optional manager to allow modifying the table.
|
||||
+ (instancetype)columns:(NSArray<NSString *> *)columnNames
|
||||
rows:(NSArray<NSArray<NSString *> *> *)rowData;
|
||||
rows:(NSArray<NSArray<NSString *> *> *)rowData
|
||||
rowIDs:(nullable NSArray<NSString *> *)rowIds
|
||||
tableName:(NSString *)tableName
|
||||
database:(nullable id<FLEXDatabaseManager>)databaseManager;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@@ -10,12 +10,16 @@
|
||||
#import "FLEXMultiColumnTableView.h"
|
||||
#import "FLEXWebViewController.h"
|
||||
#import "FLEXUtility.h"
|
||||
#import "UIBarButtonItem+FLEX.h"
|
||||
|
||||
@interface FLEXTableContentViewController () <
|
||||
FLEXMultiColumnTableViewDataSource, FLEXMultiColumnTableViewDelegate
|
||||
>
|
||||
@property (nonatomic, readonly) NSArray<NSString *> *columns;
|
||||
@property (nonatomic, copy) NSArray<NSArray *> *rows;
|
||||
@property (nonatomic) NSMutableArray<NSArray *> *rows;
|
||||
@property (nonatomic, readonly) NSString *tableName;
|
||||
@property (nonatomic, nullable) NSMutableArray<NSString *> *rowIDs;
|
||||
@property (nonatomic, readonly, nullable) id<FLEXDatabaseManager> databaseManager;
|
||||
|
||||
@property (nonatomic) FLEXMultiColumnTableView *multiColumnView;
|
||||
@end
|
||||
@@ -23,10 +27,16 @@
|
||||
@implementation FLEXTableContentViewController
|
||||
|
||||
+ (instancetype)columns:(NSArray<NSString *> *)columnNames
|
||||
rows:(NSArray<NSArray<NSString *> *> *)rowData {
|
||||
rows:(NSArray<NSArray<NSString *> *> *)rowData
|
||||
rowIDs:(nullable NSArray<NSString *> *)rowIDs
|
||||
tableName:(NSString *)tableName
|
||||
database:(nullable id<FLEXDatabaseManager>)databaseManager {
|
||||
FLEXTableContentViewController *controller = [self new];
|
||||
controller->_columns = columnNames;
|
||||
controller->_rows = rowData;
|
||||
controller->_columns = columnNames.copy;
|
||||
controller->_rows = rowData.mutableCopy;
|
||||
controller->_rowIDs = rowIDs.mutableCopy;
|
||||
controller->_tableName = tableName.copy;
|
||||
controller->_databaseManager = databaseManager;
|
||||
return controller;
|
||||
}
|
||||
|
||||
@@ -38,9 +48,10 @@
|
||||
|
||||
- (void)viewDidLoad {
|
||||
[super viewDidLoad];
|
||||
|
||||
self.title = self.tableName;
|
||||
self.edgesForExtendedLayout = UIRectEdgeNone;
|
||||
[self.multiColumnView reloadData];
|
||||
[self setupToolbarItems];
|
||||
}
|
||||
|
||||
- (FLEXMultiColumnTableView *)multiColumnView {
|
||||
@@ -84,8 +95,8 @@
|
||||
}
|
||||
|
||||
- (CGFloat)multiColumnTableView:(FLEXMultiColumnTableView *)tableView
|
||||
widthForContentCellInColumn:(NSInteger)column {
|
||||
return 120;
|
||||
minWidthForContentCellInColumn:(NSInteger)column {
|
||||
return 100;
|
||||
}
|
||||
|
||||
- (CGFloat)heightForTopHeaderInTableView:(FLEXMultiColumnTableView *)tableView {
|
||||
@@ -118,6 +129,27 @@
|
||||
make.button(@"Copy").handler(^(NSArray<NSString *> *strings) {
|
||||
UIPasteboard.generalPasteboard.string = message;
|
||||
});
|
||||
|
||||
// Option to delete row
|
||||
BOOL hasRowID = self.rows.count && row < self.rows.count;
|
||||
if (hasRowID) {
|
||||
make.button(@"Delete").destructiveStyle().handler(^(NSArray<NSString *> *strings) {
|
||||
NSString *deleteRow = [NSString stringWithFormat:
|
||||
@"DELETE FROM %@ WHERE rowid = %@",
|
||||
self.tableName, self.rowIDs[row]
|
||||
];
|
||||
|
||||
[self executeStatementAndShowResult:deleteRow completion:^(BOOL success) {
|
||||
// Remove deleted row and reload view
|
||||
if (success) {
|
||||
[self.rowIDs removeObjectAtIndex:row];
|
||||
[self.rows removeObjectAtIndex:row];
|
||||
[self.multiColumnView reloadData];
|
||||
}
|
||||
}];
|
||||
});
|
||||
}
|
||||
|
||||
make.button(@"Dismiss").cancelStyle();
|
||||
} showFrom:self];
|
||||
}
|
||||
@@ -127,7 +159,8 @@
|
||||
sortType:(FLEXTableColumnHeaderSortType)sortType {
|
||||
|
||||
NSArray<NSArray *> *sortContentData = [self.rows
|
||||
sortedArrayUsingComparator:^NSComparisonResult(NSArray *obj1, NSArray *obj2) {
|
||||
sortedArrayWithOptions:NSSortStable
|
||||
usingComparator:^NSComparisonResult(NSArray *obj1, NSArray *obj2) {
|
||||
id a = obj1[column], b = obj2[column];
|
||||
if (a == NSNull.null) {
|
||||
return NSOrderedAscending;
|
||||
@@ -135,6 +168,11 @@
|
||||
if (b == NSNull.null) {
|
||||
return NSOrderedDescending;
|
||||
}
|
||||
|
||||
if ([a respondsToSelector:@selector(compare:options:)] &&
|
||||
[b respondsToSelector:@selector(compare:options:)]) {
|
||||
return [a compare:b options:NSNumericSearch];
|
||||
}
|
||||
|
||||
if ([a respondsToSelector:@selector(compare:)] && [b respondsToSelector:@selector(compare:)]) {
|
||||
return [a compare:b];
|
||||
@@ -148,12 +186,11 @@
|
||||
sortContentData = sortContentData.reverseObjectEnumerator.allObjects.copy;
|
||||
}
|
||||
|
||||
self.rows = sortContentData;
|
||||
self.rows = sortContentData.mutableCopy;
|
||||
[self.multiColumnView reloadData];
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark About Transition
|
||||
#pragma mark - About Transition
|
||||
|
||||
- (void)willTransitionToTraitCollection:(UITraitCollection *)newCollection
|
||||
withTransitionCoordinator:(id <UIViewControllerTransitionCoordinator>)coordinator {
|
||||
@@ -171,4 +208,57 @@
|
||||
} completion:nil];
|
||||
}
|
||||
|
||||
#pragma mark - Toolbar
|
||||
|
||||
- (void)setupToolbarItems {
|
||||
// We do not support modifying realm databases
|
||||
if (![self.databaseManager respondsToSelector:@selector(executeStatement:)]) {
|
||||
return;
|
||||
}
|
||||
|
||||
UIBarButtonItem *trashButton = [FLEXBarButtonItemSystem(Trash, self, @selector(trashPressed))
|
||||
flex_withTintColor:UIColor.redColor
|
||||
];
|
||||
trashButton.enabled = self.databaseManager && self.rows.count;
|
||||
self.toolbarItems = @[UIBarButtonItem.flex_flexibleSpace, trashButton];
|
||||
}
|
||||
|
||||
- (void)trashPressed {
|
||||
[FLEXAlert makeAlert:^(FLEXAlert *make) {
|
||||
make.title(@"Delete All Rows");
|
||||
make.message(@"All rows in this table will be permanently deleted.\nDo you want to proceed?");
|
||||
|
||||
make.button(@"Yes, I'm sure").destructiveStyle().handler(^(NSArray<NSString *> *strings) {
|
||||
NSString *deleteAll = [NSString stringWithFormat:@"DELETE FROM %@", self.tableName];
|
||||
[self executeStatementAndShowResult:deleteAll completion:^(BOOL success) {
|
||||
// Only dismiss on success
|
||||
if (success) {
|
||||
[self.navigationController popViewControllerAnimated:YES];
|
||||
}
|
||||
}];
|
||||
});
|
||||
make.button(@"Cancel").cancelStyle();
|
||||
} showFrom:self];
|
||||
}
|
||||
|
||||
#pragma mark - Helpers
|
||||
|
||||
- (void)executeStatementAndShowResult:(NSString *)statement completion:(void (^_Nullable)(BOOL success))completion {
|
||||
FLEXSQLResult *result = [self.databaseManager executeStatement:statement];
|
||||
|
||||
[FLEXAlert makeAlert:^(FLEXAlert *make) {
|
||||
if (result.isError) {
|
||||
make.title(@"Error");
|
||||
}
|
||||
|
||||
make.message(result.message ?: @"<no output>");
|
||||
make.button(@"Dismiss").cancelStyle().handler(^(NSArray<NSString *> *_) {
|
||||
if (completion) {
|
||||
completion(!result.isError);
|
||||
}
|
||||
});
|
||||
} showFrom:self];
|
||||
}
|
||||
|
||||
|
||||
@end
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#import "FLEXMutableListSection.h"
|
||||
#import "NSArray+FLEX.h"
|
||||
#import "FLEXAlert.h"
|
||||
#import "FLEXMacros.h"
|
||||
|
||||
@interface FLEXTableListViewController ()
|
||||
@property (nonatomic, readonly) id<FLEXDatabaseManager> dbm;
|
||||
@@ -70,9 +71,10 @@
|
||||
self.tables.selectionHandler = ^(FLEXTableListViewController *host, NSString *tableName) {
|
||||
NSArray *rows = [host.dbm queryAllDataInTable:tableName];
|
||||
NSArray *columns = [host.dbm queryAllColumnsOfTable:tableName];
|
||||
|
||||
UIViewController *resultsScreen = [FLEXTableContentViewController columns:columns rows:rows];
|
||||
resultsScreen.title = tableName;
|
||||
NSArray *rowIDs = [host.dbm queryRowIDsInTable:tableName];
|
||||
UIViewController *resultsScreen = [FLEXTableContentViewController
|
||||
columns:columns rows:rows rowIDs:rowIDs tableName:tableName database:host.dbm
|
||||
];
|
||||
[host.navigationController pushViewController:resultsScreen animated:YES];
|
||||
};
|
||||
|
||||
@@ -100,7 +102,7 @@
|
||||
[FLEXAlert showAlert:@"Message" message:result.message from:self];
|
||||
} else {
|
||||
UIViewController *resultsScreen = [FLEXTableContentViewController
|
||||
columns:result.columns rows:result.rows
|
||||
columns:result.columns rows:result.rows rowIDs:nil tableName:@"" database:nil
|
||||
];
|
||||
|
||||
[self.navigationController pushViewController:resultsScreen animated:YES];
|
||||
|
||||
@@ -35,6 +35,7 @@ static const NSInteger kFLEXLiveObjectsSortBySizeIndex = 2;
|
||||
|
||||
self.showsSearchBar = YES;
|
||||
self.showSearchBarInitially = YES;
|
||||
self.activatesSearchBarAutomatically = YES;
|
||||
self.searchBarDebounceInterval = kFLEXDebounceInstant;
|
||||
self.showsCarousel = YES;
|
||||
self.carousel.items = @[@"A→Z", @"Count", @"Size"];
|
||||
@@ -45,15 +46,6 @@ static const NSInteger kFLEXLiveObjectsSortBySizeIndex = 2;
|
||||
[self reloadTableData];
|
||||
}
|
||||
|
||||
- (void)viewDidAppear:(BOOL)animated {
|
||||
[super viewDidAppear:animated];
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
// This doesn't work unless it's wrapped in this dispatch_async call
|
||||
[self.searchController.searchBar becomeFirstResponder];
|
||||
});
|
||||
}
|
||||
|
||||
- (NSArray<NSString *> *)allClassNames {
|
||||
return self.instanceCountsForClassNames.allKeys;
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ typedef NS_ENUM(NSUInteger, FLEXObjectReferenceSection) {
|
||||
@property (nonatomic, copy) NSArray<FLEXMutableListSection *> *sections;
|
||||
@property (nonatomic, copy) NSArray<FLEXMutableListSection *> *allSections;
|
||||
|
||||
@property (nonatomic, readonly) NSArray<FLEXObjectRef *> *references;
|
||||
@property (nonatomic, readonly, nullable) NSArray<FLEXObjectRef *> *references;
|
||||
@property (nonatomic, readonly) NSArray<NSPredicate *> *predicates;
|
||||
@property (nonatomic, readonly) NSArray<NSString *> *sectionTitles;
|
||||
|
||||
@@ -122,7 +122,7 @@ typedef NS_ENUM(NSUInteger, FLEXObjectReferenceSection) {
|
||||
|
||||
#pragma mark - Initialization
|
||||
|
||||
- (id)initWithReferences:(NSArray<FLEXObjectRef *> *)references {
|
||||
- (id)initWithReferences:(nullable NSArray<FLEXObjectRef *> *)references {
|
||||
return [self initWithReferences:references predicates:nil sectionTitles:nil];
|
||||
}
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
WKWebViewConfiguration *configuration = [WKWebViewConfiguration new];
|
||||
|
||||
if (@available(iOS 10.0, *)) {
|
||||
configuration.dataDetectorTypes = UIDataDetectorTypeLink;
|
||||
configuration.dataDetectorTypes = WKDataDetectorTypeLink;
|
||||
}
|
||||
|
||||
self.webView = [[WKWebView alloc] initWithFrame:CGRectZero configuration:configuration];
|
||||
|
||||
@@ -356,8 +356,6 @@ typedef NS_ENUM(NSUInteger, FLEXFileBrowserSortAttribute) {
|
||||
// Since our actions are outside of that protocol, we need to manually handle the action forwarding from the cells.
|
||||
}
|
||||
|
||||
#if FLEX_AT_LEAST_IOS13_SDK
|
||||
|
||||
- (UIContextMenuConfiguration *)tableView:(UITableView *)tableView
|
||||
contextMenuConfigurationForRowAtIndexPath:(NSIndexPath *)indexPath
|
||||
point:(CGPoint)point __IOS_AVAILABLE(13.0) {
|
||||
@@ -395,8 +393,6 @@ contextMenuConfigurationForRowAtIndexPath:(NSIndexPath *)indexPath
|
||||
];
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
- (void)openFileController:(NSString *)fullPath {
|
||||
UIDocumentInteractionController *controller = [UIDocumentInteractionController new];
|
||||
controller.URL = [NSURL fileURLWithPath:fullPath];
|
||||
@@ -474,6 +470,9 @@ contextMenuConfigurationForRowAtIndexPath:(NSIndexPath *)indexPath
|
||||
} else {
|
||||
// Share sheet for files
|
||||
UIActivityViewController *shareSheet = [[UIActivityViewController alloc] initWithActivityItems:@[filePath] applicationActivities:nil];
|
||||
if (UIDevice.currentDevice.userInterfaceIdiom == UIUserInterfaceIdiomPad) {
|
||||
shareSheet.popoverPresentationController.sourceView = sender;
|
||||
}
|
||||
[self presentViewController:shareSheet animated:true completion:nil];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,6 +34,9 @@ extern NSString *const kFLEXKeychainClassKey;
|
||||
/// Item description.
|
||||
extern NSString *const kFLEXKeychainDescriptionKey;
|
||||
|
||||
/// Item group.
|
||||
extern NSString *const kFLEXKeychainGroupKey;
|
||||
|
||||
/// Item label.
|
||||
extern NSString *const kFLEXKeychainLabelKey;
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ NSString * const kFLEXKeychainAccountKey = @"acct";
|
||||
NSString * const kFLEXKeychainCreatedAtKey = @"cdat";
|
||||
NSString * const kFLEXKeychainClassKey = @"labl";
|
||||
NSString * const kFLEXKeychainDescriptionKey = @"desc";
|
||||
NSString * const kFLEXKeychainGroupKey = @"agrp";
|
||||
NSString * const kFLEXKeychainLabelKey = @"labl";
|
||||
NSString * const kFLEXKeychainLastModifiedKey = @"mdat";
|
||||
NSString * const kFLEXKeychainWhereKey = @"svce";
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
id service = item[kFLEXKeychainWhereKey];
|
||||
if ([service isKindOfClass:[NSString class]]) {
|
||||
cell.textLabel.text = service;
|
||||
cell.detailTextLabel.text = item[kFLEXKeychainAccountKey];
|
||||
cell.detailTextLabel.text = [item[kFLEXKeychainAccountKey] description];
|
||||
} else {
|
||||
cell.textLabel.text = [NSString stringWithFormat:
|
||||
@"[%@]\n\n%@",
|
||||
@@ -99,8 +99,9 @@
|
||||
NSDictionary *item = self.section.filteredList[idx];
|
||||
|
||||
FLEXKeychainQuery *query = [FLEXKeychainQuery new];
|
||||
query.service = item[kFLEXKeychainWhereKey];
|
||||
query.account = item[kFLEXKeychainAccountKey];
|
||||
query.service = [item[kFLEXKeychainWhereKey] description];
|
||||
query.account = [item[kFLEXKeychainAccountKey] description];
|
||||
query.accessGroup = [item[kFLEXKeychainGroupKey] description];
|
||||
[query fetch:nil];
|
||||
|
||||
return query;
|
||||
@@ -232,6 +233,7 @@
|
||||
make.message(@"Service: ").message(query.service);
|
||||
make.message(@"\nAccount: ").message(query.account);
|
||||
make.message(@"\nPassword: ").message(query.password);
|
||||
make.message(@"\nGroup: ").message(query.accessGroup);
|
||||
|
||||
make.button(@"Copy Service").handler(^(NSArray<NSString *> *strings) {
|
||||
[UIPasteboard.generalPasteboard flex_copy:query.service];
|
||||
|
||||
@@ -51,7 +51,7 @@
|
||||
[self sizeToFit];
|
||||
|
||||
if (@available(iOS 13, *)) {
|
||||
self.appearance = UIKeyboardTypeDefault;
|
||||
self.appearance = UIKeyboardAppearanceDefault;
|
||||
} else {
|
||||
self.appearance = UIKeyboardAppearanceLight;
|
||||
}
|
||||
@@ -84,7 +84,6 @@
|
||||
switch (_appearance) {
|
||||
default:
|
||||
case UIKeyboardAppearanceDefault:
|
||||
#if FLEX_AT_LEAST_IOS13_SDK
|
||||
if (@available(iOS 13, *)) {
|
||||
titleColor = UIColor.labelColor;
|
||||
|
||||
@@ -97,7 +96,6 @@
|
||||
}
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
case UIKeyboardAppearanceLight:
|
||||
titleColor = UIColor.blackColor;
|
||||
backgroundColor = lightColor;
|
||||
|
||||
@@ -61,6 +61,7 @@
|
||||
searchBar.keyboardType = UIKeyboardTypeWebSearch;
|
||||
searchBar.autocorrectionType = UITextAutocorrectionTypeNo;
|
||||
if (@available(iOS 11, *)) {
|
||||
searchBar.smartQuotesType = UITextSmartQuotesTypeNo;
|
||||
searchBar.smartInsertDeleteType = UITextSmartInsertDeleteTypeNo;
|
||||
}
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
self.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
|
||||
|
||||
if (@available(iOS 13, *)) {
|
||||
self.appearance = UIKeyboardTypeDefault;
|
||||
self.appearance = UIKeyboardAppearanceDefault;
|
||||
} else {
|
||||
self.appearance = UIKeyboardAppearanceLight;
|
||||
}
|
||||
@@ -106,7 +106,6 @@
|
||||
|
||||
switch (_appearance) {
|
||||
case UIKeyboardAppearanceDefault:
|
||||
#if FLEX_AT_LEAST_IOS13_SDK
|
||||
if (@available(iOS 13, *)) {
|
||||
borderColor = UIColor.systemBackgroundColor;
|
||||
|
||||
@@ -119,7 +118,6 @@
|
||||
}
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
case UIKeyboardAppearanceLight: {
|
||||
borderColor = UIColor.clearColor;
|
||||
backgroundColor = lightColor;
|
||||
|
||||
@@ -46,6 +46,7 @@
|
||||
// Search bar stuff, must be first because this creates self.searchController
|
||||
self.showsSearchBar = YES;
|
||||
self.showSearchBarInitially = YES;
|
||||
self.activatesSearchBarAutomatically = YES;
|
||||
// Using pinSearchBar on this screen causes a weird visual
|
||||
// thing on the next view controller that gets pushed.
|
||||
//
|
||||
@@ -72,15 +73,6 @@
|
||||
[self.tableView deselectRowAtIndexPath:self.tableView.indexPathForSelectedRow animated:YES];
|
||||
}
|
||||
|
||||
- (void)viewDidAppear:(BOOL)animated {
|
||||
[super viewDidAppear:animated];
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
// This doesn't work unless it's wrapped in this dispatch_async call
|
||||
[self.searchController.searchBar becomeFirstResponder];
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
#pragma mark Delegate stuff
|
||||
|
||||
|
||||
@@ -266,8 +266,6 @@ static BOOL my_os_log_shim_enabled(void *addr) {
|
||||
}
|
||||
}
|
||||
|
||||
#if FLEX_AT_LEAST_IOS13_SDK
|
||||
|
||||
- (UIContextMenuConfiguration *)tableView:(UITableView *)tableView
|
||||
contextMenuConfigurationForRowAtIndexPath:(NSIndexPath *)indexPath
|
||||
point:(CGPoint)point __IOS_AVAILABLE(13.0) {
|
||||
@@ -286,6 +284,4 @@ contextMenuConfigurationForRowAtIndexPath:(NSIndexPath *)indexPath
|
||||
];
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@end
|
||||
|
||||
@@ -21,10 +21,10 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
/// The response cache uses an NSCache, so it may purge prior to hitting the limit when the app is under memory pressure.
|
||||
@property (nonatomic) NSUInteger networkResponseCacheByteLimit;
|
||||
|
||||
/// Requests whose host ends with one of the blacklisted entries in this array will be not be recorded (eg. google.com).
|
||||
/// Requests whose host ends with one of the excluded entries in this array will be not be recorded (eg. google.com).
|
||||
/// Wildcard or subdomain entries are not required (eg. google.com will match any subdomain under google.com).
|
||||
/// Useful to remove requests that are typically noisy, such as analytics requests that you aren't interested in tracking.
|
||||
@property (nonatomic) NSMutableArray<NSString *> *networkRequestHostBlacklist;
|
||||
@property (nonatomic) NSMutableArray<NSString *> *networkRequestHostDenylist;
|
||||
|
||||
/// Sets custom viewer for specific content type.
|
||||
/// @param contentType Mime type like application/json
|
||||
|
||||
@@ -48,12 +48,12 @@
|
||||
FLEXNetworkRecorder.defaultRecorder.responseCacheByteLimit = networkResponseCacheByteLimit;
|
||||
}
|
||||
|
||||
- (NSMutableArray<NSString *> *)networkRequestHostBlacklist {
|
||||
return FLEXNetworkRecorder.defaultRecorder.hostBlacklist;
|
||||
- (NSMutableArray<NSString *> *)networkRequestHostDenylist {
|
||||
return FLEXNetworkRecorder.defaultRecorder.hostDenylist;
|
||||
}
|
||||
|
||||
- (void)setNetworkRequestHostBlacklist:(NSMutableArray<NSString *> *)networkRequestHostBlacklist {
|
||||
FLEXNetworkRecorder.defaultRecorder.hostBlacklist = networkRequestHostBlacklist;
|
||||
- (void)setNetworkRequestHostDenylist:(NSMutableArray<NSString *> *)networkRequestHostDenylist {
|
||||
FLEXNetworkRecorder.defaultRecorder.hostDenylist = networkRequestHostDenylist;
|
||||
}
|
||||
|
||||
- (void)setCustomViewerForContentType:(NSString *)contentType
|
||||
|
||||
@@ -8,10 +8,6 @@
|
||||
|
||||
#import "FLEXExplorerToolbar.h"
|
||||
|
||||
#if !FLEX_AT_LEAST_IOS13_SDK
|
||||
@class UIWindowScene;
|
||||
#endif
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface FLEXManager : NSObject
|
||||
|
||||
@@ -49,7 +49,7 @@
|
||||
NSAssert(NSThread.isMainThread, @"You must use %@ from the main thread only.", NSStringFromClass([self class]));
|
||||
|
||||
if (!_explorerWindow) {
|
||||
_explorerWindow = [[FLEXWindow alloc] initWithFrame:UIScreen.mainScreen.bounds];
|
||||
_explorerWindow = [[FLEXWindow alloc] initWithFrame:FLEXUtility.appKeyWindow.bounds];
|
||||
_explorerWindow.eventDelegate = self;
|
||||
_explorerWindow.rootViewController = self.explorerViewController;
|
||||
}
|
||||
@@ -69,14 +69,12 @@
|
||||
- (void)showExplorer {
|
||||
UIWindow *flex = self.explorerWindow;
|
||||
flex.hidden = NO;
|
||||
#if FLEX_AT_LEAST_IOS13_SDK
|
||||
if (@available(iOS 13.0, *)) {
|
||||
// Only look for a new scene if we don't have one
|
||||
if (!flex.windowScene) {
|
||||
flex.windowScene = FLEXUtility.activeScene;
|
||||
flex.windowScene = FLEXUtility.appKeyWindow.windowScene;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
- (void)hideExplorer {
|
||||
@@ -85,7 +83,11 @@
|
||||
|
||||
- (void)toggleExplorer {
|
||||
if (self.explorerWindow.isHidden) {
|
||||
[self showExplorer];
|
||||
if (@available(iOS 13.0, *)) {
|
||||
[self showExplorerFromScene:FLEXUtility.appKeyWindow.windowScene];
|
||||
} else {
|
||||
[self showExplorer];
|
||||
}
|
||||
} else {
|
||||
[self hideExplorer];
|
||||
}
|
||||
@@ -100,15 +102,14 @@
|
||||
}
|
||||
|
||||
- (void)presentTool:(UINavigationController * _Nonnull (^)(void))future completion:(void (^)(void))completion {
|
||||
[self.explorerViewController toggleToolWithViewControllerProvider:future completion:completion];
|
||||
[self showExplorer];
|
||||
[self.explorerViewController presentTool:future completion:completion];
|
||||
}
|
||||
|
||||
- (void)showExplorerFromScene:(UIWindowScene *)scene {
|
||||
#if FLEX_AT_LEAST_IOS13_SDK
|
||||
if (@available(iOS 13.0, *)) {
|
||||
self.explorerWindow.windowScene = scene;
|
||||
}
|
||||
#endif
|
||||
self.explorerWindow.hidden = NO;
|
||||
}
|
||||
|
||||
|
||||
@@ -390,8 +390,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
#if FLEX_AT_LEAST_IOS13_SDK
|
||||
|
||||
- (UIContextMenuConfiguration *)tableView:(UITableView *)tableView contextMenuConfigurationForRowAtIndexPath:(NSIndexPath *)indexPath point:(CGPoint)point __IOS_AVAILABLE(13.0) {
|
||||
NSURLRequest *request = [self transactionAtIndexPath:indexPath].request;
|
||||
return [UIContextMenuConfiguration
|
||||
@@ -406,29 +404,27 @@
|
||||
UIPasteboard.generalPasteboard.string = request.URL.absoluteString ?: @"";
|
||||
}
|
||||
];
|
||||
UIAction *blacklist = [UIAction
|
||||
actionWithTitle:[NSString stringWithFormat:@"Blacklist '%@'", request.URL.host]
|
||||
UIAction *denylist = [UIAction
|
||||
actionWithTitle:[NSString stringWithFormat:@"Exclude '%@'", request.URL.host]
|
||||
image:nil
|
||||
identifier:nil
|
||||
handler:^(__kindof UIAction *action) {
|
||||
NSMutableArray *blacklist = FLEXNetworkRecorder.defaultRecorder.hostBlacklist;
|
||||
[blacklist addObject:request.URL.host];
|
||||
[FLEXNetworkRecorder.defaultRecorder clearBlacklistedTransactions];
|
||||
[FLEXNetworkRecorder.defaultRecorder synchronizeBlacklist];
|
||||
NSMutableArray *denylist = FLEXNetworkRecorder.defaultRecorder.hostDenylist;
|
||||
[denylist addObject:request.URL.host];
|
||||
[FLEXNetworkRecorder.defaultRecorder clearExcludedTransactions];
|
||||
[FLEXNetworkRecorder.defaultRecorder synchronizeDenylist];
|
||||
[self tryUpdateTransactions];
|
||||
}
|
||||
];
|
||||
return [UIMenu
|
||||
menuWithTitle:@"" image:nil identifier:nil
|
||||
options:UIMenuOptionsDisplayInline
|
||||
children:@[copy, blacklist]
|
||||
children:@[copy, denylist]
|
||||
];
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
- (FLEXNetworkTransaction *)transactionAtIndexPath:(NSIndexPath *)indexPath {
|
||||
return self.searchController.isActive ? self.filteredNetworkTransactions[indexPath.row] : self.networkTransactions[indexPath.row];
|
||||
}
|
||||
|
||||
@@ -28,13 +28,13 @@ extern NSString *const kFLEXNetworkRecorderTransactionsClearedNotification;
|
||||
/// with an "image", "video", or "audio" prefix.
|
||||
@property (nonatomic) BOOL shouldCacheMediaResponses;
|
||||
|
||||
@property (nonatomic) NSMutableArray<NSString *> *hostBlacklist;
|
||||
@property (nonatomic) NSMutableArray<NSString *> *hostDenylist;
|
||||
|
||||
/// Call this after adding to or setting the \c hostBlacklist to remove blacklisted transactions
|
||||
- (void)clearBlacklistedTransactions;
|
||||
/// Call this after adding to or setting the \c hostDenylist to remove excluded transactions
|
||||
- (void)clearExcludedTransactions;
|
||||
|
||||
/// Call this to save the blacklist to the disk to be loaded next time
|
||||
- (void)synchronizeBlacklist;
|
||||
/// Call this to save the denylist to the disk to be loaded next time
|
||||
- (void)synchronizeDenylist;
|
||||
|
||||
|
||||
// Accessing recorded network activity
|
||||
|
||||
@@ -45,7 +45,7 @@ NSString *const kFLEXNetworkRecorderResponseCacheLimitDefaultsKey = @"com.flex.r
|
||||
|
||||
self.orderedTransactions = [NSMutableArray new];
|
||||
self.requestIDsToTransactions = [NSMutableDictionary new];
|
||||
self.hostBlacklist = NSUserDefaults.standardUserDefaults.flex_networkHostBlacklist.mutableCopy;
|
||||
self.hostDenylist = NSUserDefaults.standardUserDefaults.flex_networkHostDenylist.mutableCopy;
|
||||
|
||||
// Serial queue used because we use mutable objects that are not thread safe
|
||||
self.queue = dispatch_queue_create("com.flex.FLEXNetworkRecorder", DISPATCH_QUEUE_SERIAL);
|
||||
@@ -100,13 +100,13 @@ NSString *const kFLEXNetworkRecorderResponseCacheLimitDefaultsKey = @"com.flex.r
|
||||
});
|
||||
}
|
||||
|
||||
- (void)clearBlacklistedTransactions {
|
||||
- (void)clearExcludedTransactions {
|
||||
dispatch_sync(self.queue, ^{
|
||||
self.orderedTransactions = ({
|
||||
[self.orderedTransactions flex_filtered:^BOOL(FLEXNetworkTransaction *ta, NSUInteger idx) {
|
||||
NSString *host = ta.request.URL.host;
|
||||
for (NSString *blacklisted in self.hostBlacklist) {
|
||||
if ([host hasSuffix:blacklisted]) {
|
||||
for (NSString *excluded in self.hostDenylist) {
|
||||
if ([host hasSuffix:excluded]) {
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
@@ -117,8 +117,8 @@ NSString *const kFLEXNetworkRecorderResponseCacheLimitDefaultsKey = @"com.flex.r
|
||||
});
|
||||
}
|
||||
|
||||
- (void)synchronizeBlacklist {
|
||||
NSUserDefaults.standardUserDefaults.flex_networkHostBlacklist = self.hostBlacklist;
|
||||
- (void)synchronizeDenylist {
|
||||
NSUserDefaults.standardUserDefaults.flex_networkHostDenylist = self.hostDenylist;
|
||||
}
|
||||
|
||||
#pragma mark - Network Events
|
||||
@@ -126,7 +126,7 @@ NSString *const kFLEXNetworkRecorderResponseCacheLimitDefaultsKey = @"com.flex.r
|
||||
- (void)recordRequestWillBeSentWithRequestID:(NSString *)requestID
|
||||
request:(NSURLRequest *)request
|
||||
redirectResponse:(NSURLResponse *)redirectResponse {
|
||||
for (NSString *host in self.hostBlacklist) {
|
||||
for (NSString *host in self.hostDenylist) {
|
||||
if ([request.URL.host hasSuffix:host]) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
@property (nonatomic, readonly) UISlider *cacheLimitSlider;
|
||||
@property (nonatomic) UILabel *cacheLimitLabel;
|
||||
|
||||
@property (nonatomic) NSMutableArray<NSString *> *hostBlacklist;
|
||||
@property (nonatomic) NSMutableArray<NSString *> *hostDenylist;
|
||||
@end
|
||||
|
||||
@implementation FLEXNetworkSettingsController
|
||||
@@ -32,7 +32,7 @@
|
||||
[super viewDidLoad];
|
||||
|
||||
[self disableToolbar];
|
||||
self.hostBlacklist = FLEXNetworkRecorder.defaultRecorder.hostBlacklist.mutableCopy;
|
||||
self.hostDenylist = FLEXNetworkRecorder.defaultRecorder.hostDenylist.mutableCopy;
|
||||
|
||||
NSUserDefaults *defaults = NSUserDefaults.standardUserDefaults;
|
||||
|
||||
@@ -107,13 +107,13 @@
|
||||
#pragma mark - Table View Data Source
|
||||
|
||||
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
|
||||
return self.hostBlacklist.count ? 2 : 1;
|
||||
return self.hostDenylist.count ? 2 : 1;
|
||||
}
|
||||
|
||||
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
|
||||
switch (section) {
|
||||
case 0: return 5;
|
||||
case 1: return self.hostBlacklist.count;
|
||||
case 1: return self.hostDenylist.count;
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
@@ -121,7 +121,7 @@
|
||||
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
|
||||
switch (section) {
|
||||
case 0: return @"General";
|
||||
case 1: return @"Host Blacklist";
|
||||
case 1: return @"Host Denylist";
|
||||
default: return nil;
|
||||
}
|
||||
}
|
||||
@@ -162,7 +162,7 @@
|
||||
cell.accessoryView = self.jsonViewerSwitch;
|
||||
break;
|
||||
case 3:
|
||||
cell.textLabel.text = @"Reset Host Blacklist";
|
||||
cell.textLabel.text = @"Reset Host Denylist";
|
||||
cell.textLabel.textColor = tableView.tintColor;
|
||||
break;
|
||||
case 4:
|
||||
@@ -195,9 +195,9 @@
|
||||
break;
|
||||
}
|
||||
|
||||
// Blacklist entries
|
||||
// Denylist entries
|
||||
case 1: {
|
||||
cell.textLabel.text = self.hostBlacklist[indexPath.row];
|
||||
cell.textLabel.text = self.hostDenylist[indexPath.row];
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -212,7 +212,7 @@
|
||||
#pragma mark - Table View Delegate
|
||||
|
||||
- (BOOL)tableView:(UITableView *)tableView shouldHighlightRowAtIndexPath:(NSIndexPath *)ip {
|
||||
// Can only select the "Reset Host Blacklist" row
|
||||
// Can only select the "Reset Host Denylist" row
|
||||
return ip.section == 0 && ip.row == 2;
|
||||
}
|
||||
|
||||
@@ -220,12 +220,12 @@
|
||||
[tableView deselectRowAtIndexPath:indexPath animated:YES];
|
||||
|
||||
[FLEXAlert makeAlert:^(FLEXAlert *make) {
|
||||
make.title(@"Reset Host Blacklist");
|
||||
make.title(@"Reset Host Denylist");
|
||||
make.message(@"You cannot undo this action. Are you sure?");
|
||||
make.button(@"Reset").destructiveStyle().handler(^(NSArray<NSString *> *strings) {
|
||||
self.hostBlacklist = nil;
|
||||
[FLEXNetworkRecorder.defaultRecorder.hostBlacklist removeAllObjects];
|
||||
[FLEXNetworkRecorder.defaultRecorder synchronizeBlacklist];
|
||||
self.hostDenylist = nil;
|
||||
[FLEXNetworkRecorder.defaultRecorder.hostDenylist removeAllObjects];
|
||||
[FLEXNetworkRecorder.defaultRecorder synchronizeDenylist];
|
||||
[self.tableView deleteSections:
|
||||
[NSIndexSet indexSetWithIndex:1]
|
||||
withRowAnimation:UITableViewRowAnimationAutomatic];
|
||||
@@ -242,10 +242,10 @@
|
||||
forRowAtIndexPath:(NSIndexPath *)indexPath {
|
||||
NSParameterAssert(style == UITableViewCellEditingStyleDelete);
|
||||
|
||||
NSString *host = self.hostBlacklist[indexPath.row];
|
||||
[self.hostBlacklist removeObjectAtIndex:indexPath.row];
|
||||
[FLEXNetworkRecorder.defaultRecorder.hostBlacklist removeObject:host];
|
||||
[FLEXNetworkRecorder.defaultRecorder synchronizeBlacklist];
|
||||
NSString *host = self.hostDenylist[indexPath.row];
|
||||
[self.hostDenylist removeObjectAtIndex:indexPath.row];
|
||||
[FLEXNetworkRecorder.defaultRecorder.hostDenylist removeObject:host];
|
||||
[FLEXNetworkRecorder.defaultRecorder synchronizeDenylist];
|
||||
|
||||
[tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
|
||||
}
|
||||
|
||||
@@ -211,8 +211,6 @@ typedef UIViewController *(^FLEXNetworkDetailRowSelectionFuture)(void);
|
||||
}
|
||||
}
|
||||
|
||||
#if FLEX_AT_LEAST_IOS13_SDK
|
||||
|
||||
- (UIContextMenuConfiguration *)tableView:(UITableView *)tableView contextMenuConfigurationForRowAtIndexPath:(NSIndexPath *)indexPath point:(CGPoint)point __IOS_AVAILABLE(13.0) {
|
||||
return [UIContextMenuConfiguration
|
||||
configurationWithIdentifier:nil
|
||||
@@ -236,8 +234,6 @@ typedef UIViewController *(^FLEXNetworkDetailRowSelectionFuture)(void);
|
||||
];
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#pragma mark - View Configuration
|
||||
|
||||
+ (NSAttributedString *)attributedTextForRow:(FLEXNetworkDetailRow *)row {
|
||||
|
||||
@@ -139,7 +139,10 @@
|
||||
BOOL hidePrivateMethods = defaults.flex_explorerHidesPrivateMethods;
|
||||
BOOL showMethodOverrides = defaults.flex_explorerShowsMethodOverrides;
|
||||
|
||||
NSMutableArray<NSArray<FLEXProperty *> *> *allProperties = [NSMutableArray new];
|
||||
NSMutableArray<NSArray<FLEXProperty *> *> *allClassProps = [NSMutableArray new];
|
||||
NSMutableArray<NSArray<FLEXMethod *> *> *allMethods = [NSMutableArray new];
|
||||
NSMutableArray<NSArray<FLEXMethod *> *> *allClassMethods = [NSMutableArray new];
|
||||
|
||||
// Loop over each class and each superclass, collect
|
||||
// the fresh and unique metadata in each category
|
||||
@@ -150,13 +153,13 @@
|
||||
Class cls = self.classHierarchyClasses[i];
|
||||
superclass = (i < rootIdx) ? self.classHierarchyClasses[i+1] : nil;
|
||||
|
||||
[_allProperties addObject:[self
|
||||
[allProperties addObject:[self
|
||||
metadataUniquedByName:[cls flex_allInstanceProperties]
|
||||
superclass:superclass
|
||||
kind:FLEXMetadataKindProperties
|
||||
skip:showMethodOverrides
|
||||
]];
|
||||
[_allClassProperties addObject:[self
|
||||
[allClassProps addObject:[self
|
||||
metadataUniquedByName:[cls flex_allClassProperties]
|
||||
superclass:superclass
|
||||
kind:FLEXMetadataKindClassProperties
|
||||
@@ -174,7 +177,7 @@
|
||||
kind:FLEXMetadataKindMethods
|
||||
skip:showMethodOverrides
|
||||
]];
|
||||
[_allClassMethods addObject:[self
|
||||
[allClassMethods addObject:[self
|
||||
metadataUniquedByName:[cls flex_allClassMethods]
|
||||
superclass:superclass
|
||||
kind:FLEXMetadataKindClassMethods
|
||||
@@ -201,7 +204,7 @@
|
||||
|
||||
_classHierarchy = [FLEXStaticMetadata classHierarchy:self.classHierarchyClasses];
|
||||
|
||||
NSArray<NSArray<FLEXProperty *> *> *properties = _allProperties;
|
||||
NSArray<NSArray<FLEXProperty *> *> *properties = allProperties;
|
||||
|
||||
// Potentially filter property-backing ivars
|
||||
if (hideBackingIvars) {
|
||||
@@ -211,7 +214,7 @@
|
||||
NSSet *ivarNames = [NSSet setWithArray:({
|
||||
[properties[idx] flex_mapped:^id(FLEXProperty *p, NSUInteger idx) {
|
||||
// Nil if no ivar, and array is flatted
|
||||
return p.attributes.backingIvar;
|
||||
return p.likelyIvarName;
|
||||
}];
|
||||
})];
|
||||
|
||||
@@ -250,15 +253,29 @@
|
||||
}
|
||||
|
||||
if (hidePrivateMethods) {
|
||||
allMethods = [allMethods flex_mapped:^id(NSArray<FLEXMethod *> *list, NSUInteger idx) {
|
||||
id methodMapBlock = ^id(NSArray<FLEXMethod *> *list, NSUInteger idx) {
|
||||
// Remove methods which contain an underscore
|
||||
return [list flex_filtered:^BOOL(FLEXMethod *method, NSUInteger idx) {
|
||||
return ![method.selectorString containsString:@"_"];
|
||||
}];
|
||||
}];
|
||||
};
|
||||
id propertyMapBlock = ^id(NSArray<FLEXProperty *> *list, NSUInteger idx) {
|
||||
// Remove methods which contain an underscore
|
||||
return [list flex_filtered:^BOOL(FLEXProperty *prop, NSUInteger idx) {
|
||||
return ![prop.name containsString:@"_"];
|
||||
}];
|
||||
};
|
||||
|
||||
allMethods = [allMethods flex_mapped:methodMapBlock];
|
||||
allClassMethods = [allClassMethods flex_mapped:methodMapBlock];
|
||||
allProperties = [allProperties flex_mapped:propertyMapBlock];
|
||||
allClassProps = [allClassProps flex_mapped:propertyMapBlock];
|
||||
}
|
||||
|
||||
_allProperties = allProperties;
|
||||
_allClassProperties = allClassProps;
|
||||
_allMethods = allMethods;
|
||||
_allClassMethods = allClassMethods;
|
||||
|
||||
// Set up UIKit helper data
|
||||
// Really, we only need to call this on properties and ivars
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
#import "FLEXColorPreviewSection.h"
|
||||
#import "FLEXDefaultsContentSection.h"
|
||||
#import "FLEXBundleShortcuts.h"
|
||||
#import "FLEXNSStringShortcuts.h"
|
||||
#import "FLEXNSDataShortcuts.h"
|
||||
#import "FLEXBlockShortcuts.h"
|
||||
#import "FLEXUtility.h"
|
||||
|
||||
@@ -50,6 +52,8 @@ static NSMutableDictionary<id<NSCopying>, Class> *classesToRegisteredSections =
|
||||
ClassKey(CALayer) : [FLEXLayerShortcuts class],
|
||||
ClassKey(UIColor) : [FLEXColorPreviewSection class],
|
||||
ClassKey(NSBundle) : [FLEXBundleShortcuts class],
|
||||
ClassKey(NSString) : [FLEXNSStringShortcuts class],
|
||||
ClassKey(NSData) : [FLEXNSDataShortcuts class],
|
||||
ClassKeyByName(NSBlock) : [FLEXBlockShortcuts class],
|
||||
}];
|
||||
#undef ClassKey
|
||||
@@ -72,19 +76,33 @@ static NSMutableDictionary<id<NSCopying>, Class> *classesToRegisteredSections =
|
||||
// shortcut section for NSObject.
|
||||
//
|
||||
// TODO: rename it to FLEXNSObjectShortcuts or something?
|
||||
Class sectionClass = nil;
|
||||
FLEXShortcutsSection *shortcutsSection = [FLEXShortcutsSection forObject:object];
|
||||
NSArray *sections = @[shortcutsSection];
|
||||
|
||||
Class customSectionClass = nil;
|
||||
Class cls = object_getClass(object);
|
||||
do {
|
||||
sectionClass = classesToRegisteredSections[(id<NSCopying>)cls];
|
||||
} while (!sectionClass && (cls = [cls superclass]));
|
||||
customSectionClass = classesToRegisteredSections[(id<NSCopying>)cls];
|
||||
} while (!customSectionClass && (cls = [cls superclass]));
|
||||
|
||||
if (!sectionClass) {
|
||||
sectionClass = [FLEXShortcutsSection class];
|
||||
if (customSectionClass) {
|
||||
id customSection = [customSectionClass forObject:object];
|
||||
BOOL isFLEXShortcutSection = [customSection respondsToSelector:@selector(isNewSection)];
|
||||
|
||||
// If the section "replaces" the default shortcuts section,
|
||||
// only return that section. Otherwise, return both this
|
||||
// section and the default shortcuts section.
|
||||
if (isFLEXShortcutSection && ![customSection isNewSection]) {
|
||||
sections = @[customSection];
|
||||
} else {
|
||||
// Custom section will go before shortcuts
|
||||
sections = @[customSection, shortcutsSection];
|
||||
}
|
||||
}
|
||||
|
||||
return [FLEXObjectExplorerViewController
|
||||
exploringObject:object
|
||||
customSection:[sectionClass forObject:object]
|
||||
customSections:sections
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -29,6 +29,9 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
+ (instancetype)exploringObject:(id)objectOrClass;
|
||||
/// No custom section unless you provide one.
|
||||
+ (instancetype)exploringObject:(id)objectOrClass customSection:(nullable FLEXTableViewSection *)customSection;
|
||||
/// No custom sections unless you provide some.
|
||||
+ (instancetype)exploringObject:(id)objectOrClass
|
||||
customSections:(nullable NSArray<FLEXTableViewSection *> *)customSections;
|
||||
|
||||
/// The object being explored, which may be an instance of a class or a class itself.
|
||||
@property (nonatomic, readonly) id object;
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
#pragma mark - Private properties
|
||||
@interface FLEXObjectExplorerViewController () <UIGestureRecognizerDelegate>
|
||||
@property (nonatomic, readonly) FLEXSingleRowSection *descriptionSection;
|
||||
@property (nonatomic, readonly) FLEXTableViewSection *customSection;
|
||||
@property (nonatomic, readonly) NSArray<FLEXTableViewSection *> *customSections;
|
||||
@property (nonatomic) NSIndexSet *customSectionVisibleIndexes;
|
||||
|
||||
@property (nonatomic, readonly) NSArray<NSString *> *observedNotifications;
|
||||
@@ -46,23 +46,27 @@
|
||||
}
|
||||
|
||||
+ (instancetype)exploringObject:(id)target customSection:(FLEXTableViewSection *)section {
|
||||
return [self exploringObject:target customSections:@[section]];
|
||||
}
|
||||
|
||||
+ (instancetype)exploringObject:(id)target customSections:(NSArray *)customSections {
|
||||
return [[self alloc]
|
||||
initWithObject:target
|
||||
explorer:[FLEXObjectExplorer forObject:target]
|
||||
customSection:section
|
||||
customSections:customSections
|
||||
];
|
||||
}
|
||||
|
||||
- (id)initWithObject:(id)target
|
||||
explorer:(__kindof FLEXObjectExplorer *)explorer
|
||||
customSection:(FLEXTableViewSection *)customSection {
|
||||
customSections:(NSArray<FLEXTableViewSection *> *)customSections {
|
||||
NSParameterAssert(target);
|
||||
|
||||
self = [super initWithStyle:UITableViewStyleGrouped];
|
||||
if (self) {
|
||||
_object = target;
|
||||
_explorer = explorer;
|
||||
_customSection = customSection;
|
||||
_customSections = customSections;
|
||||
}
|
||||
|
||||
return self;
|
||||
@@ -195,8 +199,10 @@
|
||||
referencesSection
|
||||
]];
|
||||
|
||||
if (self.customSection) {
|
||||
[sections insertObject:self.customSection atIndex:0];
|
||||
if (self.customSections) {
|
||||
[sections insertObjects:self.customSections atIndexes:[NSIndexSet
|
||||
indexSetWithIndexesInRange:NSMakeRange(0, self.customSections.count)
|
||||
]];
|
||||
}
|
||||
if (self.descriptionSection) {
|
||||
[sections insertObject:self.descriptionSection atIndex:0];
|
||||
@@ -269,6 +275,10 @@
|
||||
if (g2 == self.navigationController.interactivePopGestureRecognizer) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
if (g2 == self.tableView.panGestureRecognizer) {
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
|
||||
return YES;
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
@interface FLEXDefaultsContentSection ()
|
||||
@property (nonatomic) NSUserDefaults *defaults;
|
||||
@property (nonatomic) NSArray *keys;
|
||||
@property (nonatomic, readonly) NSDictionary *whitelistedDefaults;
|
||||
@property (nonatomic, readonly) NSDictionary *unexcludedDefaults;
|
||||
@end
|
||||
|
||||
@implementation FLEXDefaultsContentSection
|
||||
@@ -33,7 +33,7 @@
|
||||
FLEXDefaultsContentSection *section = [self forReusableFuture:^id(FLEXDefaultsContentSection *section) {
|
||||
section.defaults = userDefaults;
|
||||
section.onlyShowKeysForAppPrefs = YES;
|
||||
return section.whitelistedDefaults;
|
||||
return section.unexcludedDefaults;
|
||||
}];
|
||||
return section;
|
||||
}
|
||||
@@ -87,16 +87,16 @@
|
||||
_keys = [keys sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)];
|
||||
}
|
||||
|
||||
- (NSDictionary *)whitelistedDefaults {
|
||||
// Case: no whitelisting
|
||||
- (NSDictionary *)unexcludedDefaults {
|
||||
// Case: no excluding
|
||||
if (!self.onlyShowKeysForAppPrefs) {
|
||||
return self.defaults.dictionaryRepresentation;
|
||||
}
|
||||
|
||||
// Always regenerate key whitelist when this method is called
|
||||
// Always regenerate key allowlist when this method is called
|
||||
_keys = nil;
|
||||
|
||||
// Generate new dictionary from whitelisted keys
|
||||
// Generate new dictionary from unexcluded keys
|
||||
NSArray *values = [self.defaults.dictionaryRepresentation
|
||||
objectsForKeys:self.keys notFoundMarker:NSNull.null
|
||||
];
|
||||
|
||||
@@ -189,8 +189,6 @@
|
||||
cell.accessoryType = [self accessoryTypeForRow:row];
|
||||
}
|
||||
|
||||
#if FLEX_AT_LEAST_IOS13_SDK
|
||||
|
||||
- (NSString *)menuSubtitleForRow:(NSInteger)row {
|
||||
return [self.metadata[row] contextualSubtitleWithTarget:self.explorer.object];
|
||||
}
|
||||
@@ -232,6 +230,4 @@
|
||||
return [self.metadata[row] copiableMetadataWithTarget:self.explorer.object];
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@end
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
//
|
||||
|
||||
#import "FLEXMutableListSection.h"
|
||||
#import "FLEXMacros.h"
|
||||
|
||||
@interface FLEXMutableListSection ()
|
||||
@property (nonatomic, readonly) FLEXMutableListCellForElement configureCell;
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#import "FLEXBundleShortcuts.h"
|
||||
#import "FLEXShortcut.h"
|
||||
#import "FLEXAlert.h"
|
||||
#import "FLEXMacros.h"
|
||||
#import "FLEXRuntimeExporter.h"
|
||||
#import "FLEXTableListViewController.h"
|
||||
#import "FLEXFileBrowserController.h"
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#import "FLEXImagePreviewViewController.h"
|
||||
#import "FLEXShortcut.h"
|
||||
#import "FLEXAlert.h"
|
||||
#import "FLEXMacros.h"
|
||||
|
||||
@interface UIAlertController (FLEXImageShortcuts)
|
||||
- (void)flex_image:(UIImage *)image disSaveWithError:(NSError *)error :(void *)context;
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
//
|
||||
// FLEXNSDataShortcuts.h
|
||||
// FLEX
|
||||
//
|
||||
// Created by Tanner on 3/29/21.
|
||||
//
|
||||
|
||||
#import "FLEXShortcutsSection.h"
|
||||
|
||||
/// Adds a "UTF-8 String" shortcut
|
||||
@interface FLEXNSDataShortcuts : FLEXShortcutsSection
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,48 @@
|
||||
//
|
||||
// FLEXNSDataShortcuts.m
|
||||
// FLEX
|
||||
//
|
||||
// Created by Tanner on 3/29/21.
|
||||
//
|
||||
|
||||
#import "FLEXNSDataShortcuts.h"
|
||||
#import "FLEXObjectExplorerFactory.h"
|
||||
#import "FLEXShortcut.h"
|
||||
|
||||
@implementation FLEXNSDataShortcuts
|
||||
|
||||
+ (instancetype)forObject:(NSData *)data {
|
||||
NSString *string = [self stringForData:data];
|
||||
|
||||
return [self forObject:data additionalRows:@[
|
||||
[FLEXActionShortcut title:@"UTF-8 String" subtitle:^(NSData *object) {
|
||||
return string.length ? string : (string ?
|
||||
@"Data is not a UTF8 String" : @"Empty string"
|
||||
);
|
||||
} viewer:^UIViewController *(id object) {
|
||||
return [FLEXObjectExplorerFactory explorerViewControllerForObject:string];
|
||||
} accessoryType:^UITableViewCellAccessoryType(NSData *object) {
|
||||
if (string.length) {
|
||||
return UITableViewCellAccessoryDisclosureIndicator;
|
||||
}
|
||||
|
||||
return UITableViewCellAccessoryNone;
|
||||
}]
|
||||
]];
|
||||
}
|
||||
|
||||
+ (NSString *)stringForData:(NSData *)data {
|
||||
return [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@interface NSData (Overrides) @end
|
||||
@implementation NSData (Overrides)
|
||||
|
||||
// This normally crashes
|
||||
- (NSUInteger)length {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,13 @@
|
||||
//
|
||||
// FLEXNSStringShortcuts.h
|
||||
// FLEX
|
||||
//
|
||||
// Created by Tanner on 3/29/21.
|
||||
//
|
||||
|
||||
#import "FLEXShortcutsSection.h"
|
||||
|
||||
/// Adds a "UTF-8 Data" shortcut
|
||||
@interface FLEXNSStringShortcuts : FLEXShortcutsSection
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,29 @@
|
||||
//
|
||||
// FLEXNSStringShortcuts.m
|
||||
// FLEX
|
||||
//
|
||||
// Created by Tanner on 3/29/21.
|
||||
//
|
||||
|
||||
#import "FLEXNSStringShortcuts.h"
|
||||
#import "FLEXObjectExplorerFactory.h"
|
||||
#import "FLEXShortcut.h"
|
||||
|
||||
@implementation FLEXNSStringShortcuts
|
||||
|
||||
+ (instancetype)forObject:(NSString *)string {
|
||||
NSUInteger length = [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
|
||||
NSData *data = [NSData dataWithBytesNoCopy:(void *)string.UTF8String length:length freeWhenDone:NO];
|
||||
|
||||
return [self forObject:string additionalRows:@[
|
||||
[FLEXActionShortcut title:@"UTF-8 Data" subtitle:^NSString *(id _) {
|
||||
return data.description;
|
||||
} viewer:^UIViewController *(id _) {
|
||||
return [FLEXObjectExplorerFactory explorerViewControllerForObject:data];
|
||||
} accessoryType:^UITableViewCellAccessoryType(id _) {
|
||||
return UITableViewCellAccessoryDisclosureIndicator;
|
||||
}]
|
||||
]];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -25,3 +25,7 @@
|
||||
@interface FLEXShortcutsFactory (Blocks) @end
|
||||
|
||||
@interface FLEXShortcutsFactory (Foundation) @end
|
||||
|
||||
@interface FLEXShortcutsFactory (WebKit_Safari) @end
|
||||
|
||||
@interface FLEXShortcutsFactory (Pasteboard) @end
|
||||
|
||||
@@ -8,8 +8,12 @@
|
||||
|
||||
#import "FLEXShortcutsFactory+Defaults.h"
|
||||
#import "FLEXShortcut.h"
|
||||
#import "FLEXMacros.h"
|
||||
#import "FLEXRuntimeUtility.h"
|
||||
#import "NSArray+FLEX.h"
|
||||
#import "NSObject+FLEX_Reflection.h"
|
||||
#import "FLEXObjcInternal.h"
|
||||
#import "Cocoa+FLEXShortcuts.h"
|
||||
|
||||
#pragma mark - UIApplication
|
||||
|
||||
@@ -59,6 +63,7 @@
|
||||
FLEXRuntimeUtilityTryAddObjectProperty(6, constraints, UIView_, NSArray, PropertyKey(ReadOnly));
|
||||
FLEXRuntimeUtilityTryAddObjectProperty(2, subviews, UIView_, NSArray, PropertyKey(ReadOnly));
|
||||
FLEXRuntimeUtilityTryAddObjectProperty(2, superview, UIView_, UIView, PropertyKey(ReadOnly));
|
||||
FLEXRuntimeUtilityTryAddObjectProperty(7, tintColor, UIView_, UIView);
|
||||
|
||||
// UIButton, private
|
||||
FLEXRuntimeUtilityTryAddObjectProperty(2, font, UIButton.class, UIFont, PropertyKey(ReadOnly));
|
||||
@@ -141,7 +146,26 @@
|
||||
@"viewIfLoaded", @"title", @"navigationItem", @"toolbarItems", @"tabBarItem",
|
||||
@"childViewControllers", @"navigationController", @"tabBarController", @"splitViewController",
|
||||
@"parentViewController", @"presentedViewController", @"presentingViewController",
|
||||
]).methods(@[@"view"]).forClass(UIViewController.class);
|
||||
])
|
||||
.methods(@[@"view"])
|
||||
.forClass(UIViewController.class);
|
||||
|
||||
// UIAlertController
|
||||
NSMutableArray *alertControllerProps = @[
|
||||
@"title", @"message", @"actions", @"textFields",
|
||||
@"preferredAction", @"presentingViewController", @"viewIfLoaded",
|
||||
].mutableCopy;
|
||||
if (@available(iOS 14.0, *)) {
|
||||
[alertControllerProps insertObject:@"image" atIndex:4];
|
||||
}
|
||||
self.append
|
||||
.properties(alertControllerProps)
|
||||
.methods(@[@"addAction:"])
|
||||
.forClass(UIAlertController.class);
|
||||
self.append.properties(@[
|
||||
@"title", @"style", @"enabled", @"flex_styleName",
|
||||
@"image", @"keyCommandInput", @"_isPreferred", @"_alertController",
|
||||
]).forClass(UIAlertAction.class);
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -256,12 +280,164 @@
|
||||
FLEXRuntimeUtilityTryAddObjectProperty(2, abbreviationDictionary, NSTimeZone.flex_metaclass, NSDictionary);
|
||||
|
||||
self.append.classMethods(@[
|
||||
@"timeZoneWithName:", @"timeZoneWithAbbreviation:", @"timeZoneForSecondsFromGMT:", @"", @"", @"",
|
||||
@"timeZoneWithName:", @"timeZoneWithAbbreviation:", @"timeZoneForSecondsFromGMT:",
|
||||
]).forClass(NSTimeZone.flex_metaclass);
|
||||
|
||||
self.append.classProperties(@[
|
||||
@"defaultTimeZone", @"systemTimeZone", @"localTimeZone"
|
||||
@"defaultTimeZone", @"systemTimeZone", @"localTimeZone",
|
||||
]).forClass(NSTimeZone.class);
|
||||
|
||||
// UTF8String is not a real property under the hood
|
||||
FLEXRuntimeUtilityTryAddNonatomicProperty(2, UTF8String, NSString.class, const char *, PropertyKey(ReadOnly));
|
||||
|
||||
self.append.properties(@[@"length"]).methods(@[@"characterAtIndex:"]).forClass(NSString.class);
|
||||
self.append.methods(@[
|
||||
@"writeToFile:atomically:", @"subdataWithRange:", @"isEqualToData:",
|
||||
]).properties(@[
|
||||
@"length", @"bytes",
|
||||
]).forClass(NSData.class);
|
||||
|
||||
self.append.classMethods(@[
|
||||
@"dataWithJSONObject:options:error:",
|
||||
@"JSONObjectWithData:options:error:",
|
||||
@"isValidJSONObject:",
|
||||
]).forClass(NSJSONSerialization.class);
|
||||
|
||||
// NSArray
|
||||
self.append.classMethods(@[
|
||||
@"arrayWithObject:", @"arrayWithContentsOfFile:"
|
||||
]).forClass(NSArray.flex_metaclass);
|
||||
self.append.methods(@[
|
||||
@"valueForKeyPath:", @"subarrayWithRange:",
|
||||
@"arrayByAddingObject:", @"arrayByAddingObjectsFromArray:",
|
||||
@"filteredArrayUsingPredicate:", @"subarrayWithRange:",
|
||||
@"containsObject:", @"objectAtIndex:", @"indexOfObject:",
|
||||
@"makeObjectsPerformSelector:", @"makeObjectsPerformSelector:withObject:",
|
||||
@"sortedArrayUsingSelector:", @"reverseObjectEnumerator",
|
||||
@"isEqualToArray:", @"mutableCopy",
|
||||
]).forClass(NSArray.class);
|
||||
// NSDictionary
|
||||
self.append.methods(@[
|
||||
@"objectForKey:", @"valueForKeyPath:",
|
||||
@"isEqualToDictionary:", @"mutableCopy",
|
||||
]).forClass(NSDictionary.class);
|
||||
// NSSet
|
||||
self.append.classMethods(@[
|
||||
@"setWithObject:", @"setWithArray:"
|
||||
]).forClass(NSSet.flex_metaclass);
|
||||
self.append.methods(@[
|
||||
@"allObjects", @"valueForKeyPath:", @"containsObject:",
|
||||
@"setByAddingObject:", @"setByAddingObjectsFromArray:",
|
||||
@"filteredSetUsingPredicate:", @"isSubsetOfSet:",
|
||||
@"makeObjectsPerformSelector:", @"makeObjectsPerformSelector:withObject:",
|
||||
@"reverseObjectEnumerator", @"isEqualToSet:", @"mutableCopy",
|
||||
]).forClass(NSSet.class);
|
||||
|
||||
// NSMutableArray
|
||||
self.prepend.methods(@[
|
||||
@"addObject:", @"insertObject:atIndex:", @"addObjectsFromArray:",
|
||||
@"removeObject:", @"removeObjectAtIndex:",
|
||||
@"removeObjectsInArray:", @"removeAllObjects",
|
||||
@"removeLastObject", @"filterUsingPredicate:",
|
||||
@"sortUsingSelector:", @"copy",
|
||||
]).forClass(NSMutableArray.class);
|
||||
// NSMutableDictionary
|
||||
self.prepend.methods(@[
|
||||
@"setObject:forKey:", @"removeObjectForKey:",
|
||||
@"removeAllObjects", @"removeObjectsForKeys:", @"copy",
|
||||
]).forClass(NSMutableDictionary.class);
|
||||
// NSMutableSet
|
||||
self.prepend.methods(@[
|
||||
@"addObject:", @"removeObject:", @"filterUsingPredicate:",
|
||||
@"removeAllObjects", @"addObjectsFromArray:",
|
||||
@"unionSet:", @"minusSet:", @"intersectSet:", @"copy"
|
||||
]).forClass(NSMutableSet.class);
|
||||
|
||||
self.append.methods(@[@"nextObject", @"allObjects"]).forClass(NSEnumerator.class);
|
||||
|
||||
self.append.properties(@[@"flex_observers"]).forClass(NSNotificationCenter.class);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark - WebKit / Safari
|
||||
|
||||
@implementation FLEXShortcutsFactory (WebKit_Safari)
|
||||
|
||||
+ (void)load { FLEX_EXIT_IF_NO_CTORS()
|
||||
Class WKWebView = NSClassFromString(@"WKWebView");
|
||||
Class SafariVC = NSClassFromString(@"SFSafariViewController");
|
||||
|
||||
if (WKWebView) {
|
||||
self.append.properties(@[
|
||||
@"configuration", @"scrollView", @"title", @"URL",
|
||||
@"customUserAgent", @"navigationDelegate"
|
||||
]).methods(@[@"reload", @"stopLoading"]).forClass(WKWebView);
|
||||
}
|
||||
|
||||
if (SafariVC) {
|
||||
self.append.properties(@[
|
||||
@"delegate"
|
||||
]).forClass(SafariVC);
|
||||
if (@available(iOS 10.0, *)) {
|
||||
self.append.properties(@[
|
||||
@"preferredBarTintColor", @"preferredControlTintColor"
|
||||
]).forClass(SafariVC);
|
||||
}
|
||||
if (@available(iOS 11.0, *)) {
|
||||
self.append.properties(@[
|
||||
@"configuration", @"dismissButtonStyle"
|
||||
]).forClass(SafariVC);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark - Pasteboard
|
||||
|
||||
@implementation FLEXShortcutsFactory (Pasteboard)
|
||||
|
||||
+ (void)load { FLEX_EXIT_IF_NO_CTORS()
|
||||
self.append.properties(@[
|
||||
@"name", @"numberOfItems", @"items",
|
||||
@"string", @"image", @"color", @"URL",
|
||||
]).forClass(UIPasteboard.class);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@interface NSNotificationCenter (Observers)
|
||||
@property (readonly) NSArray<NSString *> *flex_observers;
|
||||
@end
|
||||
|
||||
@implementation NSNotificationCenter (Observers)
|
||||
- (id)flex_observers {
|
||||
NSString *debug = self.debugDescription;
|
||||
NSArray<NSString *> *observers = [debug componentsSeparatedByString:@"\n"];
|
||||
NSArray<NSArray<NSString *> *> *splitObservers = [observers flex_mapped:^id(NSString *entry, NSUInteger idx) {
|
||||
return [entry componentsSeparatedByString:@","];
|
||||
}];
|
||||
|
||||
NSArray *names = [splitObservers flex_mapped:^id(NSArray<NSString *> *entry, NSUInteger idx) {
|
||||
return entry[0];
|
||||
}];
|
||||
NSArray *objects = [splitObservers flex_mapped:^id(NSArray<NSString *> *entry, NSUInteger idx) {
|
||||
if (entry.count < 2) return NSNull.null;
|
||||
NSScanner *scanner = [NSScanner scannerWithString:entry[1]];
|
||||
|
||||
unsigned long long objectPointerValue;
|
||||
if ([scanner scanHexLongLong:&objectPointerValue]) {
|
||||
void *objectPointer = (void *)objectPointerValue;
|
||||
if (FLEXPointerIsValidObjcObject(objectPointer))
|
||||
return (__bridge id)(void *)objectPointer;
|
||||
}
|
||||
|
||||
return NSNull.null;
|
||||
}];
|
||||
|
||||
return [NSArray flex_forEachUpTo:names.count map:^id(NSUInteger i) {
|
||||
return @[names[i], objects[i]];
|
||||
}];
|
||||
}
|
||||
@end
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
#import "FLEXObjectInfoSection.h"
|
||||
@class FLEXProperty, FLEXIvar, FLEXMethod;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/// An abstract base class for custom object "shortcuts" where every
|
||||
/// row can possibly have some action. The section title is "Shortcuts".
|
||||
///
|
||||
@@ -25,11 +27,11 @@
|
||||
@interface FLEXShortcutsSection : FLEXTableViewSection <FLEXObjectInfoSection>
|
||||
|
||||
/// Uses \c kFLEXDefaultCell
|
||||
+ (instancetype)forObject:(id)objectOrClass rowTitles:(NSArray<NSString *> *)titles;
|
||||
+ (instancetype)forObject:(id)objectOrClass rowTitles:(nullable NSArray<NSString *> *)titles;
|
||||
/// Uses \c kFLEXDetailCell for non-empty subtitles, otherwise uses \c kFLEXDefaultCell
|
||||
+ (instancetype)forObject:(id)objectOrClass
|
||||
rowTitles:(NSArray<NSString *> *)titles
|
||||
rowSubtitles:(NSArray<NSString *> *)subtitles;
|
||||
rowTitles:(nullable NSArray<NSString *> *)titles
|
||||
rowSubtitles:(nullable NSArray<NSString *> *)subtitles;
|
||||
|
||||
/// Uses \c kFLEXDefaultCell for rows that are given a title, otherwise
|
||||
/// this uses \c kFLEXDetailCell for any other allowed object.
|
||||
@@ -44,16 +46,15 @@
|
||||
/// - a \c FLEXIvar
|
||||
/// - a \c FLEXMethodBase (includes \c FLEXMethod of course)
|
||||
/// Passing one of the latter 3 will provide a shortcut to that property/ivar/method.
|
||||
/// @return \c nil if no rows are provided
|
||||
+ (instancetype)forObject:(id)objectOrClass rows:(NSArray *)rows;
|
||||
+ (instancetype)forObject:(id)objectOrClass rows:(nullable NSArray *)rows;
|
||||
|
||||
/// Same as \c forObject:rows: but the given rows are prepended
|
||||
/// to the shortcuts already registered for the object's class.
|
||||
/// \c forObject:rows: does not use the registered shortcuts at all.
|
||||
+ (instancetype)forObject:(id)objectOrClass additionalRows:(NSArray *)rows;
|
||||
+ (instancetype)forObject:(id)objectOrClass additionalRows:(nullable NSArray *)rows;
|
||||
|
||||
/// Calls into \c forObject:rows: using the registered shortcuts for the object's class.
|
||||
/// @return \c nil if the object has no shortcuts registered at all
|
||||
/// @return An empty section if the object has no shortcuts registered at all.
|
||||
+ (instancetype)forObject:(id)objectOrClass;
|
||||
|
||||
/// Subclasses \e may override this to hide the disclosure indicator
|
||||
@@ -72,10 +73,16 @@
|
||||
/// Defaults to NO. Has no effect on static subtitles that are passed explicitly.
|
||||
@property (nonatomic) BOOL cacheSubtitles;
|
||||
|
||||
/// Whether this shortcut section overrides the default section or not.
|
||||
/// Subclasses should not override this method. To provide a second
|
||||
/// section alongside the default shortcuts section, use \c forObject:rows:
|
||||
/// @return \c NO if initialized with \c forObject: or \c forObject:additionalRows:
|
||||
@property (nonatomic, readonly) BOOL isNewSection;
|
||||
|
||||
@end
|
||||
|
||||
@class FLEXShortcutsFactory;
|
||||
typedef FLEXShortcutsFactory *(^FLEXShortcutsFactoryNames)(NSArray *names);
|
||||
typedef FLEXShortcutsFactory *_Nonnull(^FLEXShortcutsFactoryNames)(NSArray *names);
|
||||
typedef void (^FLEXShortcutsFactoryTarget)(Class targetClass);
|
||||
|
||||
/// The block properties below are to be used like SnapKit or Masonry.
|
||||
@@ -123,3 +130,5 @@ typedef void (^FLEXShortcutsFactoryTarget)(Class targetClass);
|
||||
@property (nonatomic, readonly) FLEXShortcutsFactoryTarget forClass;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
@end
|
||||
|
||||
@implementation FLEXShortcutsSection
|
||||
@synthesize isNewSection = _isNewSection;
|
||||
|
||||
#pragma mark Initialization
|
||||
|
||||
@@ -47,13 +48,13 @@
|
||||
}
|
||||
|
||||
+ (instancetype)forObject:(id)objectOrClass rows:(NSArray *)rows {
|
||||
return [[self alloc] initWithObject:objectOrClass rows:rows];
|
||||
return [[self alloc] initWithObject:objectOrClass rows:rows isNewSection:YES];
|
||||
}
|
||||
|
||||
+ (instancetype)forObject:(id)objectOrClass additionalRows:(NSArray *)toPrepend {
|
||||
NSArray *rows = [FLEXShortcutsFactory shortcutsForObjectOrClass:objectOrClass];
|
||||
NSArray *allRows = [toPrepend arrayByAddingObjectsFromArray:rows] ?: rows;
|
||||
return [self forObject:objectOrClass rows:allRows];
|
||||
return [[self alloc] initWithObject:objectOrClass rows:allRows isNewSection:NO];
|
||||
}
|
||||
|
||||
+ (instancetype)forObject:(id)objectOrClass {
|
||||
@@ -72,16 +73,18 @@
|
||||
_object = object;
|
||||
_allTitles = titles.copy;
|
||||
_allSubtitles = subtitles.copy;
|
||||
_isNewSection = YES;
|
||||
_numberOfLines = 1;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (id)initWithObject:object rows:(NSArray *)rows {
|
||||
- (id)initWithObject:object rows:(NSArray *)rows isNewSection:(BOOL)newSection {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_object = object;
|
||||
_isNewSection = newSection;
|
||||
|
||||
_allShortcuts = [rows flex_mapped:^id(id obj, NSUInteger idx) {
|
||||
return [FLEXShortcut shortcutFor:obj];
|
||||
@@ -300,6 +303,7 @@ typedef NSMutableDictionary<Class, NSMutableArray<id<FLEXRuntimeMetadata>> *> Re
|
||||
}
|
||||
|
||||
- (NSArray<id<FLEXRuntimeMetadata>> *)shortcutsForObjectOrClass:(id)objectOrClass {
|
||||
NSParameterAssert(objectOrClass);
|
||||
|
||||
NSMutableArray<id<FLEXRuntimeMetadata>> *shortcuts = [NSMutableArray new];
|
||||
BOOL isClass = object_isClass(objectOrClass);
|
||||
|
||||
@@ -50,8 +50,6 @@
|
||||
/// Return nil to use the default reuse identifier
|
||||
- (NSString *)reuseIdentifierWithTarget:(id)object;
|
||||
|
||||
#if FLEX_AT_LEAST_IOS13_SDK
|
||||
|
||||
/// An array of actions to place in the first section of the context menu.
|
||||
- (NSArray<UIAction *> *)additionalActionsWithTarget:(id)object sender:(UIViewController *)sender API_AVAILABLE(ios(13.0));
|
||||
/// An array where every 2 elements are a key-value pair. The key is a description
|
||||
@@ -60,8 +58,6 @@
|
||||
/// Properties and ivars return the address of an object, if they hold one.
|
||||
- (NSString *)contextualSubtitleWithTarget:(id)object;
|
||||
|
||||
#endif
|
||||
|
||||
@end
|
||||
|
||||
// Even if a property is readonly, it still may be editable
|
||||
|
||||
@@ -129,8 +129,6 @@ FLEXObjectExplorerDefaultsImpl
|
||||
|
||||
- (NSString *)reuseIdentifierWithTarget:(id)object { return nil; }
|
||||
|
||||
#if FLEX_AT_LEAST_IOS13_SDK
|
||||
|
||||
- (NSArray<UIAction *> *)additionalActionsWithTarget:(id)object sender:(UIViewController *)sender __IOS_AVAILABLE(13.0) {
|
||||
BOOL returnsObject = self.attributes.typeEncoding.flex_typeIsObjectOrClass;
|
||||
BOOL targetNotNil = [self appropriateTargetForPropertyType:object] != nil;
|
||||
@@ -207,8 +205,6 @@ FLEXObjectExplorerDefaultsImpl
|
||||
return nil;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@@ -285,8 +281,6 @@ FLEXObjectExplorerDefaultsImpl
|
||||
|
||||
- (NSString *)reuseIdentifierWithTarget:(id)object { return nil; }
|
||||
|
||||
#if FLEX_AT_LEAST_IOS13_SDK
|
||||
|
||||
- (NSArray<UIAction *> *)additionalActionsWithTarget:(id)object sender:(UIViewController *)sender __IOS_AVAILABLE(13.0) {
|
||||
Class ivarClass = self.typeEncoding.flex_typeClass;
|
||||
|
||||
@@ -337,8 +331,6 @@ FLEXObjectExplorerDefaultsImpl
|
||||
return nil;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@@ -383,8 +375,6 @@ FLEXObjectExplorerDefaultsImpl
|
||||
|
||||
- (NSString *)reuseIdentifierWithTarget:(id)object { return nil; }
|
||||
|
||||
#if FLEX_AT_LEAST_IOS13_SDK
|
||||
|
||||
- (NSArray<UIAction *> *)additionalActionsWithTarget:(id)object sender:(UIViewController *)sender __IOS_AVAILABLE(13.0) {
|
||||
return nil;
|
||||
}
|
||||
@@ -401,8 +391,6 @@ FLEXObjectExplorerDefaultsImpl
|
||||
return nil;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@end
|
||||
|
||||
@implementation FLEXMethod (UIKitHelpers)
|
||||
@@ -480,8 +468,6 @@ FLEXObjectExplorerDefaultsImpl
|
||||
|
||||
- (NSString *)reuseIdentifierWithTarget:(id)object { return nil; }
|
||||
|
||||
#if FLEX_AT_LEAST_IOS13_SDK
|
||||
|
||||
- (NSArray<UIAction *> *)additionalActionsWithTarget:(id)object sender:(UIViewController *)sender __IOS_AVAILABLE(13.0) {
|
||||
return nil;
|
||||
}
|
||||
@@ -499,8 +485,6 @@ FLEXObjectExplorerDefaultsImpl
|
||||
return nil;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@@ -592,8 +576,6 @@ FLEXObjectExplorerDefaultsImpl
|
||||
return UITableViewCellAccessoryNone;
|
||||
}
|
||||
|
||||
#if FLEX_AT_LEAST_IOS13_SDK
|
||||
|
||||
- (NSArray<UIAction *> *)additionalActionsWithTarget:(id)object sender:(UIViewController *)sender __IOS_AVAILABLE(13.0) {
|
||||
return nil;
|
||||
}
|
||||
@@ -606,8 +588,6 @@ FLEXObjectExplorerDefaultsImpl
|
||||
return nil;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@end
|
||||
|
||||
|
||||
|
||||
@@ -21,21 +21,21 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
/// @return The type encoding string, or \c nil if \e returnType is \c NULL.
|
||||
NSString * FLEXTypeEncodingString(const char *returnType, NSUInteger count, ...);
|
||||
|
||||
NSArray<Class> *FLEXGetAllSubclasses(_Nullable Class cls, BOOL includeSelf);
|
||||
NSArray<Class> *FLEXGetClassHierarchy(_Nullable Class cls, BOOL includeSelf);
|
||||
NSArray<FLEXProtocol *> *FLEXGetConformedProtocols(_Nullable Class cls);
|
||||
NSArray<Class> * _Nullable FLEXGetAllSubclasses(_Nullable Class cls, BOOL includeSelf);
|
||||
NSArray<Class> * _Nullable FLEXGetClassHierarchy(_Nullable Class cls, BOOL includeSelf);
|
||||
NSArray<FLEXProtocol *> * _Nullable FLEXGetConformedProtocols(_Nullable Class cls);
|
||||
|
||||
NSArray<FLEXIvar *> *FLEXGetAllIvars(_Nullable Class cls);
|
||||
NSArray<FLEXIvar *> * _Nullable FLEXGetAllIvars(_Nullable Class cls);
|
||||
/// @param cls a class object to get instance properties,
|
||||
/// or a metaclass object to get class properties
|
||||
NSArray<FLEXProperty *> *FLEXGetAllProperties(_Nullable Class cls);
|
||||
NSArray<FLEXProperty *> * _Nullable FLEXGetAllProperties(_Nullable Class cls);
|
||||
/// @param cls a class object to get instance methods,
|
||||
/// or a metaclass object to get class methods
|
||||
/// @param instance used to mark methods as instance methods or not.
|
||||
/// Not used to determine whether to get instance or class methods.
|
||||
NSArray<FLEXMethod *> *FLEXGetAllMethods(_Nullable Class cls, BOOL instance);
|
||||
NSArray<FLEXMethod *> * _Nullable FLEXGetAllMethods(_Nullable Class cls, BOOL instance);
|
||||
/// @param cls a class object to get all instance and class methods.
|
||||
NSArray<FLEXMethod *> *FLEXGetAllInstanceAndClassMethods(_Nullable Class cls);
|
||||
NSArray<FLEXMethod *> * _Nullable FLEXGetAllInstanceAndClassMethods(_Nullable Class cls);
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
|
||||
|
||||
NSString * FLEXTypeEncodingString(const char *returnType, NSUInteger count, ...) {
|
||||
if (returnType == NULL) return nil;
|
||||
if (!returnType) return nil;
|
||||
|
||||
NSMutableString *encoding = [NSMutableString new];
|
||||
[encoding appendFormat:@"%s%s%s", returnType, @encode(id), @encode(SEL)];
|
||||
@@ -37,9 +37,7 @@ NSString * FLEXTypeEncodingString(const char *returnType, NSUInteger count, ...)
|
||||
}
|
||||
|
||||
NSArray<Class> *FLEXGetAllSubclasses(Class cls, BOOL includeSelf) {
|
||||
if (!cls) {
|
||||
return nil;
|
||||
}
|
||||
if (!cls) return nil;
|
||||
|
||||
Class *buffer = NULL;
|
||||
|
||||
@@ -71,9 +69,7 @@ NSArray<Class> *FLEXGetAllSubclasses(Class cls, BOOL includeSelf) {
|
||||
}
|
||||
|
||||
NSArray<Class> *FLEXGetClassHierarchy(Class cls, BOOL includeSelf) {
|
||||
if (!cls) {
|
||||
return nil;
|
||||
}
|
||||
if (!cls) return nil;
|
||||
|
||||
NSMutableArray *classes = [NSMutableArray new];
|
||||
if (includeSelf) {
|
||||
@@ -88,9 +84,7 @@ NSArray<Class> *FLEXGetClassHierarchy(Class cls, BOOL includeSelf) {
|
||||
}
|
||||
|
||||
NSArray<FLEXProtocol *> *FLEXGetConformedProtocols(Class cls) {
|
||||
if (!cls) {
|
||||
return nil;
|
||||
}
|
||||
if (!cls) return nil;
|
||||
|
||||
unsigned int count = 0;
|
||||
Protocol *__unsafe_unretained *list = class_copyProtocolList(cls, &count);
|
||||
@@ -247,7 +241,7 @@ NSArray<FLEXMethod *> *FLEXGetAllMethods(_Nullable Class cls, BOOL instance) {
|
||||
}
|
||||
|
||||
+ (NSArray<FLEXMethod *> *)flex_allClassMethods {
|
||||
return FLEXGetAllMethods(self.flex_metaclass, NO);
|
||||
return FLEXGetAllMethods(self.flex_metaclass, NO) ?: @[];
|
||||
}
|
||||
|
||||
+ (FLEXMethod *)flex_methodNamed:(NSString *)name {
|
||||
@@ -395,7 +389,7 @@ NSArray<FLEXMethod *> *FLEXGetAllMethods(_Nullable Class cls, BOOL instance) {
|
||||
}
|
||||
|
||||
+ (NSArray<FLEXProperty *> *)flex_allClassProperties {
|
||||
return FLEXGetAllProperties(self.flex_metaclass);
|
||||
return FLEXGetAllProperties(self.flex_metaclass) ?: @[];
|
||||
}
|
||||
|
||||
+ (FLEXProperty *)flex_propertyNamed:(NSString *)name {
|
||||
|
||||
@@ -16,7 +16,7 @@ extern NSString * const kFLEXDefaultsHidePropertyMethodsKey;
|
||||
extern NSString * const kFLEXDefaultsHidePrivateMethodsKey;
|
||||
extern NSString * const kFLEXDefaultsShowMethodOverridesKey;
|
||||
extern NSString * const kFLEXDefaultsHideVariablePreviewsKey;
|
||||
extern NSString * const kFLEXDefaultsNetworkHostBlacklistKey;
|
||||
extern NSString * const kFLEXDefaultsNetworkHostDenylistKey;
|
||||
extern NSString * const kFLEXDefaultsDisableOSLogForceASLKey;
|
||||
extern NSString * const kFLEXDefaultsRegisterJSONExplorerKey;
|
||||
|
||||
@@ -28,7 +28,7 @@ extern NSString * const kFLEXDefaultsRegisterJSONExplorerKey;
|
||||
@property (nonatomic) double flex_toolbarTopMargin;
|
||||
|
||||
// Not actually stored in defaults, but written to a file
|
||||
@property (nonatomic) NSArray<NSString *> *flex_networkHostBlacklist;
|
||||
@property (nonatomic) NSArray<NSString *> *flex_networkHostDenylist;
|
||||
|
||||
/// Whether or not to register the object explorer as a JSON viewer on launch
|
||||
@property (nonatomic) BOOL flex_registerDictionaryJSONViewerOnLaunch;
|
||||
|
||||
@@ -15,7 +15,7 @@ NSString * const kFLEXDefaultsHidePropertyMethodsKey = @"com.flipboard.FLEX.hide
|
||||
NSString * const kFLEXDefaultsHidePrivateMethodsKey = @"com.flipboard.FLEX.hide_private_or_namespaced_methods";
|
||||
NSString * const kFLEXDefaultsShowMethodOverridesKey = @"com.flipboard.FLEX.show_method_overrides";
|
||||
NSString * const kFLEXDefaultsHideVariablePreviewsKey = @"com.flipboard.FLEX.hide_variable_previews";
|
||||
NSString * const kFLEXDefaultsNetworkHostBlacklistKey = @"com.flipboard.FLEX.network_host_blacklist";
|
||||
NSString * const kFLEXDefaultsNetworkHostDenylistKey = @"com.flipboard.FLEX.network_host_denylist";
|
||||
NSString * const kFLEXDefaultsDisableOSLogForceASLKey = @"com.flipboard.FLEX.try_disable_os_log";
|
||||
NSString * const kFLEXDefaultsRegisterJSONExplorerKey = @"com.flipboard.FLEX.view_json_as_object";
|
||||
|
||||
@@ -62,16 +62,16 @@ NSString * const kFLEXDefaultsRegisterJSONExplorerKey = @"com.flipboard.FLEX.vie
|
||||
[self setDouble:margin forKey:kFLEXDefaultsToolbarTopMarginKey];
|
||||
}
|
||||
|
||||
- (NSArray<NSString *> *)flex_networkHostBlacklist {
|
||||
- (NSArray<NSString *> *)flex_networkHostDenylist {
|
||||
return [NSArray arrayWithContentsOfFile:[
|
||||
self flex_defaultsPathForFile:kFLEXDefaultsNetworkHostBlacklistKey
|
||||
self flex_defaultsPathForFile:kFLEXDefaultsNetworkHostDenylistKey
|
||||
]] ?: @[];
|
||||
}
|
||||
|
||||
- (void)setFlex_networkHostBlacklist:(NSArray<NSString *> *)blacklist {
|
||||
NSParameterAssert(blacklist);
|
||||
[blacklist writeToFile:[
|
||||
self flex_defaultsPathForFile:kFLEXDefaultsNetworkHostBlacklistKey
|
||||
- (void)setFlex_networkHostDenylist:(NSArray<NSString *> *)denylist {
|
||||
NSParameterAssert(denylist);
|
||||
[denylist writeToFile:[
|
||||
self flex_defaultsPathForFile:kFLEXDefaultsNetworkHostDenylistKey
|
||||
] atomically:YES];
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
//
|
||||
// Cocoa+FLEXShortcuts.h
|
||||
// Pods
|
||||
//
|
||||
// Created by Tanner on 2/24/21.
|
||||
//
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@interface UIAlertAction (FLEXShortcuts)
|
||||
@property (nonatomic, readonly) NSString *flex_styleName;
|
||||
@end
|
||||
@@ -0,0 +1,25 @@
|
||||
//
|
||||
// Cocoa+FLEXShortcuts.m
|
||||
// Pods
|
||||
//
|
||||
// Created by Tanner on 2/24/21.
|
||||
//
|
||||
//
|
||||
|
||||
#import "Cocoa+FLEXShortcuts.h"
|
||||
|
||||
@implementation UIAlertAction (FLEXShortcuts)
|
||||
- (NSString *)flex_styleName {
|
||||
switch (self.style) {
|
||||
case UIAlertActionStyleDefault:
|
||||
return @"Default style";
|
||||
case UIAlertActionStyleCancel:
|
||||
return @"Cancel style";
|
||||
case UIAlertActionStyleDestructive:
|
||||
return @"Destructive style";
|
||||
|
||||
default:
|
||||
return [NSString stringWithFormat:@"Unknown (%@)", @(self.style)];
|
||||
}
|
||||
}
|
||||
@end
|
||||
@@ -8,23 +8,25 @@
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@class FLEXAlert, FLEXAlertAction;
|
||||
|
||||
typedef void (^FLEXAlertReveal)(void);
|
||||
typedef void (^FLEXAlertBuilder)(FLEXAlert *make);
|
||||
typedef FLEXAlert *(^FLEXAlertStringProperty)(NSString *);
|
||||
typedef FLEXAlert *(^FLEXAlertStringArg)(NSString *);
|
||||
typedef FLEXAlert *(^FLEXAlertTextField)(void(^configurationHandler)(UITextField *textField));
|
||||
typedef FLEXAlertAction *(^FLEXAlertAddAction)(NSString *title);
|
||||
typedef FLEXAlertAction *(^FLEXAlertActionStringProperty)(NSString *);
|
||||
typedef FLEXAlertAction *(^FLEXAlertActionProperty)(void);
|
||||
typedef FLEXAlertAction *(^FLEXAlertActionBOOLProperty)(BOOL);
|
||||
typedef FLEXAlertAction *(^FLEXAlertActionHandler)(void(^handler)(NSArray<NSString *> *strings));
|
||||
typedef FLEXAlert * _Nonnull (^FLEXAlertStringProperty)(NSString * _Nullable);
|
||||
typedef FLEXAlert * _Nonnull (^FLEXAlertStringArg)(NSString * _Nullable);
|
||||
typedef FLEXAlert * _Nonnull (^FLEXAlertTextField)(void(^configurationHandler)(UITextField *textField));
|
||||
typedef FLEXAlertAction * _Nonnull (^FLEXAlertAddAction)(NSString *title);
|
||||
typedef FLEXAlertAction * _Nonnull (^FLEXAlertActionStringProperty)(NSString * _Nullable);
|
||||
typedef FLEXAlertAction * _Nonnull (^FLEXAlertActionProperty)(void);
|
||||
typedef FLEXAlertAction * _Nonnull (^FLEXAlertActionBOOLProperty)(BOOL);
|
||||
typedef FLEXAlertAction * _Nonnull (^FLEXAlertActionHandler)(void(^handler)(NSArray<NSString *> *strings));
|
||||
|
||||
@interface FLEXAlert : NSObject
|
||||
|
||||
/// Shows a simple alert with one button which says "Dismiss"
|
||||
+ (void)showAlert:(NSString *)title message:(NSString *)message from:(UIViewController *)viewController;
|
||||
+ (void)showAlert:(NSString * _Nullable)title message:(NSString * _Nullable)message from:(UIViewController *)viewController;
|
||||
|
||||
/// Construct and display an alert
|
||||
+ (void)makeAlert:(FLEXAlertBuilder)block showFrom:(UIViewController *)viewController;
|
||||
@@ -79,3 +81,5 @@ typedef FLEXAlertAction *(^FLEXAlertActionHandler)(void(^handler)(NSArray<NSStri
|
||||
@property (nonatomic, readonly) UIAlertAction *action;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
#import "FLEXColor.h"
|
||||
#import "FLEXUtility.h"
|
||||
|
||||
#if FLEX_AT_LEAST_IOS13_SDK
|
||||
#define FLEXDynamicColor(dynamic, static) ({ \
|
||||
UIColor *c; \
|
||||
if (@available(iOS 13.0, *)) { \
|
||||
@@ -19,9 +18,6 @@
|
||||
} \
|
||||
c; \
|
||||
});
|
||||
#else
|
||||
#define FLEXDynamicColor(dynamic, static) [UIColor static]
|
||||
#endif
|
||||
|
||||
@implementation FLEXColor
|
||||
|
||||
|
||||
@@ -75,12 +75,6 @@ NS_INLINE CGRect FLEXRectSetHeight(CGRect r, CGFloat height) {
|
||||
r.size.height = height; return r;
|
||||
}
|
||||
|
||||
#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) \
|
||||
]
|
||||
|
||||
@@ -17,10 +17,6 @@
|
||||
#import "UIFont+FLEX.h"
|
||||
#import "FLEXMacros.h"
|
||||
|
||||
#if !FLEX_AT_LEAST_IOS13_SDK
|
||||
@class UIWindowScene;
|
||||
#endif
|
||||
|
||||
@interface FLEXUtility : NSObject
|
||||
|
||||
/// The key window of the app, if it is not a \c FLEXWindow.
|
||||
|
||||
@@ -63,7 +63,6 @@ BOOL FLEXConstructorsShouldRun() {
|
||||
return nil;
|
||||
}
|
||||
|
||||
#if FLEX_AT_LEAST_IOS13_SDK
|
||||
+ (UIWindowScene *)activeScene {
|
||||
for (UIScene *scene in UIApplication.sharedApplication.connectedScenes) {
|
||||
// Look for an active UIWindowScene
|
||||
@@ -75,7 +74,6 @@ BOOL FLEXConstructorsShouldRun() {
|
||||
|
||||
return nil;
|
||||
}
|
||||
#endif
|
||||
|
||||
+ (UIViewController *)topViewControllerInWindow:(UIWindow *)window {
|
||||
UIViewController *topViewController = window.rootViewController;
|
||||
@@ -180,8 +178,6 @@ BOOL FLEXConstructorsShouldRun() {
|
||||
dispatch_once(&onceToken, ^{
|
||||
UIImage *indentationPatternImage = FLEXResources.hierarchyIndentPattern;
|
||||
patternColor = [UIColor colorWithPatternImage:indentationPatternImage];
|
||||
|
||||
#if FLEX_AT_LEAST_IOS13_SDK
|
||||
if (@available(iOS 13.0, *)) {
|
||||
// Create a dark mode version
|
||||
UIGraphicsBeginImageContextWithOptions(
|
||||
@@ -200,9 +196,7 @@ BOOL FLEXConstructorsShouldRun() {
|
||||
? [UIColor colorWithPatternImage:indentationPatternImage]
|
||||
: [UIColor colorWithPatternImage:darkModePatternImage]);
|
||||
}];
|
||||
}
|
||||
#endif
|
||||
});
|
||||
} });
|
||||
|
||||
return patternColor;
|
||||
}
|
||||
|
||||
@@ -34,6 +34,15 @@
|
||||
#define FLEXRuntimeUtilityTryAddObjectProperty(iOS_atLeast, name, cls, type, ...) \
|
||||
FLEXRuntimeUtilityTryAddProperty(iOS_atLeast, name, cls, FLEXEncodeClass(type), PropertyKey(NonAtomic), __VA_ARGS__);
|
||||
|
||||
extern NSString * const FLEXRuntimeUtilityErrorDomain;
|
||||
|
||||
typedef NS_ENUM(NSInteger, FLEXRuntimeUtilityErrorCode) {
|
||||
// Start at a random value instead of 0 to avoid confusion with an absent code
|
||||
FLEXRuntimeUtilityErrorCodeDoesNotRecognizeSelector = 0xbabe,
|
||||
FLEXRuntimeUtilityErrorCodeInvocationFailed,
|
||||
FLEXRuntimeUtilityErrorCodeArgumentTypeMismatch
|
||||
};
|
||||
|
||||
@interface FLEXRuntimeUtility : NSObject
|
||||
|
||||
// General Helpers
|
||||
@@ -75,6 +84,12 @@
|
||||
onObject:(id)object
|
||||
withArguments:(NSArray *)arguments
|
||||
error:(NSError * __autoreleasing *)error;
|
||||
+ (id)performSelector:(SEL)selector
|
||||
onObject:(id)object
|
||||
withArguments:(NSArray *)arguments
|
||||
allowForwarding:(BOOL)mightForwardMsgSend
|
||||
error:(NSError * __autoreleasing *)error;
|
||||
|
||||
+ (NSString *)editableJSONStringForObject:(id)object;
|
||||
+ (id)objectValueFromEditableJSONString:(NSString *)string;
|
||||
+ (NSValue *)valueForNumberWithObjCType:(const char *)typeEncoding fromInputString:(NSString *)inputString;
|
||||
|
||||
@@ -11,12 +11,7 @@
|
||||
#import "FLEXObjcInternal.h"
|
||||
#import "FLEXTypeEncodingParser.h"
|
||||
|
||||
static NSString *const FLEXRuntimeUtilityErrorDomain = @"FLEXRuntimeUtilityErrorDomain";
|
||||
typedef NS_ENUM(NSInteger, FLEXRuntimeUtilityErrorCode) {
|
||||
FLEXRuntimeUtilityErrorCodeDoesNotRecognizeSelector = 0,
|
||||
FLEXRuntimeUtilityErrorCodeInvocationFailed = 1,
|
||||
FLEXRuntimeUtilityErrorCodeArgumentTypeMismatch = 2
|
||||
};
|
||||
NSString * const FLEXRuntimeUtilityErrorDomain = @"FLEXRuntimeUtilityErrorDomain";
|
||||
|
||||
@implementation FLEXRuntimeUtility
|
||||
|
||||
@@ -109,7 +104,11 @@ typedef NS_ENUM(NSInteger, FLEXRuntimeUtilityErrorCode) {
|
||||
+ (NSString *)safeDescriptionForObject:(id)object {
|
||||
// Don't assume that we have an NSObject subclass; not all objects respond to -description
|
||||
if ([self safeObject:object respondsToSelector:@selector(description)]) {
|
||||
return [object description];
|
||||
@try {
|
||||
return [object description];
|
||||
} @catch (NSException *exception) {
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
|
||||
return nil;
|
||||
@@ -120,7 +119,9 @@ typedef NS_ENUM(NSInteger, FLEXRuntimeUtilityErrorCode) {
|
||||
NSString *description = nil;
|
||||
|
||||
if ([self safeObject:object respondsToSelector:@selector(debugDescription)]) {
|
||||
description = [object debugDescription];
|
||||
@try {
|
||||
description = [object debugDescription];
|
||||
} @catch (NSException *exception) { }
|
||||
} else {
|
||||
description = [self safeDescriptionForObject:object];
|
||||
}
|
||||
@@ -294,18 +295,31 @@ typedef NS_ENUM(NSInteger, FLEXRuntimeUtilityErrorCode) {
|
||||
onObject:(id)object
|
||||
withArguments:(NSArray *)arguments
|
||||
error:(NSError * __autoreleasing *)error {
|
||||
return [self performSelector:selector
|
||||
onObject:object
|
||||
withArguments:arguments
|
||||
allowForwarding:NO
|
||||
error:error
|
||||
];
|
||||
}
|
||||
|
||||
+ (id)performSelector:(SEL)selector
|
||||
onObject:(id)object
|
||||
withArguments:(NSArray *)arguments
|
||||
allowForwarding:(BOOL)mightForwardMsgSend
|
||||
error:(NSError * __autoreleasing *)error {
|
||||
static dispatch_once_t onceToken;
|
||||
static SEL stdStringExclusion = nil;
|
||||
dispatch_once(&onceToken, ^{
|
||||
stdStringExclusion = NSSelectorFromString(@"stdString");
|
||||
});
|
||||
|
||||
// Bail if the object won't respond to this selector.
|
||||
if (![self safeObject:object respondsToSelector:selector]) {
|
||||
// Bail if the object won't respond to this selector
|
||||
if (mightForwardMsgSend || ![self safeObject:object respondsToSelector:selector]) {
|
||||
if (error) {
|
||||
NSString *msg = [NSString
|
||||
stringWithFormat:@"%@ does not respond to the selector %@",
|
||||
object, NSStringFromSelector(selector)
|
||||
stringWithFormat:@"This object does not respond to the selector %@",
|
||||
NSStringFromSelector(selector)
|
||||
];
|
||||
NSDictionary<NSString *, id> *userInfo = @{ NSLocalizedDescriptionKey : msg };
|
||||
*error = [NSError
|
||||
|
||||
@@ -51,6 +51,10 @@
|
||||
@property (nonatomic, readonly) NSString *likelyGetterString;
|
||||
/// Not valid unless initialized with the owning class.
|
||||
@property (nonatomic, readonly) BOOL likelyGetterExists;
|
||||
/// Always \c nil for class properties.
|
||||
@property (nonatomic, readonly) NSString *likelyIvarName;
|
||||
/// Not valid unless initialized with the owning class.
|
||||
@property (nonatomic, readonly) BOOL likelyIvarExists;
|
||||
|
||||
/// Whether there are certainly multiple definitions of this property,
|
||||
/// such as in categories in other binary images or something.
|
||||
|
||||
@@ -124,6 +124,10 @@
|
||||
_likelySetterString = NSStringFromSelector(_likelySetter);
|
||||
|
||||
_isClassProperty = _cls ? class_isMetaClass(_cls) : NO;
|
||||
|
||||
_likelyIvarName = _isClassProperty ? nil : (
|
||||
self.attributes.backingIvar ?: [@"_" stringByAppendingString:_name]
|
||||
);
|
||||
}
|
||||
|
||||
#pragma mark Overrides
|
||||
@@ -187,6 +191,14 @@
|
||||
return _imageName;
|
||||
}
|
||||
|
||||
- (BOOL)likelyIvarExists {
|
||||
if (_likelyIvarName && _cls) {
|
||||
return class_getInstanceVariable(_cls, _likelyIvarName.UTF8String) != nil;
|
||||
}
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (NSString *)fullDescription {
|
||||
NSMutableArray<NSString *> *attributesStrings = [NSMutableArray new];
|
||||
FLEXPropertyAttributes *attributes = self.attributes;
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
<key>UIApplicationSceneManifest</key>
|
||||
<dict>
|
||||
<key>UIApplicationSupportsMultipleScenes</key>
|
||||
<false/>
|
||||
<true/>
|
||||
<key>UISceneConfigurations</key>
|
||||
<dict>
|
||||
<key>UIWindowSceneSessionRoleApplication</key>
|
||||
|
||||
+3
-3
@@ -1,6 +1,6 @@
|
||||
Pod::Spec.new do |spec|
|
||||
spec.name = "FLEX"
|
||||
spec.version = "4.4.0"
|
||||
spec.version = "4.5.0"
|
||||
spec.summary = "A set of in-app debugging and exploration tools for iOS"
|
||||
spec.description = <<-DESC
|
||||
- Inspect and modify views in the hierarchy.
|
||||
@@ -40,6 +40,6 @@ Pod::Spec.new do |spec|
|
||||
"Classes/GlobalStateExplorers/Globals/FLEXGlobalsEntry.h",
|
||||
"Classes/Core/**/*.h", "Classes/Utility/Runtime/Objc/**/*.h",
|
||||
"Classes/ObjectExplorers/**/*.h", "Classes/Editing/**/*.h",
|
||||
"Classes/Utility/FLEXMacros.h", "Classes/Utility/Categories/*.h",
|
||||
"Classes/Utility/FLEXAlert.h", "Classes/Utility/FLEXResources.h" ]
|
||||
"Classes/Utility/Categories/*.h", "Classes/Utility/FLEXAlert.h",
|
||||
"Classes/Utility/FLEXResources.h" ]
|
||||
end
|
||||
|
||||
@@ -144,6 +144,12 @@
|
||||
C301994A2409B38A00759E8E /* CALayer+FLEX.h in Headers */ = {isa = PBXBuildFile; fileRef = C30199482409B38A00759E8E /* CALayer+FLEX.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
C301994B2409B38A00759E8E /* CALayer+FLEX.m in Sources */ = {isa = PBXBuildFile; fileRef = C30199492409B38A00759E8E /* CALayer+FLEX.m */; };
|
||||
C309B82F223ED64400B228EC /* FLEXLogController.h in Headers */ = {isa = PBXBuildFile; fileRef = C309B82D223ED64400B228EC /* FLEXLogController.h */; };
|
||||
C30D2960261FAE9E00D89649 /* FLEXNSStringShortcuts.h in Headers */ = {isa = PBXBuildFile; fileRef = C30D295C261FAE9E00D89649 /* FLEXNSStringShortcuts.h */; };
|
||||
C30D2961261FAE9E00D89649 /* FLEXNSDataShortcuts.m in Sources */ = {isa = PBXBuildFile; fileRef = C30D295D261FAE9E00D89649 /* FLEXNSDataShortcuts.m */; };
|
||||
C30D2962261FAE9E00D89649 /* FLEXNSDataShortcuts.h in Headers */ = {isa = PBXBuildFile; fileRef = C30D295E261FAE9E00D89649 /* FLEXNSDataShortcuts.h */; };
|
||||
C30D2963261FAE9E00D89649 /* FLEXNSStringShortcuts.m in Sources */ = {isa = PBXBuildFile; fileRef = C30D295F261FAE9E00D89649 /* FLEXNSStringShortcuts.m */; };
|
||||
C30D2968261FAEEE00D89649 /* Cocoa+FLEXShortcuts.h in Headers */ = {isa = PBXBuildFile; fileRef = C30D2966261FAEEE00D89649 /* Cocoa+FLEXShortcuts.h */; };
|
||||
C30D2969261FAEEE00D89649 /* Cocoa+FLEXShortcuts.m in Sources */ = {isa = PBXBuildFile; fileRef = C30D2967261FAEEE00D89649 /* Cocoa+FLEXShortcuts.m */; };
|
||||
C312A13023ECB5D300E38049 /* FLEXBookmarkManager.h in Headers */ = {isa = PBXBuildFile; fileRef = C312A12E23ECB5D300E38049 /* FLEXBookmarkManager.h */; };
|
||||
C312A13123ECB5D300E38049 /* FLEXBookmarkManager.m in Sources */ = {isa = PBXBuildFile; fileRef = C312A12F23ECB5D300E38049 /* FLEXBookmarkManager.m */; };
|
||||
C312A13423ECBE5800E38049 /* FLEXBookmarksViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = C312A13223ECBE5800E38049 /* FLEXBookmarksViewController.h */; };
|
||||
@@ -194,7 +200,7 @@
|
||||
C3531BA623E88A2100A184AD /* FLEXNavigationController.m in Sources */ = {isa = PBXBuildFile; fileRef = C3531BA423E88A2100A184AD /* FLEXNavigationController.m */; };
|
||||
C3531BAA23E88FAC00A184AD /* FLEXTabList.h in Headers */ = {isa = PBXBuildFile; fileRef = C3531BA823E88FAC00A184AD /* FLEXTabList.h */; };
|
||||
C3531BAB23E88FAC00A184AD /* FLEXTabList.m in Sources */ = {isa = PBXBuildFile; fileRef = C3531BA923E88FAC00A184AD /* FLEXTabList.m */; };
|
||||
C362AE8123C7E9D1005A86AE /* NSMapTable+FLEX_Subscripting.h in Headers */ = {isa = PBXBuildFile; fileRef = C362AE7F23C7E9D1005A86AE /* NSMapTable+FLEX_Subscripting.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
C362AE8123C7E9D1005A86AE /* NSMapTable+FLEX_Subscripting.h in Headers */ = {isa = PBXBuildFile; fileRef = C362AE7F23C7E9D1005A86AE /* NSMapTable+FLEX_Subscripting.h */; settings = {ATTRIBUTES = (Private, ); }; };
|
||||
C362AE8223C7E9D1005A86AE /* NSMapTable+FLEX_Subscripting.m in Sources */ = {isa = PBXBuildFile; fileRef = C362AE8023C7E9D1005A86AE /* NSMapTable+FLEX_Subscripting.m */; };
|
||||
C3694DBA23EA1096006625D7 /* FLEXTabsViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = C3694DB823EA1096006625D7 /* FLEXTabsViewController.h */; };
|
||||
C3694DBB23EA1096006625D7 /* FLEXTabsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C3694DB923EA1096006625D7 /* FLEXTabsViewController.m */; };
|
||||
@@ -248,7 +254,7 @@
|
||||
C3878DBE23A74A8F0038FDBE /* FLEXNetworkRecorder.m in Sources */ = {isa = PBXBuildFile; fileRef = 3A4C94B81B5B21410088C3F2 /* FLEXNetworkRecorder.m */; };
|
||||
C387C87A22DFCD6A00750E58 /* FLEXCarouselCell.h in Headers */ = {isa = PBXBuildFile; fileRef = C387C87822DFCD6A00750E58 /* FLEXCarouselCell.h */; };
|
||||
C387C87B22DFCD6A00750E58 /* FLEXCarouselCell.m in Sources */ = {isa = PBXBuildFile; fileRef = C387C87922DFCD6A00750E58 /* FLEXCarouselCell.m */; };
|
||||
C387C88322E0D24A00750E58 /* UIView+FLEX_Layout.h in Headers */ = {isa = PBXBuildFile; fileRef = C387C88122E0D24A00750E58 /* UIView+FLEX_Layout.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
C387C88322E0D24A00750E58 /* UIView+FLEX_Layout.h in Headers */ = {isa = PBXBuildFile; fileRef = C387C88122E0D24A00750E58 /* UIView+FLEX_Layout.h */; settings = {ATTRIBUTES = (Private, ); }; };
|
||||
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 */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
C38DF0EB22CFE4370077B4AD /* FLEXTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C38DF0E922CFE4370077B4AD /* FLEXTableViewController.m */; };
|
||||
@@ -282,7 +288,7 @@
|
||||
C398626E23AD71C1007E6793 /* FLEXRuntimeClient.m in Sources */ = {isa = PBXBuildFile; fileRef = C398626A23AD71C1007E6793 /* FLEXRuntimeClient.m */; };
|
||||
C398627223AD7951007E6793 /* UIGestureRecognizer+Blocks.h in Headers */ = {isa = PBXBuildFile; fileRef = C398627023AD7951007E6793 /* UIGestureRecognizer+Blocks.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
C398627323AD7951007E6793 /* UIGestureRecognizer+Blocks.m in Sources */ = {isa = PBXBuildFile; fileRef = C398627123AD7951007E6793 /* UIGestureRecognizer+Blocks.m */; };
|
||||
C398627623AD79B7007E6793 /* NSString+FLEX.h in Headers */ = {isa = PBXBuildFile; fileRef = C398627423AD79B6007E6793 /* NSString+FLEX.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
C398627623AD79B7007E6793 /* NSString+FLEX.h in Headers */ = {isa = PBXBuildFile; fileRef = C398627423AD79B6007E6793 /* NSString+FLEX.h */; settings = {ATTRIBUTES = (Private, ); }; };
|
||||
C398627723AD79B7007E6793 /* NSString+FLEX.m in Sources */ = {isa = PBXBuildFile; fileRef = C398627523AD79B7007E6793 /* NSString+FLEX.m */; };
|
||||
C398682523AC359600E9E391 /* FLEXShortcutsFactory+Defaults.m in Sources */ = {isa = PBXBuildFile; fileRef = C398682323AC359600E9E391 /* FLEXShortcutsFactory+Defaults.m */; };
|
||||
C398682623AC359600E9E391 /* FLEXShortcutsFactory+Defaults.h in Headers */ = {isa = PBXBuildFile; fileRef = C398682423AC359600E9E391 /* FLEXShortcutsFactory+Defaults.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
@@ -343,9 +349,9 @@
|
||||
C3F646F323A045DB00D4A011 /* FLEXClassShortcuts.m in Sources */ = {isa = PBXBuildFile; fileRef = C3F646F123A045DB00D4A011 /* FLEXClassShortcuts.m */; };
|
||||
C3F646F623A04A7500D4A011 /* FLEXShortcut.h in Headers */ = {isa = PBXBuildFile; fileRef = C3F646F423A04A7500D4A011 /* FLEXShortcut.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
C3F646F723A04A7500D4A011 /* FLEXShortcut.m in Sources */ = {isa = PBXBuildFile; fileRef = C3F646F523A04A7500D4A011 /* FLEXShortcut.m */; };
|
||||
C3F977832311B38F0032776D /* NSString+ObjcRuntime.h in Headers */ = {isa = PBXBuildFile; fileRef = C3F9777D2311B38E0032776D /* NSString+ObjcRuntime.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
C3F977832311B38F0032776D /* NSString+ObjcRuntime.h in Headers */ = {isa = PBXBuildFile; fileRef = C3F9777D2311B38E0032776D /* NSString+ObjcRuntime.h */; settings = {ATTRIBUTES = (Private, ); }; };
|
||||
C3F977842311B38F0032776D /* NSDictionary+ObjcRuntime.m in Sources */ = {isa = PBXBuildFile; fileRef = C3F9777E2311B38E0032776D /* NSDictionary+ObjcRuntime.m */; };
|
||||
C3F977852311B38F0032776D /* NSDictionary+ObjcRuntime.h in Headers */ = {isa = PBXBuildFile; fileRef = C3F9777F2311B38F0032776D /* NSDictionary+ObjcRuntime.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
C3F977852311B38F0032776D /* NSDictionary+ObjcRuntime.h in Headers */ = {isa = PBXBuildFile; fileRef = C3F9777F2311B38F0032776D /* NSDictionary+ObjcRuntime.h */; settings = {ATTRIBUTES = (Private, ); }; };
|
||||
C3F977862311B38F0032776D /* NSString+ObjcRuntime.m in Sources */ = {isa = PBXBuildFile; fileRef = C3F977802311B38F0032776D /* NSString+ObjcRuntime.m */; };
|
||||
C3F977872311B38F0032776D /* NSObject+FLEX_Reflection.h in Headers */ = {isa = PBXBuildFile; fileRef = C3F977812311B38F0032776D /* NSObject+FLEX_Reflection.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
C3F977882311B38F0032776D /* NSObject+FLEX_Reflection.m in Sources */ = {isa = PBXBuildFile; fileRef = C3F977822311B38F0032776D /* NSObject+FLEX_Reflection.m */; };
|
||||
@@ -506,6 +512,12 @@
|
||||
C30199482409B38A00759E8E /* CALayer+FLEX.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "CALayer+FLEX.h"; sourceTree = "<group>"; };
|
||||
C30199492409B38A00759E8E /* CALayer+FLEX.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "CALayer+FLEX.m"; sourceTree = "<group>"; };
|
||||
C309B82D223ED64400B228EC /* FLEXLogController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FLEXLogController.h; sourceTree = "<group>"; };
|
||||
C30D295C261FAE9E00D89649 /* FLEXNSStringShortcuts.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FLEXNSStringShortcuts.h; sourceTree = "<group>"; };
|
||||
C30D295D261FAE9E00D89649 /* FLEXNSDataShortcuts.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLEXNSDataShortcuts.m; sourceTree = "<group>"; };
|
||||
C30D295E261FAE9E00D89649 /* FLEXNSDataShortcuts.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FLEXNSDataShortcuts.h; sourceTree = "<group>"; };
|
||||
C30D295F261FAE9E00D89649 /* FLEXNSStringShortcuts.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLEXNSStringShortcuts.m; sourceTree = "<group>"; };
|
||||
C30D2966261FAEEE00D89649 /* Cocoa+FLEXShortcuts.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "Cocoa+FLEXShortcuts.h"; sourceTree = "<group>"; };
|
||||
C30D2967261FAEEE00D89649 /* Cocoa+FLEXShortcuts.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "Cocoa+FLEXShortcuts.m"; sourceTree = "<group>"; };
|
||||
C312A12E23ECB5D300E38049 /* FLEXBookmarkManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FLEXBookmarkManager.h; sourceTree = "<group>"; };
|
||||
C312A12F23ECB5D300E38049 /* FLEXBookmarkManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FLEXBookmarkManager.m; sourceTree = "<group>"; };
|
||||
C312A13223ECBE5800E38049 /* FLEXBookmarksViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FLEXBookmarksViewController.h; sourceTree = "<group>"; };
|
||||
@@ -1094,6 +1106,8 @@
|
||||
C31E4E53259D4A4100712288 /* Private */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
C30D2966261FAEEE00D89649 /* Cocoa+FLEXShortcuts.h */,
|
||||
C30D2967261FAEEE00D89649 /* Cocoa+FLEXShortcuts.m */,
|
||||
C362AE7F23C7E9D1005A86AE /* NSMapTable+FLEX_Subscripting.h */,
|
||||
C362AE8023C7E9D1005A86AE /* NSMapTable+FLEX_Subscripting.m */,
|
||||
C387C88122E0D24A00750E58 /* UIView+FLEX_Layout.h */,
|
||||
@@ -1392,6 +1406,10 @@
|
||||
C32F3A17247C6B3E0063542D /* FLEXUIAppShortcuts.m */,
|
||||
C31D93E223E38CBE005517BF /* FLEXBlockShortcuts.h */,
|
||||
C31D93E323E38CBE005517BF /* FLEXBlockShortcuts.m */,
|
||||
C30D295E261FAE9E00D89649 /* FLEXNSDataShortcuts.h */,
|
||||
C30D295D261FAE9E00D89649 /* FLEXNSDataShortcuts.m */,
|
||||
C30D295C261FAE9E00D89649 /* FLEXNSStringShortcuts.h */,
|
||||
C30D295F261FAE9E00D89649 /* FLEXNSStringShortcuts.m */,
|
||||
);
|
||||
path = Shortcuts;
|
||||
sourceTree = "<group>";
|
||||
@@ -1519,6 +1537,7 @@
|
||||
C32F3A18247C6B3E0063542D /* FLEXUIAppShortcuts.h in Headers */,
|
||||
779B1ED61C0C4D7C001F5E49 /* FLEXTableContentViewController.h in Headers */,
|
||||
C3DFCDB82418336D00BB7084 /* NSUserDefaults+FLEX.h in Headers */,
|
||||
C30D2968261FAEEE00D89649 /* Cocoa+FLEXShortcuts.h in Headers */,
|
||||
3A4C95221B5B21410088C3F2 /* FLEXFileBrowserSearchOperation.h in Headers */,
|
||||
C33E46AF223B02CD004BD0E6 /* FLEXASLLogController.h in Headers */,
|
||||
C34EE30821CB23CC00BD3A7C /* FLEXOSLogController.h in Headers */,
|
||||
@@ -1527,6 +1546,7 @@
|
||||
3A4C94E71B5B21410088C3F2 /* FLEXHierarchyTableViewCell.h in Headers */,
|
||||
224D49AA1C673AB5000EAB86 /* FLEXSQLiteDatabaseManager.h in Headers */,
|
||||
C386D6A9241995A800699085 /* FLEXTypeEncodingParser.h in Headers */,
|
||||
C30D2962261FAE9E00D89649 /* FLEXNSDataShortcuts.h in Headers */,
|
||||
3A4C95031B5B21410088C3F2 /* FLEXArgumentInputView.h in Headers */,
|
||||
C398627623AD79B7007E6793 /* NSString+FLEX.h in Headers */,
|
||||
94A5151D1C4CA1F10063292F /* FLEXExplorerViewController.h in Headers */,
|
||||
@@ -1593,6 +1613,7 @@
|
||||
C3F977872311B38F0032776D /* NSObject+FLEX_Reflection.h in Headers */,
|
||||
C3DB9F642107FC9600B46809 /* FLEXObjectRef.h in Headers */,
|
||||
3A4C95401B5B21410088C3F2 /* FLEXNetworkTransactionCell.h in Headers */,
|
||||
C30D2960261FAE9E00D89649 /* FLEXNSStringShortcuts.h in Headers */,
|
||||
C398626523AD70F5007E6793 /* FLEXKBToolbarButton.h in Headers */,
|
||||
3A4C95241B5B21410088C3F2 /* FLEXFileBrowserController.h in Headers */,
|
||||
C31D93E423E38CBE005517BF /* FLEXBlockShortcuts.h in Headers */,
|
||||
@@ -1662,7 +1683,7 @@
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
CLASSPREFIX = FLEX;
|
||||
LastUpgradeCheck = 1200;
|
||||
LastUpgradeCheck = 1240;
|
||||
ORGANIZATIONNAME = Flipboard;
|
||||
TargetAttributes = {
|
||||
1C27A8B51F0E5A0300F0D02D = {
|
||||
@@ -1733,6 +1754,7 @@
|
||||
C3878DBE23A74A8F0038FDBE /* FLEXNetworkRecorder.m in Sources */,
|
||||
C3878DBC23A749F70038FDBE /* FLEXFieldEditorViewController.m in Sources */,
|
||||
942DCD871BAE0CA300DB5DC2 /* FLEXKeyboardShortcutManager.m in Sources */,
|
||||
C30D2961261FAE9E00D89649 /* FLEXNSDataShortcuts.m in Sources */,
|
||||
C3F977862311B38F0032776D /* NSString+ObjcRuntime.m in Sources */,
|
||||
C3A9424A23C78878006871A3 /* FLEXHierarchyViewController.m in Sources */,
|
||||
C31C4A6A23342A2200C35F12 /* FLEXMetadataSection.m in Sources */,
|
||||
@@ -1835,6 +1857,7 @@
|
||||
C32A195F231732E800EB02AC /* FLEXCollectionContentSection.m in Sources */,
|
||||
C36FBFCD230F3B98008D95D5 /* FLEXMethod.m in Sources */,
|
||||
779B1ED31C0C4D7C001F5E49 /* FLEXTableColumnHeader.m in Sources */,
|
||||
C30D2963261FAE9E00D89649 /* FLEXNSStringShortcuts.m in Sources */,
|
||||
C37A0C94218BAC9600848CA7 /* FLEXObjcInternal.mm in Sources */,
|
||||
C31D93E523E38CBE005517BF /* FLEXBlockShortcuts.m in Sources */,
|
||||
C312A13123ECB5D300E38049 /* FLEXBookmarkManager.m in Sources */,
|
||||
@@ -1864,6 +1887,7 @@
|
||||
3A4C953F1B5B21410088C3F2 /* FLEXNetworkTransactionDetailController.m in Sources */,
|
||||
224D49AB1C673AB5000EAB86 /* FLEXSQLiteDatabaseManager.m in Sources */,
|
||||
C312A13C23ECE79000E38049 /* FLEXWindowManagerController.m in Sources */,
|
||||
C30D2969261FAEEE00D89649 /* Cocoa+FLEXShortcuts.m in Sources */,
|
||||
C36FBFDA230F3B98008D95D5 /* FLEXPropertyAttributes.m in Sources */,
|
||||
779B1ED91C0C4D7C001F5E49 /* FLEXTableLeftCell.m in Sources */,
|
||||
3A4C94E61B5B21410088C3F2 /* FLEXUtility.m in Sources */,
|
||||
@@ -1987,7 +2011,7 @@
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
|
||||
MTL_ENABLE_DEBUG_INFO = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
OTHER_LDFLAGS = "";
|
||||
@@ -2042,7 +2066,7 @@
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
OTHER_LDFLAGS = "";
|
||||
SDKROOT = iphoneos;
|
||||
@@ -2061,7 +2085,7 @@
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = NO;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = NO;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_IDENTITY = "";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
|
||||
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
@@ -2069,13 +2093,11 @@
|
||||
DEVELOPMENT_TEAM = "";
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
DYLIB_CURRENT_VERSION = 1;
|
||||
DYLIB_INSTALL_NAME_BASE = "@rpath";
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"$(PROJECT_DIR)",
|
||||
);
|
||||
INFOPLIST_FILE = Classes/Info.plist;
|
||||
INSTALL_PATH = "@rpath";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.flipboard.$(PRODUCT_NAME:rfc1034identifier)";
|
||||
@@ -2083,8 +2105,10 @@
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
"PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = "";
|
||||
SKIP_INSTALL = YES;
|
||||
SUPPORTS_MACCATALYST = NO;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
WARNING_CFLAGS = "-Wno-unsupported-availability-guard";
|
||||
};
|
||||
name = Debug;
|
||||
@@ -2096,7 +2120,7 @@
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = NO;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = NO;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_IDENTITY = "";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
|
||||
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
@@ -2104,13 +2128,11 @@
|
||||
DEVELOPMENT_TEAM = "";
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
DYLIB_CURRENT_VERSION = 1;
|
||||
DYLIB_INSTALL_NAME_BASE = "@rpath";
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"$(PROJECT_DIR)",
|
||||
);
|
||||
INFOPLIST_FILE = Classes/Info.plist;
|
||||
INSTALL_PATH = "@rpath";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.flipboard.$(PRODUCT_NAME:rfc1034identifier)";
|
||||
@@ -2118,7 +2140,9 @@
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
"PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = "";
|
||||
SKIP_INSTALL = YES;
|
||||
SUPPORTS_MACCATALYST = NO;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
WARNING_CFLAGS = "-Wno-unsupported-availability-guard";
|
||||
};
|
||||
name = Release;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1200"
|
||||
LastUpgradeVersion = "1240"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1200"
|
||||
LastUpgradeVersion = "1240"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
|
||||
@@ -15,6 +15,9 @@ are permitted provided that the following conditions are met:
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
* You must NOT include this project in an application to be submitted
|
||||
to the App Store™, as this project uses too many private APIs.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
|
||||
Reference in New Issue
Block a user