mirror of
				https://github.com/SoPat712/YTLitePlus.git
				synced 2025-10-31 04:44:14 -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 | ||||
		Reference in New Issue
	
	Block a user
	 Balackburn
					Balackburn