mirror of
				https://github.com/SoPat712/YTLitePlus.git
				synced 2025-10-31 04:44:14 -04:00 
			
		
		
		
	
		
			
				
	
	
		
			619 lines
		
	
	
		
			20 KiB
		
	
	
	
		
			Objective-C
		
	
	
	
	
	
			
		
		
	
	
			619 lines
		
	
	
		
			20 KiB
		
	
	
	
		
			Objective-C
		
	
	
	
	
	
| //
 | |
| //  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
 | 
