diff --git a/FBTweak.xcodeproj/project.pbxproj b/FBTweak.xcodeproj/project.pbxproj index 8b9837c7..de99403b 100644 --- a/FBTweak.xcodeproj/project.pbxproj +++ b/FBTweak.xcodeproj/project.pbxproj @@ -27,6 +27,7 @@ 18EFE528189F19B300DA6A5D /* FBTweakCategory.m in Sources */ = {isa = PBXBuildFile; fileRef = 18EFE526189F19B300DA6A5D /* FBTweakCategory.m */; }; 18EFE52D189F250700DA6A5D /* FBTweakInlineTestsMRR.m in Sources */ = {isa = PBXBuildFile; fileRef = 18EFE52C189F250700DA6A5D /* FBTweakInlineTestsMRR.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; 29E9F17B18E35C9C001EAF7D /* MessageUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 29E9F17A18E35C9C001EAF7D /* MessageUI.framework */; }; + 3A95D4611E2C2125003FEC4A /* _FBTweakSearchUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = 3A95D4601E2C2125003FEC4A /* _FBTweakSearchUtil.m */; }; 4F930D6C1C4C73F8007DC0E1 /* FBTweakEnabled.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 18EFE535189F38D500DA6A5D /* FBTweakEnabled.h */; }; 4F930D6D1C4C7406007DC0E1 /* FBTweakInlineInternal.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 18EFE4CF189EC70300DA6A5D /* FBTweakInlineInternal.h */; }; 4F930D6E1C4C740F007DC0E1 /* FBTweakInline.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 18EFE4A8189EBAAD00DA6A5D /* FBTweakInline.h */; }; @@ -123,6 +124,8 @@ 18EFE52C189F250700DA6A5D /* FBTweakInlineTestsMRR.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FBTweakInlineTestsMRR.m; sourceTree = ""; }; 18EFE535189F38D500DA6A5D /* FBTweakEnabled.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FBTweakEnabled.h; sourceTree = ""; }; 29E9F17A18E35C9C001EAF7D /* MessageUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MessageUI.framework; path = System/Library/Frameworks/MessageUI.framework; sourceTree = SDKROOT; }; + 3A95D45F1E2C2125003FEC4A /* _FBTweakSearchUtil.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = _FBTweakSearchUtil.h; sourceTree = ""; }; + 3A95D4601E2C2125003FEC4A /* _FBTweakSearchUtil.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = _FBTweakSearchUtil.m; sourceTree = ""; }; 5BACB31C1A12813C00C4F79D /* _FBTweakArrayViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = _FBTweakArrayViewController.h; sourceTree = ""; }; 5BACB31D1A12813C00C4F79D /* _FBTweakArrayViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = _FBTweakArrayViewController.m; sourceTree = ""; }; 5BE25A501A0AA9D500C97C3E /* _FBTweakDictionaryViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = _FBTweakDictionaryViewController.h; sourceTree = ""; }; @@ -294,6 +297,8 @@ 5E1708C41905B89800402135 /* _FBSliderView.m */, 5E1708DB190B147000402135 /* _FBKeyboardManager.h */, 5E1708DC190B147000402135 /* _FBKeyboardManager.m */, + 3A95D45F1E2C2125003FEC4A /* _FBTweakSearchUtil.h */, + 3A95D4601E2C2125003FEC4A /* _FBTweakSearchUtil.m */, ); name = UI; sourceTree = ""; @@ -407,6 +412,7 @@ 18EFE4BD189EBC4B00DA6A5D /* FBTweakCollection.m in Sources */, 18EFE4C2189EBEAD00DA6A5D /* FBTweakStore.m in Sources */, 5E66FDD91B80ECDA007464F3 /* _FBTweakColorViewControllerHSBDataSource.m in Sources */, + 3A95D4611E2C2125003FEC4A /* _FBTweakSearchUtil.m in Sources */, 18EFE4AB189EBAAD00DA6A5D /* FBTweakInline.m in Sources */, 18EFE4B8189EBC3100DA6A5D /* FBTweak.m in Sources */, 18EFE4DE189EF75800DA6A5D /* _FBTweakTableViewCell.m in Sources */, diff --git a/FBTweak/_FBTweakCategoryViewController.m b/FBTweak/_FBTweakCategoryViewController.m index da7808d8..2752087c 100644 --- a/FBTweak/_FBTweakCategoryViewController.m +++ b/FBTweak/_FBTweakCategoryViewController.m @@ -9,10 +9,14 @@ #import "FBTweakStore.h" #import "FBTweakCategory.h" +#import "FBTweakCollection.h" +#import "FBTweak.h" +#import "_FBTweakSearchUtil.h" #import "_FBTweakCategoryViewController.h" +#import "_FBTweakTableViewCell.h" #import -@interface _FBTweakCategoryViewController () +@interface _FBTweakCategoryViewController () @end #if (__IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE8_0) && (!defined(__has_feature) || !__has_feature(attribute_availability_app_extension)) @@ -23,8 +27,11 @@ @interface _FBTweakCategoryViewController () @implementation _FBTweakCategoryViewController { UITableView *_tableView; UIToolbar *_toolbar; + UISearchBar *_searchBar; + UISearchDisplayController *_searchController; NSArray *_sortedCategories; + NSArray *_filteredCollections; } - (instancetype)initWithStore:(FBTweakStore *)store @@ -58,6 +65,15 @@ - (void)viewDidLoad _tableView.dataSource = self; _tableView.autoresizingMask = (UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight); [self.view insertSubview:_tableView belowSubview:_toolbar]; + + _searchBar = [[UISearchBar alloc] initWithFrame:CGRectMake(0, 0, CGRectGetWidth(self.view.bounds), 44)]; + _searchBar.delegate = self; + _tableView.tableHeaderView = _searchBar; + + _searchController = [[UISearchDisplayController alloc] initWithSearchBar:_searchBar contentsController:self]; + _searchController.delegate = self; + _searchController.searchResultsDelegate = self; + _searchController.searchResultsDataSource = self; UIEdgeInsets contentInset = _tableView.contentInset; UIEdgeInsets scrollIndictatorInsets = _tableView.scrollIndicatorInsets; @@ -149,39 +165,91 @@ - (void)viewWillAppear:(BOOL)animated [_tableView deselectRowAtIndexPath:_tableView.indexPathForSelectedRow animated:animated]; } +- (void)_filterTweaksForQuery:(NSString*)query +{ + _filteredCollections = [_FBTweakSearchUtil filteredCollectionsInCategories:_sortedCategories forQuery:query]; +} + +#pragma mark UITableViewDataSource + - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { - return 1; + if (tableView == self.searchDisplayController.searchResultsTableView) { + return _filteredCollections.count; + } else { + return 1; + } } - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { - return _sortedCategories.count; + if (tableView == self.searchDisplayController.searchResultsTableView) { + FBTweakCollection *collection = _filteredCollections[section]; + return collection.tweaks.count; + } else { + return _sortedCategories.count; + } } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { - static NSString *_FBTweakCategoryViewControllerCellIdentifier = @"_FBTweakCategoryViewControllerCellIdentifier"; - UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:_FBTweakCategoryViewControllerCellIdentifier]; - if (cell == nil) { - cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:_FBTweakCategoryViewControllerCellIdentifier]; + if (tableView == self.searchDisplayController.searchResultsTableView) { + static NSString *_FBTweakCollectionViewControllerCellIdentifier = @"_FBTweakCollectionViewControllerCellIdentifier"; + _FBTweakTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:_FBTweakCollectionViewControllerCellIdentifier]; + if (cell == nil) { + cell = [[_FBTweakTableViewCell alloc] initWithReuseIdentifier:_FBTweakCollectionViewControllerCellIdentifier]; + } + + FBTweakCollection *collection = _filteredCollections[indexPath.section]; + FBTweak *tweak = collection.tweaks[indexPath.row]; + cell.tweak = tweak; + cell.searchQuery = _searchBar.text; + + return cell; + } else { + static NSString *_FBTweakCategoryViewControllerCellIdentifier = @"_FBTweakCategoryViewControllerCellIdentifier"; + UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:_FBTweakCategoryViewControllerCellIdentifier]; + if (cell == nil) { + cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:_FBTweakCategoryViewControllerCellIdentifier]; + } + + cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator; + + FBTweakCategory *category =_sortedCategories[indexPath.row]; + cell.textLabel.text = category.name; + + return cell; } - - cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator; +} - FBTweakCategory *category = _sortedCategories[indexPath.row]; - cell.textLabel.text = category.name; - - return cell; +- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section +{ + if (tableView == self.searchDisplayController.searchResultsTableView) { + FBTweakCollection *collection = _filteredCollections[section]; + return collection.name; + } else { + return nil; + } } +#pragma mark UITableViewDelegate + - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { - FBTweakCategory *category = _sortedCategories[indexPath.row]; - [_delegate tweakCategoryViewController:self selectedCategory:category]; + if (tableView == self.searchDisplayController.searchResultsTableView) { + FBTweakCollection *collection = _filteredCollections[indexPath.section]; + FBTweak *tweak = collection.tweaks[indexPath.row]; + [_FBTweakSearchUtil handleTweakSelection:tweak inTableView:tableView atIndexPath:indexPath navigationController:self.navigationController]; + } else { + FBTweakCategory *category =_sortedCategories[indexPath.row]; + [_delegate tweakCategoryViewController:self selectedCategory:category]; + } } #if (__IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE8_0) && (!defined(__has_feature) || !__has_feature(attribute_availability_app_extension)) + +#pragma mark UIAlertViewDelegate + - (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex { if (buttonIndex != alertView.cancelButtonIndex) { @@ -190,9 +258,26 @@ - (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)butto } #endif +#pragma mark MFMailComposeViewControllerDelegate + - (void)mailComposeController:(MFMailComposeViewController *)controller didFinishWithResult:(MFMailComposeResult)result error:(NSError *)error { [self dismissViewControllerAnimated:YES completion:nil]; } +#pragma mark UISearchDisplayDelegate + +- (BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchString:(nullable NSString *)searchString +{ + [self _filterTweaksForQuery:searchString]; + return YES; +} + +#pragma mark UISearchBarDelegate + +- (BOOL)searchBarShouldEndEditing:(UISearchBar *)searchBar +{ + return YES; +} + @end diff --git a/FBTweak/_FBTweakCollectionViewController.m b/FBTweak/_FBTweakCollectionViewController.m index 0a584c45..b0202e1a 100644 --- a/FBTweak/_FBTweakCollectionViewController.m +++ b/FBTweak/_FBTweakCollectionViewController.m @@ -10,19 +10,21 @@ #import "FBTweakCollection.h" #import "FBTweakCategory.h" #import "FBTweak.h" +#import "_FBTweakSearchUtil.h" #import "_FBTweakCollectionViewController.h" #import "_FBTweakTableViewCell.h" -#import "_FBTweakColorViewController.h" -#import "_FBTweakDictionaryViewController.h" -#import "_FBTweakArrayViewController.h" #import "_FBKeyboardManager.h" -@interface _FBTweakCollectionViewController () +@interface _FBTweakCollectionViewController () @end @implementation _FBTweakCollectionViewController { UITableView *_tableView; + UISearchBar *_searchBar; + UISearchDisplayController *_searchController; + NSArray *_sortedCollections; + NSArray *_filteredCollections; _FBKeyboardManager *_keyboardManager; } @@ -46,6 +48,15 @@ - (void)viewDidLoad _tableView.dataSource = self; _tableView.autoresizingMask = (UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight); [self.view addSubview:_tableView]; + + _searchBar = [[UISearchBar alloc] initWithFrame:CGRectMake(0, 0, CGRectGetWidth(self.view.bounds), 44)]; + _searchBar.delegate = self; + _tableView.tableHeaderView = _searchBar; + + _searchController = [[UISearchDisplayController alloc] initWithSearchBar:_searchBar contentsController:self]; + _searchController.delegate = self; + _searchController.searchResultsDelegate = self; + _searchController.searchResultsDataSource = self; self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone target:self action:@selector(_done)]; @@ -87,20 +98,36 @@ - (void)_done [_delegate tweakCollectionViewControllerSelectedDone:self]; } +- (void)_filterTweaksForQuery:(NSString*)query +{ + _filteredCollections = [_FBTweakSearchUtil filteredCollectionsInCategories:@[self.tweakCategory] forQuery:query]; +} + +- (NSArray*)_collectionsToDisplayInTableView:(UITableView*)tableView +{ + if (tableView == self.searchDisplayController.searchResultsTableView) { + return _filteredCollections; + } else { + return _sortedCollections; + } +} + +#pragma mark UITableViewDataSource + - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { - return _sortedCollections.count; + return [self _collectionsToDisplayInTableView:tableView].count; } - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { - FBTweakCollection *collection = _sortedCollections[section]; + FBTweakCollection *collection = [self _collectionsToDisplayInTableView:tableView][section]; return collection.tweaks.count; } - (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section { - FBTweakCollection *collection = _sortedCollections[section]; + FBTweakCollection *collection = [self _collectionsToDisplayInTableView:tableView][section]; return collection.name; } @@ -112,33 +139,41 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N cell = [[_FBTweakTableViewCell alloc] initWithReuseIdentifier:_FBTweakCollectionViewControllerCellIdentifier]; } - FBTweakCollection *collection = _sortedCollections[indexPath.section]; + FBTweakCollection *collection = [self _collectionsToDisplayInTableView:tableView][indexPath.section]; FBTweak *tweak = collection.tweaks[indexPath.row]; cell.tweak = tweak; + cell.searchQuery = _searchBar.text; return cell; } +#pragma mark UITableViewDelegate + - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { - FBTweakCollection *collection = _sortedCollections[indexPath.section]; + FBTweakCollection *collection = [self _collectionsToDisplayInTableView:tableView][indexPath.section]; FBTweak *tweak = collection.tweaks[indexPath.row]; - if ([tweak.possibleValues isKindOfClass:[NSDictionary class]]) { - _FBTweakDictionaryViewController *vc = [[_FBTweakDictionaryViewController alloc] initWithTweak:tweak]; - [self.navigationController pushViewController:vc animated:YES]; - } else if ([tweak.possibleValues isKindOfClass:[NSArray class]]) { - _FBTweakArrayViewController *vc = [[_FBTweakArrayViewController alloc] initWithTweak:tweak]; - [self.navigationController pushViewController:vc animated:YES]; - } else if ([tweak.defaultValue isKindOfClass:[UIColor class]]) { - _FBTweakColorViewController *vc = [[_FBTweakColorViewController alloc] initWithTweak:tweak]; - [self.navigationController pushViewController:vc animated:YES]; - } else if (tweak.isAction) { - dispatch_block_t block = tweak.defaultValue; - if (block != NULL) { - block(); - } - [tableView deselectRowAtIndexPath:indexPath animated:YES]; - } + [_FBTweakSearchUtil handleTweakSelection:tweak inTableView:tableView atIndexPath:indexPath navigationController:self.navigationController]; +} + +#pragma mark UISearchDisplayDelegate + +- (void)searchDisplayControllerWillEndSearch:(UISearchDisplayController *)controller +{ + [self _reloadData]; +} + +- (BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchString:(nullable NSString *)searchString +{ + [self _filterTweaksForQuery:searchString]; + return YES; +} + +#pragma mark UISearchBarDelegate + +- (BOOL)searchBarShouldEndEditing:(UISearchBar *)searchBar +{ + return YES; } @end diff --git a/FBTweak/_FBTweakSearchUtil.h b/FBTweak/_FBTweakSearchUtil.h new file mode 100644 index 00000000..4af3cb17 --- /dev/null +++ b/FBTweak/_FBTweakSearchUtil.h @@ -0,0 +1,25 @@ +/** + Copyright (c) 2014-present, Facebook, Inc. + All rights reserved. + + This source code is licensed under the BSD-style license found in the + LICENSE file in the root directory of this source tree. An additional grant + of patent rights can be found in the PATENTS file in the same directory. + */ + +#import +#import "FBTweak.h" + +/** + @abstract A util class to provide helpers in search functionality + */ +@interface _FBTweakSearchUtil : NSObject + ++ (void)handleTweakSelection:(FBTweak *)tweak + inTableView:(UITableView *)tableView + atIndexPath:(NSIndexPath *)indexPath + navigationController:(UINavigationController *)navigationController; + ++ (NSArray *)filteredCollectionsInCategories:(NSArray *)categories forQuery:(NSString *)searchQuery; + +@end diff --git a/FBTweak/_FBTweakSearchUtil.m b/FBTweak/_FBTweakSearchUtil.m new file mode 100644 index 00000000..05992c84 --- /dev/null +++ b/FBTweak/_FBTweakSearchUtil.m @@ -0,0 +1,67 @@ +/** + Copyright (c) 2014-present, Facebook, Inc. + All rights reserved. + + This source code is licensed under the BSD-style license found in the + LICENSE file in the root directory of this source tree. An additional grant + of patent rights can be found in the PATENTS file in the same directory. + */ + +#import "_FBTweakSearchUtil.h" +#import "_FBTweakArrayViewController.h" +#import "_FBTweakDictionaryViewController.h" +#import "_FBTweakColorViewController.h" +#import "FBTweakCategory.h" +#import "FBTweakCollection.h" + +@implementation _FBTweakSearchUtil + ++ (void)handleTweakSelection:(FBTweak *)tweak + inTableView:(UITableView *)tableView + atIndexPath:(NSIndexPath *)indexPath + navigationController:(UINavigationController *)navigationController +{ + if ([tweak.possibleValues isKindOfClass:[NSDictionary class]]) { + _FBTweakDictionaryViewController *vc = [[_FBTweakDictionaryViewController alloc] initWithTweak:tweak]; + [navigationController pushViewController:vc animated:YES]; + } else if ([tweak.possibleValues isKindOfClass:[NSArray class]]) { + _FBTweakArrayViewController *vc = [[_FBTweakArrayViewController alloc] initWithTweak:tweak]; + [navigationController pushViewController:vc animated:YES]; + } else if ([tweak.defaultValue isKindOfClass:[UIColor class]]) { + _FBTweakColorViewController *vc = [[_FBTweakColorViewController alloc] initWithTweak:tweak]; + [navigationController pushViewController:vc animated:YES]; + } else if (tweak.isAction) { + dispatch_block_t block = tweak.defaultValue; + if (block != NULL) { + block(); + } + [tableView deselectRowAtIndexPath:indexPath animated:YES]; + } +} + ++ (NSArray *)filteredCollectionsInCategories:(NSArray *)categories forQuery:(NSString *)searchQuery +{ + NSMutableArray *collections = [NSMutableArray array]; + + NSPredicate *filter = [NSPredicate predicateWithFormat:@"name CONTAINS[cd] %@", searchQuery]; + for (FBTweakCategory *category in categories) { + for (FBTweakCollection *collection in category.tweakCollections) { + NSArray *filteredTweaks = [collection.tweaks filteredArrayUsingPredicate:filter]; + if (filteredTweaks.count > 0) { + NSString *name = collection.name; + if (categories.count > 1) { + name = [NSString stringWithFormat:@"%@ / %@", category.name, collection.name]; + } + FBTweakCollection *filteredCollection = [[FBTweakCollection alloc] initWithName:name]; + for (FBTweak *tweak in filteredTweaks) { + [filteredCollection addTweak:tweak]; + } + [collections addObject:filteredCollection]; + } + } + } + + return [collections copy]; +} + +@end diff --git a/FBTweak/_FBTweakTableViewCell.h b/FBTweak/_FBTweakTableViewCell.h index 3910a952..22886b36 100644 --- a/FBTweak/_FBTweakTableViewCell.h +++ b/FBTweak/_FBTweakTableViewCell.h @@ -26,4 +26,7 @@ //! @abstract The tweak to show in the cell. @property (nonatomic, strong, readwrite) FBTweak *tweak; +//! @abstract Search phrase to highlight in title. +@property (nonatomic, strong, readwrite) NSString *searchQuery; + @end diff --git a/FBTweak/_FBTweakTableViewCell.m b/FBTweak/_FBTweakTableViewCell.m index 2a2eafe6..376e38f7 100644 --- a/FBTweak/_FBTweakTableViewCell.m +++ b/FBTweak/_FBTweakTableViewCell.m @@ -153,6 +153,20 @@ - (void)setTweak:(FBTweak *)tweak [self _updateValue:value primary:YES write:NO]; } +- (void)setSearchQuery:(NSString *)searchQuery +{ + _searchQuery = searchQuery; + + if (searchQuery.length > 0) { + NSMutableAttributedString *attributedName = [[NSMutableAttributedString alloc] initWithString:self.textLabel.text]; + NSRange range = [self.textLabel.text rangeOfString:self.searchQuery options:NSCaseInsensitiveSearch | NSDiacriticInsensitiveSearch]; + [attributedName addAttribute:NSForegroundColorAttributeName value:[UIColor darkGrayColor] range:NSMakeRange(0, self.textLabel.text.length)]; + [attributedName addAttribute:NSForegroundColorAttributeName value:[UIColor blackColor] range:range]; + [attributedName addAttribute:NSFontAttributeName value:[UIFont boldSystemFontOfSize:self.textLabel.font.pointSize] range:range]; + self.textLabel.attributedText = attributedName; + } +} + - (void)_updateMode:(_FBTweakTableViewCellMode)mode { _mode = mode;