Просмотр исходного кода

Refactor FLEXGlobals*

- Make FLEXGlobalsSection inherit from FLEXTableViewSection
- Adopt changes in FLEXGlobalsTableViewController
Tanner Bennett лет назад: 6
Родитель
Сommit
7703757dab

+ 20 - 13
Classes/GlobalStateExplorers/Globals/FLEXGlobalsEntry.h

@@ -7,9 +7,10 @@
 //
 
 #import <UIKit/UIKit.h>
-#import "FLEXGlobalsSection.h"
 @class FLEXGlobalsTableViewController;
 
+NS_ASSUME_NONNULL_BEGIN
+
 typedef NS_ENUM(NSUInteger, FLEXGlobalsRow) {
     FLEXGlobalsRowProcessInfo,
     FLEXGlobalsRowNetworkHistory,
@@ -33,12 +34,12 @@ typedef NS_ENUM(NSUInteger, FLEXGlobalsRow) {
     FLEXGlobalsRowCount
 };
 
-typedef NSString *(^FLEXGlobalsEntryNameFuture)(void);
+typedef NSString * _Nonnull (^FLEXGlobalsEntryNameFuture)(void);
 /// Simply return a view controller to be pushed on the navigation stack
-typedef UIViewController *(^FLEXGlobalsTableViewControllerViewControllerFuture)(void);
+typedef UIViewController * _Nullable (^FLEXGlobalsTableViewControllerViewControllerFuture)(void);
 /// Do something like present an alert, then use the host
 /// view controller to present or push another view controller.
-typedef void (^FLEXGlobalsTableViewControllerRowAction)(FLEXGlobalsTableViewController *host);
+typedef void (^FLEXGlobalsTableViewControllerRowAction)(FLEXGlobalsTableViewController * _Nonnull host);
 
 /// For view controllers to conform to to indicate they support being used
 /// in the globals table view controller. These methods help create concrete entries.
@@ -61,20 +62,24 @@ typedef void (^FLEXGlobalsTableViewControllerRowAction)(FLEXGlobalsTableViewCont
 // globalsEntryRowAction: takes precedence if both are implemented.
 @optional
 
-+ (UIViewController *)globalsEntryViewController:(FLEXGlobalsRow)row;
-+ (FLEXGlobalsTableViewControllerRowAction)globalsEntryRowAction:(FLEXGlobalsRow)row;
++ (nullable UIViewController *)globalsEntryViewController:(FLEXGlobalsRow)row;
++ (nullable FLEXGlobalsTableViewControllerRowAction)globalsEntryRowAction:(FLEXGlobalsRow)row;
 
 @end
 
-@interface FLEXGlobalsEntry : NSObject <FLEXPatternMatching>
+@interface FLEXGlobalsEntry : NSObject
 
-@property (nonatomic, readonly) FLEXGlobalsEntryNameFuture entryNameFuture;
-@property (nonatomic, readonly) FLEXGlobalsTableViewControllerViewControllerFuture viewControllerFuture;
-@property (nonatomic, readonly) FLEXGlobalsTableViewControllerRowAction rowAction;
+@property (nonatomic, readonly, nonnull)  FLEXGlobalsEntryNameFuture entryNameFuture;
+@property (nonatomic, readonly, nullable) FLEXGlobalsTableViewControllerViewControllerFuture viewControllerFuture;
+@property (nonatomic, readonly, nullable) FLEXGlobalsTableViewControllerRowAction rowAction;
 
 + (instancetype)entryWithEntry:(Class<FLEXGlobalsEntry>)entry row:(FLEXGlobalsRow)row;
-+ (instancetype)entryWithNameFuture:(FLEXGlobalsEntryNameFuture)nameFuture viewControllerFuture:(FLEXGlobalsTableViewControllerViewControllerFuture)viewControllerFuture;
-+ (instancetype)entryWithNameFuture:(FLEXGlobalsEntryNameFuture)nameFuture action:(FLEXGlobalsTableViewControllerRowAction)rowSelectedAction;
+
++ (instancetype)entryWithNameFuture:(FLEXGlobalsEntryNameFuture)nameFuture
+               viewControllerFuture:(FLEXGlobalsTableViewControllerViewControllerFuture)viewControllerFuture;
+
++ (instancetype)entryWithNameFuture:(FLEXGlobalsEntryNameFuture)nameFuture
+                             action:(FLEXGlobalsTableViewControllerRowAction)rowSelectedAction;
 
 @end
 
@@ -83,6 +88,8 @@ typedef void (^FLEXGlobalsTableViewControllerRowAction)(FLEXGlobalsTableViewCont
 
 /// @return The result of passing self to +[FLEXGlobalsEntry entryWithEntry:]
 /// if the class conforms to FLEXGlobalsEntry, else, nil.
-+ (FLEXGlobalsEntry *)flex_concreteGlobalsEntry:(FLEXGlobalsRow)row;
++ (nullable FLEXGlobalsEntry *)flex_concreteGlobalsEntry:(FLEXGlobalsRow)row;
 
 @end
+
+NS_ASSUME_NONNULL_END

+ 0 - 7
Classes/GlobalStateExplorers/Globals/FLEXGlobalsEntry.m

@@ -60,13 +60,6 @@
     return entry;
 }
 
-#pragma mark FLEXPatternMatching
-
-- (BOOL)matches:(NSString *)query
-{
-    return [self.entryNameFuture() localizedCaseInsensitiveContainsString:query];
-}
-
 @end
 
 #pragma mark - flex_concreteGlobalsEntry

+ 4 - 23
Classes/GlobalStateExplorers/Globals/FLEXGlobalsSection.h

@@ -6,34 +6,15 @@
 //  Copyright © 2019 Flipboard. All rights reserved.
 //
 
-#import <Foundation/Foundation.h>
+#import "FLEXTableViewSection.h"
+#import "FLEXGlobalsEntry.h"
 
 NS_ASSUME_NONNULL_BEGIN
 
-/// A protocol for arbitrary case-insensitive pattern matching
-@protocol FLEXPatternMatching <NSObject>
-/// @return YES if the receiver matches the query, case-insensitive
-- (BOOL)matches:(NSString *)query;
-@end
-
-@interface FLEXGlobalsSection<__covariant ObjectType> : NSObject
-
-+ (instancetype)section:(NSInteger)section title:(NSString *)title rows:(NSArray<ObjectType<FLEXPatternMatching>> *)rows;
-
-@property (nonatomic, readonly) NSInteger section;
-@property (nonatomic, readonly) NSString *title;
-@property (nonatomic, readonly) NSArray<ObjectType<FLEXPatternMatching>> *rows;
+@interface FLEXGlobalsSection : FLEXTableViewSection
 
-@property (nonatomic, readonly) NSInteger count;
-
-/// @return A new section containing only rows that match the string,
-/// or nil if the section was empty and no rows matched the string.
-- (nullable instancetype)newSectionWithRowsMatchingQuery:(NSString *)query;
-
-@end
++ (instancetype)title:(NSString *)title rows:(NSArray<FLEXGlobalsEntry *> *)rows;
 
-@interface FLEXGlobalsSection<__covariant ObjectType> (Subscripting)
-- (ObjectType)objectAtIndexedSubscript:(NSUInteger)idx;
 @end
 
 NS_ASSUME_NONNULL_END

+ 50 - 16
Classes/GlobalStateExplorers/Globals/FLEXGlobalsSection.m

@@ -7,39 +7,73 @@
 //
 
 #import "FLEXGlobalsSection.h"
+#import "NSArray+Functional.h"
 
+@interface FLEXGlobalsSection ()
+/// Filtered rows
+@property (nonatomic) NSArray<FLEXGlobalsEntry *> *rows;
+/// Unfiltered rows
+@property (nonatomic) NSArray<FLEXGlobalsEntry *> *allRows;
+@end
 @implementation FLEXGlobalsSection
 
-+ (instancetype)section:(NSInteger)section title:(NSString *)title rows:(NSArray *)rows {
+#pragma mark - Initialization
+
++ (instancetype)title:(NSString *)title rows:(NSArray<FLEXGlobalsEntry *> *)rows {
     FLEXGlobalsSection *s = [self new];
-    s->_section = section;
     s->_title = title;
-    s->_rows = rows.copy;
+    s.allRows = rows;
 
     return s;
 }
 
-- (instancetype)newSectionWithRowsMatchingQuery:(NSString *)query {
-    // Find rows containing the search string
-    NSPredicate *containsString = [NSPredicate predicateWithBlock:^BOOL(id<FLEXPatternMatching> obj, NSDictionary *bindings) {
-        return [obj matches:query];
-    }];
-    NSArray *filteredRows = [self.rows filteredArrayUsingPredicate:containsString];
+- (void)setAllRows:(NSArray<FLEXGlobalsEntry *> *)allRows {
+    _allRows = allRows.copy;
+    [self reloadData];
+}
+
+#pragma mark - Overrides
+
+- (NSInteger)numberOfRows {
+    return self.rows.count;
+}
+
+- (void)setFilterText:(NSString *)filterText {
+    super.filterText = filterText;
+    [self reloadData];
+}
+
+- (void)reloadData {
+    NSString *filterText = self.filterText;
     
-    // Only return new section if not empty
-    if (filteredRows.count) {
-        return [[self class] section:self.section title:self.title rows:filteredRows];
+    if (filterText.length) {
+        self.rows = [self.allRows flex_filtered:^BOOL(FLEXGlobalsEntry *entry, NSUInteger idx) {
+            return [entry.entryNameFuture() localizedCaseInsensitiveContainsString:filterText];
+        }];
+    } else {
+        self.rows = self.allRows;
     }
-    
-    return nil;
 }
 
-- (NSInteger)count {
-    return self.rows.count;
+- (BOOL)canSelectRow:(NSInteger)row {
+    return YES;
+}
+
+- (void (^)(__kindof UIViewController *))didSelectRowAction:(NSInteger)row {
+    return (id)self.rows[row].rowAction;
+}
+
+- (UIViewController *)viewControllerToPushForRow:(NSInteger)row {
+    return self.rows[row].viewControllerFuture ? self.rows[row].viewControllerFuture() : nil;
+}
+
+- (void)configureCell:(__kindof UITableViewCell *)cell forRow:(NSInteger)row {
+    cell.textLabel.text = self.rows[row].entryNameFuture();
 }
 
 @end
 
+
 @implementation FLEXGlobalsSection (Subscripting)
 
 - (id)objectAtIndexedSubscript:(NSUInteger)idx {

+ 44 - 68
Classes/GlobalStateExplorers/Globals/FLEXGlobalsTableViewController.m

@@ -26,10 +26,10 @@
 static __weak UIWindow *s_applicationWindow = nil;
 
 @interface FLEXGlobalsTableViewController ()
-
-@property (nonatomic, readonly) NSArray<FLEXGlobalsSection<FLEXGlobalsEntry *> *> *sections;
-@property (nonatomic, copy) NSArray<FLEXGlobalsSection<FLEXGlobalsEntry *> *> *filteredSections;
-
+/// Only displayed sections of the table view; empty sections are purged from this array.
+@property (nonatomic, copy) NSArray<FLEXGlobalsSection *> *sections;
+/// Every section in the table view, regardless of whether or not a section is empty.
+@property (nonatomic, readonly) NSArray<FLEXGlobalsSection *> *allSections;
 @end
 
 @implementation FLEXGlobalsTableViewController
@@ -98,12 +98,12 @@ static __weak UIWindow *s_applicationWindow = nil;
     }
 }
 
-+ (NSArray<FLEXGlobalsSection<FLEXGlobalsEntry *> *> *)defaultGlobalSections
++ (NSArray<FLEXGlobalsSection *> *)defaultGlobalSections
 {
-    static NSArray<FLEXGlobalsSection<FLEXGlobalsEntry *> *> *sections = nil;
+    static NSArray<FLEXGlobalsSection *> *sections = nil;
     static dispatch_once_t onceToken;
     dispatch_once(&onceToken, ^{
-        NSArray *rows = @[
+        NSArray *rowsBySection = @[
             @[
                 [self globalsEntryForRow:FLEXGlobalsRowNetworkHistory],
                 [self globalsEntryForRow:FLEXGlobalsRowSystemLog],
@@ -131,13 +131,10 @@ static __weak UIWindow *s_applicationWindow = nil;
             ]
         ];
         
-        NSMutableArray *tmp = [NSMutableArray array];
-        for (NSInteger i = 0; i < FLEXGlobalsSectionCount - 1; i++) { // Skip custom
+        sections = [NSArray flex_forEachUpTo:rowsBySection.count map:^FLEXGlobalsSection *(NSUInteger i) {
             NSString *title = [self globalsTitleForSection:i];
-            [tmp addObject:[FLEXGlobalsSection section:i title:title rows:rows[i]]];
-        }
-        
-        sections = tmp.copy;
+            return [FLEXGlobalsSection title:title rows:rowsBySection[i]];
+        }];
     });
     
     return sections;
@@ -161,17 +158,17 @@ static __weak UIWindow *s_applicationWindow = nil;
     self.searchBarDebounceInterval = kFLEXDebounceInstant;
 
     // Table view data
-    _sections = [[self class] defaultGlobalSections];
+    _allSections = [[self class] defaultGlobalSections];
     if ([FLEXManager sharedManager].userGlobalEntries.count) {
         // Make custom section
         NSString *title = [[self class] globalsTitleForSection:FLEXGlobalsSectionCustom];
         FLEXGlobalsSection *custom = [FLEXGlobalsSection
-            section:FLEXGlobalsSectionCustom
             title:title
             rows:[FLEXManager sharedManager].userGlobalEntries
         ];
-        _sections = [_sections arrayByAddingObject:custom];
+        _allSections = [_allSections arrayByAddingObject:custom];
     }
+    self.sections = self.allSections;
 
     // Done button
     self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc]
@@ -184,73 +181,44 @@ static __weak UIWindow *s_applicationWindow = nil;
 #pragma mark - Search Bar
 
 - (void)updateSearchResults:(NSString *)newText {
-    if (!newText.length) {
-        self.filteredSections = nil;
-        [self.tableView reloadData];
-        return;
+    // Sections will adjust data based on this property
+    for (FLEXTableViewSection *section in self.allSections) {
+        section.filterText = newText;
     }
 
-    // Sections are a map of index to rows, since empty sections are omitted
-    NSMutableArray *filteredSections = [NSMutableArray array];
+    // Recalculate empty sections
+    self.sections = [self nonemptySections];
 
-    [self.sections enumerateObjectsUsingBlock:^(FLEXGlobalsSection<FLEXGlobalsEntry *> *section, NSUInteger idx, BOOL *stop) {
-        section = [section newSectionWithRowsMatchingQuery:newText];
-        if (section) {
-            [filteredSections addObject:section];
-        }
-    }];
-
-    self.filteredSections = filteredSections.copy;
-    [self.tableView reloadData];
+    // Refresh table view
+    if (self.isViewLoaded) {
+        [self.tableView reloadData];
+    }
 }
 
-#pragma mark - Misc
+#pragma mark - Private
 
 - (void)donePressed:(id)sender
 {
     [self.delegate globalsViewControllerDidFinish:self];
 }
 
-#pragma mark - Table Data Helpers
-
-- (FLEXGlobalsEntry *)globalEntryAtIndexPath:(NSIndexPath *)indexPath
-{
-    if (self.filteredSections) {
-        return self.filteredSections[indexPath.section][indexPath.row];
-    } else {
-        return self.sections[indexPath.section][indexPath.row];
-    }
-}
-
-- (NSString *)titleForSection:(NSInteger)section
+- (NSArray<FLEXGlobalsSection *> *)nonemptySections
 {
-    if (self.filteredSections) {
-        return self.filteredSections[section].title;
-    } else {
-        return self.sections[section].title;
-    }
-}
-
-- (NSString *)titleForRowAtIndexPath:(NSIndexPath *)indexPath
-{
-    FLEXGlobalsEntry *entry = [self globalEntryAtIndexPath:indexPath];
-    return entry.entryNameFuture();
+    return [self.allSections flex_filtered:^BOOL(FLEXTableViewSection *section, NSUInteger idx) {
+        return section.numberOfRows > 0;
+    }];
 }
 
 #pragma mark - Table View Data Source
 
 - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
 {
-    return self.filteredSections ? self.filteredSections.count : self.sections.count;
+    return self.sections.count;
 }
 
 - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
 {
-    if (self.filteredSections) {
-        return self.filteredSections[section].count;
-    } else {
-        return self.sections[section].count;
-    }
+    return self.sections[section].numberOfRows;
 }
 
 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
@@ -262,26 +230,34 @@ static __weak UIWindow *s_applicationWindow = nil;
         cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
         cell.textLabel.font = [UIFont systemFontOfSize:14.0];
     }
-
-    cell.textLabel.text = [self titleForRowAtIndexPath:indexPath];
+    
+    [self.sections[indexPath.section] configureCell:cell forRow:indexPath.row];
     
     return cell;
 }
 
 - (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
 {
-    return [self titleForSection:section];
+    return self.sections[section].title;
 }
 
 #pragma mark - Table View Delegate
 
 - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
 {
-    FLEXGlobalsEntry *entry = [self globalEntryAtIndexPath:indexPath];
-    if (entry.viewControllerFuture) {
-        [self.navigationController pushViewController:entry.viewControllerFuture() animated:YES];
+    FLEXTableViewSection *section = self.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 {
-        entry.rowAction(self);
+        [NSException raise:NSInternalInconsistencyException
+                    format:@"Row is selectable but has no action or view controller"];
     }
 }