Bladeren bron

Add FLEXTableViewController

Tanner Bennett 7 jaren geleden
bovenliggende
commit
642b1810c5
3 gewijzigde bestanden met toevoegingen van 262 en 0 verwijderingen
  1. 78 0
      Classes/Core/FLEXTableViewController.h
  2. 169 0
      Classes/Core/FLEXTableViewController.m
  3. 15 0
      FLEX.xcodeproj/project.pbxproj

+ 78 - 0
Classes/Core/FLEXTableViewController.h

@@ -0,0 +1,78 @@
+//
+//  FLEXTableViewController.h
+//  FLEX
+//
+//  Created by Tanner on 7/5/19.
+//  Copyright © 2019 Flipboard. All rights reserved.
+//
+
+#import <UIKit/UIKit.h>
+
+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;
+
+@interface FLEXTableViewController : UITableViewController <UISearchResultsUpdating, UISearchControllerDelegate>
+
+/// A grouped table view. Inset on iOS 13.
+/// 
+/// Simply calls into initWithStyle:
+- (id)init;
+
+/// Defaults to NO.
+/// 
+/// Setting this to YES will initialize searchController.
+@property (nonatomic) BOOL showsSearchBar;
+
+/// nil unless showsSearchBar is set to YES.
+/// 
+/// self is used as the default search results updater and delegate.
+/// Make sure your subclass conforms to UISearchControllerDelegate.
+/// 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.
+@property (nonatomic) BOOL automaticallyShowsSearchBarCancelButton;
+
+/// self.searchController.searchBar.selectedScopeButtonIndex
+@property (nonatomic, readonly) NSInteger selectedScope;
+/// self.searchController.searchBar.text
+@property (nonatomic, readonly) NSString *searchText;
+
+/// Subclasses should override to handle search query update events.
+/// 
+/// 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;
+
+/// Convenient for doing some async processor-intensive searching
+/// in the background before updating the UI back on the main queue.
+- (void)onBackgroundQueue:(NSArray *(^)())backgroundBlock thenOnMainQueue:(void(^)(NSArray *))mainBlock;
+
+@end

+ 169 - 0
Classes/Core/FLEXTableViewController.m

@@ -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

+ 15 - 0
FLEX.xcodeproj/project.pbxproj

@@ -174,6 +174,8 @@
 		C34EE30821CB23CC00BD3A7C /* FLEXOSLogController.h in Headers */ = {isa = PBXBuildFile; fileRef = C34EE30621CB23CC00BD3A7C /* FLEXOSLogController.h */; };
 		C37A0C93218BAC9600848CA7 /* FLEXObjcInternal.h in Headers */ = {isa = PBXBuildFile; fileRef = C37A0C91218BAC9600848CA7 /* FLEXObjcInternal.h */; };
 		C37A0C94218BAC9600848CA7 /* FLEXObjcInternal.mm in Sources */ = {isa = PBXBuildFile; fileRef = C37A0C92218BAC9600848CA7 /* FLEXObjcInternal.mm */; };
+		C38DF0EA22CFE4370077B4AD /* FLEXTableViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = C38DF0E822CFE4370077B4AD /* FLEXTableViewController.h */; };
+		C38DF0EB22CFE4370077B4AD /* FLEXTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C38DF0E922CFE4370077B4AD /* FLEXTableViewController.m */; };
 		C395D6D921789BD800BEAD4D /* FLEXColorExplorerViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = C395D6D721789BD800BEAD4D /* FLEXColorExplorerViewController.h */; };
 		C395D6DA21789BD800BEAD4D /* FLEXColorExplorerViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C395D6D821789BD800BEAD4D /* FLEXColorExplorerViewController.m */; };
 		C3DA55FE21A76406005DDA60 /* FLEXMutableFieldEditorViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = C3DA55FC21A76406005DDA60 /* FLEXMutableFieldEditorViewController.h */; };
@@ -375,6 +377,8 @@
 		C34EE30A21CB249E00BD3A7C /* ActivityStreamAPI.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ActivityStreamAPI.h; sourceTree = "<group>"; };
 		C37A0C91218BAC9600848CA7 /* FLEXObjcInternal.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FLEXObjcInternal.h; sourceTree = "<group>"; };
 		C37A0C92218BAC9600848CA7 /* FLEXObjcInternal.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = FLEXObjcInternal.mm; sourceTree = "<group>"; };
+		C38DF0E822CFE4370077B4AD /* FLEXTableViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FLEXTableViewController.h; sourceTree = "<group>"; };
+		C38DF0E922CFE4370077B4AD /* FLEXTableViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FLEXTableViewController.m; sourceTree = "<group>"; };
 		C395D6D721789BD800BEAD4D /* FLEXColorExplorerViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FLEXColorExplorerViewController.h; sourceTree = "<group>"; };
 		C395D6D821789BD800BEAD4D /* FLEXColorExplorerViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FLEXColorExplorerViewController.m; sourceTree = "<group>"; };
 		C3DA55FC21A76406005DDA60 /* FLEXMutableFieldEditorViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FLEXMutableFieldEditorViewController.h; sourceTree = "<group>"; };
@@ -719,6 +723,15 @@
 			path = ExplorerInterface;
 			sourceTree = "<group>";
 		};
+		C38DF0E722CFE4140077B4AD /* Core */ = {
+			isa = PBXGroup;
+			children = (
+				C38DF0E822CFE4370077B4AD /* FLEXTableViewController.h */,
+				C38DF0E922CFE4370077B4AD /* FLEXTableViewController.m */,
+			);
+			path = Core;
+			sourceTree = "<group>";
+		};
 		C3F31D3A2267D883003C991A /* Views */ = {
 			isa = PBXGroup;
 			children = (
@@ -799,6 +812,7 @@
 				3A4C94D11B5B21410088C3F2 /* FLEXLayerExplorerViewController.h in Headers */,
 				779B1ED01C0C4D7C001F5E49 /* FLEXMultiColumnTableView.h in Headers */,
 				3A4C94D31B5B21410088C3F2 /* FLEXObjectExplorerFactory.h in Headers */,
+				C38DF0EA22CFE4370077B4AD /* FLEXTableViewController.h in Headers */,
 				94A515271C4CA2080063292F /* FLEXToolbarItem.h in Headers */,
 				3A4C94E31B5B21410088C3F2 /* FLEXRuntimeUtility.h in Headers */,
 				3A4C95341B5B21410088C3F2 /* FLEXSystemLogTableViewController.h in Headers */,
@@ -996,6 +1010,7 @@
 				779B1ED11C0C4D7C001F5E49 /* FLEXMultiColumnTableView.m in Sources */,
 				C3F31D412267D883003C991A /* FLEXMultilineTableViewCell.m in Sources */,
 				3A4C94EE1B5B21410088C3F2 /* FLEXArgumentInputColorView.m in Sources */,
+				C38DF0EB22CFE4370077B4AD /* FLEXTableViewController.m in Sources */,
 				3A4C94F01B5B21410088C3F2 /* FLEXArgumentInputDateView.m in Sources */,
 				3A4C94CA1B5B21410088C3F2 /* FLEXDefaultsExplorerViewController.m in Sources */,
 				3A4C94D01B5B21410088C3F2 /* FLEXImageExplorerViewController.m in Sources */,