|
|
@@ -0,0 +1,169 @@
|
|
|
+//
|
|
|
+// FLEXTableViewController.m
|
|
|
+// FLEX
|
|
|
+//
|
|
|
+// Created by Tanner on 7/5/19.
|
|
|
+// Copyright © 2019 Flipboard. All rights reserved.
|
|
|
+//
|
|
|
+
|
|
|
+#import "FLEXTableViewController.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;
|
|
|
+@end
|
|
|
+
|
|
|
+@implementation FLEXTableViewController
|
|
|
+
|
|
|
+#pragma mark - Public
|
|
|
+
|
|
|
+- (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) {
|
|
|
+ self.searchBarDebounceInterval = kFLEXDebounceFast;
|
|
|
+ }
|
|
|
+
|
|
|
+ return self;
|
|
|
+}
|
|
|
+
|
|
|
+- (void)setShowsSearchBar:(BOOL)showsSearchBar {
|
|
|
+ if (_showsSearchBar == showsSearchBar) return;
|
|
|
+ _showsSearchBar = 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;
|
|
|
+
|
|
|
+ if (@available(iOS 11.0, *)) {
|
|
|
+ self.navigationItem.searchController = self.searchController;
|
|
|
+ } else {
|
|
|
+ self.tableView.tableHeaderView = self.searchController.searchBar;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+- (NSInteger)selectedScope {
|
|
|
+ return self.searchController.searchBar.selectedScopeButtonIndex;
|
|
|
+}
|
|
|
+
|
|
|
+- (NSString *)searchText {
|
|
|
+ return self.searchController.searchBar.text;
|
|
|
+}
|
|
|
+
|
|
|
+- (void)setAutomaticallyShowsSearchBarCancelButton:(BOOL)autoShowCancel {
|
|
|
+ if (@available(iOS 13, *)) {
|
|
|
+ self.searchController.automaticallyShowsCancelButton = autoShowCancel;
|
|
|
+ } else {
|
|
|
+ _automaticallyShowsSearchBarCancelButton = autoShowCancel;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+- (void)updateSearchResults:(NSString *)newText { }
|
|
|
+
|
|
|
+- (void)onBackgroundQueue:(NSArray *(^)())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);
|
|
|
+ });
|
|
|
+ });
|
|
|
+}
|
|
|
+
|
|
|
+#pragma mark - View Controller Lifecycle
|
|
|
+
|
|
|
+- (void)viewDidLoad {
|
|
|
+ [super viewDidLoad];
|
|
|
+
|
|
|
+ self.tableView.keyboardDismissMode = UIScrollViewKeyboardDismissModeOnDrag;
|
|
|
+}
|
|
|
+
|
|
|
+- (void)viewWillAppear:(BOOL)animated {
|
|
|
+ [super viewWillAppear:animated];
|
|
|
+
|
|
|
+ // Make the search bar re-appear instead of hiding
|
|
|
+ if (@available(iOS 11.0, *)) {
|
|
|
+ self.navigationItem.hidesSearchBarWhenScrolling = NO;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+- (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, *)) {
|
|
|
+ self.navigationItem.hidesSearchBarWhenScrolling = !self.pinSearchBar;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+#pragma mark - Private
|
|
|
+
|
|
|
+- (void)debounce:(void(^)())block {
|
|
|
+ [self.debounceTimer invalidate];
|
|
|
+
|
|
|
+ self.debounceTimer = [NSTimer
|
|
|
+ scheduledTimerWithTimeInterval:self.searchBarDebounceInterval
|
|
|
+ target:block
|
|
|
+ selector:@selector(invoke)
|
|
|
+ userInfo:nil
|
|
|
+ repeats:NO
|
|
|
+ ];
|
|
|
+}
|
|
|
+
|
|
|
+#pragma mark - Search Bar
|
|
|
+
|
|
|
+#pragma mark UISearchResultsUpdating
|
|
|
+
|
|
|
+- (void)updateSearchResultsForSearchController:(UISearchController *)searchController
|
|
|
+{
|
|
|
+ [self.debounceTimer invalidate];
|
|
|
+ NSString *text = searchController.searchBar.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:^{
|
|
|
+ [self updateSearchResults:text];
|
|
|
+ }];
|
|
|
+ } else {
|
|
|
+ [self updateSearchResults:text];
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+#pragma mark UISearchControllerDelegate
|
|
|
+
|
|
|
+- (void)willPresentSearchController:(UISearchController *)searchController {
|
|
|
+ if (self.automaticallyShowsSearchBarCancelButton) {
|
|
|
+ [searchController.searchBar setShowsCancelButton:YES animated:YES];
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+- (void)willDismissSearchController:(UISearchController *)searchController {
|
|
|
+ if (self.automaticallyShowsSearchBarCancelButton) {
|
|
|
+ [searchController.searchBar setShowsCancelButton:NO animated:YES];
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+@end
|