Compare commits

..

6 Commits

Author SHA1 Message Date
Tanner Bennett b908d66b94 Argument input view work 2020-10-18 17:09:47 -05:00
Tanner Bennett e6c324d761 FLEXMethod.imagePath should use the method's IMP
We can already tell which image the class comes from, and that is usually the same as the original method implementation. What is more useful to know is the image of the _current_ implementation.
2020-10-18 17:04:44 -05:00
Tanner Bennett 784e030e44 Silence new weird documentation warning… 2020-10-18 17:04:44 -05:00
Tanner Bennett 046eb49f7b Commit FLEXTests scheme 2020-10-18 17:04:44 -05:00
Tanner Bennett 6ec1814f2e Xcode 12 upgrade check for main scheme 2020-10-18 17:04:44 -05:00
Tanner Bennett f179545972 Add FLEX_EXIT_IF_NO_CTORS 2020-10-18 17:02:09 -05:00
429 changed files with 2245 additions and 8569 deletions
-3
View File
@@ -1,3 +0,0 @@
# These are supported funding model platforms
github: [NSExceptional]
-27
View File
@@ -1,27 +0,0 @@
---
name: Bug report
about: Report a bug in FLEX
title: ''
labels: bug
assignees: ''
---
### Environment
- Platform+version: **iOS 14** <!--- Change to match your platform and version -->
- FLEX version: **9.9.9** <!--- Change to the version of FLEX you're using -->
<!--- FLEXing / libFLEX users: please include FLEXing and libFLEX versions separately -->
### Bug Report
Here, you can provide a description of the bug. Some tips:
- Please do not paste an entire crash log. Upload the crash log to something like [ghostbin.co](https://ghostbin.co/) or another paste service. Alternatively, you can cut out the relevant stack trace and paste that inside a ` ```code block``` `
- If the bug is more complex than "this button is broken" or a crash, consider including a sample project. For example, if your app's requests aren't showing up in the network history page.
- Providing steps to reproduce is always helpful!
- If you want to include a screenshot or GIF, consider modifying the default markdown for uploaded images to use this code to make the image smaller on desktop:
```
<img width="50%" src=your-image-url >
```
This template is a suggestion. You may format your issue however you want, but generally you should at least include your iOS version and FLEX version.
-10
View File
@@ -1,10 +0,0 @@
---
name: Feature request
about: Suggest a new feature for FLEX
title: ''
labels: enhancement
assignees: ''
---
-2
View File
@@ -20,5 +20,3 @@ DerivedData
/Example/Pods
Podfile.lock
IDEWorkspaceChecks.plist
*.xcworkspace
.build
-5
View File
@@ -1,5 +0,0 @@
{
"search.exclude": {
"Classes/Headers": true
}
}
@@ -3,7 +3,7 @@
// FLEX
//
// Created by Tanner on 3/9/20.
// Copyright © 2020 FLEX Team. All rights reserved.
// Copyright © 2020 Flipboard. All rights reserved.
//
#import "FLEXTableViewController.h"
@@ -3,13 +3,12 @@
// FLEX
//
// Created by Tanner on 3/9/20.
// Copyright © 2020 FLEX Team. All rights reserved.
// Copyright © 2020 Flipboard. All rights reserved.
//
#import "FLEXFilteringTableViewController.h"
#import "FLEXTableViewSection.h"
#import "NSArray+FLEX.h"
#import "FLEXMacros.h"
@interface FLEXFilteringTableViewController ()
@@ -74,7 +73,7 @@
#pragma mark - Search
- (void)updateSearchResults:(NSString *)newText {
NSArray *(^filter)(void) = ^NSArray *{
NSArray *(^filter)() = ^NSArray *{
self.filterText = newText;
// Sections will adjust data based on this property
@@ -112,18 +111,9 @@
- (void)setAllSections:(NSArray<FLEXTableViewSection *> *)allSections {
_allSections = allSections.copy;
// Only display nonempty sections
self.sections = self.nonemptySections;
}
- (void)setSections:(NSArray<FLEXTableViewSection *> *)sections {
// Allow sections to reload a portion of the table view at will
[sections enumerateObjectsUsingBlock:^(FLEXTableViewSection *s, NSUInteger idx, BOOL *stop) {
[s setTable:self.tableView section:idx];
}];
_sections = sections.copy;
}
#pragma mark - UITableViewDataSource
@@ -188,6 +178,8 @@
[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];
@@ -206,4 +198,6 @@
return nil;
}
#endif
@end
@@ -3,7 +3,7 @@
// FLEX
//
// Created by Tanner on 1/30/20.
// Copyright © 2020 FLEX Team. All rights reserved.
// Copyright © 2020 Flipboard. All rights reserved.
//
#import <UIKit/UIKit.h>
@@ -16,13 +16,4 @@ NS_ASSUME_NONNULL_BEGIN
@end
@interface UINavigationController (FLEXObjectExploring)
/// Push an object explorer view controller onto the navigation stack
- (void)pushExplorerForObject:(id)object;
/// Push an object explorer view controller onto the navigation stack
- (void)pushExplorerForObject:(id)object animated:(BOOL)animated;
@end
NS_ASSUME_NONNULL_END
@@ -3,12 +3,11 @@
// FLEX
//
// Created by Tanner on 1/30/20.
// Copyright © 2020 FLEX Team. All rights reserved.
// Copyright © 2020 Flipboard. All rights reserved.
//
#import "FLEXNavigationController.h"
#import "FLEXExplorerViewController.h"
#import "FLEXObjectExplorerFactory.h"
#import "FLEXTabList.h"
@interface UINavigationController (Private) <UIGestureRecognizerDelegate>
@@ -21,7 +20,6 @@
@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
@@ -29,8 +27,7 @@
@implementation FLEXNavigationController
+ (instancetype)withRootViewController:(UIViewController *)rootVC {
FLEXNavigationController *nav = [[self alloc] initWithRootViewController:rootVC];
return nav;
return [[self alloc] initWithRootViewController:rootVC];
}
- (void)viewDidLoad {
@@ -39,13 +36,10 @@
self.waitingToAddTab = YES;
// Add gesture to reveal toolbar if hidden
UITapGestureRecognizer *navbarTapGesture = [[UITapGestureRecognizer alloc]
self.navigationBar.userInteractionEnabled = YES;
[self.navigationBar addGestureRecognizer:[[UITapGestureRecognizer alloc]
initWithTarget:self action:@selector(handleNavigationBarTap:)
];
// Don't cancel touches to work around bug on versions of iOS prior to 13
navbarTapGesture.cancelsTouchesInView = NO;
[self.navigationBar addGestureRecognizer:navbarTapGesture];
]];
// Add gesture to dismiss if not presented with a sheet style
if (@available(iOS 13, *)) {
@@ -67,17 +61,6 @@
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
if (@available(iOS 15.0, *)) {
UISheetPresentationController *presenter = self.sheetPresentationController;
presenter.detents = @[
UISheetPresentationControllerDetent.mediumDetent,
UISheetPresentationControllerDetent.largeDetent,
];
presenter.prefersScrollingExpandsWhenScrolledToEdge = NO;
presenter.selectedDetentIdentifier = UISheetPresentationControllerDetentIdentifierLarge;
presenter.largestUndimmedDetentIdentifier = UISheetPresentationControllerDetentIdentifierLarge;
}
if (self.beingPresented && !self.didSetupPendingDismissButtons) {
for (UIViewController *vc in self.viewControllers) {
[self addNavigationBarItemsToViewController:vc.navigationItem];
@@ -106,27 +89,13 @@
[self addNavigationBarItemsToViewController:viewController.navigationItem];
}
- (void)dismissViewControllerAnimated:(BOOL)flag completion:(void (^)(void))completion {
// Workaround for UIActivityViewController trying to dismiss us for some reason
if (![self.viewControllers.lastObject.presentedViewController isKindOfClass:UIActivityViewController.self]) {
[super dismissViewControllerAnimated:flag completion:completion];
}
}
- (void)dismissAnimated {
// Tabs are only closed if the done button is pressed; this
// allows you to leave a tab open by dragging down to dismiss
if ([self.presentingViewController isKindOfClass:[FLEXExplorerViewController class]]) {
[FLEXTabList.sharedList closeTab:self];
}
[FLEXTabList.sharedList closeTab:self];
[self.presentingViewController dismissViewControllerAnimated:YES completion:nil];
}
- (BOOL)canShowToolbar {
return self.topViewController.toolbarItems.count > 0;
}
- (void)addNavigationBarItemsToViewController:(UINavigationItem *)navigationItem {
if (!self.presentingViewController) {
return;
@@ -176,15 +145,8 @@
}
- (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 && self.canShowToolbar) {
if (self.toolbarHidden) {
[self setToolbarHidden:NO animated:YES];
}
}
@@ -200,7 +162,7 @@
- (void)_gestureRecognizedInteractiveHide:(UIPanGestureRecognizer *)sender {
if (sender.state == UIGestureRecognizerStateRecognized) {
BOOL show = self.canShowToolbar;
BOOL show = self.topViewController.toolbarItems.count;
CGFloat yTranslation = [sender translationInView:self.view].y;
CGFloat yVelocity = [sender velocityInView:self.view].y;
if (yVelocity > 2000) {
@@ -214,18 +176,3 @@
}
@end
@implementation UINavigationController (FLEXObjectExploring)
- (void)pushExplorerForObject:(id)object {
[self pushExplorerForObject:object animated:YES];
}
- (void)pushExplorerForObject:(id)object animated:(BOOL)animated {
UIViewController *explorer = [FLEXObjectExplorerFactory explorerViewControllerForObject:object];
if (explorer) {
[self pushViewController:explorer animated:animated];
}
}
@end
@@ -3,7 +3,7 @@
// FLEX
//
// Created by Tanner on 7/5/19.
// Copyright © 2020 FLEX Team. All rights reserved.
// Copyright © 2019 Flipboard. All rights reserved.
//
#import <UIKit/UIKit.h>
@@ -67,10 +67,6 @@ 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.
///
@@ -3,7 +3,7 @@
// FLEX
//
// Created by Tanner on 7/5/19.
// Copyright © 2020 FLEX Team. All rights reserved.
// Copyright © 2019 Flipboard. All rights reserved.
//
#import "FLEXTableViewController.h"
@@ -50,12 +50,15 @@ 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;
}
@@ -66,9 +69,9 @@ CGFloat const kFLEXDebounceForExpensiveIO = 0.5;
_searchBarDebounceInterval = kFLEXDebounceFast;
_showSearchBarInitially = YES;
_style = style;
_manuallyDeactivateSearchOnDisappear = (
NSProcessInfo.processInfo.operatingSystemVersion.majorVersion < 11
);
_manuallyDeactivateSearchOnDisappear = ({
NSProcessInfo.processInfo.operatingSystemVersion.majorVersion < 11;
});
// We will be our own search delegate if we implement this method
if ([self respondsToSelector:@selector(updateSearchResults:)]) {
@@ -96,20 +99,18 @@ CGFloat const kFLEXDebounceForExpensiveIO = 0.5;
self.searchController.searchBar.placeholder = @"Filter";
self.searchController.searchResultsUpdater = (id)self;
self.searchController.delegate = (id)self;
if (@available(iOS 9.1, *)) {
self.searchController.obscuresBackgroundDuringPresentation = NO;
} else {
self.searchController.dimsBackgroundDuringPresentation = NO;
}
self.searchController.dimsBackgroundDuringPresentation = NO;
self.searchController.hidesNavigationBarDuringPresentation = NO;
/// Not necessary in iOS 13; remove this when iOS 13 is the minimum deployment target
self.searchController.searchBar.delegate = self;
self.automaticallyShowsSearchBarCancelButton = YES;
#if FLEX_AT_LEAST_IOS13_SDK
if (@available(iOS 13, *)) {
self.searchController.automaticallyShowsScopeBar = NO;
}
#endif
[self addSearchController:self.searchController];
} else {
@@ -123,15 +124,18 @@ CGFloat const kFLEXDebounceForExpensiveIO = 0.5;
_showsCarousel = showsCarousel;
if (showsCarousel) {
_carousel = ({ weakify(self)
_carousel = ({
__weak __typeof(self) weakSelf = self;
FLEXScopeCarousel *carousel = [FLEXScopeCarousel new];
carousel.selectedIndexChangedAction = ^(NSInteger idx) { strongify(self);
carousel.selectedIndexChangedAction = ^(NSInteger idx) {
__typeof(self) self = weakSelf;
[self.searchDelegate updateSearchResults:self.searchText];
};
// UITableView won't update the header size unless you reset the header view
[carousel registerBlockForDynamicTypeChanges:^(FLEXScopeCarousel *_) { strongify(self);
[carousel registerBlockForDynamicTypeChanges:^(FLEXScopeCarousel *carousel) {
__typeof(self) self = weakSelf;
[self layoutTableHeaderIfNeeded];
}];
@@ -169,17 +173,21 @@ 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;
}
@@ -214,14 +222,12 @@ CGFloat const kFLEXDebounceForExpensiveIO = 0.5;
self.tableView.dataSource = self;
self.tableView.delegate = self;
self.tableView.estimatedRowHeight = 10;
_shareToolbarItem = FLEXBarButtonItemSystem(Action, self, @selector(shareButtonPressed:));
_bookmarksToolbarItem = [UIBarButtonItem
flex_itemWithImage:FLEXResources.bookmarksIcon target:self action:@selector(showBookmarks)
itemWithImage:FLEXResources.bookmarksIcon target:self action:@selector(showBookmarks)
];
_openTabsToolbarItem = [UIBarButtonItem
flex_itemWithImage:FLEXResources.openTabsIcon target:self action:@selector(showTabSwitcher)
itemWithImage:FLEXResources.openTabsIcon target:self action:@selector(showTabSwitcher)
];
self.leftmostToolbarItem = UIBarButtonItem.flex_fixedSpace;
@@ -235,7 +241,7 @@ CGFloat const kFLEXDebounceForExpensiveIO = 0.5;
self.tableView.keyboardDismissMode = UIScrollViewKeyboardDismissModeOnDrag;
// Toolbar
self.navigationController.toolbarHidden = self.toolbarItems.count > 0;
self.navigationController.toolbarHidden = NO;
self.navigationController.hidesBarsOnSwipe = YES;
// On iOS 13, the root view controller shows it's search bar no matter what.
@@ -253,17 +259,12 @@ 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];
}
@@ -285,17 +286,6 @@ 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;
@@ -535,37 +525,13 @@ 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 {
[self.debounceTimer invalidate];
NSString *text = searchController.searchBar.text;
void (^updateSearchResults)(void) = ^{
void (^updateSearchResults)() = ^{
if (self.searchResultsUpdater) {
[self.searchResultsUpdater updateSearchResults:text];
} else {
+5 -9
View File
@@ -3,13 +3,11 @@
// FLEX
//
// Created by Tanner Bennett on 9/25/19.
// Copyright © 2020 FLEX Team. All rights reserved.
// Copyright © 2019 Flipboard. All rights reserved.
//
#import "FLEXTableViewSection.h"
NS_ASSUME_NONNULL_BEGIN
/// A section providing a specific single row.
///
/// You may optionally provide a view controller to push when the row
@@ -18,15 +16,13 @@ NS_ASSUME_NONNULL_BEGIN
@interface FLEXSingleRowSection : FLEXTableViewSection
/// @param reuseIdentifier if nil, kFLEXDefaultCell is used.
+ (instancetype)title:(nullable NSString *)sectionTitle
reuse:(nullable NSString *)reuseIdentifier
+ (instancetype)title:(NSString *)sectionTitle
reuse:(NSString *)reuseIdentifier
cell:(void(^)(__kindof UITableViewCell *cell))cellConfiguration;
@property (nullable, nonatomic) UIViewController *pushOnSelection;
@property (nullable, nonatomic) void (^selectionAction)(UIViewController *host);
@property (nonatomic) UIViewController *pushOnSelection;
@property (nonatomic) void (^selectionAction)(UIViewController *host);
/// Called to determine whether the single row should display itself or not.
@property (nonatomic) BOOL (^filterMatcher)(NSString *filterText);
@end
NS_ASSUME_NONNULL_END
+1 -3
View File
@@ -3,7 +3,7 @@
// FLEX
//
// Created by Tanner Bennett on 9/25/19.
// Copyright © 2020 FLEX Team. All rights reserved.
// Copyright © 2019 Flipboard. All rights reserved.
//
#import "FLEXSingleRowSection.h"
@@ -30,8 +30,6 @@
- (id)initWithTitle:(NSString *)sectionTitle
reuse:(NSString *)reuseIdentifier
cell:(void (^)(__kindof UITableViewCell *))cellConfiguration {
NSParameterAssert(cellConfiguration);
self = [super init];
if (self) {
_title = sectionTitle;
+4 -16
View File
@@ -3,10 +3,11 @@
// FLEX
//
// Created by Tanner on 1/29/20.
// Copyright © 2020 FLEX Team. All rights reserved.
// Copyright © 2020 Flipboard. All rights reserved.
//
#import <UIKit/UIKit.h>
#import "FLEXMacros.h"
#import "NSArray+FLEX.h"
@class FLEXTableView;
@@ -23,10 +24,6 @@ NS_ASSUME_NONNULL_BEGIN
@protected
/// Unused by default, use if you want
NSString *_title;
@private
__weak UITableView *_tableView;
NSInteger _sectionIndex;
}
#pragma mark - Data
@@ -60,17 +57,6 @@ NS_ASSUME_NONNULL_BEGIN
/// \c setFilterText: to call \c super and call \c reloadData.
- (void)reloadData;
/// Like \c reloadData, but optionally reloads the table view section
/// associated with this section object, if any. Do not override.
/// Do not call outside of the main thread.
- (void)reloadData:(BOOL)updateTable;
/// Provide a table view and section index to allow the section to efficiently reload
/// its own section of the table when something changes it. The table reference is
/// held weakly, and subclasses cannot access it or the index. Call this method again
/// if the section numbers have changed since you last called it.
- (void)setTable:(UITableView *)tableView section:(NSInteger)index;
#pragma mark - Row Selection
/// Whether the given row should be selectable, such as if tapping the cell
@@ -100,6 +86,7 @@ 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.
@@ -119,6 +106,7 @@ 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
+8 -17
View File
@@ -3,7 +3,7 @@
// FLEX
//
// Created by Tanner on 1/29/20.
// Copyright © 2020 FLEX Team. All rights reserved.
// Copyright © 2020 Flipboard. All rights reserved.
//
#import "FLEXTableViewSection.h"
@@ -22,19 +22,6 @@
- (void)reloadData { }
- (void)reloadData:(BOOL)updateTable {
[self reloadData];
if (updateTable) {
NSIndexSet *index = [NSIndexSet indexSetWithIndex:_sectionIndex];
[_tableView reloadSections:index withRowAnimation:UITableViewRowAnimationNone];
}
}
- (void)setTable:(UITableView *)tableView section:(NSInteger)index {
_tableView = tableView;
_sectionIndex = index;
}
- (NSDictionary<NSString *,Class> *)cellRegistrationMapping {
return nil;
}
@@ -64,6 +51,8 @@
return kFLEXDefaultCell;
}
#if FLEX_AT_LEAST_IOS13_SDK
- (NSString *)menuTitleForRow:(NSInteger)row {
NSString *title = [self titleForRow:row];
NSString *subtitle = [self menuSubtitleForRow:row];
@@ -79,7 +68,7 @@
return @"";
}
- (NSArray<UIMenuElement *> *)menuItemsForRow:(NSInteger)row sender:(UIViewController *)sender API_AVAILABLE(ios(13.0)) {
- (NSArray<UIMenuElement *> *)menuItemsForRow:(NSInteger)row sender:(UIViewController *)sender API_AVAILABLE(ios(13)) {
NSArray<NSString *> *copyItems = [self copyMenuItemsForRow:row];
NSAssert(copyItems.count % 2 == 0, @"copyMenuItemsForRow: should return an even list");
@@ -110,13 +99,13 @@
}
UIMenu *copyMenu = [UIMenu
flex_inlineMenuWithTitle:@"Copy…"
inlineMenuWithTitle:@"Copy…"
image:copyIcon
children:actions
];
if (collapseMenu) {
return @[[copyMenu flex_collapsed]];
return @[[copyMenu collapsed]];
} else {
return @[copyMenu];
}
@@ -125,6 +114,8 @@
return @[];
}
#endif
- (NSArray<NSString *> *)copyMenuItemsForRow:(NSInteger)row {
return nil;
}
@@ -3,7 +3,7 @@
// FLEX
//
// Created by Tanner Bennett on 7/17/19.
// Copyright © 2020 FLEX Team. All rights reserved.
// Copyright © 2019 Flipboard. All rights reserved.
//
#import <UIKit/UIKit.h>
@@ -3,7 +3,7 @@
// FLEX
//
// Created by Tanner Bennett on 7/17/19.
// Copyright © 2020 FLEX Team. All rights reserved.
// Copyright © 2019 Flipboard. All rights reserved.
//
#import "FLEXCarouselCell.h"
@@ -77,7 +77,7 @@
self.selectionIndicatorStripe.translatesAutoresizingMaskIntoConstraints = NO;
UIView *superview = self.contentView;
[self.titleLabel flex_pinEdgesToSuperviewWithInsets:UIEdgeInsetsMake(10, 15, 8 + stripeHeight, 15)];
[self.titleLabel pinEdgesToSuperviewWithInsets:UIEdgeInsetsMake(10, 15, 8 + stripeHeight, 15)];
[self.selectionIndicatorStripe.leadingAnchor constraintEqualToAnchor:superview.leadingAnchor].active = YES;
[self.selectionIndicatorStripe.bottomAnchor constraintEqualToAnchor:superview.bottomAnchor].active = YES;
@@ -3,7 +3,7 @@
// FLEX
//
// Created by Tanner Bennett on 7/17/19.
// Copyright © 2020 FLEX Team. All rights reserved.
// Copyright © 2019 Flipboard. All rights reserved.
//
#import <UIKit/UIKit.h>
@@ -3,13 +3,12 @@
// FLEX
//
// Created by Tanner Bennett on 7/17/19.
// Copyright © 2020 FLEX Team. All rights reserved.
// Copyright © 2019 Flipboard. All rights reserved.
//
#import "FLEXScopeCarousel.h"
#import "FLEXCarouselCell.h"
#import "FLEXColor.h"
#import "FLEXMacros.h"
#import "UIView+FLEX_Layout.h"
const CGFloat kCarouselItemSpacing = 0;
@@ -73,14 +72,15 @@ NSString * const kCarouselCellReuseIdentifier = @"kCarouselCellReuseIdentifier";
self.sizingCell.title = @"NSObject";
// Dynamic type
weakify(self);
__weak __typeof(self) weakSelf = self;
_dynamicTypeObserver = [NSNotificationCenter.defaultCenter
addObserverForName:UIContentSizeCategoryDidChangeNotification
object:nil queue:nil usingBlock:^(NSNotification *note) { strongify(self)
object:nil queue:nil usingBlock:^(NSNotification *note) {
[self.collectionView setNeedsLayout];
[self setNeedsUpdateConstraints];
// Notify observers
__typeof(self) self = weakSelf;
for (void (^block)(FLEXScopeCarousel *) in self.dynamicTypeHandlers) {
block(self);
}
@@ -118,7 +118,7 @@ NSString * const kCarouselCellReuseIdentifier = @"kCarouselCellReuseIdentifier";
- (void)updateConstraints {
if (!self.constraintsInstalled) {
self.collectionView.translatesAutoresizingMaskIntoConstraints = NO;
[self.collectionView flex_pinEdgesToSuperview];
[self.collectionView pinEdgesToSuperview];
self.constraintsInstalled = YES;
}
+1 -1
View File
@@ -3,7 +3,7 @@
// FLEX
//
// Created by Tanner on 12/27/19.
// Copyright © 2020 FLEX Team. All rights reserved.
// Copyright © 2019 Flipboard. All rights reserved.
//
#import "FLEXMultilineTableViewCell.h"
+1 -1
View File
@@ -3,7 +3,7 @@
// FLEX
//
// Created by Tanner on 12/27/19.
// Copyright © 2020 FLEX Team. All rights reserved.
// Copyright © 2019 Flipboard. All rights reserved.
//
#import "FLEXCodeFontCell.h"
@@ -3,7 +3,7 @@
// FLEX
//
// Created by Tanner Bennett on 1/23/20.
// Copyright © 2020 FLEX Team. All rights reserved.
// Copyright © 2020 Flipboard. All rights reserved.
//
#import "FLEXTableViewCell.h"
@@ -3,7 +3,7 @@
// FLEX
//
// Created by Tanner Bennett on 1/23/20.
// Copyright © 2020 FLEX Team. All rights reserved.
// Copyright © 2020 Flipboard. All rights reserved.
//
#import "FLEXKeyValueTableViewCell.h"
@@ -3,7 +3,7 @@
// FLEX
//
// Created by Ryan Olson on 2/13/15.
// Copyright (c) 2020 FLEX Team. All rights reserved.
// Copyright (c) 2015 f. All rights reserved.
//
#import "FLEXTableViewCell.h"
@@ -3,7 +3,7 @@
// FLEX
//
// Created by Ryan Olson on 2/13/15.
// Copyright (c) 2020 FLEX Team. All rights reserved.
// Copyright (c) 2015 f. All rights reserved.
//
#import "FLEXMultilineTableViewCell.h"
@@ -3,7 +3,7 @@
// FLEX
//
// Created by Tanner on 4/17/19.
// Copyright © 2020 FLEX Team. All rights reserved.
// Copyright © 2019 Flipboard. All rights reserved.
//
#import "FLEXTableViewCell.h"
@@ -3,7 +3,7 @@
// FLEX
//
// Created by Tanner on 4/17/19.
// Copyright © 2020 FLEX Team. All rights reserved.
// Copyright © 2019 Flipboard. All rights reserved.
//
#import "FLEXSubtitleTableViewCell.h"
+1 -1
View File
@@ -3,7 +3,7 @@
// FLEX
//
// Created by Tanner on 4/17/19.
// Copyright © 2020 FLEX Team. All rights reserved.
// Copyright © 2019 Flipboard. All rights reserved.
//
#import <UIKit/UIKit.h>
+1 -1
View File
@@ -3,7 +3,7 @@
// FLEX
//
// Created by Tanner on 4/17/19.
// Copyright © 2020 FLEX Team. All rights reserved.
// Copyright © 2019 Flipboard. All rights reserved.
//
#import "FLEXTableViewCell.h"
+1 -1
View File
@@ -3,7 +3,7 @@
// FLEX
//
// Created by Tanner on 4/17/19.
// Copyright © 2020 FLEX Team. All rights reserved.
// Copyright © 2019 Flipboard. All rights reserved.
//
#import <UIKit/UIKit.h>
+9 -1
View File
@@ -3,7 +3,7 @@
// FLEX
//
// Created by Tanner on 4/17/19.
// Copyright © 2020 FLEX Team. All rights reserved.
// Copyright © 2019 Flipboard. All rights reserved.
//
#import "FLEXTableView.h"
@@ -30,21 +30,29 @@ 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 {
@@ -3,7 +3,7 @@
// Flipboard
//
// Created by Ryan Olson on 6/30/14.
// Copyright (c) 2020 FLEX Team. All rights reserved.
// Copyright (c) 2020 Flipboard. All rights reserved.
//
#import "FLEXArgumentInputView.h"
@@ -3,7 +3,7 @@
// Flipboard
//
// Created by Ryan Olson on 6/30/14.
// Copyright (c) 2020 FLEX Team. All rights reserved.
// Copyright (c) 2020 Flipboard. All rights reserved.
//
#import "FLEXArgumentInputColorView.h"
@@ -3,7 +3,7 @@
// Flipboard
//
// Created by Daniel Rodriguez Troitino on 2/14/15.
// Copyright (c) 2020 FLEX Team. All rights reserved.
// Copyright (c) 2020 Flipboard. All rights reserved.
//
#import "FLEXArgumentInputView.h"
@@ -3,7 +3,7 @@
// Flipboard
//
// Created by Daniel Rodriguez Troitino on 2/14/15.
// Copyright (c) 2020 FLEX Team. All rights reserved.
// Copyright (c) 2020 Flipboard. All rights reserved.
//
#import "FLEXArgumentInputDateView.h"
@@ -3,7 +3,7 @@
// Flipboard
//
// Created by Ryan Olson on 6/28/14.
// Copyright (c) 2020 FLEX Team. All rights reserved.
// Copyright (c) 2020 Flipboard. All rights reserved.
//
#import "FLEXArgumentInputView.h"
@@ -3,7 +3,7 @@
// Flipboard
//
// Created by Ryan Olson on 6/28/14.
// Copyright (c) 2020 FLEX Team. All rights reserved.
// Copyright (c) 2020 Flipboard. All rights reserved.
//
#import "FLEXArgumentInputFontView.h"
@@ -46,13 +46,7 @@
UIPickerView *fontsPicker = [UIPickerView new];
fontsPicker.dataSource = self;
fontsPicker.delegate = self;
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
// Deprecated in iOS 13; from then on, selection is always shown
fontsPicker.showsSelectionIndicator = YES;
#pragma clang diagnostic pop
return fontsPicker;
}
@@ -3,7 +3,7 @@
// Flipboard
//
// Created by Ryan Olson on 6/18/14.
// Copyright (c) 2020 FLEX Team. All rights reserved.
// Copyright (c) 2020 Flipboard. All rights reserved.
//
#import "FLEXArgumentInputTextView.h"
@@ -3,7 +3,7 @@
// Flipboard
//
// Created by Ryan Olson on 6/18/14.
// Copyright (c) 2020 FLEX Team. All rights reserved.
// Copyright (c) 2020 Flipboard. All rights reserved.
//
#import "FLEXArgumentInputNotSupportedView.h"
@@ -3,7 +3,7 @@
// Flipboard
//
// Created by Ryan Olson on 6/15/14.
// Copyright (c) 2020 FLEX Team. All rights reserved.
// Copyright (c) 2020 Flipboard. All rights reserved.
//
#import "FLEXArgumentInputTextView.h"
@@ -3,7 +3,7 @@
// Flipboard
//
// Created by Ryan Olson on 6/15/14.
// Copyright (c) 2020 FLEX Team. All rights reserved.
// Copyright (c) 2020 Flipboard. All rights reserved.
//
#import "FLEXArgumentInputNumberView.h"
@@ -3,7 +3,7 @@
// Flipboard
//
// Created by Ryan Olson on 6/15/14.
// Copyright (c) 2020 FLEX Team. All rights reserved.
// Copyright (c) 2020 Flipboard. All rights reserved.
//
#import "FLEXArgumentInputTextView.h"
@@ -3,7 +3,7 @@
// Flipboard
//
// Created by Ryan Olson on 6/15/14.
// Copyright (c) 2020 FLEX Team. All rights reserved.
// Copyright (c) 2020 Flipboard. All rights reserved.
//
#import "FLEXArgumentInputObjectView.h"
@@ -3,7 +3,7 @@
// Flipboard
//
// Created by Ryan Olson on 6/28/14.
// Copyright (c) 2020 FLEX Team. All rights reserved.
// Copyright (c) 2020 Flipboard. All rights reserved.
//
#import "FLEXArgumentInputTextView.h"
@@ -3,7 +3,7 @@
// Flipboard
//
// Created by Ryan Olson on 6/28/14.
// Copyright (c) 2020 FLEX Team. All rights reserved.
// Copyright (c) 2020 Flipboard. All rights reserved.
//
#import "FLEXArgumentInputStringView.h"
@@ -3,14 +3,11 @@
// Flipboard
//
// Created by Ryan Olson on 6/16/14.
// Copyright (c) 2020 FLEX Team. All rights reserved.
// Copyright (c) 2020 Flipboard. All rights reserved.
//
#import "FLEXArgumentInputView.h"
@interface FLEXArgumentInputStructView : FLEXArgumentInputView
/// Enable displaying ivar names for custom struct types
+ (void)registerFieldNames:(NSArray<NSString *> *)names forTypeEncoding:(NSString *)typeEncoding;
@end
@@ -3,7 +3,7 @@
// Flipboard
//
// Created by Ryan Olson on 6/16/14.
// Copyright (c) 2020 FLEX Team. All rights reserved.
// Copyright (c) 2020 Flipboard. All rights reserved.
//
#import "FLEXArgumentInputStructView.h"
@@ -11,7 +11,7 @@
#import "FLEXRuntimeUtility.h"
#import "FLEXTypeEncodingParser.h"
@interface FLEXArgumentInputStructView ()
@interface FLEXArgumentInputStructView () <FLEXArgumentInputViewDelegate>
@property (nonatomic) NSArray<FLEXArgumentInputView *> *argumentInputViews;
@@ -19,41 +19,6 @@
@implementation FLEXArgumentInputStructView
static NSMutableDictionary<NSString *, NSArray<NSString *> *> *structFieldNameRegistrar = nil;
+ (void)initialize {
if (self == [FLEXArgumentInputStructView class]) {
structFieldNameRegistrar = [NSMutableDictionary new];
[self registerDefaultFieldNames];
}
}
+ (void)registerDefaultFieldNames {
NSDictionary *defaults = @{
@(@encode(CGRect)): @[@"CGPoint origin", @"CGSize size"],
@(@encode(CGPoint)): @[@"CGFloat x", @"CGFloat y"],
@(@encode(CGSize)): @[@"CGFloat width", @"CGFloat height"],
@(@encode(CGVector)): @[@"CGFloat dx", @"CGFloat dy"],
@(@encode(UIEdgeInsets)): @[@"CGFloat top", @"CGFloat left", @"CGFloat bottom", @"CGFloat right"],
@(@encode(UIOffset)): @[@"CGFloat horizontal", @"CGFloat vertical"],
@(@encode(NSRange)): @[@"NSUInteger location", @"NSUInteger length"],
@(@encode(CATransform3D)): @[@"CGFloat m11", @"CGFloat m12", @"CGFloat m13", @"CGFloat m14",
@"CGFloat m21", @"CGFloat m22", @"CGFloat m23", @"CGFloat m24",
@"CGFloat m31", @"CGFloat m32", @"CGFloat m33", @"CGFloat m34",
@"CGFloat m41", @"CGFloat m42", @"CGFloat m43", @"CGFloat m44"],
@(@encode(CGAffineTransform)): @[@"CGFloat a", @"CGFloat b",
@"CGFloat c", @"CGFloat d",
@"CGFloat tx", @"CGFloat ty"],
};
[structFieldNameRegistrar addEntriesFromDictionary:defaults];
if (@available(iOS 11.0, *)) {
structFieldNameRegistrar[@(@encode(NSDirectionalEdgeInsets))] = @[
@"CGFloat top", @"CGFloat leading", @"CGFloat bottom", @"CGFloat trailing"
];
}
}
- (instancetype)initWithArgumentTypeEncoding:(const char *)typeEncoding {
self = [super initWithArgumentTypeEncoding:typeEncoding];
if (self) {
@@ -65,8 +30,11 @@ static NSMutableDictionary<NSString *, NSArray<NSString *> *> *structFieldNameRe
NSUInteger fieldIndex,
NSUInteger fieldOffset) {
FLEXArgumentInputView *inputView = [FLEXArgumentInputViewFactory argumentInputViewForTypeEncoding:fieldTypeEncoding];
FLEXArgumentInputView *inputView = [FLEXArgumentInputViewFactory
argumentInputViewForTypeEncoding:fieldTypeEncoding
];
inputView.targetSize = FLEXArgumentInputViewSizeSmall;
inputView.delegate = self;
if (fieldIndex < customTitles.count) {
inputView.title = customTitles[fieldIndex];
@@ -160,15 +128,45 @@ static NSMutableDictionary<NSString *, NSArray<NSString *> *> *structFieldNameRe
return boxedStruct;
}
- (BOOL)inputViewIsFirstResponder {
BOOL isFirstResponder = NO;
- (FLEXArgumentInputView *)firstResponderInputView {
for (FLEXArgumentInputView *inputView in self.argumentInputViews) {
if ([inputView inputViewIsFirstResponder]) {
isFirstResponder = YES;
break;
return inputView;
}
}
return isFirstResponder;
return nil;
}
- (BOOL)resignFirstResponder {
FLEXArgumentInputView *responder = [self firstResponderInputView];
if (responder) {
return [responder resignFirstResponder];
} else {
return [super resignFirstResponder];
}
}
#pragma mark - FLEXArgumentInputViewDelegate
- (void)argumentInputViewValueDidChange:(FLEXArgumentInputView *)inputView {
// Nothing to see here
}
- (void)argumentInputViewWantsNextAsFirstResponder:(FLEXArgumentInputView *)inputView {
if (self.argumentInputViews.lastObject == inputView) {
// If this is our last or only input view,
// notify the delegate or dismiss the keyboard
if (self.delegate) {
[self.delegate argumentInputViewWantsNextAsFirstResponder:self];
} else {
[inputView resignFirstResponder];
}
} else {
NSInteger idx = [self.argumentInputViews indexOfObject:inputView];
[self.argumentInputViews[idx+1] becomeFirstResponder];
}
}
@@ -216,13 +214,40 @@ static NSMutableDictionary<NSString *, NSArray<NSString *> *> *structFieldNameRe
return NO;
}
+ (void)registerFieldNames:(NSArray<NSString *> *)names forTypeEncoding:(NSString *)typeEncoding {
NSParameterAssert(typeEncoding); NSParameterAssert(names);
structFieldNameRegistrar[typeEncoding] = names;
}
+ (NSArray<NSString *> *)customFieldTitlesForTypeEncoding:(const char *)typeEncoding {
return structFieldNameRegistrar[@(typeEncoding)];
NSArray<NSString *> *customTitles = nil;
if (strcmp(typeEncoding, @encode(CGRect)) == 0) {
customTitles = @[@"CGPoint origin", @"CGSize size"];
} else if (strcmp(typeEncoding, @encode(CGPoint)) == 0) {
customTitles = @[@"CGFloat x", @"CGFloat y"];
} else if (strcmp(typeEncoding, @encode(CGSize)) == 0) {
customTitles = @[@"CGFloat width", @"CGFloat height"];
} else if (strcmp(typeEncoding, @encode(CGVector)) == 0) {
customTitles = @[@"CGFloat dx", @"CGFloat dy"];
} else if (strcmp(typeEncoding, @encode(UIEdgeInsets)) == 0) {
customTitles = @[@"CGFloat top", @"CGFloat left", @"CGFloat bottom", @"CGFloat right"];
} else if (strcmp(typeEncoding, @encode(UIOffset)) == 0) {
customTitles = @[@"CGFloat horizontal", @"CGFloat vertical"];
} else if (strcmp(typeEncoding, @encode(NSRange)) == 0) {
customTitles = @[@"NSUInteger location", @"NSUInteger length"];
} else if (strcmp(typeEncoding, @encode(CATransform3D)) == 0) {
customTitles = @[@"CGFloat m11", @"CGFloat m12", @"CGFloat m13", @"CGFloat m14",
@"CGFloat m21", @"CGFloat m22", @"CGFloat m23", @"CGFloat m24",
@"CGFloat m31", @"CGFloat m32", @"CGFloat m33", @"CGFloat m34",
@"CGFloat m41", @"CGFloat m42", @"CGFloat m43", @"CGFloat m44"];
} else if (strcmp(typeEncoding, @encode(CGAffineTransform)) == 0) {
customTitles = @[@"CGFloat a", @"CGFloat b",
@"CGFloat c", @"CGFloat d",
@"CGFloat tx", @"CGFloat ty"];
} else {
if (@available(iOS 11.0, *)) {
if (strcmp(typeEncoding, @encode(NSDirectionalEdgeInsets)) == 0) {
customTitles = @[@"CGFloat top", @"CGFloat leading",
@"CGFloat bottom", @"CGFloat trailing"];
}
}
}
return customTitles;
}
@end
@@ -3,7 +3,7 @@
// Flipboard
//
// Created by Ryan Olson on 6/16/14.
// Copyright (c) 2020 FLEX Team. All rights reserved.
// Copyright (c) 2020 Flipboard. All rights reserved.
//
#import "FLEXArgumentInputView.h"
@@ -3,7 +3,7 @@
// Flipboard
//
// Created by Ryan Olson on 6/16/14.
// Copyright (c) 2020 FLEX Team. All rights reserved.
// Copyright (c) 2020 Flipboard. All rights reserved.
//
#import "FLEXArgumentInputSwitchView.h"
@@ -8,11 +8,15 @@
#import "FLEXArgumentInputView.h"
NS_ASSUME_NONNULL_BEGIN
@interface FLEXArgumentInputTextView : FLEXArgumentInputView <UITextViewDelegate>
// For subclass eyes only
@property (nonatomic, readonly) UITextView *inputTextView;
@property (nonatomic) NSString *inputPlaceholderText;
@property (nonatomic, nullable) NSString *inputPlaceholderText;
@end
NS_ASSUME_NONNULL_END
@@ -31,9 +31,8 @@
self.inputTextView.autocapitalizationType = UITextAutocapitalizationTypeNone;
self.inputTextView.autocorrectionType = UITextAutocorrectionTypeNo;
self.inputTextView.delegate = self;
self.inputTextView.inputAccessoryView = [self createToolBar];
self.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;
@@ -52,6 +51,14 @@
return self;
}
- (UIToolbar *)inputAccessoryView {
return (id)self.inputTextView.inputAccessoryView;
}
- (void)setInputAccessoryView:(UIToolbar *)inputAccessoryView {
self.inputTextView.inputAccessoryView = inputAccessoryView;
}
#pragma mark - Private
- (UIToolbar *)createToolBar {
@@ -63,7 +70,7 @@
];
UIBarButtonItem *pasteItem = [[UIBarButtonItem alloc]
initWithTitle:@"Paste" style:UIBarButtonItemStyleDone
target:self.inputTextView action:@selector(paste:)
target:self action:@selector(paste:)
];
UIBarButtonItem *doneItem = [[UIBarButtonItem alloc]
initWithBarButtonSystemItem:UIBarButtonSystemItemDone
@@ -92,6 +99,16 @@
return self.placeholderLabel.text;
}
- (void)paste:(id)sender {
[self.inputTextView paste:sender];
if (self.delegate) {
[self.delegate argumentInputViewWantsNextAsFirstResponder:self];
} else {
[self.inputTextView resignFirstResponder];
}
}
#pragma mark - Superclass Overrides
@@ -99,6 +116,18 @@
return self.inputTextView.isFirstResponder;
}
- (BOOL)becomeFirstResponder {
return [self.inputTextView becomeFirstResponder];
}
- (BOOL)resignFirstResponder {
if (self.inputViewIsFirstResponder) {
return self.inputTextView.resignFirstResponder;
} else {
return [super resignFirstResponder];
}
}
#pragma mark - Layout and Sizing
@@ -3,7 +3,7 @@
// Flipboard
//
// Created by Ryan Olson on 5/30/14.
// Copyright (c) 2020 FLEX Team. All rights reserved.
// Copyright (c) 2020 Flipboard. All rights reserved.
//
#import <UIKit/UIKit.h>
@@ -19,6 +19,8 @@ typedef NS_ENUM(NSUInteger, FLEXArgumentInputViewSize) {
@protocol FLEXArgumentInputViewDelegate;
NS_ASSUME_NONNULL_BEGIN
@interface FLEXArgumentInputView : UIView
- (instancetype)initWithArgumentTypeEncoding:(const char *)typeEncoding;
@@ -31,23 +33,25 @@ typedef NS_ENUM(NSUInteger, FLEXArgumentInputViewSize) {
/// Primitive types and structs should/will be boxed in NSValue containers.
/// Concrete subclasses should override both the setter and getter for this property.
/// Subclasses can call super.inputValue to access a backing store for the value.
@property (nonatomic) id inputValue;
@property (nonatomic, nullable) id inputValue;
/// Setting this value to large will make some argument input views increase the size of their input field(s).
/// Useful to increase the use of space if there is only one input view on screen (i.e. for property and ivar editing).
@property (nonatomic) FLEXArgumentInputViewSize targetSize;
/// Users of the input view can get delegate callbacks for incremental changes in user input.
@property (nonatomic, weak) id <FLEXArgumentInputViewDelegate> delegate;
@property (nonatomic, weak, nullable) id <FLEXArgumentInputViewDelegate> delegate;
// Subclasses can override
@property (nonatomic, nullable) UIToolbar *inputAccessoryView;
/// If the input view has one or more text views, returns YES when one of them is focused.
@property (nonatomic, readonly) BOOL inputViewIsFirstResponder;
/// For subclasses to indicate that they can handle editing a field the give type and value.
/// Used by FLEXArgumentInputViewFactory to create appropriate input views.
+ (BOOL)supportsObjCType:(const char *)type withCurrentValue:(id)value;
+ (BOOL)supportsObjCType:(const char *)type withCurrentValue:(nullable id)value;
// For subclass eyes only
@@ -59,6 +63,11 @@ typedef NS_ENUM(NSUInteger, FLEXArgumentInputViewSize) {
@protocol FLEXArgumentInputViewDelegate <NSObject>
//- (void)
- (void)argumentInputViewValueDidChange:(FLEXArgumentInputView *)argumentInputView;
- (void)argumentInputViewWantsNextAsFirstResponder:(FLEXArgumentInputView *)argumentInputView;
@end
NS_ASSUME_NONNULL_END
@@ -3,7 +3,7 @@
// Flipboard
//
// Created by Ryan Olson on 5/30/14.
// Copyright (c) 2020 FLEX Team. All rights reserved.
// Copyright (c) 2020 Flipboard. All rights reserved.
//
#import "FLEXArgumentInputView.h"
@@ -21,7 +21,4 @@
/// Useful when deciding whether to edit or explore a property, ivar, or NSUserDefaults value.
+ (BOOL)canEditFieldWithTypeEncoding:(const char *)typeEncoding currentValue:(id)currentValue;
/// Enable displaying ivar names for custom struct types
+ (void)registerFieldNames:(NSArray<NSString *> *)names forTypeEncoding:(NSString *)typeEncoding;
@end
@@ -67,9 +67,4 @@
return [self argumentInputViewSubclassForTypeEncoding:typeEncoding currentValue:currentValue] != nil;
}
/// Enable displaying ivar names for custom struct types
+ (void)registerFieldNames:(NSArray<NSString *> *)names forTypeEncoding:(NSString *)typeEncoding {
[FLEXArgumentInputStructView registerFieldNames:names forTypeEncoding:typeEncoding];
}
@end
@@ -3,19 +3,15 @@
// Flipboard
//
// Created by Ryan Olson on 5/23/14.
// Copyright (c) 2020 FLEX Team. All rights reserved.
// Copyright (c) 2020 Flipboard. All rights reserved.
//
#import "FLEXFieldEditorViewController.h"
NS_ASSUME_NONNULL_BEGIN
@interface FLEXDefaultEditorViewController : FLEXFieldEditorViewController
@interface FLEXDefaultEditorViewController : FLEXVariableEditorViewController
- (id)initWithDefaults:(NSUserDefaults *)defaults key:(NSString *)key;
+ (instancetype)target:(NSUserDefaults *)defaults key:(NSString *)key commitHandler:(void(^_Nullable)(void))onCommit;
+ (BOOL)canEditDefaultWithValue:(nullable id)currentValue;
+ (BOOL)canEditDefaultWithValue:(id)currentValue;
@end
NS_ASSUME_NONNULL_END
@@ -3,7 +3,7 @@
// Flipboard
//
// Created by Ryan Olson on 5/23/14.
// Copyright (c) 2020 FLEX Team. All rights reserved.
// Copyright (c) 2020 Flipboard. All rights reserved.
//
#import "FLEXDefaultEditorViewController.h"
@@ -15,24 +15,23 @@
@interface FLEXDefaultEditorViewController ()
@property (nonatomic, readonly) NSUserDefaults *defaults;
@property (nonatomic, readonly) NSString *key;
@property (nonatomic) NSString *key;
@end
@implementation FLEXDefaultEditorViewController
+ (instancetype)target:(NSUserDefaults *)defaults key:(NSString *)key commitHandler:(void(^_Nullable)(void))onCommit {
FLEXDefaultEditorViewController *editor = [self target:defaults data:key commitHandler:onCommit];
editor.title = @"Edit Default";
return editor;
- (id)initWithDefaults:(NSUserDefaults *)defaults key:(NSString *)key {
self = [super initWithTarget:defaults];
if (self) {
self.key = key;
self.title = @"Edit Default";
}
return self;
}
- (NSUserDefaults *)defaults {
return [_target isKindOfClass:[NSUserDefaults class]] ? _target : nil;
}
- (NSString *)key {
return _data;
return [self.target isKindOfClass:[NSUserDefaults class]] ? self.target : nil;
}
- (void)viewDidLoad {
@@ -47,10 +46,13 @@
];
inputView.backgroundColor = self.view.backgroundColor;
inputView.inputValue = currentValue;
inputView.delegate = self;
self.fieldEditorView.argumentInputViews = @[inputView];
}
- (void)actionButtonPressed:(id)sender {
[super actionButtonPressed:sender];
id value = self.firstInputView.inputValue;
if (value) {
[self.defaults setObject:value forKey:self.key];
@@ -58,16 +60,14 @@
[self.defaults removeObjectForKey:self.key];
}
[self.defaults synchronize];
// Dismiss keyboard and handle committed changes
[super actionButtonPressed:sender];
// Go back after setting, but not for switches.
if (sender) {
[self.navigationController popViewControllerAnimated:YES];
} else {
self.firstInputView.inputValue = [self.defaults objectForKey:self.key];
}
self.firstInputView.inputValue = [self.defaults objectForKey:self.key];
}
- (void)getterButtonPressed:(id)sender {
[super getterButtonPressed:sender];
id returnedObject = [self.defaults objectForKey:self.key];
[self exploreObjectOrPopViewController:returnedObject];
}
+ (BOOL)canEditDefaultWithValue:(id)currentValue {
+1 -1
View File
@@ -3,7 +3,7 @@
// Flipboard
//
// Created by Ryan Olson on 5/16/14.
// Copyright (c) 2020 FLEX Team. All rights reserved.
// Copyright (c) 2020 Flipboard. All rights reserved.
//
#import <UIKit/UIKit.h>
+2 -3
View File
@@ -3,13 +3,12 @@
// Flipboard
//
// Created by Ryan Olson on 5/16/14.
// Copyright (c) 2020 FLEX Team. All rights reserved.
// Copyright (c) 2020 Flipboard. All rights reserved.
//
#import "FLEXFieldEditorView.h"
#import "FLEXArgumentInputView.h"
#import "FLEXUtility.h"
#import "FLEXColor.h"
@interface FLEXFieldEditorView ()
@@ -123,7 +122,7 @@
}
+ (UIColor *)dividerColor {
return FLEXColor.tertiaryBackgroundColor;
return UIColor.lightGrayColor;
}
+ (CGFloat)horizontalPadding {
@@ -3,7 +3,7 @@
// FLEX
//
// Created by Tanner on 11/22/18.
// Copyright © 2020 FLEX Team. All rights reserved.
// Copyright © 2018 Flipboard. All rights reserved.
//
#import "FLEXVariableEditorViewController.h"
@@ -15,9 +15,9 @@ NS_ASSUME_NONNULL_BEGIN
@interface FLEXFieldEditorViewController : FLEXVariableEditorViewController
/// @return nil if the property is readonly or if the type is unsupported
+ (nullable instancetype)target:(id)target property:(FLEXProperty *)property commitHandler:(void(^_Nullable)(void))onCommit;
+ (nullable instancetype)target:(id)target property:(FLEXProperty *)property;
/// @return nil if the ivar type is unsupported
+ (nullable instancetype)target:(id)target ivar:(FLEXIvar *)ivar commitHandler:(void(^_Nullable)(void))onCommit;
+ (nullable instancetype)target:(id)target ivar:(FLEXIvar *)ivar;
/// Subclasses can change the button title via the \c title property
@property (nonatomic, readonly) UIBarButtonItem *getterButton;
+24 -29
View File
@@ -3,7 +3,7 @@
// FLEX
//
// Created by Tanner on 11/22/18.
// Copyright © 2020 FLEX Team. All rights reserved.
// Copyright © 2018 Flipboard. All rights reserved.
//
#import "FLEXFieldEditorViewController.h"
@@ -11,14 +11,12 @@
#import "FLEXArgumentInputViewFactory.h"
#import "FLEXPropertyAttributes.h"
#import "FLEXRuntimeUtility.h"
#import "FLEXMetadataExtras.h"
#import "FLEXUtility.h"
#import "FLEXColor.h"
#import "UIBarButtonItem+FLEX.h"
@interface FLEXFieldEditorViewController () <FLEXArgumentInputViewDelegate>
@property (nonatomic, readonly) id<FLEXMetadataAuxiliaryInfo> auxiliaryInfoProvider;
@property (nonatomic) FLEXProperty *property;
@property (nonatomic) FLEXIvar *ivar;
@@ -32,15 +30,20 @@
#pragma mark - Initialization
+ (instancetype)target:(id)target property:(nonnull FLEXProperty *)property commitHandler:(void(^)(void))onCommit {
FLEXFieldEditorViewController *editor = [self target:target data:property commitHandler:onCommit];
+ (instancetype)target:(id)target property:(FLEXProperty *)property {
id value = [property getValue:target];
if (![self canEditProperty:property onObject:target currentValue:value]) {
return nil;
}
FLEXFieldEditorViewController *editor = [self target:target];
editor.title = [@"Property: " stringByAppendingString:property.name];
editor.property = property;
return editor;
}
+ (instancetype)target:(id)target ivar:(nonnull FLEXIvar *)ivar commitHandler:(void(^)(void))onCommit {
FLEXFieldEditorViewController *editor = [self target:target data:ivar commitHandler:onCommit];
+ (instancetype)target:(id)target ivar:(nonnull FLEXIvar *)ivar {
FLEXFieldEditorViewController *editor = [self target:target];
editor.title = [@"Ivar: " stringByAppendingString:ivar.name];
editor.ivar = ivar;
return editor;
@@ -63,8 +66,6 @@
self.toolbarItems = @[
UIBarButtonItem.flex_flexibleSpace, self.getterButton, self.actionButton
];
[self registerAuxiliaryInfo];
// Configure input view
self.fieldEditorView.fieldDescription = self.fieldDescription;
@@ -85,6 +86,8 @@
}
- (void)actionButtonPressed:(id)sender {
[super actionButtonPressed:sender];
if (self.property) {
id userInputObject = self.firstInputView.inputValue;
NSArray *arguments = userInputObject ? @[userInputObject] : nil;
@@ -100,9 +103,6 @@
// this currently could and would assign NSArray to NSMutableArray
[self.ivar setValue:self.firstInputView.inputValue onObject:self.target];
}
// Dismiss keyboard and handle committed changes
[super actionButtonPressed:sender];
// Go back after setting, but not for switches.
if (sender) {
@@ -118,25 +118,14 @@
[self exploreObjectOrPopViewController:self.currentValue];
}
- (void)argumentInputViewValueDidChange:(FLEXArgumentInputView *)argumentInputView {
if ([argumentInputView isKindOfClass:[FLEXArgumentInputSwitchView class]]) {
- (void)argumentInputViewValueDidChange:(FLEXArgumentInputView *)inputView {
if ([inputView isKindOfClass:[FLEXArgumentInputSwitchView class]]) {
[self actionButtonPressed:nil];
}
}
#pragma mark - Private
- (void)registerAuxiliaryInfo {
// This is how Reflex will get Swift struct field names into the editor at runtime
NSDictionary<NSString *, NSArray *> *labels = [self.auxiliaryInfoProvider
auxiliaryInfoForKey:FLEXAuxiliarynfoKeyFieldLabels
];
for (NSString *type in labels) {
[FLEXArgumentInputViewFactory registerFieldNames:labels[type] forTypeEncoding:type];
}
}
- (id)currentValue {
if (self.property) {
return [self.property getValue:self.target];
@@ -145,10 +134,6 @@
}
}
- (id<FLEXMetadataAuxiliaryInfo>)auxiliaryInfoProvider {
return self.ivar ?: self.property;
}
- (const FLEXTypeEncoding *)typeEncoding {
if (self.property) {
return self.property.attributes.typeEncoding.UTF8String;
@@ -165,4 +150,14 @@
}
}
+ (BOOL)canEditProperty:(FLEXProperty *)property onObject:(id)object currentValue:(id)value {
const FLEXTypeEncoding *typeEncoding = property.attributes.typeEncoding.UTF8String;
BOOL canEditType = [FLEXArgumentInputViewFactory canEditFieldWithTypeEncoding:typeEncoding currentValue:value];
return canEditType && [object respondsToSelector:property.likelySetter];
}
+ (BOOL)canEditIvar:(Ivar)ivar currentValue:(id)value {
return [FLEXArgumentInputViewFactory canEditFieldWithTypeEncoding:ivar_getTypeEncoding(ivar) currentValue:value];
}
@end
@@ -3,7 +3,7 @@
// Flipboard
//
// Created by Ryan Olson on 5/23/14.
// Copyright (c) 2020 FLEX Team. All rights reserved.
// Copyright (c) 2020 Flipboard. All rights reserved.
//
#import "FLEXVariableEditorViewController.h"
@@ -3,7 +3,7 @@
// Flipboard
//
// Created by Ryan Olson on 5/23/14.
// Copyright (c) 2020 FLEX Team. All rights reserved.
// Copyright (c) 2020 Flipboard. All rights reserved.
//
#import "FLEXMethodCallingViewController.h"
@@ -16,7 +16,7 @@
#import "FLEXUtility.h"
@interface FLEXMethodCallingViewController ()
@property (nonatomic, readonly) FLEXMethod *method;
@property (nonatomic) FLEXMethod *method;
@end
@implementation FLEXMethodCallingViewController
@@ -28,8 +28,9 @@
- (id)initWithTarget:(id)target method:(FLEXMethod *)method {
NSParameterAssert(method.isInstanceMethod == !object_isClass(target));
self = [super initWithTarget:target data:method commitHandler:nil];
self = [super initWithTarget:target];
if (self) {
self.method = method;
self.title = method.isInstanceMethod ? @"Method: " : @"Class Method: ";
self.title = [self.title stringByAppendingString:method.selectorString];
}
@@ -63,6 +64,7 @@
inputView.backgroundColor = self.view.backgroundColor;
inputView.title = methodComponent;
inputView.delegate = self;
[argumentInputViews addObject:inputView];
argumentIndex++;
}
@@ -71,6 +73,8 @@
}
- (void)actionButtonPressed:(id)sender {
[super actionButtonPressed:sender];
// Gather arguments
NSMutableArray *arguments = [NSMutableArray new];
for (FLEXArgumentInputView *inputView in self.fieldEditorView.argumentInputViews) {
@@ -86,9 +90,6 @@
withArguments:arguments
error:&error
];
// Dismiss keyboard and handle committed changes
[super actionButtonPressed:sender];
// Display return value or error
if (error) {
@@ -103,8 +104,4 @@
}
}
- (FLEXMethod *)method {
return _data;
}
@end
@@ -3,53 +3,34 @@
// Flipboard
//
// Created by Ryan Olson on 5/16/14.
// Copyright (c) 2020 FLEX Team. All rights reserved.
// Copyright (c) 2020 Flipboard. All rights reserved.
//
#import <UIKit/UIKit.h>
#import "FLEXArgumentInputView.h"
@class FLEXFieldEditorView;
@class FLEXArgumentInputView;
NS_ASSUME_NONNULL_BEGIN
/// Provides a screen for editing or configuring one or more variables.
@interface FLEXVariableEditorViewController : UIViewController <FLEXArgumentInputViewDelegate>
/// An abstract screen for editing or configuring one or more variables.
/// "Target" is the target of the edit operation, and "data" is the data
/// you want to mutate or pass to the target when the action is performed.
/// The action may be something like calling a method, setting an ivar, etc.
@interface FLEXVariableEditorViewController : UIViewController {
@protected
id _target;
_Nullable id _data;
void (^_Nullable _commitHandler)(void);
}
+ (instancetype)target:(id)target;
- (id)initWithTarget:(id)target;
/// @param target The target of the operation
/// @param data The data associated with the operation
/// @param onCommit An action to perform when the data changes
+ (instancetype)target:(id)target data:(nullable id)data commitHandler:(void(^_Nullable)(void))onCommit;
/// @param target The target of the operation
/// @param data The data associated with the operation
/// @param onCommit An action to perform when the data changes
- (id)initWithTarget:(id)target data:(nullable id)data commitHandler:(void(^_Nullable)(void))onCommit;
// Convenience accessor since many subclasses only use one input view
@property (nonatomic, readonly) FLEXArgumentInputView *firstInputView;
// Also a convenience accessor
@property (nonatomic, readonly) NSArray<FLEXArgumentInputView *> *inputViews;
// For subclass use only.
@property (nonatomic, readonly) id target;
/// Convenience accessor since many subclasses only use one input view
@property (nonatomic, readonly, nullable) FLEXArgumentInputView *firstInputView;
@property (nonatomic, readonly) FLEXFieldEditorView *fieldEditorView;
/// Subclasses can change the button title via the button's \c title property
@property (nonatomic, readonly) UIBarButtonItem *actionButton;
/// Subclasses should override to provide "set" functionality.
/// The commit handler--if present--is called here.
- (void)actionButtonPressed:(nullable id)sender;
- (void)actionButtonPressed:(id)sender;
/// Pushes an explorer view controller for the given object
/// or pops the current view controller.
- (void)exploreObjectOrPopViewController:(nullable id)objectOrNil;
- (void)exploreObjectOrPopViewController:(id)objectOrNil;
@end
NS_ASSUME_NONNULL_END
@@ -3,7 +3,7 @@
// Flipboard
//
// Created by Ryan Olson on 5/16/14.
// Copyright (c) 2020 FLEX Team. All rights reserved.
// Copyright (c) 2020 Flipboard. All rights reserved.
//
#import "FLEXColor.h"
@@ -19,25 +19,24 @@
@interface FLEXVariableEditorViewController () <UIScrollViewDelegate>
@property (nonatomic) UIScrollView *scrollView;
@property (nonatomic) id target;
@end
@implementation FLEXVariableEditorViewController
#pragma mark - Initialization
+ (instancetype)target:(id)target data:(nullable id)data commitHandler:(void(^_Nullable)(void))onCommit {
return [[self alloc] initWithTarget:target data:data commitHandler:onCommit];
+ (instancetype)target:(id)target {
return [[self alloc] initWithTarget:target];
}
- (id)initWithTarget:(id)target data:(nullable id)data commitHandler:(void(^_Nullable)(void))onCommit {
- (id)initWithTarget:(id)target {
self = [super init];
if (self) {
_target = target;
_data = data;
_commitHandler = onCommit;
self.target = target;
[NSNotificationCenter.defaultCenter
addObserver:self selector:@selector(keyboardDidShow:)
name:UIKeyboardWillShowNotification object:nil
name:UIKeyboardDidShowNotification object:nil
];
[NSNotificationCenter.defaultCenter
addObserver:self selector:@selector(keyboardWillHide:)
@@ -55,8 +54,8 @@
#pragma mark - UIViewController methods
- (void)keyboardDidShow:(NSNotification *)notification {
CGRect keyboardRectInWindow = [[[notification userInfo] objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue];
CGSize keyboardSize = [self.view convertRect:keyboardRectInWindow fromView:nil].size;
CGRect keyboardRect = [notification.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue];
CGSize keyboardSize = [self.view convertRect:keyboardRect fromView:nil].size;
UIEdgeInsets scrollInsets = self.scrollView.contentInset;
scrollInsets.bottom = keyboardSize.height;
self.scrollView.contentInset = scrollInsets;
@@ -115,15 +114,16 @@
#pragma mark - Public
- (FLEXArgumentInputView *)firstInputView {
return [self.fieldEditorView argumentInputViews].firstObject;
return self.fieldEditorView.argumentInputViews.firstObject;
}
- (NSArray<FLEXArgumentInputView *> *)inputViews {
return self.fieldEditorView.argumentInputViews;
}
- (void)actionButtonPressed:(id)sender {
// Subclasses can override
[self.fieldEditorView endEditing:YES];
if (_commitHandler) {
_commitHandler();
}
}
- (void)exploreObjectOrPopViewController:(id)objectOrNil {
@@ -138,4 +138,20 @@
}
}
#pragma mark - FLEXArgumentInputViewDelegate
- (void)argumentInputViewValueDidChange:(FLEXArgumentInputView *)inputView {
// Subclasses might want to do something here but we don't
}
- (void)argumentInputViewWantsNextAsFirstResponder:(FLEXArgumentInputView *)inputView {
if (self.inputViews.lastObject == inputView) {
// If this is our last or only input view, dismiss the keyboard
[inputView resignFirstResponder];
} else {
NSInteger idx = [self.inputViews indexOfObject:inputView];
[self.inputViews[idx+1] becomeFirstResponder];
}
}
@end
@@ -3,7 +3,7 @@
// FLEX
//
// Created by Tanner on 2/6/20.
// Copyright © 2020 FLEX Team. All rights reserved.
// Copyright © 2020 Flipboard. All rights reserved.
//
#import <Foundation/Foundation.h>
@@ -3,7 +3,7 @@
// FLEX
//
// Created by Tanner on 2/6/20.
// Copyright © 2020 FLEX Team. All rights reserved.
// Copyright © 2020 Flipboard. All rights reserved.
//
#import "FLEXBookmarkManager.h"
@@ -3,7 +3,7 @@
// FLEX
//
// Created by Tanner on 2/6/20.
// Copyright © 2020 FLEX Team. All rights reserved.
// Copyright © 2020 Flipboard. All rights reserved.
//
#import "FLEXTableViewController.h"
@@ -3,7 +3,7 @@
// FLEX
//
// Created by Tanner on 2/6/20.
// Copyright © 2020 FLEX Team. All rights reserved.
// Copyright © 2020 Flipboard. All rights reserved.
//
#import "FLEXBookmarksViewController.h"
@@ -68,10 +68,10 @@
- (void)setupEditingBarItems {
self.navigationItem.rightBarButtonItem = nil;
self.toolbarItems = @[
[UIBarButtonItem flex_itemWithTitle:@"Close All" target:self action:@selector(closeAllButtonPressed:)],
[UIBarButtonItem itemWithTitle:@"Close All" target:self action:@selector(closeAllButtonPressed:)],
UIBarButtonItem.flex_flexibleSpace,
// We use a non-system done item because we change its title dynamically
[UIBarButtonItem flex_doneStyleitemWithTitle:@"Done" target:self action:@selector(toggleEditing)]
[UIBarButtonItem doneStyleitemWithTitle:@"Done" target:self action:@selector(toggleEditing)]
];
self.toolbarItems.firstObject.tintColor = FLEXColor.destructiveColor;
@@ -3,7 +3,7 @@
// Flipboard
//
// Created by Ryan Olson on 4/4/14.
// Copyright (c) 2020 FLEX Team. All rights reserved.
// Copyright (c) 2020 Flipboard. All rights reserved.
//
#import "FLEXExplorerToolbar.h"
@@ -21,21 +21,11 @@
- (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;
- (void)toggleToolWithViewControllerProvider:(UINavigationController *(^)(void))future completion:(void(^)(void))completion;
// Keyboard shortcut helpers
@@ -3,7 +3,7 @@
// Flipboard
//
// Created by Ryan Olson on 4/4/14.
// Copyright (c) 2020 FLEX Team. All rights reserved.
// Copyright (c) 2020 Flipboard. All rights reserved.
//
#import "FLEXExplorerViewController.h"
@@ -45,9 +45,6 @@ typedef NS_ENUM(NSUInteger, FLEXExplorerMode) {
/// Only valid while a toolbar drag pan gesture is in progress.
@property (nonatomic) CGRect toolbarFrameBeforeDragging;
/// Only valid while a selected view pan gesture is in progress.
@property (nonatomic) CGFloat selectedViewLastPanX;
/// Borders of all the visible views in the hierarchy at the selection point.
/// The keys are NSValues with the corresponding view (nonretained).
@property (nonatomic) NSDictionary<NSValue *, UIView *> *outlineViewsForVisibleViews;
@@ -61,9 +58,6 @@ typedef NS_ENUM(NSUInteger, FLEXExplorerMode) {
/// A colored transparent overlay to indicate that the view is selected.
@property (nonatomic) UIView *selectedViewOverlay;
/// Used to actuate changes in view selection on iOS 10+
@property (nonatomic, readonly) UISelectionFeedbackGenerator *selectionFBG API_AVAILABLE(ios(10.0));
/// self.view.window as a \c FLEXWindow
@property (nonatomic, readonly) FLEXWindow *window;
@@ -124,19 +118,6 @@ typedef NS_ENUM(NSUInteger, FLEXExplorerMode) {
self.movePanGR = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handleMovePan:)];
self.movePanGR.enabled = self.currentMode == FLEXExplorerModeMove;
[self.view addGestureRecognizer:self.movePanGR];
// Feedback
if (@available(iOS 10.0, *)) {
_selectionFBG = [UISelectionFeedbackGenerator new];
}
// Observe keyboard to move self out of the way
[NSNotificationCenter.defaultCenter
addObserver:self
selector:@selector(keyboardShown:)
name:UIKeyboardWillShowNotification
object:nil
];
}
- (void)viewWillAppear:(BOOL)animated {
@@ -170,10 +151,7 @@ typedef NS_ENUM(NSUInteger, FLEXExplorerMode) {
UIViewController *viewControllerToAsk = [self viewControllerForRotationAndOrientation];
UIInterfaceOrientationMask supportedOrientations = FLEXUtility.infoPlistSupportedInterfaceOrientationsMask;
// We check its class by name because using isKindOfClass will fail for the same class defined
// twice in the runtime; and the goal here is to avoid calling -supportedInterfaceOrientations
// recursively when I'm inspecting FLEX with itself from a tweak dylib
if (viewControllerToAsk && ![NSStringFromClass([viewControllerToAsk class]) hasPrefix:@"FLEX"]) {
if (viewControllerToAsk && ![viewControllerToAsk isKindOfClass:[self class]]) {
supportedOrientations = [viewControllerToAsk supportedInterfaceOrientations];
}
@@ -386,21 +364,6 @@ typedef NS_ENUM(NSUInteger, FLEXExplorerMode) {
return [self.view convertRect:frameInWindow fromView:nil];
}
- (void)keyboardShown:(NSNotification *)notif {
CGRect keyboardFrame = [notif.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue];
CGRect toolbarFrame = self.explorerToolbar.frame;
if (CGRectGetMinY(keyboardFrame) < CGRectGetMaxY(toolbarFrame)) {
toolbarFrame.origin.y = keyboardFrame.origin.y - toolbarFrame.size.height;
// Subtract a little more, to ignore accessory input views
toolbarFrame.origin.y -= 50;
[UIView animateWithDuration:0.5 delay:0 usingSpringWithDamping:1 initialSpringVelocity:0.5
options:UIViewAnimationOptionCurveEaseOut animations:^{
[self updateToolbarPositionWithUnconstrainedFrame:toolbarFrame];
} completion:nil];
}
}
#pragma mark - Toolbar Buttons
@@ -429,12 +392,8 @@ typedef NS_ENUM(NSUInteger, FLEXExplorerMode) {
}
- (UIWindow *)statusWindow {
if (!@available(iOS 16, *)) {
NSString *statusBarString = [NSString stringWithFormat:@"%@arWindow", @"_statusB"];
return [UIApplication.sharedApplication valueForKey:statusBarString];
}
return nil;
NSString *statusBarString = [NSString stringWithFormat:@"%@arWindow", @"_statusB"];
return [UIApplication.sharedApplication valueForKey:statusBarString];
}
- (void)recentButtonTapped:(FLEXExplorerToolbarItem *)sender {
@@ -466,11 +425,7 @@ typedef NS_ENUM(NSUInteger, FLEXExplorerMode) {
toolbar.moveItem.selected = self.currentMode == FLEXExplorerModeMove;
// Recent only enabled when we have a last active tab
if (!self.presentedViewController) {
toolbar.recentItem.enabled = FLEXTabList.sharedList.activeTab != nil;
} else {
toolbar.recentItem.enabled = NO;
}
toolbar.recentItem.enabled = FLEXTabList.sharedList.activeTab != nil;
}
@@ -494,12 +449,17 @@ typedef NS_ENUM(NSUInteger, FLEXExplorerMode) {
initWithTarget:self action:@selector(handleToolbarDetailsTapGesture:)
];
[toolbar.selectedViewDescriptionContainer addGestureRecognizer:self.detailsTapGR];
// Swipe gestures for selecting deeper / higher views at a point
UIPanGestureRecognizer *panGesture = [[UIPanGestureRecognizer alloc]
UISwipeGestureRecognizer *leftSwipe = [[UISwipeGestureRecognizer alloc]
initWithTarget:self action:@selector(handleChangeViewAtPointGesture:)
];
[toolbar.selectedViewDescriptionContainer addGestureRecognizer:panGesture];
UISwipeGestureRecognizer *rightSwipe = [[UISwipeGestureRecognizer alloc]
initWithTarget:self action:@selector(handleChangeViewAtPointGesture:)
];
leftSwipe.direction = UISwipeGestureRecognizerDirectionLeft;
rightSwipe.direction = UISwipeGestureRecognizerDirectionRight;
[toolbar.selectedViewDescriptionContainer addGestureRecognizer:leftSwipe];
[toolbar.selectedViewDescriptionContainer addGestureRecognizer:rightSwipe];
// Long press gesture to present tabs manager
[toolbar.globalsItem addGestureRecognizer:[[UILongPressGestureRecognizer alloc]
@@ -638,54 +598,19 @@ typedef NS_ENUM(NSUInteger, FLEXExplorerMode) {
}
}
- (void)handleChangeViewAtPointGesture:(UIPanGestureRecognizer *)sender {
- (void)handleChangeViewAtPointGesture:(UISwipeGestureRecognizer *)sender {
NSInteger max = self.viewsAtTapPoint.count - 1;
NSInteger currentIdx = [self.viewsAtTapPoint indexOfObject:self.selectedView];
CGFloat locationX = [sender locationInView:self.view].x;
// Track the pan gesture: every N points we move along the X axis,
// actuate some haptic feedback and move up or down the hierarchy.
// We only store the "last" location when we've met the threshold.
// We only change the view and actuate feedback if the view selection
// changes; that is, as long as we don't go outside or under the array.
switch (sender.state) {
case UIGestureRecognizerStateBegan: {
self.selectedViewLastPanX = locationX;
switch (sender.direction) {
case UISwipeGestureRecognizerDirectionLeft:
self.selectedView = self.viewsAtTapPoint[MIN(max, currentIdx + 1)];
break;
}
case UIGestureRecognizerStateChanged: {
static CGFloat kNextLevelThreshold = 20.f;
CGFloat lastX = self.selectedViewLastPanX;
NSInteger newSelection = currentIdx;
// Left, go down the hierarchy
if (locationX < lastX && (lastX - locationX) >= kNextLevelThreshold) {
// Choose a new view index up to the max index
newSelection = MIN(max, currentIdx + 1);
self.selectedViewLastPanX = locationX;
}
// Right, go up the hierarchy
else if (lastX < locationX && (locationX - lastX) >= kNextLevelThreshold) {
// Choose a new view index down to the min index
newSelection = MAX(0, currentIdx - 1);
self.selectedViewLastPanX = locationX;
}
if (currentIdx != newSelection) {
self.selectedView = self.viewsAtTapPoint[newSelection];
[self actuateSelectionChangedFeedback];
}
case UISwipeGestureRecognizerDirectionRight:
self.selectedView = self.viewsAtTapPoint[MAX(0, currentIdx - 1)];
break;
}
default: break;
}
}
- (void)actuateSelectionChangedFeedback {
if (@available(iOS 10.0, *)) {
[self.selectionFBG selectionChanged];
default:
break;
}
}
@@ -852,34 +777,31 @@ typedef NS_ENUM(NSUInteger, FLEXExplorerMode) {
#pragma mark - Touch Handling
- (BOOL)shouldReceiveTouchAtWindowPoint:(CGPoint)pointInWindowCoordinates {
BOOL shouldReceiveTouch = NO;
CGPoint pointInLocalCoordinates = [self.view convertPoint:pointInWindowCoordinates fromView:nil];
// If we have a modal presented, is it in the modal?
if (self.presentedViewController) {
UIView *presentedView = self.presentedViewController.view;
CGPoint pipvc = [presentedView convertPoint:pointInLocalCoordinates fromView:self.view];
UIView *hit = [presentedView hitTest:pipvc withEvent:nil];
if (hit != nil) {
return YES;
}
}
// Always if we're in selection mode
if (self.currentMode == FLEXExplorerModeSelect) {
return YES;
}
// Always in move mode too
if (self.currentMode == FLEXExplorerModeMove) {
return YES;
}
// Always if it's on the toolbar
if (CGRectContainsPoint(self.explorerToolbar.frame, pointInLocalCoordinates)) {
return YES;
shouldReceiveTouch = YES;
}
return NO;
// Always if we're in selection mode
if (!shouldReceiveTouch && self.currentMode == FLEXExplorerModeSelect) {
shouldReceiveTouch = YES;
}
// Always in move mode too
if (!shouldReceiveTouch && self.currentMode == FLEXExplorerModeMove) {
shouldReceiveTouch = YES;
}
// Always if we have a modal presented
if (!shouldReceiveTouch && self.presentedViewController) {
shouldReceiveTouch = YES;
}
return shouldReceiveTouch;
}
@@ -925,14 +847,8 @@ typedef NS_ENUM(NSUInteger, FLEXExplorerMode) {
// up in case we start replacing them again in the future
self.appMenuItems = UIMenuController.sharedMenuController.menuItems;
[self updateButtonStates];
// Show the view controller
[super presentViewController:toPresent animated:animated completion:^{
[self updateButtonStates];
if (completion) completion();
}];
[super presentViewController:toPresent animated:animated completion:completion];
}
- (void)dismissViewControllerAnimated:(BOOL)animated completion:(void (^)(void))completion {
@@ -953,40 +869,23 @@ typedef NS_ENUM(NSUInteger, FLEXExplorerMode) {
[self updateButtonStates];
[super dismissViewControllerAnimated:animated completion:^{
[self updateButtonStates];
if (completion) completion();
}];
[super dismissViewControllerAnimated:animated completion:completion];
}
- (BOOL)wantsWindowToBecomeKey {
- (BOOL)wantsWindowToBecomeKey
{
return self.window.previousKeyWindow != nil;
}
- (void)toggleToolWithViewControllerProvider:(UINavigationController *(^)(void))future
completion:(void (^)(void))completion {
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;
}
@@ -1025,7 +924,11 @@ typedef NS_ENUM(NSUInteger, FLEXExplorerMode) {
} else {
return [FLEXHierarchyViewController delegate:self];
}
} completion:completion];
} completion:^{
if (completion) {
completion();
}
}];
}
- (void)toggleMenuTool {
@@ -3,7 +3,7 @@
// FLEX
//
// Created by Tanner Bennett on 2/13/20.
// Copyright © 2020 FLEX Team. All rights reserved.
// Copyright © 2020 Flipboard. All rights reserved.
//
#import "FLEXFilteringTableViewController.h"
@@ -3,7 +3,7 @@
// FLEX
//
// Created by Tanner Bennett on 2/13/20.
// Copyright © 2020 FLEX Team. All rights reserved.
// Copyright © 2020 Flipboard. All rights reserved.
//
#import "FLEXViewControllersViewController.h"
+1 -1
View File
@@ -3,7 +3,7 @@
// Flipboard
//
// Created by Ryan Olson on 4/13/14.
// Copyright (c) 2020 FLEX Team. All rights reserved.
// Copyright (c) 2020 Flipboard. All rights reserved.
//
#import <UIKit/UIKit.h>
+8 -3
View File
@@ -3,7 +3,7 @@
// Flipboard
//
// Created by Ryan Olson on 4/13/14.
// Copyright (c) 2020 FLEX Team. All rights reserved.
// Copyright (c) 2020 Flipboard. All rights reserved.
//
#import "FLEXWindow.h"
@@ -18,13 +18,18 @@
// Some apps have windows at UIWindowLevelStatusBar + n.
// If we make the window level too high, we block out UIAlertViews.
// There's a balance between staying above the app's windows and staying below alerts.
self.windowLevel = UIWindowLevelAlert - 1;
// UIWindowLevelStatusBar + 100 seems to hit that balance.
self.windowLevel = UIWindowLevelStatusBar + 100.0;
}
return self;
}
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
return [self.eventDelegate shouldHandleTouchAtPoint:point];
BOOL pointInside = NO;
if ([self.eventDelegate shouldHandleTouchAtPoint:point]) {
pointInside = [super pointInside:point withEvent:event];
}
return pointInside;
}
- (BOOL)shouldAffectStatusBarAppearance {
@@ -3,7 +3,7 @@
// FLEX
//
// Created by Tanner on 2/6/20.
// Copyright © 2020 FLEX Team. All rights reserved.
// Copyright © 2020 Flipboard. All rights reserved.
//
#import "FLEXTableViewController.h"
@@ -3,7 +3,7 @@
// FLEX
//
// Created by Tanner on 2/6/20.
// Copyright © 2020 FLEX Team. All rights reserved.
// Copyright © 2020 Flipboard. All rights reserved.
//
#import "FLEXWindowManagerController.h"
@@ -72,7 +72,7 @@
[self dismissViewControllerAnimated:YES completion:nil];
}
- (void)showRevertOrDismissAlert:(void(^)(void))revertBlock {
- (void)showRevertOrDismissAlert:(void(^)())revertBlock {
[self.tableView deselectRowAtIndexPath:self.tableView.indexPathForSelectedRow animated:YES];
[self reloadData];
[self.tableView reloadData];
+1 -1
View File
@@ -3,7 +3,7 @@
// FLEX
//
// Created by Tanner on 2/1/20.
// Copyright © 2020 FLEX Team. All rights reserved.
// Copyright © 2020 Flipboard. All rights reserved.
//
#import <UIKit/UIKit.h>
+3 -13
View File
@@ -3,7 +3,7 @@
// FLEX
//
// Created by Tanner on 2/1/20.
// Copyright © 2020 FLEX Team. All rights reserved.
// Copyright © 2020 Flipboard. All rights reserved.
//
#import "FLEXTabList.h"
@@ -80,20 +80,10 @@
- (void)closeTab:(UINavigationController *)tab {
NSParameterAssert(tab);
NSParameterAssert([self.openTabs containsObject:tab]);
NSInteger idx = [self.openTabs indexOfObject:tab];
if (idx != NSNotFound) {
[self closeTabAtIndex:idx];
}
// Not sure how this is possible, but it happens sometimes
if (self.activeTab == tab) {
[self chooseNewActiveTab];
}
// It is possible for an object explorer to form a retain cycle
// with its own navigation controller; clearing the view controllers
// manually when closing a tab breaks the cycle
tab.viewControllers = @[];
[self closeTabAtIndex:idx];
}
- (void)closeTabAtIndex:(NSInteger)idx {
@@ -3,7 +3,7 @@
// FLEX
//
// Created by Tanner on 2/4/20.
// Copyright © 2020 FLEX Team. All rights reserved.
// Copyright © 2020 Flipboard. All rights reserved.
//
#import "FLEXTableViewController.h"
@@ -3,7 +3,7 @@
// FLEX
//
// Created by Tanner on 2/4/20.
// Copyright © 2020 FLEX Team. All rights reserved.
// Copyright © 2020 Flipboard. All rights reserved.
//
#import "FLEXTabsViewController.h"
@@ -121,12 +121,12 @@
- (void)setupEditingBarItems {
self.navigationItem.rightBarButtonItem = nil;
self.toolbarItems = @[
[UIBarButtonItem flex_itemWithTitle:@"Close All" target:self action:@selector(closeAllButtonPressed:)],
[UIBarButtonItem itemWithTitle:@"Close All" target:self action:@selector(closeAllButtonPressed:)],
UIBarButtonItem.flex_flexibleSpace,
[UIBarButtonItem flex_disabledSystemItem:UIBarButtonSystemItemAdd],
[UIBarButtonItem disabledSystemItem:UIBarButtonSystemItemAdd],
UIBarButtonItem.flex_flexibleSpace,
// We use a non-system done item because we change its title dynamically
[UIBarButtonItem flex_doneStyleitemWithTitle:@"Done" target:self action:@selector(toggleEditing)]
[UIBarButtonItem doneStyleitemWithTitle:@"Done" target:self action:@selector(toggleEditing)]
];
self.toolbarItems.firstObject.tintColor = FLEXColor.destructiveColor;
+19 -13
View File
@@ -3,19 +3,25 @@
// FLEX
//
// Created by Tanner on 3/12/20.
// Copyright © 2020 FLEX Team. All rights reserved.
// Copyright © 2020 Flipboard. All rights reserved.
//
#import "UIBarButtonItem+FLEX.h"
#import "CALayer+FLEX.h"
#import "UIFont+FLEX.h"
#import "UIGestureRecognizer+Blocks.h"
#import "UIPasteboard+FLEX.h"
#import "UIMenu+FLEX.h"
#import "UITextField+Range.h"
#import "NSObject+FLEX_Reflection.h"
#import "NSArray+FLEX.h"
#import "NSUserDefaults+FLEX.h"
#import "NSTimer+FLEX.h"
#import "NSDateFormatter+FLEX.h"
#import <FLEX/UIBarButtonItem+FLEX.h>
#import <FLEX/CALayer+FLEX.h>
#import <FLEX/UIFont+FLEX.h>
#import <FLEX/UIGestureRecognizer+Blocks.h>
#import <FLEX/UIView+FLEX_Layout.h>
#import <FLEX/UIPasteboard+FLEX.h>
#import <FLEX/UIMenu+FLEX.h>
#import <FLEX/UITextField+Range.h>
#import <FLEX/NSObject+FLEX_Reflection.h>
#import <FLEX/NSArray+FLEX.h>
#import <FLEX/NSDictionary+ObjcRuntime.h>
#import <FLEX/NSString+ObjcRuntime.h>
#import <FLEX/NSString+FLEX.h>
#import <FLEX/NSUserDefaults+FLEX.h>
#import <FLEX/NSMapTable+FLEX_Subscripting.h>
#import <FLEX/NSTimer+FLEX.h>
+13 -12
View File
@@ -3,20 +3,21 @@
// FLEX
//
// Created by Tanner on 3/11/20.
// Copyright © 2020 FLEX Team. All rights reserved.
// Copyright © 2020 Flipboard. All rights reserved.
//
#import "FLEXFilteringTableViewController.h"
#import "FLEXNavigationController.h"
#import "FLEXTableViewController.h"
#import "FLEXTableView.h"
#import <FLEX/FLEXFilteringTableViewController.h>
#import <FLEX/FLEXNavigationController.h>
#import <FLEX/FLEXTableViewController.h>
#import <FLEX/FLEXTableView.h>
#import "FLEXSingleRowSection.h"
#import "FLEXTableViewSection.h"
#import <FLEX/FLEXSingleRowSection.h>
#import <FLEX/FLEXTableViewSection.h>
#import "FLEXCodeFontCell.h"
#import "FLEXSubtitleTableViewCell.h"
#import "FLEXTableViewCell.h"
#import "FLEXMultilineTableViewCell.h"
#import "FLEXKeyValueTableViewCell.h"
#import <FLEX/FLEXCodeFontCell.h>
#import <FLEX/FLEXSubtitleTableViewCell.h>
#import <FLEX/FLEXTableViewCell.h>
#import <FLEX/FLEXMultilineTableViewCell.h>
#import <FLEX/FLEXKeyValueTableViewCell.h>
#import <FLEX/FLEXScopeCarousel.h>
+20 -12
View File
@@ -3,20 +3,28 @@
// FLEX
//
// Created by Tanner on 3/11/20.
// Copyright © 2020 FLEX Team. All rights reserved.
// Copyright © 2020 Flipboard. All rights reserved.
//
#import "FLEXObjectExplorerFactory.h"
#import "FLEXObjectExplorerViewController.h"
#import <FLEX/FLEXObjectExplorerFactory.h>
#import <FLEX/FLEXObjectExplorerViewController.h>
#import "FLEXObjectExplorer.h"
#import <FLEX/FLEXObjectExplorer.h>
#import "FLEXShortcut.h"
#import "FLEXShortcutsSection.h"
#import <FLEX/FLEXShortcut.h>
#import <FLEX/FLEXShortcutsFactory+Defaults.h>
#import <FLEX/FLEXShortcutsSection.h>
#import <FLEX/FLEXBlockShortcuts.h>
#import <FLEX/FLEXBundleShortcuts.h>
#import <FLEX/FLEXClassShortcuts.h>
#import <FLEX/FLEXImageShortcuts.h>
#import <FLEX/FLEXLayerShortcuts.h>
#import <FLEX/FLEXViewControllerShortcuts.h>
#import <FLEX/FLEXViewShortcuts.h>
#import "FLEXCollectionContentSection.h"
#import "FLEXColorPreviewSection.h"
#import "FLEXDefaultsContentSection.h"
#import "FLEXMetadataSection.h"
#import "FLEXMutableListSection.h"
#import "FLEXObjectInfoSection.h"
#import <FLEX/FLEXCollectionContentSection.h>
#import <FLEX/FLEXColorPreviewSection.h>
#import <FLEX/FLEXDefaultsContentSection.h>
#import <FLEX/FLEXMetadataSection.h>
#import <FLEX/FLEXMutableListSection.h>
#import <FLEX/FLEXObjectInfoSection.h>
+16 -18
View File
@@ -3,25 +3,23 @@
// FLEX
//
// Created by Tanner on 3/11/20.
// Copyright © 2020 FLEX Team. All rights reserved.
// Copyright © 2020 Flipboard. All rights reserved.
//
#import "FLEXObjcInternal.h"
#import "FLEXSwiftInternal.h"
#import "FLEXRuntimeSafety.h"
#import "FLEXBlockDescription.h"
#import "FLEXTypeEncodingParser.h"
#import <FLEX/FLEXObjcInternal.h>
#import <FLEX/FLEXRuntimeSafety.h>
#import <FLEX/FLEXBlockDescription.h>
#import <FLEX/FLEXTypeEncodingParser.h>
#import "FLEXMirror.h"
#import "FLEXProtocol.h"
#import "FLEXProperty.h"
#import "FLEXIvar.h"
#import "FLEXMethodBase.h"
#import "FLEXMethod.h"
#import "FLEXPropertyAttributes.h"
#import "FLEXRuntime+Compare.h"
#import "FLEXRuntime+UIKitHelpers.h"
#import "FLEXMetadataExtras.h"
#import <FLEX/FLEXMirror.h>
#import <FLEX/FLEXProtocol.h>
#import <FLEX/FLEXProperty.h>
#import <FLEX/FLEXIvar.h>
#import <FLEX/FLEXMethodBase.h>
#import <FLEX/FLEXMethod.h>
#import <FLEX/FLEXPropertyAttributes.h>
#import <FLEX/FLEXRuntime+Compare.h>
#import <FLEX/FLEXRuntime+UIKitHelpers.h>
#import "FLEXProtocolBuilder.h"
#import "FLEXClassBuilder.h"
#import <FLEX/FLEXProtocolBuilder.h>
#import <FLEX/FLEXClassBuilder.h>
+14 -14
View File
@@ -4,22 +4,22 @@
//
// Created by Eric Horacek on 7/18/15.
// Modified by Tanner Bennett on 3/12/20.
// Copyright (c) 2020 FLEX Team. All rights reserved.
// Copyright (c) 2020 Flipboard. All rights reserved.
//
#import "FLEXManager.h"
#import "FLEXManager+Extensibility.h"
#import "FLEXManager+Networking.h"
#import <FLEX/FLEXManager.h>
#import <FLEX/FLEXManager+Extensibility.h>
#import <FLEX/FLEXManager+Networking.h>
#import "FLEXExplorerToolbar.h"
#import "FLEXExplorerToolbarItem.h"
#import "FLEXGlobalsEntry.h"
#import <FLEX/FLEXExplorerToolbar.h>
#import <FLEX/FLEXExplorerToolbarItem.h>
#import <FLEX/FLEXGlobalsEntry.h>
#import "FLEX-Core.h"
#import "FLEX-Runtime.h"
#import "FLEX-Categories.h"
#import "FLEX-ObjectExploring.h"
#import <FLEX/FLEX-Core.h>
#import <FLEX/FLEX-Runtime.h>
#import <FLEX/FLEX-Categories.h>
#import <FLEX/FLEX-ObjectExploring.h>
#import "FLEXMacros.h"
#import "FLEXAlert.h"
#import "FLEXResources.h"
#import <FLEX/FLEXMacros.h>
#import <FLEX/FLEXAlert.h>
#import <FLEX/FLEXResources.h>
@@ -8,21 +8,12 @@
#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,12 +63,11 @@ 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) {
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);
label.frame = CGRectMake(width * i + 5, 0, (width - 10), height);
}];
}
@@ -29,7 +29,6 @@
@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 minWidthForContentCellInColumn:(NSInteger)column;
- (CGFloat)multiColumnTableView:(FLEXMultiColumnTableView *)tableView widthForContentCellInColumn:(NSInteger)column;
- (CGFloat)multiColumnTableView:(FLEXMultiColumnTableView *)tableView heightForContentCellInRow:(NSInteger)row;
- (CGFloat)heightForTopHeaderInTableView:(FLEXMultiColumnTableView *)tableView;
- (CGFloat)widthForLeftHeaderInTableView:(FLEXMultiColumnTableView *)tableView;
@@ -9,12 +9,10 @@
#import "FLEXMultiColumnTableView.h"
#import "FLEXDBQueryRowCell.h"
#import "FLEXTableLeftCell.h"
#import "NSArray+FLEX.h"
#import "FLEXColor.h"
@interface FLEXMultiColumnTableView () <
UITableViewDataSource, UITableViewDelegate,
UIScrollViewDelegate, FLEXDBQueryRowCellLayoutSource
UITableViewDataSource, UITableViewDelegate, UIScrollViewDelegate
>
@property (nonatomic) UIScrollView *contentScrollView;
@@ -23,12 +21,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;
@@ -73,9 +71,9 @@ static const CGFloat kColumnMargin = 1;
}
CGFloat contentWidth = 0.0;
NSInteger columnsCount = self.numberOfColumns;
for (int i = 0; i < columnsCount; i++) {
contentWidth += CGRectGetWidth(self.headerViews[i].bounds);
NSInteger rowsCount = self.numberOfColumns;
for (int i = 0; i < rowsCount; i++) {
contentWidth += [self contentWidthForColumn:i];
}
CGFloat contentHeight = height - topheaderHeight - topInsets;
@@ -149,30 +147,26 @@ 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.headerViews) {
for (UIView *subview in self.headerScrollView.subviews) {
[subview removeFromSuperview];
}
__block CGFloat xOffset = 0;
self.headerViews = [NSArray flex_forEachUpTo:self.numberOfColumns map:^id(NSUInteger column) {
FLEXTableColumnHeader *header = [FLEXTableColumnHeader new];
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)
];
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;
}
@@ -184,22 +178,21 @@ static const CGFloat kColumnMargin = 1;
[header addGestureRecognizer:gesture];
header.userInteractionEnabled = YES;
xOffset += width;
[self.headerScrollView addSubview:header];
return header;
}];
xOffset += width;
}
}
- (void)contentHeaderTap:(UIGestureRecognizer *)gesture {
NSInteger newSortColumn = [self.headerViews indexOfObject:gesture.view];
NSInteger newSortColumn = [self.headerScrollView.subviews indexOfObject:gesture.view];
FLEXTableColumnHeaderSortType newType = FLEXNextTableColumnHeaderSortType(self.sortType);
// Reset old header
FLEXTableColumnHeader *oldHeader = (id)self.headerViews[self.sortColumn];
FLEXTableColumnHeader *oldHeader = (id)self.headerScrollView.subviews[self.sortColumn];
oldHeader.sortType = FLEXTableColumnHeaderSortTypeNone;
// Update new header
FLEXTableColumnHeader *newHeader = (id)self.headerViews[newSortColumn];
FLEXTableColumnHeader *newHeader = (id)self.headerScrollView.subviews[newSortColumn];
newHeader.sortType = newType;
// Update self
@@ -234,13 +227,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;
}
@@ -287,17 +280,6 @@ 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 {
@@ -316,8 +298,8 @@ static const CGFloat kColumnMargin = 1;
return [self.dataSource rowTitle:row];
}
- (CGFloat)minContentWidthForColumn:(NSInteger)column {
return [self.dataSource multiColumnTableView:self minWidthForContentCellInColumn:column];
- (CGFloat)contentWidthForColumn:(NSInteger)column {
return [self.dataSource multiColumnTableView:self widthForContentCellInColumn:column];
}
- (CGFloat)contentHeightForRow:(NSInteger)row {
@@ -3,7 +3,7 @@
// FLEX
//
// Created by Tanner on 3/3/20.
// Copyright © 2020 FLEX Team. All rights reserved.
// Copyright © 2020 Flipboard. All rights reserved.
//
#import <Foundation/Foundation.h>
@@ -3,7 +3,7 @@
// FLEX
//
// Created by Tanner on 3/3/20.
// Copyright © 2020 FLEX Team. All rights reserved.
// Copyright © 2020 Flipboard. All rights reserved.
//
#import "FLEXSQLResult.h"
@@ -13,7 +13,7 @@
@synthesize keyedRows = _keyedRows;
+ (instancetype)message:(NSString *)message {
return [[self alloc] initWithMessage:message columns:nil rows:nil];
return [[self alloc] initWithmessage:message columns:nil rows:nil];
}
+ (instancetype)error:(NSString *)message {
@@ -23,12 +23,12 @@
}
+ (instancetype)columns:(NSArray<NSString *> *)columnNames rows:(NSArray<NSArray<NSString *> *> *)rowData {
return [[self alloc] initWithMessage:nil columns:columnNames rows:rowData];
return [[self alloc] initWithmessage:nil columns:columnNames rows:rowData];
}
- (instancetype)initWithMessage:(NSString *)message columns:(NSArray<NSString *> *)columns rows:(NSArray<NSArray<NSString *> *> *)rows {
- (id)initWithmessage:(NSString *)message columns:(NSArray *)columns rows:(NSArray<NSArray *> *)rows {
NSParameterAssert(message || (columns && rows));
NSParameterAssert(rows.count == 0 || columns.count == rows.firstObject.count);
NSParameterAssert(columns.count == rows.firstObject.count);
self = [super init];
if (self) {
@@ -12,10 +12,7 @@
#import "FLEXRuntimeConstants.h"
#import <sqlite3.h>
#define kQuery(name, str) static NSString * const QUERY_##name = str
kQuery(TABLENAMES, @"SELECT name FROM sqlite_master WHERE type='table' ORDER BY name");
kQuery(ROWIDS, @"SELECT rowid FROM \"%@\" ORDER BY rowid ASC");
static NSString * const QUERY_TABLENAMES = @"SELECT name FROM sqlite_master WHERE type='table' ORDER BY name";
@interface FLEXSQLiteDatabaseManager ()
@property (nonatomic) sqlite3 *db;
@@ -33,7 +30,7 @@ kQuery(ROWIDS, @"SELECT rowid FROM \"%@\" ORDER BY rowid ASC");
- (instancetype)initWithPath:(NSString *)path {
self = [super init];
if (self) {
self.path = path;
self.path = path;;
}
return self;
@@ -110,36 +107,16 @@ kQuery(ROWIDS, @"SELECT rowid FROM \"%@\" ORDER BY rowid ASC");
- (NSArray<NSString *> *)queryAllColumnsOfTable:(NSString *)tableName {
NSString *sql = [NSString stringWithFormat:@"PRAGMA table_info('%@')",tableName];
FLEXSQLResult *results = [self executeStatement:sql];
// https://github.com/FLEXTool/FLEX/issues/554
if (!results.keyedRows.count) {
sql = [NSString stringWithFormat:@"SELECT * FROM pragma_table_info('%@')", tableName];
results = [self executeStatement:sql];
// Fallback to empty query
if (!results.keyedRows.count) {
sql = [NSString stringWithFormat:@"SELECT * FROM \"%@\" where 0=1", tableName];
return [self executeStatement:sql].columns ?: @[];
}
}
return [results.keyedRows flex_mapped:^id(NSDictionary *column, NSUInteger idx) {
return column[@"name"];
}] ?: @[];
}
- (NSArray<NSArray *> *)queryAllDataInTable:(NSString *)tableName {
NSString *command = [NSString stringWithFormat:@"SELECT * FROM \"%@\"", tableName];
return [self executeStatement:command].rows ?: @[];
}
- (NSArray<NSString *> *)queryRowIDsInTable:(NSString *)tableName {
NSString *command = [NSString stringWithFormat:QUERY_ROWIDS, tableName];
NSArray<NSArray<NSString *> *> *data = [self executeStatement:command].rows ?: @[];
return [data flex_mapped:^id(NSArray<NSString *> *obj, NSUInteger idx) {
return obj.firstObject;
}];
return [self executeStatement:[@"SELECT * FROM "
stringByAppendingString:tableName
]].rows ?: @[];
}
- (FLEXSQLResult *)executeStatement:(NSString *)sql {
@@ -161,7 +138,7 @@ kQuery(ROWIDS, @"SELECT rowid FROM \"%@\" ORDER BY rowid ASC");
return self.lastResult;
}
// Grab columns (columnCount will be 0 for insert/update/delete)
// Grab columns
int columnCount = sqlite3_column_count(pstmt);
NSArray<NSString *> *columns = [NSArray flex_forEachUpTo:columnCount map:^id(NSUInteger i) {
return @(sqlite3_column_name(pstmt, (int)i));
@@ -179,9 +156,8 @@ kQuery(ROWIDS, @"SELECT rowid FROM \"%@\" ORDER BY rowid ASC");
}
if (status == SQLITE_DONE) {
// columnCount will be 0 for insert/update/delete
if (rows.count || columnCount > 0) {
// We executed a SELECT query
if (rows.count) {
// We selected some rows
result = _lastResult = [FLEXSQLResult columns:columns rows:rows];
} else {
// We executed a query like INSERT, UDPATE, or DELETE
@@ -281,7 +257,7 @@ kQuery(ROWIDS, @"SELECT rowid FROM \"%@\" ORDER BY rowid ASC");
- (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,9 +11,6 @@
#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;
@@ -63,16 +60,9 @@ static const CGFloat kArrowWidth = 20;
CGSize size = self.frame.size;
self.titleLabel.frame = CGRectMake(kMargin, 0, size.width - kArrowWidth - kMargin, size.height);
self.arrowLabel.frame = CGRectMake(size.width - kArrowWidth, 0, kArrowWidth, size.height);
self.titleLabel.frame = CGRectMake(5, 0, size.width - 25, size.height);
self.arrowLabel.frame = CGRectMake(size.width - 20, 0, 20, 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,30 +7,10 @@
//
#import <UIKit/UIKit.h>
#import "FLEXDatabaseManager.h"
NS_ASSUME_NONNULL_BEGIN
@interface FLEXTableContentViewController : UIViewController
/// Display a mutable table with the given columns, rows, and name.
///
/// @param columnNames self explanatory.
/// @param rowData an array of rows, where each row is an array of column data.
/// @param rowIDs an array of stringy row IDs. Required for deleting rows.
/// @param tableName an optional name of the table being viewed, if any. Enables adding rows.
/// @param databaseManager an optional manager to allow modifying the table.
/// Required for deleting rows. Required for adding rows if \c tableName is supplied.
+ (instancetype)columns:(NSArray<NSString *> *)columnNames
rows:(NSArray<NSArray<NSString *> *> *)rowData
rowIDs:(NSArray<NSString *> *)rowIDs
tableName:(NSString *)tableName
database:(id<FLEXDatabaseManager>)databaseManager;
/// Display an immutable table with the given columns and rows.
+ (instancetype)columns:(NSArray<NSString *> *)columnNames
rows:(NSArray<NSArray<NSString *> *> *)rowData;
@end
NS_ASSUME_NONNULL_END
@@ -7,22 +7,15 @@
//
#import "FLEXTableContentViewController.h"
#import "FLEXTableRowDataViewController.h"
#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) NSMutableArray<NSArray *> *rows;
@property (nonatomic, readonly) NSString *tableName;
@property (nonatomic, nullable) NSMutableArray<NSString *> *rowIDs;
@property (nonatomic, readonly, nullable) id<FLEXDatabaseManager> databaseManager;
@property (nonatomic, readonly) BOOL canRefresh;
@property (nonatomic, copy) NSArray<NSArray *> *rows;
@property (nonatomic) FLEXMultiColumnTableView *multiColumnView;
@end
@@ -30,44 +23,11 @@
@implementation FLEXTableContentViewController
+ (instancetype)columns:(NSArray<NSString *> *)columnNames
rows:(NSArray<NSArray<NSString *> *> *)rowData
rowIDs:(NSArray<NSString *> *)rowIDs
tableName:(NSString *)tableName
database:(id<FLEXDatabaseManager>)databaseManager {
return [[self alloc]
initWithColumns:columnNames
rows:rowData
rowIDs:rowIDs
tableName:tableName
database:databaseManager
];
}
+ (instancetype)columns:(NSArray<NSString *> *)cols
rows:(NSArray<NSArray<NSString *> *> *)rowData {
return [[self alloc] initWithColumns:cols rows:rowData rowIDs:nil tableName:nil database:nil];
}
- (instancetype)initWithColumns:(NSArray<NSString *> *)columnNames
rows:(NSArray<NSArray<NSString *> *> *)rowData
rowIDs:(nullable NSArray<NSString *> *)rowIDs
tableName:(nullable NSString *)tableName
database:(nullable id<FLEXDatabaseManager>)databaseManager {
// Must supply all optional parameters as one, or none
BOOL all = rowIDs && tableName && databaseManager;
BOOL none = !rowIDs && !tableName && !databaseManager;
NSParameterAssert(all || none);
self = [super init];
if (self) {
self->_columns = columnNames.copy;
self->_rows = rowData.mutableCopy;
self->_rowIDs = rowIDs.mutableCopy;
self->_tableName = tableName.copy;
self->_databaseManager = databaseManager;
}
return self;
FLEXTableContentViewController *controller = [self new];
controller->_columns = columnNames;
controller->_rows = rowData;
return controller;
}
- (void)loadView {
@@ -78,9 +38,9 @@
- (void)viewDidLoad {
[super viewDidLoad];
self.title = self.tableName;
self.edgesForExtendedLayout = UIRectEdgeNone;
[self.multiColumnView reloadData];
[self setupToolbarItems];
}
- (FLEXMultiColumnTableView *)multiColumnView {
@@ -96,10 +56,6 @@
return _multiColumnView;
}
- (BOOL)canRefresh {
return self.databaseManager && self.tableName;
}
#pragma mark MultiColumnTableView DataSource
- (NSInteger)numberOfColumnsInTableView:(FLEXMultiColumnTableView *)tableView {
@@ -128,8 +84,8 @@
}
- (CGFloat)multiColumnTableView:(FLEXMultiColumnTableView *)tableView
minWidthForContentCellInColumn:(NSInteger)column {
return 100;
widthForContentCellInColumn:(NSInteger)column {
return 120;
}
- (CGFloat)heightForTopHeaderInTableView:(FLEXMultiColumnTableView *)tableView {
@@ -155,10 +111,6 @@
return [NSString stringWithFormat:@"%@:\n%@", self.columns[idx], field];
}];
NSArray<NSString *> *values = [self.rows[row] flex_mapped:^id(NSString *value, NSUInteger idx) {
return [NSString stringWithFormat:@"'%@'", value];
}];
[FLEXAlert makeAlert:^(FLEXAlert *make) {
make.title([@"Row " stringByAppendingString:@(row).stringValue]);
NSString *message = [fields componentsJoinedByString:@"\n\n"];
@@ -166,34 +118,6 @@
make.button(@"Copy").handler(^(NSArray<NSString *> *strings) {
UIPasteboard.generalPasteboard.string = message;
});
make.button(@"Copy as CSV").handler(^(NSArray<NSString *> *strings) {
UIPasteboard.generalPasteboard.string = [values componentsJoinedByString:@", "];
});
make.button(@"Focus on Row").handler(^(NSArray<NSString *> *strings) {
UIViewController *focusedRow = [FLEXTableRowDataViewController
rows:[NSDictionary dictionaryWithObjects:self.rows[row] forKeys:self.columns]
];
[self.navigationController pushViewController:focusedRow animated:YES];
});
// Option to delete row
BOOL hasRowID = self.rows.count && row < self.rows.count;
if (hasRowID && self.canRefresh) {
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 reloadTableDataFromDB];
}
}];
});
}
make.button(@"Dismiss").cancelStyle();
} showFrom:self];
}
@@ -203,8 +127,7 @@
sortType:(FLEXTableColumnHeaderSortType)sortType {
NSArray<NSArray *> *sortContentData = [self.rows
sortedArrayWithOptions:NSSortStable
usingComparator:^NSComparisonResult(NSArray *obj1, NSArray *obj2) {
sortedArrayUsingComparator:^NSComparisonResult(NSArray *obj1, NSArray *obj2) {
id a = obj1[column], b = obj2[column];
if (a == NSNull.null) {
return NSOrderedAscending;
@@ -212,11 +135,6 @@
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];
@@ -230,11 +148,12 @@
sortContentData = sortContentData.reverseObjectEnumerator.allObjects.copy;
}
self.rows = sortContentData.mutableCopy;
self.rows = sortContentData;
[self.multiColumnView reloadData];
}
#pragma mark - About Transition
#pragma mark -
#pragma mark About Transition
- (void)willTransitionToTraitCollection:(UITraitCollection *)newCollection
withTransitionCoordinator:(id <UIViewControllerTransitionCoordinator>)coordinator {
@@ -252,108 +171,4 @@
} 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));
UIBarButtonItem *addButton = FLEXBarButtonItemSystem(Add, self, @selector(addPressed));
// Only allow adding rows or deleting rows if we have a table name
trashButton.enabled = self.canRefresh;
addButton.enabled = self.canRefresh;
self.toolbarItems = @[
UIBarButtonItem.flex_flexibleSpace,
addButton,
UIBarButtonItem.flex_flexibleSpace,
[trashButton flex_withTintColor:UIColor.redColor],
];
}
- (void)trashPressed {
NSParameterAssert(self.tableName);
[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];
}
- (void)addPressed {
NSParameterAssert(self.tableName);
[FLEXAlert makeAlert:^(FLEXAlert *make) {
make.title(@"Add a New Row");
make.message(@"Comma separate values to use in an INSERT statement.\n\n");
make.message(@"INSERT INTO [table] VALUES (your_input)");
make.textField(@"5, 'John Smith', 14,...");
make.button(@"Insert").handler(^(NSArray<NSString *> *strings) {
NSString *statement = [NSString stringWithFormat:
@"INSERT INTO %@ VALUES (%@)", self.tableName, strings[0]
];
[self executeStatementAndShowResult:statement completion:^(BOOL success) {
if (success) {
[self reloadTableDataFromDB];
}
}];
});
make.button(@"Cancel").cancelStyle();
} showFrom:self];
}
#pragma mark - Helpers
- (void)executeStatementAndShowResult:(NSString *)statement
completion:(void (^_Nullable)(BOOL success))completion {
NSParameterAssert(self.databaseManager);
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];
}
- (void)reloadTableDataFromDB {
if (!self.canRefresh) {
return;
}
NSArray<NSArray *> *rows = [self.databaseManager queryAllDataInTable:self.tableName];
NSArray<NSString *> *rowIDs = nil;
if ([self.databaseManager respondsToSelector:@selector(queryRowIDsInTable:)]) {
rowIDs = [self.databaseManager queryRowIDsInTable:self.tableName];
}
self.rows = rows.mutableCopy;
self.rowIDs = rowIDs.mutableCopy;
[self.multiColumnView reloadData];
}
@end
@@ -14,7 +14,6 @@
#import "FLEXMutableListSection.h"
#import "NSArray+FLEX.h"
#import "FLEXAlert.h"
#import "FLEXMacros.h"
@interface FLEXTableListViewController ()
@property (nonatomic, readonly) id<FLEXDatabaseManager> dbm;
@@ -71,13 +70,9 @@
self.tables.selectionHandler = ^(FLEXTableListViewController *host, NSString *tableName) {
NSArray *rows = [host.dbm queryAllDataInTable:tableName];
NSArray *columns = [host.dbm queryAllColumnsOfTable:tableName];
NSArray *rowIDs = nil;
if ([host.dbm respondsToSelector:@selector(queryRowIDsInTable:)]) {
rowIDs = [host.dbm queryRowIDsInTable:tableName];
}
UIViewController *resultsScreen = [FLEXTableContentViewController
columns:columns rows:rows rowIDs:rowIDs tableName:tableName database:host.dbm
];
UIViewController *resultsScreen = [FLEXTableContentViewController columns:columns rows:rows];
resultsScreen.title = tableName;
[host.navigationController pushViewController:resultsScreen animated:YES];
};
@@ -93,37 +88,16 @@
}
- (void)queryButtonPressed {
[self showQueryInput:nil];
}
- (void)showQueryInput:(NSString *)prefillQuery {
FLEXSQLiteDatabaseManager *database = self.dbm;
[FLEXAlert makeAlert:^(FLEXAlert *make) {
make.title(@"Execute an SQL query");
make.configuredTextField(^(UITextField *textField) {
textField.text = prefillQuery;
});
make.textField(nil);
make.button(@"Run").handler(^(NSArray<NSString *> *strings) {
NSString *query = strings[0];
FLEXSQLResult *result = [database executeStatement:query];
FLEXSQLResult *result = [database executeStatement:strings[0]];
if (result.message) {
// Allow users to edit their last query if it had an error
if ([result.message containsString:@"error"]) {
[FLEXAlert makeAlert:^(FLEXAlert *make) {
make.title(@"Error").message(result.message);
make.button(@"Edit Query").preferred().handler(^(NSArray<NSString *> *_) {
// Show query editor again with our last input
[self showQueryInput:query];
});
make.button(@"Cancel").cancelStyle();
} showFrom:self];
} else {
[FLEXAlert showAlert:@"Message" message:result.message from:self];
}
[FLEXAlert showAlert:@"Message" message:result.message from:self];
} else {
UIViewController *resultsScreen = [FLEXTableContentViewController
columns:result.columns rows:result.rows
@@ -1,14 +0,0 @@
//
// FLEXTableRowDataViewController.h
// FLEX
//
// Created by Chaoshuai Lu on 7/8/20.
//
#import "FLEXFilteringTableViewController.h"
@interface FLEXTableRowDataViewController : FLEXFilteringTableViewController
+ (instancetype)rows:(NSDictionary<NSString *, id> *)rowData;
@end

Some files were not shown because too many files have changed in this diff Show More