mirror of
				https://github.com/SoPat712/YTLitePlus.git
				synced 2025-10-30 20:34:03 -04:00 
			
		
		
		
	added files via upload
This commit is contained in:
		| @@ -0,0 +1,89 @@ | ||||
| // | ||||
| //  FLEXFilteringTableViewController.h | ||||
| //  FLEX | ||||
| // | ||||
| //  Created by Tanner on 3/9/20. | ||||
| //  Copyright © 2020 FLEX Team. All rights reserved. | ||||
| // | ||||
|  | ||||
| #import "FLEXTableViewController.h" | ||||
|  | ||||
| #pragma mark - FLEXTableViewFiltering | ||||
| @protocol FLEXTableViewFiltering <FLEXSearchResultsUpdating> | ||||
|  | ||||
| /// An array of visible, "filtered" sections. For example, | ||||
| /// if you have 3 sections in \c allSections and the user searches | ||||
| /// for something that matches rows in only one section, then | ||||
| /// this property would only contain that on matching section. | ||||
| @property (nonatomic, copy) NSArray<FLEXTableViewSection *> *sections; | ||||
| /// An array of all possible sections. Empty sections are to be removed | ||||
| /// and the resulting array stored in the \c section property. Setting | ||||
| /// this property should immediately set \c sections to \c nonemptySections  | ||||
| /// | ||||
| /// Do not manually initialize this property, it will be | ||||
| /// initialized for you using the result of \c makeSections. | ||||
| @property (nonatomic, copy) NSArray<FLEXTableViewSection *> *allSections; | ||||
|  | ||||
| /// This computed property should filter \c allSections for assignment to \c sections | ||||
| @property (nonatomic, readonly, copy) NSArray<FLEXTableViewSection *> *nonemptySections; | ||||
|  | ||||
| /// This should be able to re-initialize \c allSections | ||||
| - (NSArray<FLEXTableViewSection *> *)makeSections; | ||||
|  | ||||
| @end | ||||
|  | ||||
|  | ||||
| #pragma mark - FLEXFilteringTableViewController | ||||
| /// A table view which implements \c UITableView* methods using arrays of | ||||
| /// \c FLEXTableViewSection objects provied by a special delegate. | ||||
| @interface FLEXFilteringTableViewController : FLEXTableViewController <FLEXTableViewFiltering> | ||||
|  | ||||
| /// Stores the current search query. | ||||
| @property (nonatomic, copy) NSString *filterText; | ||||
|  | ||||
| /// This property is set to \c self by default. | ||||
| /// | ||||
| /// This property is used to power almost all of the table view's data source | ||||
| /// and delegate methods automatically, including row and section filtering | ||||
| /// when the user searches, 3D Touch context menus, row selection, etc. | ||||
| /// | ||||
| /// Setting this property will also set \c searchDelegate to that object. | ||||
| @property (nonatomic, weak) id<FLEXTableViewFiltering> filterDelegate; | ||||
|  | ||||
| /// Defaults to \c NO. If enabled, all filtering will be done by calling | ||||
| /// \c onBackgroundQueue:thenOnMainQueue: with the UI updated on the main queue. | ||||
| @property (nonatomic) BOOL filterInBackground; | ||||
|  | ||||
| /// Defaults to \c NO. If enabled, one • will be supplied as an index title for each section. | ||||
| @property (nonatomic) BOOL wantsSectionIndexTitles; | ||||
|  | ||||
| /// Recalculates the non-empty sections and reloads the table view. | ||||
| /// | ||||
| /// Subclasses may override to perform additional reloading logic, | ||||
| /// such as calling \c -reloadSections if needed. Be sure to call | ||||
| /// \c super after any logic that would affect the appearance of  | ||||
| /// the table view, since the table view is reloaded last. | ||||
| /// | ||||
| /// Called at the end of this class's implementation of \c updateSearchResults: | ||||
| - (void)reloadData; | ||||
|  | ||||
| /// Invoke this method to call \c -reloadData on each section | ||||
| /// in \c self.filterDelegate.allSections. | ||||
| - (void)reloadSections; | ||||
|  | ||||
| #pragma mark FLEXTableViewFiltering | ||||
|  | ||||
| @property (nonatomic, copy) NSArray<FLEXTableViewSection *> *sections; | ||||
| @property (nonatomic, copy) NSArray<FLEXTableViewSection *> *allSections; | ||||
|  | ||||
| /// Subclasses can override to hide specific sections under certain conditions | ||||
| /// if using \c self as the \c filterDelegate, as is the default. | ||||
| /// | ||||
| /// For example, the object explorer hides the description section when searching. | ||||
| @property (nonatomic, readonly, copy) NSArray<FLEXTableViewSection *> *nonemptySections; | ||||
|  | ||||
| /// If using \c self as the \c filterDelegate, as is the default, | ||||
| /// subclasses should override to provide the sections for the table view. | ||||
| - (NSArray<FLEXTableViewSection *> *)makeSections; | ||||
|  | ||||
| @end | ||||
							
								
								
									
										209
									
								
								Tweaks/FLEX/Core/Controllers/FLEXFilteringTableViewController.m
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										209
									
								
								Tweaks/FLEX/Core/Controllers/FLEXFilteringTableViewController.m
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,209 @@ | ||||
| // | ||||
| //  FLEXFilteringTableViewController.m | ||||
| //  FLEX | ||||
| // | ||||
| //  Created by Tanner on 3/9/20. | ||||
| //  Copyright © 2020 FLEX Team. All rights reserved. | ||||
| // | ||||
|  | ||||
| #import "FLEXFilteringTableViewController.h" | ||||
| #import "FLEXTableViewSection.h" | ||||
| #import "NSArray+FLEX.h" | ||||
| #import "FLEXMacros.h" | ||||
|  | ||||
| @interface FLEXFilteringTableViewController () | ||||
|  | ||||
| @end | ||||
|  | ||||
| @implementation FLEXFilteringTableViewController | ||||
| @synthesize allSections = _allSections; | ||||
|  | ||||
| #pragma mark - View controller lifecycle | ||||
|  | ||||
| - (void)loadView { | ||||
|     [super loadView]; | ||||
|      | ||||
|     if (!self.filterDelegate) { | ||||
|         self.filterDelegate = self; | ||||
|     } else { | ||||
|         [self _registerCellsForReuse]; | ||||
|     } | ||||
| } | ||||
|  | ||||
| - (void)_registerCellsForReuse { | ||||
|     for (FLEXTableViewSection *section in self.filterDelegate.allSections) { | ||||
|         if (section.cellRegistrationMapping) { | ||||
|             [self.tableView registerCells:section.cellRegistrationMapping]; | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
|  | ||||
| #pragma mark - Public | ||||
|  | ||||
| - (void)setFilterDelegate:(id<FLEXTableViewFiltering>)filterDelegate { | ||||
|     _filterDelegate = filterDelegate; | ||||
|     filterDelegate.allSections = [filterDelegate makeSections]; | ||||
|      | ||||
|     if (self.isViewLoaded) { | ||||
|         [self _registerCellsForReuse]; | ||||
|     } | ||||
| } | ||||
|  | ||||
| - (void)reloadData { | ||||
|     [self reloadData:self.nonemptySections]; | ||||
| } | ||||
|  | ||||
| - (void)reloadData:(NSArray *)nonemptySections { | ||||
|     // Recalculate displayed sections | ||||
|     self.filterDelegate.sections = nonemptySections; | ||||
|  | ||||
|     // Refresh table view | ||||
|     if (self.isViewLoaded) { | ||||
|         [self.tableView reloadData]; | ||||
|     } | ||||
| } | ||||
|  | ||||
| - (void)reloadSections { | ||||
|     for (FLEXTableViewSection *section in self.filterDelegate.allSections) { | ||||
|         [section reloadData]; | ||||
|     } | ||||
| } | ||||
|  | ||||
|  | ||||
| #pragma mark - Search | ||||
|  | ||||
| - (void)updateSearchResults:(NSString *)newText { | ||||
|     NSArray *(^filter)(void) = ^NSArray *{ | ||||
|         self.filterText = newText; | ||||
|  | ||||
|         // Sections will adjust data based on this property | ||||
|         for (FLEXTableViewSection *section in self.filterDelegate.allSections) { | ||||
|             section.filterText = newText; | ||||
|         } | ||||
|          | ||||
|         return nil; | ||||
|     }; | ||||
|      | ||||
|     if (self.filterInBackground) { | ||||
|         [self onBackgroundQueue:filter thenOnMainQueue:^(NSArray *unused) { | ||||
|             if ([self.searchText isEqualToString:newText]) { | ||||
|                 [self reloadData]; | ||||
|             } | ||||
|         }]; | ||||
|     } else { | ||||
|         filter(); | ||||
|         [self reloadData]; | ||||
|     } | ||||
| } | ||||
|  | ||||
|  | ||||
| #pragma mark Filtering | ||||
|  | ||||
| - (NSArray<FLEXTableViewSection *> *)nonemptySections { | ||||
|     return [self.filterDelegate.allSections flex_filtered:^BOOL(FLEXTableViewSection *section, NSUInteger idx) { | ||||
|         return section.numberOfRows > 0; | ||||
|     }]; | ||||
| } | ||||
|  | ||||
| - (NSArray<FLEXTableViewSection *> *)makeSections { | ||||
|     return @[]; | ||||
| } | ||||
|  | ||||
| - (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 | ||||
|  | ||||
| - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { | ||||
|     return self.filterDelegate.sections.count; | ||||
| } | ||||
|  | ||||
| - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { | ||||
|     return self.filterDelegate.sections[section].numberOfRows; | ||||
| } | ||||
|  | ||||
| - (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section { | ||||
|     return self.filterDelegate.sections[section].title; | ||||
| } | ||||
|  | ||||
| - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { | ||||
|     NSString *reuse = [self.filterDelegate.sections[indexPath.section] reuseIdentifierForRow:indexPath.row]; | ||||
|     UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:reuse forIndexPath:indexPath]; | ||||
|     [self.filterDelegate.sections[indexPath.section] configureCell:cell forRow:indexPath.row]; | ||||
|     return cell; | ||||
| } | ||||
|  | ||||
| - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { | ||||
|     return UITableViewAutomaticDimension; | ||||
| } | ||||
|  | ||||
| - (NSArray<NSString *> *)sectionIndexTitlesForTableView:(UITableView *)tableView { | ||||
|     if (self.wantsSectionIndexTitles) { | ||||
|         return [NSArray flex_forEachUpTo:self.filterDelegate.sections.count map:^id(NSUInteger i) { | ||||
|             return @"⦁"; | ||||
|         }]; | ||||
|     } | ||||
|      | ||||
|     return nil; | ||||
| } | ||||
|  | ||||
|  | ||||
| #pragma mark - UITableViewDelegate | ||||
|  | ||||
| - (BOOL)tableView:(UITableView *)tableView shouldHighlightRowAtIndexPath:(NSIndexPath *)indexPath { | ||||
|     return [self.filterDelegate.sections[indexPath.section] canSelectRow:indexPath.row]; | ||||
| } | ||||
|  | ||||
| - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { | ||||
|     FLEXTableViewSection *section = self.filterDelegate.sections[indexPath.section]; | ||||
|  | ||||
|     void (^action)(UIViewController *) = [section didSelectRowAction:indexPath.row]; | ||||
|     UIViewController *details = [section viewControllerToPushForRow:indexPath.row]; | ||||
|  | ||||
|     if (action) { | ||||
|         action(self); | ||||
|         [tableView deselectRowAtIndexPath:indexPath animated:YES]; | ||||
|     } else if (details) { | ||||
|         [self.navigationController pushViewController:details animated:YES]; | ||||
|     } else { | ||||
|         [NSException raise:NSInternalInconsistencyException | ||||
|                     format:@"Row is selectable but has no action or view controller"]; | ||||
|     } | ||||
| } | ||||
|  | ||||
| - (void)tableView:(UITableView *)tableView accessoryButtonTappedForRowWithIndexPath:(NSIndexPath *)indexPath { | ||||
|     [self.filterDelegate.sections[indexPath.section] didPressInfoButtonAction:indexPath.row](self); | ||||
| } | ||||
|  | ||||
| - (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]; | ||||
|     NSArray<UIMenuElement *> *menuItems = [section menuItemsForRow:indexPath.row sender:self]; | ||||
|      | ||||
|     if (menuItems.count) { | ||||
|         return [UIContextMenuConfiguration | ||||
|             configurationWithIdentifier:nil | ||||
|             previewProvider:nil | ||||
|             actionProvider:^UIMenu *(NSArray<UIMenuElement *> *suggestedActions) { | ||||
|                 return [UIMenu menuWithTitle:title children:menuItems]; | ||||
|             } | ||||
|         ]; | ||||
|     } | ||||
|      | ||||
|     return nil; | ||||
| } | ||||
|  | ||||
| @end | ||||
							
								
								
									
										19
									
								
								Tweaks/FLEX/Core/Controllers/FLEXNavigationController.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								Tweaks/FLEX/Core/Controllers/FLEXNavigationController.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,19 @@ | ||||
| // | ||||
| //  FLEXNavigationController.h | ||||
| //  FLEX | ||||
| // | ||||
| //  Created by Tanner on 1/30/20. | ||||
| //  Copyright © 2020 FLEX Team. All rights reserved. | ||||
| // | ||||
|  | ||||
| #import <UIKit/UIKit.h> | ||||
|  | ||||
| NS_ASSUME_NONNULL_BEGIN | ||||
|  | ||||
| @interface FLEXNavigationController : UINavigationController | ||||
|  | ||||
| + (instancetype)withRootViewController:(UIViewController *)rootVC; | ||||
|  | ||||
| @end | ||||
|  | ||||
| NS_ASSUME_NONNULL_END | ||||
							
								
								
									
										196
									
								
								Tweaks/FLEX/Core/Controllers/FLEXNavigationController.m
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										196
									
								
								Tweaks/FLEX/Core/Controllers/FLEXNavigationController.m
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,196 @@ | ||||
| // | ||||
| //  FLEXNavigationController.m | ||||
| //  FLEX | ||||
| // | ||||
| //  Created by Tanner on 1/30/20. | ||||
| //  Copyright © 2020 FLEX Team. All rights reserved. | ||||
| // | ||||
|  | ||||
| #import "FLEXNavigationController.h" | ||||
| #import "FLEXExplorerViewController.h" | ||||
| #import "FLEXTabList.h" | ||||
|  | ||||
| @interface UINavigationController (Private) <UIGestureRecognizerDelegate> | ||||
| - (void)_gestureRecognizedInteractiveHide:(UIGestureRecognizer *)sender; | ||||
| @end | ||||
| @interface UIPanGestureRecognizer (Private) | ||||
| - (void)_setDelegate:(id)delegate; | ||||
| @end | ||||
|  | ||||
| @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 | ||||
|  | ||||
| @implementation FLEXNavigationController | ||||
|  | ||||
| + (instancetype)withRootViewController:(UIViewController *)rootVC { | ||||
|     return [[self alloc] initWithRootViewController:rootVC]; | ||||
| } | ||||
|  | ||||
| - (void)viewDidLoad { | ||||
|     [super viewDidLoad]; | ||||
|      | ||||
|     self.waitingToAddTab = YES; | ||||
|      | ||||
|     // Add gesture to reveal toolbar if hidden | ||||
|     UITapGestureRecognizer *navbarTapGesture = [[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, *)) { | ||||
|         switch (self.modalPresentationStyle) { | ||||
|             case UIModalPresentationAutomatic: | ||||
|             case UIModalPresentationPageSheet: | ||||
|             case UIModalPresentationFormSheet: | ||||
|                 break; | ||||
|                  | ||||
|             default: | ||||
|                 [self addNavigationBarSwipeGesture]; | ||||
|                 break; | ||||
|         } | ||||
|     } else { | ||||
|         [self addNavigationBarSwipeGesture]; | ||||
|     } | ||||
| } | ||||
|  | ||||
| - (void)viewWillAppear:(BOOL)animated { | ||||
|     [super viewWillAppear:animated]; | ||||
|      | ||||
|     if (self.beingPresented && !self.didSetupPendingDismissButtons) { | ||||
|         for (UIViewController *vc in self.viewControllers) { | ||||
|             [self addNavigationBarItemsToViewController:vc.navigationItem]; | ||||
|         } | ||||
|          | ||||
|         self.didSetupPendingDismissButtons = YES; | ||||
|     } | ||||
| } | ||||
|  | ||||
| - (void)viewDidAppear:(BOOL)animated { | ||||
|     [super viewDidAppear:animated]; | ||||
|      | ||||
|     if (self.waitingToAddTab) { | ||||
|         // Only add new tab if we're presented properly | ||||
|         if ([self.presentingViewController isKindOfClass:[FLEXExplorerViewController class]]) { | ||||
|             // New navigation controllers always add themselves as new tabs, | ||||
|             // tabs are closed by FLEXExplorerViewController | ||||
|             [FLEXTabList.sharedList addTab:self]; | ||||
|             self.waitingToAddTab = NO; | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| - (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated { | ||||
|     [super pushViewController:viewController animated:animated]; | ||||
|     [self addNavigationBarItemsToViewController:viewController.navigationItem]; | ||||
| } | ||||
|  | ||||
| - (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];         | ||||
|     } | ||||
|      | ||||
|     [self.presentingViewController dismissViewControllerAnimated:YES completion:nil]; | ||||
| } | ||||
|  | ||||
| - (BOOL)canShowToolbar { | ||||
|     return self.topViewController.toolbarItems.count > 0; | ||||
| } | ||||
|  | ||||
| - (void)addNavigationBarItemsToViewController:(UINavigationItem *)navigationItem { | ||||
|     if (!self.presentingViewController) { | ||||
|         return; | ||||
|     } | ||||
|      | ||||
|     // Check if a done item already exists | ||||
|     for (UIBarButtonItem *item in navigationItem.rightBarButtonItems) { | ||||
|         if (item.style == UIBarButtonItemStyleDone) { | ||||
|             return; | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     // Give root view controllers a Done button if it does not already have one | ||||
|     UIBarButtonItem *done = [[UIBarButtonItem alloc] | ||||
|         initWithBarButtonSystemItem:UIBarButtonSystemItemDone | ||||
|         target:self | ||||
|         action:@selector(dismissAnimated) | ||||
|     ]; | ||||
|      | ||||
|     // Prepend the button if other buttons exist already | ||||
|     NSArray *existingItems = navigationItem.rightBarButtonItems; | ||||
|     if (existingItems.count) { | ||||
|         navigationItem.rightBarButtonItems = [@[done] arrayByAddingObjectsFromArray:existingItems]; | ||||
|     } else { | ||||
|         navigationItem.rightBarButtonItem = done; | ||||
|     } | ||||
|      | ||||
|     // Keeps us from calling this method again on | ||||
|     // the same view controllers in -viewWillAppear: | ||||
|     self.didSetupPendingDismissButtons = YES; | ||||
| } | ||||
|  | ||||
| - (void)addNavigationBarSwipeGesture { | ||||
|     UISwipeGestureRecognizer *swipe = [[UISwipeGestureRecognizer alloc] | ||||
|         initWithTarget:self action:@selector(handleNavigationBarSwipe:) | ||||
|     ]; | ||||
|     swipe.direction = UISwipeGestureRecognizerDirectionDown; | ||||
|     swipe.delegate = self; | ||||
|     self.navigationBarSwipeGesture = swipe; | ||||
|     [self.navigationBar addGestureRecognizer:swipe]; | ||||
| } | ||||
|  | ||||
| - (void)handleNavigationBarSwipe:(UISwipeGestureRecognizer *)sender { | ||||
|     if (sender.state == UIGestureRecognizerStateRecognized) { | ||||
|         [self.presentingViewController dismissViewControllerAnimated:YES completion:nil]; | ||||
|     } | ||||
| } | ||||
|       | ||||
| - (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) { | ||||
|             [self setToolbarHidden:NO animated:YES]; | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| - (BOOL)gestureRecognizer:(UIGestureRecognizer *)g1 shouldBeRequiredToFailByGestureRecognizer:(UIGestureRecognizer *)g2 { | ||||
|     if (g1 == self.navigationBarSwipeGesture && g2 == self.barHideOnSwipeGestureRecognizer) { | ||||
|         return YES; | ||||
|     } | ||||
|      | ||||
|     return NO; | ||||
| } | ||||
|  | ||||
| - (void)_gestureRecognizedInteractiveHide:(UIPanGestureRecognizer *)sender { | ||||
|     if (sender.state == UIGestureRecognizerStateRecognized) { | ||||
|         BOOL show = self.canShowToolbar; | ||||
|         CGFloat yTranslation = [sender translationInView:self.view].y; | ||||
|         CGFloat yVelocity = [sender velocityInView:self.view].y; | ||||
|         if (yVelocity > 2000) { | ||||
|             [self setToolbarHidden:YES animated:YES]; | ||||
|         } else if (show && yTranslation > 20 && yVelocity > 250) { | ||||
|             [self setToolbarHidden:NO animated:YES]; | ||||
|         } else if (yTranslation < -20) { | ||||
|             [self setToolbarHidden:YES animated:YES]; | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| @end | ||||
							
								
								
									
										153
									
								
								Tweaks/FLEX/Core/Controllers/FLEXTableViewController.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										153
									
								
								Tweaks/FLEX/Core/Controllers/FLEXTableViewController.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,153 @@ | ||||
| // | ||||
| //  FLEXTableViewController.h | ||||
| //  FLEX | ||||
| // | ||||
| //  Created by Tanner on 7/5/19. | ||||
| //  Copyright © 2020 FLEX Team. All rights reserved. | ||||
| // | ||||
|  | ||||
| #import <UIKit/UIKit.h> | ||||
| #import "FLEXTableView.h" | ||||
| @class FLEXScopeCarousel, FLEXWindow, FLEXTableViewSection; | ||||
|  | ||||
| typedef CGFloat FLEXDebounceInterval; | ||||
| /// No delay, all events delivered | ||||
| extern CGFloat const kFLEXDebounceInstant; | ||||
| /// Small delay which makes UI seem smoother by avoiding rapid events | ||||
| extern CGFloat const kFLEXDebounceFast; | ||||
| /// Slower than Fast, faster than ExpensiveIO | ||||
| extern CGFloat const kFLEXDebounceForAsyncSearch; | ||||
| /// The least frequent, at just over once per second; for I/O or other expensive operations | ||||
| extern CGFloat const kFLEXDebounceForExpensiveIO; | ||||
|  | ||||
| @protocol FLEXSearchResultsUpdating <NSObject> | ||||
| /// A method to handle search query update events. | ||||
| /// | ||||
| /// \c searchBarDebounceInterval is used to reduce the frequency at which this | ||||
| /// method is called. This method is also called when the search bar becomes | ||||
| /// the first responder, and when the selected search bar scope index changes. | ||||
| - (void)updateSearchResults:(NSString *)newText; | ||||
| @end | ||||
|  | ||||
| @interface FLEXTableViewController : UITableViewController < | ||||
|     UISearchResultsUpdating, UISearchControllerDelegate, UISearchBarDelegate | ||||
| > | ||||
|  | ||||
| /// A grouped table view. Inset on iOS 13. | ||||
| /// | ||||
| /// Simply calls into \c initWithStyle: | ||||
| - (id)init; | ||||
|  | ||||
| /// Subclasses may override to configure the controller before \c viewDidLoad: | ||||
| - (id)initWithStyle:(UITableViewStyle)style; | ||||
|  | ||||
| @property (nonatomic) FLEXTableView *tableView; | ||||
|  | ||||
| /// If your subclass conforms to \c FLEXSearchResultsUpdating | ||||
| /// then this property is assigned to \c self automatically. | ||||
| /// | ||||
| /// Setting \c filterDelegate will also set this property to that object. | ||||
| @property (nonatomic, weak) id<FLEXSearchResultsUpdating> searchDelegate; | ||||
|  | ||||
| /// Defaults to NO. | ||||
| /// | ||||
| /// Setting this to YES will initialize the carousel and the view. | ||||
| @property (nonatomic) BOOL showsCarousel; | ||||
| /// A horizontally scrolling list with functionality similar to | ||||
| /// that of a search bar's scope bar. You'd want to use this when | ||||
| /// you have potentially more than 4 scope options. | ||||
| @property (nonatomic) FLEXScopeCarousel *carousel; | ||||
|  | ||||
| /// Defaults to NO. | ||||
| /// | ||||
| /// Setting this to YES will initialize searchController and the view. | ||||
| @property (nonatomic) BOOL showsSearchBar; | ||||
| /// Defaults to NO. | ||||
| /// | ||||
| /// 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. | ||||
| /// | ||||
| /// self is used as the default search results updater and delegate. | ||||
| /// The search bar will not dim the background or hide the navigation bar by default. | ||||
| /// On iOS 11 and up, the search bar will appear in the navigation bar below the title. | ||||
| @property (nonatomic) UISearchController *searchController; | ||||
| /// Used to initialize the search controller. Defaults to nil. | ||||
| @property (nonatomic) UIViewController *searchResultsController; | ||||
| /// Defaults to "Fast" | ||||
| /// | ||||
| /// Determines how often search bar results will be "debounced." | ||||
| /// Empty query events are always sent instantly. Query events will | ||||
| /// be sent when the user has not changed the query for this interval. | ||||
| @property (nonatomic) FLEXDebounceInterval searchBarDebounceInterval; | ||||
| /// Whether the search bar stays at the top of the view while scrolling. | ||||
| /// | ||||
| /// Calls into self.navigationItem.hidesSearchBarWhenScrolling. | ||||
| /// Do not change self.navigationItem.hidesSearchBarWhenScrolling directly, | ||||
| /// or it will not be respsected. Use this instead. | ||||
| /// Defaults to NO. | ||||
| @property (nonatomic) BOOL pinSearchBar; | ||||
| /// By default, we will show the search bar's cancel button when | ||||
| /// search becomes active and hide it when search is dismissed. | ||||
| /// | ||||
| /// Do not set the showsCancelButton property on the searchController's | ||||
| /// searchBar manually. Set this property after turning on showsSearchBar. | ||||
| /// | ||||
| /// Does nothing pre-iOS 13, safe to call on any version. | ||||
| @property (nonatomic) BOOL automaticallyShowsSearchBarCancelButton; | ||||
|  | ||||
| /// If using the scope bar, self.searchController.searchBar.selectedScopeButtonIndex. | ||||
| /// Otherwise, this is the selected index of the carousel, or NSNotFound if using neither. | ||||
| @property (nonatomic) NSInteger selectedScope; | ||||
| /// self.searchController.searchBar.text | ||||
| @property (nonatomic, readonly, copy) NSString *searchText; | ||||
|  | ||||
| /// A totally optional delegate to forward search results updater calls to. | ||||
| /// If a delegate is set, updateSearchResults: is not called on this view controller. | ||||
| @property (nonatomic, weak) id<FLEXSearchResultsUpdating> searchResultsUpdater; | ||||
|  | ||||
| /// self.view.window as a \c FLEXWindow | ||||
| @property (nonatomic, readonly) FLEXWindow *window; | ||||
|  | ||||
| /// Convenient for doing some async processor-intensive searching | ||||
| /// in the background before updating the UI back on the main queue. | ||||
| - (void)onBackgroundQueue:(NSArray *(^)(void))backgroundBlock thenOnMainQueue:(void(^)(NSArray *))mainBlock; | ||||
|  | ||||
| /// Adds up to 3 additional items to the toolbar in right-to-left order. | ||||
| /// | ||||
| /// That is, the first item in the given array will be the rightmost item behind | ||||
| /// any existing toolbar items. By default, buttons for bookmarks and tabs are shown. | ||||
| /// | ||||
| /// If you wish to have more control over how the buttons are arranged or which | ||||
| /// buttons are displayed, you can access the properties for the pre-existing | ||||
| /// toolbar items directly and manually set \c self.toolbarItems by overriding | ||||
| /// the \c setupToolbarItems method below. | ||||
| - (void)addToolbarItems:(NSArray<UIBarButtonItem *> *)items; | ||||
|  | ||||
| /// Subclasses may override. You should not need to call this method directly. | ||||
| - (void)setupToolbarItems; | ||||
|  | ||||
| @property (nonatomic, readonly) UIBarButtonItem *shareToolbarItem; | ||||
| @property (nonatomic, readonly) UIBarButtonItem *bookmarksToolbarItem; | ||||
| @property (nonatomic, readonly) UIBarButtonItem *openTabsToolbarItem; | ||||
|  | ||||
| /// Whether or not to display the "share" icon in the middle of the toolbar. NO by default. | ||||
| /// | ||||
| /// Turning this on after you have added custom toolbar items will | ||||
| /// push off the leftmost toolbar item and shift the others leftward. | ||||
| @property (nonatomic) BOOL showsShareToolbarItem; | ||||
| /// Called when the share button is pressed. | ||||
| /// Default implementation does nothign. Subclasses may override. | ||||
| - (void)shareButtonPressed:(UIBarButtonItem *)sender; | ||||
|  | ||||
| /// Subclasses may call this to opt-out of all toolbar related behavior. | ||||
| /// This is necessary if you want to disable the gesture which reveals the toolbar. | ||||
| - (void)disableToolbar; | ||||
|  | ||||
| @end | ||||
							
								
								
									
										618
									
								
								Tweaks/FLEX/Core/Controllers/FLEXTableViewController.m
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										618
									
								
								Tweaks/FLEX/Core/Controllers/FLEXTableViewController.m
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,618 @@ | ||||
| // | ||||
| //  FLEXTableViewController.m | ||||
| //  FLEX | ||||
| // | ||||
| //  Created by Tanner on 7/5/19. | ||||
| //  Copyright © 2020 FLEX Team. All rights reserved. | ||||
| // | ||||
|  | ||||
| #import "FLEXTableViewController.h" | ||||
| #import "FLEXExplorerViewController.h" | ||||
| #import "FLEXBookmarksViewController.h" | ||||
| #import "FLEXTabsViewController.h" | ||||
| #import "FLEXScopeCarousel.h" | ||||
| #import "FLEXTableView.h" | ||||
| #import "FLEXUtility.h" | ||||
| #import "FLEXResources.h" | ||||
| #import "UIBarButtonItem+FLEX.h" | ||||
| #import <objc/runtime.h> | ||||
|  | ||||
| @interface Block : NSObject | ||||
| - (void)invoke; | ||||
| @end | ||||
|  | ||||
| CGFloat const kFLEXDebounceInstant = 0.f; | ||||
| CGFloat const kFLEXDebounceFast = 0.05; | ||||
| CGFloat const kFLEXDebounceForAsyncSearch = 0.15; | ||||
| CGFloat const kFLEXDebounceForExpensiveIO = 0.5; | ||||
|  | ||||
| @interface FLEXTableViewController () | ||||
| @property (nonatomic) NSTimer *debounceTimer; | ||||
| @property (nonatomic) BOOL didInitiallyRevealSearchBar; | ||||
| @property (nonatomic) UITableViewStyle style; | ||||
|  | ||||
| @property (nonatomic) BOOL hasAppeared; | ||||
| @property (nonatomic, readonly) UIView *tableHeaderViewContainer; | ||||
|  | ||||
| @property (nonatomic, readonly) BOOL manuallyDeactivateSearchOnDisappear; | ||||
|  | ||||
| @property (nonatomic) UIBarButtonItem *middleToolbarItem; | ||||
| @property (nonatomic) UIBarButtonItem *middleLeftToolbarItem; | ||||
| @property (nonatomic) UIBarButtonItem *leftmostToolbarItem; | ||||
| @end | ||||
|  | ||||
| @implementation FLEXTableViewController | ||||
| @dynamic tableView; | ||||
| @synthesize showsShareToolbarItem = _showsShareToolbarItem; | ||||
| @synthesize tableHeaderViewContainer = _tableHeaderViewContainer; | ||||
| @synthesize automaticallyShowsSearchBarCancelButton = _automaticallyShowsSearchBarCancelButton; | ||||
|  | ||||
| #pragma mark - Initialization | ||||
|  | ||||
| - (id)init { | ||||
|     if (@available(iOS 13.0, *)) { | ||||
|         self = [self initWithStyle:UITableViewStyleInsetGrouped]; | ||||
|     } else { | ||||
|         self = [self initWithStyle:UITableViewStyleGrouped]; | ||||
|     } | ||||
|      | ||||
|     return self; | ||||
| } | ||||
|  | ||||
| - (id)initWithStyle:(UITableViewStyle)style { | ||||
|     self = [super initWithStyle:style]; | ||||
|      | ||||
|     if (self) { | ||||
|         _searchBarDebounceInterval = kFLEXDebounceFast; | ||||
|         _showSearchBarInitially = YES; | ||||
|         _style = style; | ||||
|         _manuallyDeactivateSearchOnDisappear = ( | ||||
|             NSProcessInfo.processInfo.operatingSystemVersion.majorVersion < 11 | ||||
|         ); | ||||
|          | ||||
|         // We will be our own search delegate if we implement this method | ||||
|         if ([self respondsToSelector:@selector(updateSearchResults:)]) { | ||||
|             self.searchDelegate = (id)self; | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     return self; | ||||
| } | ||||
|  | ||||
|  | ||||
| #pragma mark - Public | ||||
|  | ||||
| - (FLEXWindow *)window { | ||||
|     return (id)self.view.window; | ||||
| } | ||||
|  | ||||
| - (void)setShowsSearchBar:(BOOL)showsSearchBar { | ||||
|     if (_showsSearchBar == showsSearchBar) return; | ||||
|     _showsSearchBar = showsSearchBar; | ||||
|      | ||||
|     if (showsSearchBar) { | ||||
|         UIViewController *results = self.searchResultsController; | ||||
|         self.searchController = [[UISearchController alloc] initWithSearchResultsController:results]; | ||||
|         self.searchController.searchBar.placeholder = @"Filter"; | ||||
|         self.searchController.searchResultsUpdater = (id)self; | ||||
|         self.searchController.delegate = (id)self; | ||||
|         self.searchController.dimsBackgroundDuringPresentation = NO; | ||||
|         self.searchController.hidesNavigationBarDuringPresentation = NO; | ||||
|         /// Not necessary in iOS 13; remove this when iOS 13 is the minimum deployment target | ||||
|         self.searchController.searchBar.delegate = self; | ||||
|  | ||||
|         self.automaticallyShowsSearchBarCancelButton = YES; | ||||
|  | ||||
|         if (@available(iOS 13, *)) { | ||||
|             self.searchController.automaticallyShowsScopeBar = NO; | ||||
|         } | ||||
|          | ||||
|         [self addSearchController:self.searchController]; | ||||
|     } else { | ||||
|         // Search already shown and just set to NO, so remove it | ||||
|         [self removeSearchController:self.searchController]; | ||||
|     } | ||||
| } | ||||
|  | ||||
| - (void)setShowsCarousel:(BOOL)showsCarousel { | ||||
|     if (_showsCarousel == showsCarousel) return; | ||||
|     _showsCarousel = showsCarousel; | ||||
|      | ||||
|     if (showsCarousel) { | ||||
|         _carousel = ({ weakify(self) | ||||
|              | ||||
|             FLEXScopeCarousel *carousel = [FLEXScopeCarousel new]; | ||||
|             carousel.selectedIndexChangedAction = ^(NSInteger idx) { strongify(self); | ||||
|                 [self.searchDelegate updateSearchResults:self.searchText]; | ||||
|             }; | ||||
|  | ||||
|             // UITableView won't update the header size unless you reset the header view | ||||
|             [carousel registerBlockForDynamicTypeChanges:^(FLEXScopeCarousel *_) { strongify(self); | ||||
|                 [self layoutTableHeaderIfNeeded]; | ||||
|             }]; | ||||
|  | ||||
|             carousel; | ||||
|         }); | ||||
|         [self addCarousel:_carousel]; | ||||
|     } else { | ||||
|         // Carousel already shown and just set to NO, so remove it | ||||
|         [self removeCarousel:_carousel]; | ||||
|     } | ||||
| } | ||||
|  | ||||
| - (NSInteger)selectedScope { | ||||
|     if (self.searchController.searchBar.showsScopeBar) { | ||||
|         return self.searchController.searchBar.selectedScopeButtonIndex; | ||||
|     } else if (self.showsCarousel) { | ||||
|         return self.carousel.selectedIndex; | ||||
|     } else { | ||||
|         return 0; | ||||
|     } | ||||
| } | ||||
|  | ||||
| - (void)setSelectedScope:(NSInteger)selectedScope { | ||||
|     if (self.searchController.searchBar.showsScopeBar) { | ||||
|         self.searchController.searchBar.selectedScopeButtonIndex = selectedScope; | ||||
|     } else if (self.showsCarousel) { | ||||
|         self.carousel.selectedIndex = selectedScope; | ||||
|     } | ||||
|  | ||||
|     [self.searchDelegate updateSearchResults:self.searchText]; | ||||
| } | ||||
|  | ||||
| - (NSString *)searchText { | ||||
|     return self.searchController.searchBar.text; | ||||
| } | ||||
|  | ||||
| - (BOOL)automaticallyShowsSearchBarCancelButton { | ||||
|     if (@available(iOS 13, *)) { | ||||
|         return self.searchController.automaticallyShowsCancelButton; | ||||
|     } | ||||
|  | ||||
|     return _automaticallyShowsSearchBarCancelButton; | ||||
| } | ||||
|  | ||||
| - (void)setAutomaticallyShowsSearchBarCancelButton:(BOOL)value { | ||||
|     if (@available(iOS 13, *)) { | ||||
|         self.searchController.automaticallyShowsCancelButton = value; | ||||
|     } | ||||
|  | ||||
|     _automaticallyShowsSearchBarCancelButton = value; | ||||
| } | ||||
|  | ||||
| - (void)onBackgroundQueue:(NSArray *(^)(void))backgroundBlock thenOnMainQueue:(void(^)(NSArray *))mainBlock { | ||||
|     dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ | ||||
|         NSArray *items = backgroundBlock(); | ||||
|         dispatch_async(dispatch_get_main_queue(), ^{ | ||||
|             mainBlock(items); | ||||
|         }); | ||||
|     }); | ||||
| } | ||||
|  | ||||
| - (void)setsShowsShareToolbarItem:(BOOL)showsShareToolbarItem { | ||||
|     _showsShareToolbarItem = showsShareToolbarItem; | ||||
|     if (self.isViewLoaded) { | ||||
|         [self setupToolbarItems]; | ||||
|     } | ||||
| } | ||||
|  | ||||
| - (void)disableToolbar { | ||||
|     self.navigationController.toolbarHidden = YES; | ||||
|     self.navigationController.hidesBarsOnSwipe = NO; | ||||
|     self.toolbarItems = nil; | ||||
| } | ||||
|  | ||||
|  | ||||
| #pragma mark - View Controller Lifecycle | ||||
|  | ||||
| - (void)loadView { | ||||
|     self.view = [FLEXTableView style:self.style]; | ||||
|     self.tableView.dataSource = self; | ||||
|     self.tableView.delegate = self; | ||||
|      | ||||
|     _shareToolbarItem = FLEXBarButtonItemSystem(Action, self, @selector(shareButtonPressed:)); | ||||
|     _bookmarksToolbarItem = [UIBarButtonItem | ||||
|         flex_itemWithImage:FLEXResources.bookmarksIcon target:self action:@selector(showBookmarks) | ||||
|     ]; | ||||
|     _openTabsToolbarItem = [UIBarButtonItem | ||||
|         flex_itemWithImage:FLEXResources.openTabsIcon target:self action:@selector(showTabSwitcher) | ||||
|     ]; | ||||
|      | ||||
|     self.leftmostToolbarItem = UIBarButtonItem.flex_fixedSpace; | ||||
|     self.middleLeftToolbarItem = UIBarButtonItem.flex_fixedSpace; | ||||
|     self.middleToolbarItem = UIBarButtonItem.flex_fixedSpace; | ||||
| } | ||||
|  | ||||
| - (void)viewDidLoad { | ||||
|     [super viewDidLoad]; | ||||
|      | ||||
|     self.tableView.keyboardDismissMode = UIScrollViewKeyboardDismissModeOnDrag; | ||||
|      | ||||
|     // Toolbar | ||||
|     self.navigationController.toolbarHidden = self.toolbarItems.count > 0; | ||||
|     self.navigationController.hidesBarsOnSwipe = YES; | ||||
|  | ||||
|     // On iOS 13, the root view controller shows it's search bar no matter what. | ||||
|     // Turning this off avoids some weird flash the navigation bar does when we | ||||
|     // toggle navigationItem.hidesSearchBarWhenScrolling on and off. The flash | ||||
|     // will still happen on subsequent view controllers, but we can at least | ||||
|     // avoid it for the root view controller | ||||
|     if (@available(iOS 13, *)) { | ||||
|         if (self.navigationController.viewControllers.firstObject == self) { | ||||
|             _showSearchBarInitially = NO; | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| - (void)viewWillAppear:(BOOL)animated { | ||||
|     [super viewWillAppear:animated]; | ||||
|      | ||||
|     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]; | ||||
| } | ||||
|  | ||||
| - (void)viewDidAppear:(BOOL)animated { | ||||
|     [super viewDidAppear:animated]; | ||||
|  | ||||
|     // Allow scrolling to collapse the search bar, only if we don't want it pinned | ||||
|     if (@available(iOS 11.0, *)) { | ||||
|         if (self.showSearchBarInitially && !self.pinSearchBar && !self.didInitiallyRevealSearchBar) { | ||||
|             // All this mumbo jumbo is necessary to work around a bug in iOS 13 up to 13.2 | ||||
|             // wherein quickly toggling navigationItem.hidesSearchBarWhenScrolling to make | ||||
|             // the search bar appear initially results in a bugged search bar that | ||||
|             // becomes transparent and floats over the screen as you scroll | ||||
|             [UIView animateWithDuration:0.2 animations:^{ | ||||
|                 self.navigationItem.hidesSearchBarWhenScrolling = YES; | ||||
|                 [self.navigationController.view setNeedsLayout]; | ||||
|                 [self.navigationController.view layoutIfNeeded]; | ||||
|             }]; | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     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; | ||||
| } | ||||
|  | ||||
| - (void)viewWillDisappear:(BOOL)animated { | ||||
|     [super viewWillDisappear:animated]; | ||||
|      | ||||
|     if (self.manuallyDeactivateSearchOnDisappear && self.searchController.isActive) { | ||||
|         self.searchController.active = NO; | ||||
|     } | ||||
| } | ||||
|  | ||||
| - (void)didMoveToParentViewController:(UIViewController *)parent { | ||||
|     [super didMoveToParentViewController:parent]; | ||||
|     // Reset this since we are re-appearing under a new | ||||
|     // parent view controller and need to show it again | ||||
|     self.didInitiallyRevealSearchBar = NO; | ||||
| } | ||||
|  | ||||
|  | ||||
| #pragma mark - Toolbar, Public | ||||
|  | ||||
| - (void)setupToolbarItems { | ||||
|     if (!self.isViewLoaded) { | ||||
|         return; | ||||
|     } | ||||
|      | ||||
|     self.toolbarItems = @[ | ||||
|         self.leftmostToolbarItem, | ||||
|         UIBarButtonItem.flex_flexibleSpace, | ||||
|         self.middleLeftToolbarItem, | ||||
|         UIBarButtonItem.flex_flexibleSpace, | ||||
|         self.middleToolbarItem, | ||||
|         UIBarButtonItem.flex_flexibleSpace, | ||||
|         self.bookmarksToolbarItem, | ||||
|         UIBarButtonItem.flex_flexibleSpace, | ||||
|         self.openTabsToolbarItem, | ||||
|     ]; | ||||
|      | ||||
|     for (UIBarButtonItem *item in self.toolbarItems) { | ||||
|         [item _setWidth:60]; | ||||
|         // This does not work for anything but fixed spaces for some reason | ||||
|         // item.width = 60; | ||||
|     } | ||||
|      | ||||
|     // Disable tabs entirely when not presented by FLEXExplorerViewController | ||||
|     UIViewController *presenter = self.navigationController.presentingViewController; | ||||
|     if (![presenter isKindOfClass:[FLEXExplorerViewController class]]) { | ||||
|         self.openTabsToolbarItem.enabled = NO; | ||||
|     } | ||||
| } | ||||
|  | ||||
| - (void)addToolbarItems:(NSArray<UIBarButtonItem *> *)items { | ||||
|     if (self.showsShareToolbarItem) { | ||||
|         // Share button is in the middle, skip middle button | ||||
|         if (items.count > 0) { | ||||
|             self.middleLeftToolbarItem = items[0]; | ||||
|         } | ||||
|         if (items.count > 1) { | ||||
|             self.leftmostToolbarItem = items[1]; | ||||
|         } | ||||
|     } else { | ||||
|         // Add buttons right-to-left | ||||
|         if (items.count > 0) { | ||||
|             self.middleToolbarItem = items[0]; | ||||
|         } | ||||
|         if (items.count > 1) { | ||||
|             self.middleLeftToolbarItem = items[1]; | ||||
|         } | ||||
|         if (items.count > 2) { | ||||
|             self.leftmostToolbarItem = items[2]; | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     [self setupToolbarItems]; | ||||
| } | ||||
|  | ||||
| - (void)setShowsShareToolbarItem:(BOOL)showShare { | ||||
|     if (_showsShareToolbarItem != showShare) { | ||||
|         _showsShareToolbarItem = showShare; | ||||
|          | ||||
|         if (showShare) { | ||||
|             // Push out leftmost item | ||||
|             self.leftmostToolbarItem = self.middleLeftToolbarItem; | ||||
|             self.middleLeftToolbarItem = self.middleToolbarItem; | ||||
|              | ||||
|             // Use share for middle | ||||
|             self.middleToolbarItem = self.shareToolbarItem; | ||||
|         } else { | ||||
|             // Remove share, shift custom items rightward | ||||
|             self.middleToolbarItem = self.middleLeftToolbarItem; | ||||
|             self.middleLeftToolbarItem = self.leftmostToolbarItem; | ||||
|             self.leftmostToolbarItem = UIBarButtonItem.flex_fixedSpace; | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     [self setupToolbarItems]; | ||||
| } | ||||
|  | ||||
| - (void)shareButtonPressed:(UIBarButtonItem *)sender { | ||||
|  | ||||
| } | ||||
|  | ||||
|  | ||||
| #pragma mark - Private | ||||
|  | ||||
| - (void)debounce:(void(^)(void))block { | ||||
|     [self.debounceTimer invalidate]; | ||||
|      | ||||
|     self.debounceTimer = [NSTimer | ||||
|         scheduledTimerWithTimeInterval:self.searchBarDebounceInterval | ||||
|         target:block | ||||
|         selector:@selector(invoke) | ||||
|         userInfo:nil | ||||
|         repeats:NO | ||||
|     ]; | ||||
| } | ||||
|  | ||||
| - (void)layoutTableHeaderIfNeeded { | ||||
|     if (self.showsCarousel) { | ||||
|         self.carousel.frame = FLEXRectSetHeight( | ||||
|             self.carousel.frame, self.carousel.intrinsicContentSize.height | ||||
|         ); | ||||
|     } | ||||
|      | ||||
|     self.tableView.tableHeaderView = self.tableView.tableHeaderView; | ||||
| } | ||||
|  | ||||
| - (void)addCarousel:(FLEXScopeCarousel *)carousel { | ||||
|     if (@available(iOS 11.0, *)) { | ||||
|         self.tableView.tableHeaderView = carousel; | ||||
|     } else { | ||||
|         carousel.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleTopMargin; | ||||
|          | ||||
|         CGRect frame = self.tableHeaderViewContainer.frame; | ||||
|         CGRect subviewFrame = carousel.frame; | ||||
|         subviewFrame.origin.y = 0; | ||||
|          | ||||
|         // Put the carousel below the search bar if it's already there | ||||
|         if (self.showsSearchBar) { | ||||
|             carousel.frame = subviewFrame = FLEXRectSetY( | ||||
|                 subviewFrame, self.searchController.searchBar.frame.size.height | ||||
|             ); | ||||
|             frame.size.height += carousel.intrinsicContentSize.height; | ||||
|         } else { | ||||
|             frame.size.height = carousel.intrinsicContentSize.height; | ||||
|         } | ||||
|          | ||||
|         self.tableHeaderViewContainer.frame = frame; | ||||
|         [self.tableHeaderViewContainer addSubview:carousel]; | ||||
|     } | ||||
|      | ||||
|     [self layoutTableHeaderIfNeeded]; | ||||
| } | ||||
|  | ||||
| - (void)removeCarousel:(FLEXScopeCarousel *)carousel { | ||||
|     [carousel removeFromSuperview]; | ||||
|      | ||||
|     if (@available(iOS 11.0, *)) { | ||||
|         self.tableView.tableHeaderView = nil; | ||||
|     } else { | ||||
|         if (self.showsSearchBar) { | ||||
|             [self removeSearchController:self.searchController]; | ||||
|             [self addSearchController:self.searchController]; | ||||
|         } else { | ||||
|             self.tableView.tableHeaderView = nil; | ||||
|             _tableHeaderViewContainer = nil; | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| - (void)addSearchController:(UISearchController *)controller { | ||||
|     if (@available(iOS 11.0, *)) { | ||||
|         self.navigationItem.searchController = controller; | ||||
|     } else { | ||||
|         controller.searchBar.autoresizingMask |= UIViewAutoresizingFlexibleBottomMargin; | ||||
|         [self.tableHeaderViewContainer addSubview:controller.searchBar]; | ||||
|         CGRect subviewFrame = controller.searchBar.frame; | ||||
|         CGRect frame = self.tableHeaderViewContainer.frame; | ||||
|         frame.size.width = MAX(frame.size.width, subviewFrame.size.width); | ||||
|         frame.size.height = subviewFrame.size.height; | ||||
|          | ||||
|         // Move the carousel down if it's already there | ||||
|         if (self.showsCarousel) { | ||||
|             self.carousel.frame = FLEXRectSetY( | ||||
|                 self.carousel.frame, subviewFrame.size.height | ||||
|             ); | ||||
|             frame.size.height += self.carousel.frame.size.height; | ||||
|         } | ||||
|          | ||||
|         self.tableHeaderViewContainer.frame = frame; | ||||
|         [self layoutTableHeaderIfNeeded]; | ||||
|     } | ||||
| } | ||||
|  | ||||
| - (void)removeSearchController:(UISearchController *)controller { | ||||
|     if (@available(iOS 11.0, *)) { | ||||
|         self.navigationItem.searchController = nil; | ||||
|     } else { | ||||
|         [controller.searchBar removeFromSuperview]; | ||||
|          | ||||
|         if (self.showsCarousel) { | ||||
| //            self.carousel.frame = FLEXRectRemake(CGPointZero, self.carousel.frame.size); | ||||
|             [self removeCarousel:self.carousel]; | ||||
|             [self addCarousel:self.carousel]; | ||||
|         } else { | ||||
|             self.tableView.tableHeaderView = nil; | ||||
|             _tableHeaderViewContainer = nil; | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| - (UIView *)tableHeaderViewContainer { | ||||
|     if (!_tableHeaderViewContainer) { | ||||
|         _tableHeaderViewContainer = [UIView new]; | ||||
|         self.tableView.tableHeaderView = self.tableHeaderViewContainer; | ||||
|     } | ||||
|      | ||||
|     return _tableHeaderViewContainer; | ||||
| } | ||||
|  | ||||
| - (void)showBookmarks { | ||||
|     UINavigationController *nav = [[UINavigationController alloc] | ||||
|         initWithRootViewController:[FLEXBookmarksViewController new] | ||||
|     ]; | ||||
|     [self presentViewController:nav animated:YES completion:nil]; | ||||
| } | ||||
|  | ||||
| - (void)showTabSwitcher { | ||||
|     UINavigationController *nav = [[UINavigationController alloc] | ||||
|         initWithRootViewController:[FLEXTabsViewController new] | ||||
|     ]; | ||||
|     [self presentViewController:nav animated:YES completion:nil]; | ||||
| } | ||||
|  | ||||
|  | ||||
| #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) = ^{ | ||||
|         if (self.searchResultsUpdater) { | ||||
|             [self.searchResultsUpdater updateSearchResults:text]; | ||||
|         } else { | ||||
|             [self.searchDelegate updateSearchResults:text]; | ||||
|         } | ||||
|     }; | ||||
|      | ||||
|     // Only debounce if we want to, and if we have a non-empty string | ||||
|     // Empty string events are sent instantly | ||||
|     if (text.length && self.searchBarDebounceInterval > kFLEXDebounceInstant) { | ||||
|         [self debounce:updateSearchResults]; | ||||
|     } else { | ||||
|         updateSearchResults(); | ||||
|     } | ||||
| } | ||||
|  | ||||
|  | ||||
| #pragma mark UISearchControllerDelegate | ||||
|  | ||||
| - (void)willPresentSearchController:(UISearchController *)searchController { | ||||
|     // Manually show cancel button for < iOS 13 | ||||
|     if (!@available(iOS 13, *) && self.automaticallyShowsSearchBarCancelButton) { | ||||
|         [searchController.searchBar setShowsCancelButton:YES animated:YES]; | ||||
|     } | ||||
| } | ||||
|  | ||||
| - (void)willDismissSearchController:(UISearchController *)searchController { | ||||
|     // Manually hide cancel button for < iOS 13 | ||||
|     if (!@available(iOS 13, *) && self.automaticallyShowsSearchBarCancelButton) { | ||||
|         [searchController.searchBar setShowsCancelButton:NO animated:YES]; | ||||
|     } | ||||
| } | ||||
|  | ||||
|  | ||||
| #pragma mark UISearchBarDelegate | ||||
|  | ||||
| /// Not necessary in iOS 13; remove this when iOS 13 is the deployment target | ||||
| - (void)searchBar:(UISearchBar *)searchBar selectedScopeButtonIndexDidChange:(NSInteger)selectedScope { | ||||
|     [self updateSearchResultsForSearchController:self.searchController]; | ||||
| } | ||||
|  | ||||
|  | ||||
| #pragma mark Table View | ||||
|  | ||||
| /// Not having a title in the first section looks weird with a rounded-corner table view style | ||||
| - (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section { | ||||
|     if (@available(iOS 13, *)) { | ||||
|         if (self.style == UITableViewStyleInsetGrouped) { | ||||
|             return @" "; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     return nil; // For plain/gropued style | ||||
| } | ||||
|  | ||||
| @end | ||||
							
								
								
									
										28
									
								
								Tweaks/FLEX/Core/FLEXSingleRowSection.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								Tweaks/FLEX/Core/FLEXSingleRowSection.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,28 @@ | ||||
| // | ||||
| //  FLEXSingleRowSection.h | ||||
| //  FLEX | ||||
| // | ||||
| //  Created by Tanner Bennett on 9/25/19. | ||||
| //  Copyright © 2020 FLEX Team. All rights reserved. | ||||
| // | ||||
|  | ||||
| #import "FLEXTableViewSection.h" | ||||
|  | ||||
| /// A section providing a specific single row. | ||||
| /// | ||||
| /// You may optionally provide a view controller to push when the row | ||||
| /// is selected, or an action to perform when it is selected. | ||||
| /// Which one is used first is up to the table view data source. | ||||
| @interface FLEXSingleRowSection : FLEXTableViewSection | ||||
|  | ||||
| /// @param reuseIdentifier if nil, kFLEXDefaultCell is used. | ||||
| + (instancetype)title:(NSString *)sectionTitle | ||||
|                 reuse:(NSString *)reuseIdentifier | ||||
|                  cell:(void(^)(__kindof UITableViewCell *cell))cellConfiguration; | ||||
|  | ||||
| @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 | ||||
							
								
								
									
										87
									
								
								Tweaks/FLEX/Core/FLEXSingleRowSection.m
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										87
									
								
								Tweaks/FLEX/Core/FLEXSingleRowSection.m
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,87 @@ | ||||
| // | ||||
| //  FLEXSingleRowSection.m | ||||
| //  FLEX | ||||
| // | ||||
| //  Created by Tanner Bennett on 9/25/19. | ||||
| //  Copyright © 2020 FLEX Team. All rights reserved. | ||||
| // | ||||
|  | ||||
| #import "FLEXSingleRowSection.h" | ||||
| #import "FLEXTableView.h" | ||||
|  | ||||
| @interface FLEXSingleRowSection () | ||||
| @property (nonatomic, readonly) NSString *reuseIdentifier; | ||||
| @property (nonatomic, readonly) void (^cellConfiguration)(__kindof UITableViewCell *cell); | ||||
|  | ||||
| @property (nonatomic) NSString *lastTitle; | ||||
| @property (nonatomic) NSString *lastSubitle; | ||||
| @end | ||||
|  | ||||
| @implementation FLEXSingleRowSection | ||||
|  | ||||
| #pragma mark - Public | ||||
|  | ||||
| + (instancetype)title:(NSString *)title | ||||
|                 reuse:(NSString *)reuse | ||||
|                  cell:(void (^)(__kindof UITableViewCell *))config { | ||||
|     return [[self alloc] initWithTitle:title reuse:reuse cell:config]; | ||||
| } | ||||
|  | ||||
| - (id)initWithTitle:(NSString *)sectionTitle | ||||
|               reuse:(NSString *)reuseIdentifier | ||||
|                cell:(void (^)(__kindof UITableViewCell *))cellConfiguration { | ||||
|     self = [super init]; | ||||
|     if (self) { | ||||
|         _title = sectionTitle; | ||||
|         _reuseIdentifier = reuseIdentifier ?: kFLEXDefaultCell; | ||||
|         _cellConfiguration = cellConfiguration; | ||||
|     } | ||||
|  | ||||
|     return self; | ||||
| } | ||||
|  | ||||
| #pragma mark - Overrides | ||||
|  | ||||
| - (NSInteger)numberOfRows { | ||||
|     if (self.filterMatcher && self.filterText.length) { | ||||
|         return self.filterMatcher(self.filterText) ? 1 : 0; | ||||
|     } | ||||
|      | ||||
|     return 1; | ||||
| } | ||||
|  | ||||
| - (BOOL)canSelectRow:(NSInteger)row { | ||||
|     return self.pushOnSelection || self.selectionAction; | ||||
| } | ||||
|  | ||||
| - (void (^)(__kindof UIViewController *))didSelectRowAction:(NSInteger)row { | ||||
|     return self.selectionAction; | ||||
| } | ||||
|  | ||||
| - (UIViewController *)viewControllerToPushForRow:(NSInteger)row { | ||||
|     return self.pushOnSelection; | ||||
| } | ||||
|  | ||||
| - (NSString *)reuseIdentifierForRow:(NSInteger)row { | ||||
|     return self.reuseIdentifier; | ||||
| } | ||||
|  | ||||
| - (void)configureCell:(__kindof UITableViewCell *)cell forRow:(NSInteger)row { | ||||
|     cell.textLabel.text = nil; | ||||
|     cell.detailTextLabel.text = nil; | ||||
|     cell.accessoryType = UITableViewCellAccessoryNone; | ||||
|      | ||||
|     self.cellConfiguration(cell); | ||||
|     self.lastTitle = cell.textLabel.text; | ||||
|     self.lastSubitle = cell.detailTextLabel.text; | ||||
| } | ||||
|  | ||||
| - (NSString *)titleForRow:(NSInteger)row { | ||||
|     return self.lastTitle; | ||||
| } | ||||
|  | ||||
| - (NSString *)subtitleForRow:(NSInteger)row { | ||||
|     return self.lastSubitle; | ||||
| } | ||||
|  | ||||
| @end | ||||
							
								
								
									
										146
									
								
								Tweaks/FLEX/Core/FLEXTableViewSection.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										146
									
								
								Tweaks/FLEX/Core/FLEXTableViewSection.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,146 @@ | ||||
| // | ||||
| //  FLEXTableViewSection.h | ||||
| //  FLEX | ||||
| // | ||||
| //  Created by Tanner on 1/29/20. | ||||
| //  Copyright © 2020 FLEX Team. All rights reserved. | ||||
| // | ||||
|  | ||||
| #import <UIKit/UIKit.h> | ||||
| #import "NSArray+FLEX.h" | ||||
| @class FLEXTableView; | ||||
|  | ||||
| NS_ASSUME_NONNULL_BEGIN | ||||
|  | ||||
| #pragma mark FLEXTableViewSection | ||||
|  | ||||
| /// An abstract base class for table view sections. | ||||
| /// | ||||
| /// Many properties or methods here return nil or some logical equivalent by default. | ||||
| /// Even so, most of the methods with defaults are intended to be overriden by subclasses. | ||||
| /// Some methods are not implemented at all and MUST be implemented by a subclass. | ||||
| @interface FLEXTableViewSection : NSObject { | ||||
|     @protected | ||||
|     /// Unused by default, use if you want | ||||
|     NSString *_title; | ||||
|      | ||||
|     @private | ||||
|     __weak UITableView *_tableView; | ||||
|     NSInteger _sectionIndex; | ||||
| } | ||||
|  | ||||
| #pragma mark - Data | ||||
|  | ||||
| /// A title to be displayed for the custom section. | ||||
| /// Subclasses may override or use the \c _title ivar. | ||||
| @property (nonatomic, readonly, nullable, copy) NSString *title; | ||||
| /// The number of rows in this section. Subclasses must override. | ||||
| /// This should not change until \c filterText is changed or \c reloadData is called. | ||||
| @property (nonatomic, readonly) NSInteger numberOfRows; | ||||
| /// A map of reuse identifiers to \c UITableViewCell (sub)class objects. | ||||
| /// Subclasses \e may override this as necessary, but are not required to. | ||||
| /// See \c FLEXTableView.h for more information. | ||||
| /// @return nil by default. | ||||
| @property (nonatomic, readonly, nullable) NSDictionary<NSString *, Class> *cellRegistrationMapping; | ||||
|  | ||||
| /// The section should filter itself based on the contents of this property | ||||
| /// as it is set. If it is set to nil or an empty string, it should not filter. | ||||
| /// Subclasses should override or observe this property and react to changes. | ||||
| /// | ||||
| /// It is common practice to use two arrays for the underlying model: | ||||
| /// One to hold all rows, and one to hold unfiltered rows. When \c setFilterText: | ||||
| /// is called, call \c super to store the new value, and re-filter your model accordingly. | ||||
| @property (nonatomic, nullable) NSString *filterText; | ||||
|  | ||||
| /// Provides an avenue for the section to refresh data or change the number of rows. | ||||
| /// | ||||
| /// This is called before reloading the table view itself. If your section pulls data | ||||
| /// from an external data source, this is a good place to refresh that data entirely. | ||||
| /// If your section does not, then it might be simpler for you to just override | ||||
| /// \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 | ||||
| /// should take the user to a new screen or trigger an action. | ||||
| /// Subclasses \e may override this as necessary, but are not required to. | ||||
| /// @return \c NO by default | ||||
| - (BOOL)canSelectRow:(NSInteger)row; | ||||
|  | ||||
| /// An action "future" to be triggered when the row is selected, if the row | ||||
| /// supports being selected as indicated by \c canSelectRow:. Subclasses | ||||
| /// must implement this in accordance with how they implement \c canSelectRow: | ||||
| /// if they do not implement \c viewControllerToPushForRow: | ||||
| /// @return This returns \c nil if no view controller is provided by | ||||
| /// \c viewControllerToPushForRow: — otherwise it pushes that view controller | ||||
| /// onto \c host.navigationController | ||||
| - (nullable void(^)(__kindof UIViewController *host))didSelectRowAction:(NSInteger)row; | ||||
|  | ||||
| /// A view controller to display when the row is selected, if the row | ||||
| /// supports being selected as indicated by \c canSelectRow:. Subclasses | ||||
| /// must implement this in accordance with how they implement \c canSelectRow: | ||||
| /// if they do not implement \c didSelectRowAction: | ||||
| /// @return \c nil by default | ||||
| - (nullable UIViewController *)viewControllerToPushForRow:(NSInteger)row; | ||||
|  | ||||
| /// Called when the accessory view's detail button is pressed. | ||||
| /// @return \c nil by default. | ||||
| - (nullable void(^)(__kindof UIViewController *host))didPressInfoButtonAction:(NSInteger)row; | ||||
|  | ||||
| #pragma mark - Context Menus | ||||
|  | ||||
| /// By default, this is the title of the row. | ||||
| /// @return The title of the context menu, if any. | ||||
| - (nullable NSString *)menuTitleForRow:(NSInteger)row API_AVAILABLE(ios(13.0)); | ||||
| /// Protected, not intended for public use. \c menuTitleForRow: | ||||
| /// already includes the value returned from this method. | ||||
| ///  | ||||
| /// By default, this returns \c @"". Subclasses may override to | ||||
| /// provide a detailed description of the target of the context menu. | ||||
| - (NSString *)menuSubtitleForRow:(NSInteger)row API_AVAILABLE(ios(13.0)); | ||||
| /// The context menu items, if any. Subclasses may override. | ||||
| /// By default, only inludes items for \c copyMenuItemsForRow:. | ||||
| - (nullable NSArray<UIMenuElement *> *)menuItemsForRow:(NSInteger)row sender:(UIViewController *)sender API_AVAILABLE(ios(13.0)); | ||||
| /// Subclasses may override to return a list of copiable items. | ||||
| /// | ||||
| /// Every two elements in the list compose a key-value pair, where the key | ||||
| /// 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)); | ||||
|  | ||||
| #pragma mark - Cell Configuration | ||||
|  | ||||
| /// Provide a reuse identifier for the given row. Subclasses should override. | ||||
| /// | ||||
| /// Custom reuse identifiers should be specified in \c cellRegistrationMapping. | ||||
| /// You may return any of the identifiers in \c FLEXTableView.h | ||||
| /// without including them in the \c cellRegistrationMapping. | ||||
| /// @return \c kFLEXDefaultCell by default. | ||||
| - (NSString *)reuseIdentifierForRow:(NSInteger)row; | ||||
| /// Configure a cell for the given row. Subclasses must override. | ||||
| - (void)configureCell:(__kindof UITableViewCell *)cell forRow:(NSInteger)row; | ||||
|  | ||||
| #pragma mark - External Convenience | ||||
|  | ||||
| /// For use by whatever view controller uses your section. Not required. | ||||
| /// @return An optional title. | ||||
| - (nullable NSString *)titleForRow:(NSInteger)row; | ||||
| /// For use by whatever view controller uses your section. Not required. | ||||
| /// @return An optional subtitle. | ||||
| - (nullable NSString *)subtitleForRow:(NSInteger)row; | ||||
|  | ||||
| @end | ||||
|  | ||||
| NS_ASSUME_NONNULL_END | ||||
							
								
								
									
										137
									
								
								Tweaks/FLEX/Core/FLEXTableViewSection.m
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										137
									
								
								Tweaks/FLEX/Core/FLEXTableViewSection.m
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,137 @@ | ||||
| // | ||||
| //  FLEXTableViewSection.m | ||||
| //  FLEX | ||||
| // | ||||
| //  Created by Tanner on 1/29/20. | ||||
| //  Copyright © 2020 FLEX Team. All rights reserved. | ||||
| // | ||||
|  | ||||
| #import "FLEXTableViewSection.h" | ||||
| #import "FLEXTableView.h" | ||||
| #import "FLEXUtility.h" | ||||
| #import "UIMenu+FLEX.h" | ||||
|  | ||||
| #pragma clang diagnostic push | ||||
| #pragma clang diagnostic ignored "-Wincomplete-implementation" | ||||
|  | ||||
| @implementation FLEXTableViewSection | ||||
|  | ||||
| - (NSInteger)numberOfRows { | ||||
|     return 0; | ||||
| } | ||||
|  | ||||
| - (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; | ||||
| } | ||||
|  | ||||
| - (BOOL)canSelectRow:(NSInteger)row { return NO; } | ||||
|  | ||||
| - (void (^)(__kindof UIViewController *))didSelectRowAction:(NSInteger)row { | ||||
|     UIViewController *toPush = [self viewControllerToPushForRow:row]; | ||||
|     if (toPush) { | ||||
|         return ^(UIViewController *host) { | ||||
|             [host.navigationController pushViewController:toPush animated:YES]; | ||||
|         }; | ||||
|     } | ||||
|  | ||||
|     return nil; | ||||
| } | ||||
|  | ||||
| - (UIViewController *)viewControllerToPushForRow:(NSInteger)row { | ||||
|     return nil; | ||||
| } | ||||
|  | ||||
| - (void (^)(__kindof UIViewController *))didPressInfoButtonAction:(NSInteger)row { | ||||
|     return nil; | ||||
| } | ||||
|  | ||||
| - (NSString *)reuseIdentifierForRow:(NSInteger)row { | ||||
|     return kFLEXDefaultCell; | ||||
| } | ||||
|  | ||||
| - (NSString *)menuTitleForRow:(NSInteger)row { | ||||
|     NSString *title = [self titleForRow:row]; | ||||
|     NSString *subtitle = [self menuSubtitleForRow:row]; | ||||
|      | ||||
|     if (subtitle.length) { | ||||
|         return [NSString stringWithFormat:@"%@\n\n%@", title, subtitle]; | ||||
|     } | ||||
|      | ||||
|     return title; | ||||
| } | ||||
|  | ||||
| - (NSString *)menuSubtitleForRow:(NSInteger)row { | ||||
|     return @""; | ||||
| } | ||||
|  | ||||
| - (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"); | ||||
|      | ||||
|     if (copyItems.count) { | ||||
|         NSInteger numberOfActions = copyItems.count / 2; | ||||
|         BOOL collapseMenu = numberOfActions > 4; | ||||
|         UIImage *copyIcon = [UIImage systemImageNamed:@"doc.on.doc"]; | ||||
|          | ||||
|         NSMutableArray *actions = [NSMutableArray new]; | ||||
|          | ||||
|         for (NSInteger i = 0; i < copyItems.count; i += 2) { | ||||
|             NSString *key = copyItems[i], *value = copyItems[i+1]; | ||||
|             NSString *title = collapseMenu ? key : [@"Copy " stringByAppendingString:key]; | ||||
|              | ||||
|             UIAction *copy = [UIAction | ||||
|                 actionWithTitle:title | ||||
|                 image:copyIcon | ||||
|                 identifier:nil | ||||
|                 handler:^(__kindof UIAction *action) { | ||||
|                     UIPasteboard.generalPasteboard.string = value; | ||||
|                 } | ||||
|             ]; | ||||
|             if (!value.length) { | ||||
|                 copy.attributes = UIMenuElementAttributesDisabled; | ||||
|             } | ||||
|              | ||||
|             [actions addObject:copy]; | ||||
|         } | ||||
|          | ||||
|         UIMenu *copyMenu = [UIMenu | ||||
|             flex_inlineMenuWithTitle:@"Copy…"  | ||||
|             image:copyIcon | ||||
|             children:actions | ||||
|         ]; | ||||
|          | ||||
|         if (collapseMenu) { | ||||
|             return @[[copyMenu flex_collapsed]]; | ||||
|         } else { | ||||
|             return @[copyMenu]; | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     return @[]; | ||||
| } | ||||
|  | ||||
| - (NSArray<NSString *> *)copyMenuItemsForRow:(NSInteger)row { | ||||
|     return nil; | ||||
| } | ||||
|  | ||||
| - (NSString *)titleForRow:(NSInteger)row { return nil; } | ||||
| - (NSString *)subtitleForRow:(NSInteger)row { return nil; } | ||||
|  | ||||
| @end | ||||
|  | ||||
| #pragma clang diagnostic pop | ||||
							
								
								
									
										15
									
								
								Tweaks/FLEX/Core/Views/Carousel/FLEXCarouselCell.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								Tweaks/FLEX/Core/Views/Carousel/FLEXCarouselCell.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,15 @@ | ||||
| // | ||||
| //  FLEXCarouselCell.h | ||||
| //  FLEX | ||||
| // | ||||
| //  Created by Tanner Bennett on 7/17/19. | ||||
| //  Copyright © 2020 FLEX Team. All rights reserved. | ||||
| // | ||||
|  | ||||
| #import <UIKit/UIKit.h> | ||||
|  | ||||
| @interface FLEXCarouselCell : UICollectionViewCell | ||||
|  | ||||
| @property (nonatomic, copy) NSString *title; | ||||
|  | ||||
| @end | ||||
							
								
								
									
										93
									
								
								Tweaks/FLEX/Core/Views/Carousel/FLEXCarouselCell.m
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										93
									
								
								Tweaks/FLEX/Core/Views/Carousel/FLEXCarouselCell.m
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,93 @@ | ||||
| // | ||||
| //  FLEXCarouselCell.m | ||||
| //  FLEX | ||||
| // | ||||
| //  Created by Tanner Bennett on 7/17/19. | ||||
| //  Copyright © 2020 FLEX Team. All rights reserved. | ||||
| // | ||||
|  | ||||
| #import "FLEXCarouselCell.h" | ||||
| #import "FLEXColor.h" | ||||
| #import "UIView+FLEX_Layout.h" | ||||
|  | ||||
| @interface FLEXCarouselCell () | ||||
| @property (nonatomic, readonly) UILabel *titleLabel; | ||||
| @property (nonatomic, readonly) UIView *selectionIndicatorStripe; | ||||
| @property (nonatomic) BOOL constraintsInstalled; | ||||
| @end | ||||
|  | ||||
| @implementation FLEXCarouselCell | ||||
|  | ||||
| - (instancetype)initWithFrame:(CGRect)frame { | ||||
|     self = [super initWithFrame:frame]; | ||||
|     if (self) { | ||||
|         _titleLabel = [UILabel new]; | ||||
|         _selectionIndicatorStripe = [UIView new]; | ||||
|  | ||||
|         self.titleLabel.font = [UIFont preferredFontForTextStyle:UIFontTextStyleBody]; | ||||
|         self.selectionIndicatorStripe.backgroundColor = self.tintColor; | ||||
|         if (@available(iOS 10, *)) { | ||||
|             self.titleLabel.adjustsFontForContentSizeCategory = YES; | ||||
|         } | ||||
|  | ||||
|         [self.contentView addSubview:self.titleLabel]; | ||||
|         [self.contentView addSubview:self.selectionIndicatorStripe]; | ||||
|  | ||||
|         [self installConstraints]; | ||||
|  | ||||
|         [self updateAppearance]; | ||||
|     } | ||||
|  | ||||
|     return self; | ||||
| } | ||||
|  | ||||
| - (void)updateAppearance { | ||||
|     self.selectionIndicatorStripe.hidden = !self.selected; | ||||
|  | ||||
|     if (self.selected) { | ||||
|         self.titleLabel.textColor = self.tintColor; | ||||
|     } else { | ||||
|         self.titleLabel.textColor = FLEXColor.deemphasizedTextColor; | ||||
|     } | ||||
| } | ||||
|  | ||||
| #pragma mark Public | ||||
|  | ||||
| - (NSString *)title { | ||||
|     return self.titleLabel.text; | ||||
| } | ||||
|  | ||||
| - (void)setTitle:(NSString *)title { | ||||
|     self.titleLabel.text = title; | ||||
|     [self.titleLabel sizeToFit]; | ||||
|     [self setNeedsLayout]; | ||||
| } | ||||
|  | ||||
| #pragma mark Overrides | ||||
|  | ||||
| - (void)prepareForReuse { | ||||
|     [super prepareForReuse]; | ||||
|     [self updateAppearance]; | ||||
| } | ||||
|  | ||||
| - (void)installConstraints { | ||||
|     CGFloat stripeHeight = 2; | ||||
|  | ||||
|     self.titleLabel.translatesAutoresizingMaskIntoConstraints = NO; | ||||
|     self.selectionIndicatorStripe.translatesAutoresizingMaskIntoConstraints = NO; | ||||
|  | ||||
|     UIView *superview = self.contentView; | ||||
|     [self.titleLabel flex_pinEdgesToSuperviewWithInsets:UIEdgeInsetsMake(10, 15, 8 + stripeHeight, 15)]; | ||||
|  | ||||
|     [self.selectionIndicatorStripe.leadingAnchor constraintEqualToAnchor:superview.leadingAnchor].active = YES; | ||||
|     [self.selectionIndicatorStripe.bottomAnchor constraintEqualToAnchor:superview.bottomAnchor].active = YES; | ||||
|     [self.selectionIndicatorStripe.trailingAnchor constraintEqualToAnchor:superview.trailingAnchor].active = YES; | ||||
|     [self.selectionIndicatorStripe.heightAnchor constraintEqualToConstant:stripeHeight].active = YES; | ||||
| } | ||||
|  | ||||
| - (void)setSelected:(BOOL)selected { | ||||
|     super.selected = selected; | ||||
|     [self updateAppearance]; | ||||
| } | ||||
|  | ||||
| @end | ||||
							
								
								
									
										20
									
								
								Tweaks/FLEX/Core/Views/Carousel/FLEXScopeCarousel.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								Tweaks/FLEX/Core/Views/Carousel/FLEXScopeCarousel.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,20 @@ | ||||
| // | ||||
| //  FLEXScopeCarousel.h | ||||
| //  FLEX | ||||
| // | ||||
| //  Created by Tanner Bennett on 7/17/19. | ||||
| //  Copyright © 2020 FLEX Team. All rights reserved. | ||||
| // | ||||
|  | ||||
| #import <UIKit/UIKit.h> | ||||
|  | ||||
| /// Only use on iOS 10 and up. Requires iOS 10 APIs for calculating row sizes. | ||||
| @interface FLEXScopeCarousel : UIControl | ||||
|  | ||||
| @property (nonatomic, copy) NSArray<NSString *> *items; | ||||
| @property (nonatomic) NSInteger selectedIndex; | ||||
| @property (nonatomic) void(^selectedIndexChangedAction)(NSInteger idx); | ||||
|  | ||||
| - (void)registerBlockForDynamicTypeChanges:(void(^)(FLEXScopeCarousel *))handler; | ||||
|  | ||||
| @end | ||||
							
								
								
									
										204
									
								
								Tweaks/FLEX/Core/Views/Carousel/FLEXScopeCarousel.m
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										204
									
								
								Tweaks/FLEX/Core/Views/Carousel/FLEXScopeCarousel.m
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,204 @@ | ||||
| // | ||||
| //  FLEXScopeCarousel.m | ||||
| //  FLEX | ||||
| // | ||||
| //  Created by Tanner Bennett on 7/17/19. | ||||
| //  Copyright © 2020 FLEX Team. All rights reserved. | ||||
| // | ||||
|  | ||||
| #import "FLEXScopeCarousel.h" | ||||
| #import "FLEXCarouselCell.h" | ||||
| #import "FLEXColor.h" | ||||
| #import "FLEXMacros.h" | ||||
| #import "UIView+FLEX_Layout.h" | ||||
|  | ||||
| const CGFloat kCarouselItemSpacing = 0; | ||||
| NSString * const kCarouselCellReuseIdentifier = @"kCarouselCellReuseIdentifier"; | ||||
|  | ||||
| @interface FLEXScopeCarousel () <UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout> | ||||
| @property (nonatomic, readonly) UICollectionView *collectionView; | ||||
| @property (nonatomic, readonly) FLEXCarouselCell *sizingCell; | ||||
|  | ||||
| @property (nonatomic, readonly) id dynamicTypeObserver; | ||||
| @property (nonatomic, readonly) NSMutableArray *dynamicTypeHandlers; | ||||
|  | ||||
| @property (nonatomic) BOOL constraintsInstalled; | ||||
| @end | ||||
|  | ||||
| @implementation FLEXScopeCarousel | ||||
|  | ||||
| - (id)initWithFrame:(CGRect)frame { | ||||
|     self = [super initWithFrame:frame]; | ||||
|     if (self) { | ||||
|         self.backgroundColor = FLEXColor.primaryBackgroundColor; | ||||
|         self.autoresizingMask = UIViewAutoresizingFlexibleWidth; | ||||
|         self.translatesAutoresizingMaskIntoConstraints = YES; | ||||
|         _dynamicTypeHandlers = [NSMutableArray new]; | ||||
|          | ||||
|         CGSize itemSize = CGSizeZero; | ||||
|         if (@available(iOS 10.0, *)) { | ||||
|             itemSize = UICollectionViewFlowLayoutAutomaticSize; | ||||
|         } | ||||
|  | ||||
|         // Collection view layout | ||||
|         UICollectionViewFlowLayout *layout = ({ | ||||
|             UICollectionViewFlowLayout *layout = [UICollectionViewFlowLayout new]; | ||||
|             layout.scrollDirection = UICollectionViewScrollDirectionHorizontal; | ||||
|             layout.sectionInset = UIEdgeInsetsZero; | ||||
|             layout.minimumLineSpacing = kCarouselItemSpacing; | ||||
|             layout.itemSize = itemSize; | ||||
|             layout.estimatedItemSize = itemSize; | ||||
|             layout; | ||||
|         }); | ||||
|  | ||||
|         // Collection view | ||||
|         _collectionView = ({ | ||||
|             UICollectionView *cv = [[UICollectionView alloc] | ||||
|                 initWithFrame:CGRectZero | ||||
|                 collectionViewLayout:layout | ||||
|             ]; | ||||
|             cv.showsHorizontalScrollIndicator = NO; | ||||
|             cv.backgroundColor = UIColor.clearColor; | ||||
|             cv.delegate = self; | ||||
|             cv.dataSource = self; | ||||
|             [cv registerClass:[FLEXCarouselCell class] forCellWithReuseIdentifier:kCarouselCellReuseIdentifier]; | ||||
|  | ||||
|             [self addSubview:cv]; | ||||
|             cv; | ||||
|         }); | ||||
|  | ||||
|  | ||||
|         // Sizing cell | ||||
|         _sizingCell = [FLEXCarouselCell new]; | ||||
|         self.sizingCell.title = @"NSObject"; | ||||
|  | ||||
|         // Dynamic type | ||||
|         weakify(self); | ||||
|         _dynamicTypeObserver = [NSNotificationCenter.defaultCenter | ||||
|             addObserverForName:UIContentSizeCategoryDidChangeNotification | ||||
|             object:nil queue:nil usingBlock:^(NSNotification *note) { strongify(self) | ||||
|                 [self.collectionView setNeedsLayout]; | ||||
|                 [self setNeedsUpdateConstraints]; | ||||
|  | ||||
|                 // Notify observers | ||||
|                 for (void (^block)(FLEXScopeCarousel *) in self.dynamicTypeHandlers) { | ||||
|                     block(self); | ||||
|                 } | ||||
|             } | ||||
|         ]; | ||||
|     } | ||||
|  | ||||
|     return self; | ||||
| } | ||||
|  | ||||
| - (void)dealloc { | ||||
|     [NSNotificationCenter.defaultCenter removeObserver:self.dynamicTypeObserver]; | ||||
| } | ||||
|  | ||||
| #pragma mark - Overrides | ||||
|  | ||||
| - (void)drawRect:(CGRect)rect { | ||||
|     [super drawRect:rect]; | ||||
|  | ||||
|     CGFloat width = 1.f / UIScreen.mainScreen.scale; | ||||
|  | ||||
|     // Draw hairline | ||||
|     CGContextRef context = UIGraphicsGetCurrentContext(); | ||||
|     CGContextSetStrokeColorWithColor(context, FLEXColor.hairlineColor.CGColor); | ||||
|     CGContextSetLineWidth(context, width); | ||||
|     CGContextMoveToPoint(context, 0, rect.size.height - width); | ||||
|     CGContextAddLineToPoint(context, rect.size.width, rect.size.height - width); | ||||
|     CGContextStrokePath(context); | ||||
| } | ||||
|  | ||||
| + (BOOL)requiresConstraintBasedLayout { | ||||
|     return YES; | ||||
| } | ||||
|  | ||||
| - (void)updateConstraints { | ||||
|     if (!self.constraintsInstalled) { | ||||
|         self.collectionView.translatesAutoresizingMaskIntoConstraints = NO; | ||||
|         [self.collectionView flex_pinEdgesToSuperview]; | ||||
|          | ||||
|         self.constraintsInstalled = YES; | ||||
|     } | ||||
|      | ||||
|     [super updateConstraints]; | ||||
| } | ||||
|  | ||||
| - (CGSize)intrinsicContentSize { | ||||
|     return CGSizeMake( | ||||
|         UIViewNoIntrinsicMetric, | ||||
|         [self.sizingCell systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height | ||||
|     ); | ||||
| } | ||||
|  | ||||
| #pragma mark - Public | ||||
|  | ||||
| - (void)setItems:(NSArray<NSString *> *)items { | ||||
|     NSParameterAssert(items.count); | ||||
|  | ||||
|     _items = items.copy; | ||||
|  | ||||
|     // Refresh list, select first item initially | ||||
|     [self.collectionView reloadData]; | ||||
|     self.selectedIndex = 0; | ||||
| } | ||||
|  | ||||
| - (void)setSelectedIndex:(NSInteger)idx { | ||||
|     NSParameterAssert(idx < self.items.count); | ||||
|  | ||||
|     _selectedIndex = idx; | ||||
|     NSIndexPath *path = [NSIndexPath indexPathForItem:idx inSection:0]; | ||||
|     [self.collectionView selectItemAtIndexPath:path | ||||
|                                       animated:YES | ||||
|                                 scrollPosition:UICollectionViewScrollPositionCenteredHorizontally]; | ||||
|     [self collectionView:self.collectionView didSelectItemAtIndexPath:path]; | ||||
| } | ||||
|  | ||||
| - (void)registerBlockForDynamicTypeChanges:(void (^)(FLEXScopeCarousel *))handler { | ||||
|     [self.dynamicTypeHandlers addObject:handler]; | ||||
| } | ||||
|  | ||||
| #pragma mark - UICollectionView | ||||
|  | ||||
| - (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath { | ||||
| //    if (@available(iOS 10.0, *)) { | ||||
| //        return UICollectionViewFlowLayoutAutomaticSize; | ||||
| //    } | ||||
|      | ||||
|     self.sizingCell.title = self.items[indexPath.item]; | ||||
|     return [self.sizingCell systemLayoutSizeFittingSize:UILayoutFittingCompressedSize]; | ||||
| } | ||||
|  | ||||
| - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section { | ||||
|     return self.items.count; | ||||
| } | ||||
|  | ||||
| - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView | ||||
|                   cellForItemAtIndexPath:(NSIndexPath *)indexPath { | ||||
|     FLEXCarouselCell *cell = (id)[collectionView dequeueReusableCellWithReuseIdentifier:kCarouselCellReuseIdentifier | ||||
|                                                                            forIndexPath:indexPath]; | ||||
|     cell.title = self.items[indexPath.row]; | ||||
|     return cell; | ||||
| } | ||||
|  | ||||
| - (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath { | ||||
|     _selectedIndex = indexPath.item; // In case self.selectedIndex didn't trigger this call | ||||
|  | ||||
|     if (self.selectedIndexChangedAction) { | ||||
|         self.selectedIndexChangedAction(indexPath.row); | ||||
|     } | ||||
|  | ||||
|     // TODO: dynamically choose a scroll position. Very wide items should | ||||
|     // get "Left" while smaller items should not scroll at all, unless | ||||
|     // they are only partially on the screen, in which case they | ||||
|     // should get "HorizontallyCentered" to bring them onto the screen. | ||||
|     // For now, everything goes to the left, as this has a similar effect. | ||||
|     [collectionView scrollToItemAtIndexPath:indexPath | ||||
|                            atScrollPosition:UICollectionViewScrollPositionLeft | ||||
|                                    animated:YES]; | ||||
|     [self sendActionsForControlEvents:UIControlEventValueChanged]; | ||||
| } | ||||
|  | ||||
| @end | ||||
							
								
								
									
										17
									
								
								Tweaks/FLEX/Core/Views/Cells/FLEXCodeFontCell.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								Tweaks/FLEX/Core/Views/Cells/FLEXCodeFontCell.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,17 @@ | ||||
| // | ||||
| //  FLEXCodeFontCell.h | ||||
| //  FLEX | ||||
| // | ||||
| //  Created by Tanner on 12/27/19. | ||||
| //  Copyright © 2020 FLEX Team. All rights reserved. | ||||
| // | ||||
|  | ||||
| #import "FLEXMultilineTableViewCell.h" | ||||
|  | ||||
| NS_ASSUME_NONNULL_BEGIN | ||||
|  | ||||
| @interface FLEXCodeFontCell : FLEXMultilineDetailTableViewCell | ||||
|  | ||||
| @end | ||||
|  | ||||
| NS_ASSUME_NONNULL_END | ||||
							
								
								
									
										34
									
								
								Tweaks/FLEX/Core/Views/Cells/FLEXCodeFontCell.m
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								Tweaks/FLEX/Core/Views/Cells/FLEXCodeFontCell.m
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,34 @@ | ||||
| // | ||||
| //  FLEXCodeFontCell.m | ||||
| //  FLEX | ||||
| // | ||||
| //  Created by Tanner on 12/27/19. | ||||
| //  Copyright © 2020 FLEX Team. All rights reserved. | ||||
| // | ||||
|  | ||||
| #import "FLEXCodeFontCell.h" | ||||
| #import "UIFont+FLEX.h" | ||||
|  | ||||
| @implementation FLEXCodeFontCell | ||||
|  | ||||
| - (void)postInit { | ||||
|     [super postInit]; | ||||
|      | ||||
|     self.titleLabel.font = UIFont.flex_codeFont; | ||||
|     self.subtitleLabel.font = UIFont.flex_codeFont; | ||||
|  | ||||
|     self.titleLabel.adjustsFontSizeToFitWidth = YES; | ||||
|     self.titleLabel.minimumScaleFactor = 0.9; | ||||
|     self.subtitleLabel.adjustsFontSizeToFitWidth = YES; | ||||
|     self.subtitleLabel.minimumScaleFactor = 0.75; | ||||
|      | ||||
|     // Disable mutli-line pre iOS 11 | ||||
|     if (@available(iOS 11, *)) { | ||||
|         self.subtitleLabel.numberOfLines = 5; | ||||
|     } else { | ||||
|         self.titleLabel.numberOfLines = 1; | ||||
|         self.subtitleLabel.numberOfLines = 1; | ||||
|     } | ||||
| } | ||||
|  | ||||
| @end | ||||
							
								
								
									
										13
									
								
								Tweaks/FLEX/Core/Views/Cells/FLEXKeyValueTableViewCell.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								Tweaks/FLEX/Core/Views/Cells/FLEXKeyValueTableViewCell.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,13 @@ | ||||
| // | ||||
| //  FLEXKeyValueTableViewCell.h | ||||
| //  FLEX | ||||
| // | ||||
| //  Created by Tanner Bennett on 1/23/20. | ||||
| //  Copyright © 2020 FLEX Team. All rights reserved. | ||||
| // | ||||
|  | ||||
| #import "FLEXTableViewCell.h" | ||||
|  | ||||
| @interface FLEXKeyValueTableViewCell : FLEXTableViewCell | ||||
|  | ||||
| @end | ||||
							
								
								
									
										17
									
								
								Tweaks/FLEX/Core/Views/Cells/FLEXKeyValueTableViewCell.m
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								Tweaks/FLEX/Core/Views/Cells/FLEXKeyValueTableViewCell.m
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,17 @@ | ||||
| // | ||||
| //  FLEXKeyValueTableViewCell.m | ||||
| //  FLEX | ||||
| // | ||||
| //  Created by Tanner Bennett on 1/23/20. | ||||
| //  Copyright © 2020 FLEX Team. All rights reserved. | ||||
| // | ||||
|  | ||||
| #import "FLEXKeyValueTableViewCell.h" | ||||
|  | ||||
| @implementation FLEXKeyValueTableViewCell | ||||
|  | ||||
| - (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier { | ||||
|     return [super initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:reuseIdentifier]; | ||||
| } | ||||
|  | ||||
| @end | ||||
							
								
								
									
										24
									
								
								Tweaks/FLEX/Core/Views/Cells/FLEXMultilineTableViewCell.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								Tweaks/FLEX/Core/Views/Cells/FLEXMultilineTableViewCell.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,24 @@ | ||||
| // | ||||
| //  FLEXMultilineTableViewCell.h | ||||
| //  FLEX | ||||
| // | ||||
| //  Created by Ryan Olson on 2/13/15. | ||||
| //  Copyright (c) 2020 FLEX Team. All rights reserved. | ||||
| // | ||||
|  | ||||
| #import "FLEXTableViewCell.h" | ||||
|  | ||||
| /// A cell with both labels set to be multi-line capable. | ||||
| @interface FLEXMultilineTableViewCell : FLEXTableViewCell | ||||
|  | ||||
| + (CGFloat)preferredHeightWithAttributedText:(NSAttributedString *)attributedText | ||||
|                                     maxWidth:(CGFloat)contentViewWidth | ||||
|                                        style:(UITableViewStyle)style | ||||
|                               showsAccessory:(BOOL)showsAccessory; | ||||
|  | ||||
| @end | ||||
|  | ||||
| /// A \c FLEXMultilineTableViewCell initialized with \c UITableViewCellStyleSubtitle | ||||
| @interface FLEXMultilineDetailTableViewCell : FLEXMultilineTableViewCell | ||||
|  | ||||
| @end | ||||
							
								
								
									
										67
									
								
								Tweaks/FLEX/Core/Views/Cells/FLEXMultilineTableViewCell.m
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										67
									
								
								Tweaks/FLEX/Core/Views/Cells/FLEXMultilineTableViewCell.m
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,67 @@ | ||||
| // | ||||
| //  FLEXMultilineTableViewCell.m | ||||
| //  FLEX | ||||
| // | ||||
| //  Created by Ryan Olson on 2/13/15. | ||||
| //  Copyright (c) 2020 FLEX Team. All rights reserved. | ||||
| // | ||||
|  | ||||
| #import "FLEXMultilineTableViewCell.h" | ||||
| #import "UIView+FLEX_Layout.h" | ||||
| #import "FLEXUtility.h" | ||||
|  | ||||
| @interface FLEXMultilineTableViewCell () | ||||
| @property (nonatomic, readonly) UILabel *_titleLabel; | ||||
| @property (nonatomic, readonly) UILabel *_subtitleLabel; | ||||
| @property (nonatomic) BOOL constraintsUpdated; | ||||
| @end | ||||
|  | ||||
| @implementation FLEXMultilineTableViewCell | ||||
|  | ||||
| - (void)postInit { | ||||
|     [super postInit]; | ||||
|      | ||||
|     self.titleLabel.numberOfLines = 0; | ||||
|     self.subtitleLabel.numberOfLines = 0; | ||||
| } | ||||
|  | ||||
| + (UIEdgeInsets)labelInsets { | ||||
|     return UIEdgeInsetsMake(10.0, 16.0, 10.0, 8.0); | ||||
| } | ||||
|  | ||||
| + (CGFloat)preferredHeightWithAttributedText:(NSAttributedString *)attributedText | ||||
|                                     maxWidth:(CGFloat)contentViewWidth | ||||
|                                        style:(UITableViewStyle)style | ||||
|                               showsAccessory:(BOOL)showsAccessory { | ||||
|     CGFloat labelWidth = contentViewWidth; | ||||
|  | ||||
|     // Content view inset due to accessory view observed on iOS 8.1 iPhone 6. | ||||
|     if (showsAccessory) { | ||||
|         labelWidth -= 34.0; | ||||
|     } | ||||
|  | ||||
|     UIEdgeInsets labelInsets = [self labelInsets]; | ||||
|     labelWidth -= (labelInsets.left + labelInsets.right); | ||||
|  | ||||
|     CGSize constrainSize = CGSizeMake(labelWidth, CGFLOAT_MAX); | ||||
|     CGRect boundingBox = [attributedText | ||||
|         boundingRectWithSize:constrainSize | ||||
|         options:NSStringDrawingUsesLineFragmentOrigin | ||||
|         context:nil | ||||
|     ]; | ||||
|     CGFloat preferredLabelHeight = FLEXFloor(boundingBox.size.height); | ||||
|     CGFloat preferredCellHeight = preferredLabelHeight + labelInsets.top + labelInsets.bottom + 1.0; | ||||
|  | ||||
|     return preferredCellHeight; | ||||
| } | ||||
|  | ||||
| @end | ||||
|  | ||||
|  | ||||
| @implementation FLEXMultilineDetailTableViewCell | ||||
|  | ||||
| - (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier { | ||||
|     return [super initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:reuseIdentifier]; | ||||
| } | ||||
|  | ||||
| @end | ||||
							
								
								
									
										14
									
								
								Tweaks/FLEX/Core/Views/Cells/FLEXSubtitleTableViewCell.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								Tweaks/FLEX/Core/Views/Cells/FLEXSubtitleTableViewCell.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | ||||
| // | ||||
| //  FLEXSubtitleTableViewCell.h | ||||
| //  FLEX | ||||
| // | ||||
| //  Created by Tanner on 4/17/19. | ||||
| //  Copyright © 2020 FLEX Team. All rights reserved. | ||||
| // | ||||
|  | ||||
| #import "FLEXTableViewCell.h" | ||||
|  | ||||
| /// A cell initialized with \c UITableViewCellStyleSubtitle | ||||
| @interface FLEXSubtitleTableViewCell : FLEXTableViewCell | ||||
|  | ||||
| @end | ||||
							
								
								
									
										17
									
								
								Tweaks/FLEX/Core/Views/Cells/FLEXSubtitleTableViewCell.m
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								Tweaks/FLEX/Core/Views/Cells/FLEXSubtitleTableViewCell.m
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,17 @@ | ||||
| // | ||||
| //  FLEXSubtitleTableViewCell.m | ||||
| //  FLEX | ||||
| // | ||||
| //  Created by Tanner on 4/17/19. | ||||
| //  Copyright © 2020 FLEX Team. All rights reserved. | ||||
| // | ||||
|  | ||||
| #import "FLEXSubtitleTableViewCell.h" | ||||
|  | ||||
| @implementation FLEXSubtitleTableViewCell | ||||
|  | ||||
| - (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier { | ||||
|     return [super initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:reuseIdentifier]; | ||||
| } | ||||
|  | ||||
| @end | ||||
							
								
								
									
										23
									
								
								Tweaks/FLEX/Core/Views/Cells/FLEXTableViewCell.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								Tweaks/FLEX/Core/Views/Cells/FLEXTableViewCell.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,23 @@ | ||||
| // | ||||
| //  FLEXTableViewCell.h | ||||
| //  FLEX | ||||
| // | ||||
| //  Created by Tanner on 4/17/19. | ||||
| //  Copyright © 2020 FLEX Team. All rights reserved. | ||||
| // | ||||
|  | ||||
| #import <UIKit/UIKit.h> | ||||
|  | ||||
| @interface FLEXTableViewCell : UITableViewCell | ||||
|  | ||||
| /// Use this instead of .textLabel | ||||
| @property (nonatomic, readonly) UILabel *titleLabel; | ||||
| /// Use this instead of .detailTextLabel | ||||
| @property (nonatomic, readonly) UILabel *subtitleLabel; | ||||
|  | ||||
| /// Subclasses can override this instead of initializers to | ||||
| /// perform additional initialization without lots of boilerplate. | ||||
| /// Remember to call super! | ||||
| - (void)postInit; | ||||
|  | ||||
| @end | ||||
							
								
								
									
										57
									
								
								Tweaks/FLEX/Core/Views/Cells/FLEXTableViewCell.m
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								Tweaks/FLEX/Core/Views/Cells/FLEXTableViewCell.m
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,57 @@ | ||||
| // | ||||
| //  FLEXTableViewCell.m | ||||
| //  FLEX | ||||
| // | ||||
| //  Created by Tanner on 4/17/19. | ||||
| //  Copyright © 2020 FLEX Team. All rights reserved. | ||||
| // | ||||
|  | ||||
| #import "FLEXTableViewCell.h" | ||||
| #import "FLEXUtility.h" | ||||
| #import "FLEXColor.h" | ||||
| #import "FLEXTableView.h" | ||||
|  | ||||
| @interface UITableView (Internal) | ||||
| // Exists at least since iOS 5 | ||||
| - (BOOL)_canPerformAction:(SEL)action forCell:(UITableViewCell *)cell sender:(id)sender; | ||||
| - (void)_performAction:(SEL)action forCell:(UITableViewCell *)cell sender:(id)sender; | ||||
| @end | ||||
|  | ||||
| @interface UITableViewCell (Internal) | ||||
| // Exists at least since iOS 5 | ||||
| @property (nonatomic, readonly) FLEXTableView *_tableView; | ||||
| @end | ||||
|  | ||||
| @implementation FLEXTableViewCell | ||||
|  | ||||
| - (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier { | ||||
|     self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]; | ||||
|     if (self) { | ||||
|         [self postInit]; | ||||
|     } | ||||
|  | ||||
|     return self; | ||||
| } | ||||
|  | ||||
| - (void)postInit { | ||||
|     UIFont *cellFont = UIFont.flex_defaultTableCellFont; | ||||
|     self.titleLabel.font = cellFont; | ||||
|     self.subtitleLabel.font = cellFont; | ||||
|     self.subtitleLabel.textColor = FLEXColor.deemphasizedTextColor; | ||||
|      | ||||
|     self.titleLabel.lineBreakMode = NSLineBreakByTruncatingMiddle; | ||||
|     self.subtitleLabel.lineBreakMode = NSLineBreakByTruncatingMiddle; | ||||
|      | ||||
|     self.titleLabel.numberOfLines = 1; | ||||
|     self.subtitleLabel.numberOfLines = 1; | ||||
| } | ||||
|  | ||||
| - (UILabel *)titleLabel { | ||||
|     return self.textLabel; | ||||
| } | ||||
|  | ||||
| - (UILabel *)subtitleLabel { | ||||
|     return self.detailTextLabel; | ||||
| } | ||||
|  | ||||
| @end | ||||
							
								
								
									
										48
									
								
								Tweaks/FLEX/Core/Views/FLEXTableView.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								Tweaks/FLEX/Core/Views/FLEXTableView.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,48 @@ | ||||
| // | ||||
| //  FLEXTableView.h | ||||
| //  FLEX | ||||
| // | ||||
| //  Created by Tanner on 4/17/19. | ||||
| //  Copyright © 2020 FLEX Team. All rights reserved. | ||||
| // | ||||
|  | ||||
| #import <UIKit/UIKit.h> | ||||
|  | ||||
| NS_ASSUME_NONNULL_BEGIN | ||||
|  | ||||
| #pragma mark Reuse identifiers | ||||
|  | ||||
| typedef NSString * FLEXTableViewCellReuseIdentifier; | ||||
|  | ||||
| /// A regular \c FLEXTableViewCell initialized with \c UITableViewCellStyleDefault | ||||
| extern FLEXTableViewCellReuseIdentifier const kFLEXDefaultCell; | ||||
| /// A \c FLEXSubtitleTableViewCell initialized with \c UITableViewCellStyleSubtitle | ||||
| extern FLEXTableViewCellReuseIdentifier const kFLEXDetailCell; | ||||
| /// A \c FLEXMultilineTableViewCell initialized with \c UITableViewCellStyleDefault | ||||
| extern FLEXTableViewCellReuseIdentifier const kFLEXMultilineCell; | ||||
| /// A \c FLEXMultilineTableViewCell initialized with \c UITableViewCellStyleSubtitle | ||||
| extern FLEXTableViewCellReuseIdentifier const kFLEXMultilineDetailCell; | ||||
| /// A \c FLEXTableViewCell initialized with \c UITableViewCellStyleValue1 | ||||
| extern FLEXTableViewCellReuseIdentifier const kFLEXKeyValueCell; | ||||
| /// A \c FLEXSubtitleTableViewCell which uses monospaced fonts for both labels | ||||
| extern FLEXTableViewCellReuseIdentifier const kFLEXCodeFontCell; | ||||
|  | ||||
| #pragma mark - FLEXTableView | ||||
| @interface FLEXTableView : UITableView | ||||
|  | ||||
| + (instancetype)flexDefaultTableView; | ||||
| + (instancetype)groupedTableView; | ||||
| + (instancetype)plainTableView; | ||||
| + (instancetype)style:(UITableViewStyle)style; | ||||
|  | ||||
| /// You do not need to register classes for any of the default reuse identifiers above | ||||
| /// (annotated as \c FLEXTableViewCellReuseIdentifier types) unless you wish to provide | ||||
| /// a custom cell for any of those reuse identifiers. By default, \c FLEXTableViewCell, | ||||
| /// \c FLEXSubtitleTableViewCell, and \c FLEXMultilineTableViewCell are used, respectively. | ||||
| /// | ||||
| /// @param registrationMapping A map of reuse identifiers to \c UITableViewCell (sub)class objects. | ||||
| - (void)registerCells:(NSDictionary<NSString *, Class> *)registrationMapping; | ||||
|  | ||||
| @end | ||||
|  | ||||
| NS_ASSUME_NONNULL_END | ||||
							
								
								
									
										83
									
								
								Tweaks/FLEX/Core/Views/FLEXTableView.m
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										83
									
								
								Tweaks/FLEX/Core/Views/FLEXTableView.m
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,83 @@ | ||||
| // | ||||
| //  FLEXTableView.m | ||||
| //  FLEX | ||||
| // | ||||
| //  Created by Tanner on 4/17/19. | ||||
| //  Copyright © 2020 FLEX Team. All rights reserved. | ||||
| // | ||||
|  | ||||
| #import "FLEXTableView.h" | ||||
| #import "FLEXUtility.h" | ||||
| #import "FLEXSubtitleTableViewCell.h" | ||||
| #import "FLEXMultilineTableViewCell.h" | ||||
| #import "FLEXKeyValueTableViewCell.h" | ||||
| #import "FLEXCodeFontCell.h" | ||||
|  | ||||
| FLEXTableViewCellReuseIdentifier const kFLEXDefaultCell = @"kFLEXDefaultCell"; | ||||
| FLEXTableViewCellReuseIdentifier const kFLEXDetailCell = @"kFLEXDetailCell"; | ||||
| FLEXTableViewCellReuseIdentifier const kFLEXMultilineCell = @"kFLEXMultilineCell"; | ||||
| FLEXTableViewCellReuseIdentifier const kFLEXMultilineDetailCell = @"kFLEXMultilineDetailCell"; | ||||
| FLEXTableViewCellReuseIdentifier const kFLEXKeyValueCell = @"kFLEXKeyValueCell"; | ||||
| FLEXTableViewCellReuseIdentifier const kFLEXCodeFontCell = @"kFLEXCodeFontCell"; | ||||
|  | ||||
| #pragma mark Private | ||||
|  | ||||
| @interface UITableView (Private) | ||||
| - (CGFloat)_heightForHeaderInSection:(NSInteger)section; | ||||
| - (NSString *)_titleForHeaderInSection:(NSInteger)section; | ||||
| @end | ||||
|  | ||||
| @implementation FLEXTableView | ||||
|  | ||||
| + (instancetype)flexDefaultTableView { | ||||
|     if (@available(iOS 13.0, *)) { | ||||
|         return [[self alloc] initWithFrame:CGRectZero style:UITableViewStyleInsetGrouped]; | ||||
|     } else { | ||||
|         return [[self alloc] initWithFrame:CGRectZero style:UITableViewStyleGrouped]; | ||||
|     } | ||||
| } | ||||
|  | ||||
| #pragma mark - Initialization | ||||
|  | ||||
| + (id)groupedTableView { | ||||
|     if (@available(iOS 13.0, *)) { | ||||
|         return [[self alloc] initWithFrame:CGRectZero style:UITableViewStyleInsetGrouped]; | ||||
|     } else { | ||||
|         return [[self alloc] initWithFrame:CGRectZero style:UITableViewStyleGrouped]; | ||||
|     } | ||||
| } | ||||
|  | ||||
| + (id)plainTableView { | ||||
|     return [[self alloc] initWithFrame:CGRectZero style:UITableViewStylePlain]; | ||||
| } | ||||
|  | ||||
| + (id)style:(UITableViewStyle)style { | ||||
|     return [[self alloc] initWithFrame:CGRectZero style:style]; | ||||
| } | ||||
|  | ||||
| - (id)initWithFrame:(CGRect)frame style:(UITableViewStyle)style { | ||||
|     self = [super initWithFrame:frame style:style]; | ||||
|     if (self) { | ||||
|         [self registerCells:@{ | ||||
|             kFLEXDefaultCell : [FLEXTableViewCell class], | ||||
|             kFLEXDetailCell : [FLEXSubtitleTableViewCell class], | ||||
|             kFLEXMultilineCell : [FLEXMultilineTableViewCell class], | ||||
|             kFLEXMultilineDetailCell : [FLEXMultilineDetailTableViewCell class], | ||||
|             kFLEXKeyValueCell : [FLEXKeyValueTableViewCell class], | ||||
|             kFLEXCodeFontCell : [FLEXCodeFontCell class], | ||||
|         }]; | ||||
|     } | ||||
|  | ||||
|     return self; | ||||
| } | ||||
|  | ||||
|  | ||||
| #pragma mark - Public | ||||
|  | ||||
| - (void)registerCells:(NSDictionary<NSString*, Class> *)registrationMapping { | ||||
|     [registrationMapping enumerateKeysAndObjectsUsingBlock:^(NSString *identifier, Class cellClass, BOOL *stop) { | ||||
|         [self registerClass:cellClass forCellReuseIdentifier:identifier]; | ||||
|     }]; | ||||
| } | ||||
|  | ||||
| @end | ||||
		Reference in New Issue
	
	Block a user
	 Balackburn
					Balackburn