blob: 063ea03f63a8fe5e6c796735040bfa9d81edf103 [file] [log] [blame]
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "ios/showcase/core/showcase_view_controller.h"
#include "base/notreached.h"
#import "ios/showcase/common/coordinator.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
namespace showcase {
NSString* const kClassForDisplayKey = @"classForDisplay";
NSString* const kClassForInstantiationKey = @"classForInstantiation";
NSString* const kUseCaseKey = @"useCase";
} // namespace showcase
@interface ShowcaseViewController ()<UITableViewDataSource,
UITableViewDelegate,
UISearchResultsUpdating>
// Search controller that contains search bar.
@property(nonatomic, strong) UISearchController* searchController;
// Full data set displayed when tableView is not filtered.
@property(nonatomic, strong) NSArray<showcase::ModelRow*>* allRows;
// Displayed rows in tableView.
@property(nonatomic, strong) NSArray<showcase::ModelRow*>* displayedRows;
// Selected coordinator.
@property(nonatomic, strong) id<Coordinator> activeCoordinator;
@end
@implementation ShowcaseViewController
@synthesize searchController = _searchController;
@synthesize allRows = _allRows;
@synthesize displayedRows = _displayedRows;
@synthesize activeCoordinator = _activeCoordinator;
- (instancetype)initWithRows:(NSArray<showcase::ModelRow*>*)rows {
self = [super initWithStyle:UITableViewStylePlain];
if (self) {
self.allRows = [rows copy];
// Default to displaying all rows.
self.displayedRows = self.allRows;
}
return self;
}
#pragma mark - UIViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.title = @"SC";
self.tableView.rowHeight = 70.0;
self.tableView.accessibilityIdentifier = @"showcase_home_collection";
self.searchController =
[[UISearchController alloc] initWithSearchResultsController:nil];
self.searchController.searchResultsUpdater = self;
self.searchController.obscuresBackgroundDuringPresentation = NO;
self.tableView.tableHeaderView = self.searchController.searchBar;
UINavigationBarAppearance* appearance =
[[UINavigationBarAppearance alloc] init];
[appearance configureWithOpaqueBackground];
self.navigationController.navigationBar.standardAppearance = appearance;
self.navigationController.navigationBar.scrollEdgeAppearance = appearance;
// Presentation of searchController will walk up the view controller hierarchy
// until it finds the root view controller or one that defines a presentation
// context. Make this class the presentation context so that the search
// controller does not present on top of the navigation controller.
self.definesPresentationContext = YES;
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
self.navigationController.hidesBarsOnSwipe = NO;
// Resets the current coordinator whenever the navigation controller pops
// back to this view controller.
self.activeCoordinator = nil;
}
#pragma mark - Table view data source
- (NSInteger)tableView:(UITableView*)tableView
numberOfRowsInSection:(NSInteger)section {
return self.displayedRows.count;
}
- (UITableViewCell*)tableView:(UITableView*)tableView
cellForRowAtIndexPath:(NSIndexPath*)indexPath {
UITableViewCell* cell = [tableView dequeueReusableCellWithIdentifier:@"cell"];
if (!cell) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle
reuseIdentifier:@"cell"];
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
}
showcase::ModelRow* row = self.displayedRows[indexPath.row];
cell.textLabel.text = row[showcase::kClassForDisplayKey];
cell.detailTextLabel.text = row[showcase::kUseCaseKey];
return cell;
}
#pragma mark - Table view delegate
- (void)tableView:(UITableView*)tableView
didSelectRowAtIndexPath:(NSIndexPath*)indexPath {
showcase::ModelRow* row = self.displayedRows[indexPath.row];
Class classForInstantiation =
NSClassFromString(row[showcase::kClassForInstantiationKey]);
if ([classForInstantiation isSubclassOfClass:[UIViewController class]]) {
UIViewController* viewController = [[classForInstantiation alloc] init];
viewController.title = row[showcase::kUseCaseKey];
[self.navigationController pushViewController:viewController animated:YES];
} else if ([classForInstantiation
conformsToProtocol:@protocol(Coordinator)]) {
self.activeCoordinator = [[classForInstantiation alloc] init];
self.activeCoordinator.baseViewController = self.navigationController;
[self.activeCoordinator start];
} else {
NOTREACHED();
}
}
#pragma mark - UISearchResultsUpdating
- (void)updateSearchResultsForSearchController:
(UISearchController*)searchController {
[self filterContentForSearchText:searchController.searchBar.text];
}
#pragma mark - Private
// Filters |allRows| for |searchText| and reloads the tableView with
// animation. If |searchText| is empty, tableView will be loaded with |allRows|.
- (void)filterContentForSearchText:(NSString*)searchText {
NSArray<showcase::ModelRow*>* newFilteredRows = self.allRows;
if (![self.searchController.searchBar.text isEqualToString:@""]) {
// The search is case-insensitive and searches both displayed texts.
NSIndexSet* matchingRows =
[self.allRows indexesOfObjectsPassingTest:^BOOL(
showcase::ModelRow* row, NSUInteger idx, BOOL* stop) {
return [row[showcase::kClassForDisplayKey]
localizedCaseInsensitiveContainsString:searchText] ||
[row[showcase::kUseCaseKey]
localizedCaseInsensitiveContainsString:searchText];
}];
newFilteredRows = [self.allRows objectsAtIndexes:matchingRows];
}
NSArray<NSIndexPath*>* indexPathsToInsert =
[self indexPathsToInsertFromArray:self.displayedRows
toArray:newFilteredRows
indexPathSection:0];
NSArray<NSIndexPath*>* indexPathsToDelete =
[self indexPathsToDeleteFromArray:self.displayedRows
toArray:newFilteredRows
indexPathSection:0];
[self.tableView beginUpdates];
[self.tableView insertRowsAtIndexPaths:indexPathsToInsert
withRowAnimation:UITableViewRowAnimationFade];
[self.tableView deleteRowsAtIndexPaths:indexPathsToDelete
withRowAnimation:UITableViewRowAnimationFade];
self.displayedRows = newFilteredRows;
[self.tableView endUpdates];
}
// Returns indexPaths that need to be inserted into the tableView.
- (NSArray<NSIndexPath*>*)indexPathsToInsertFromArray:(NSArray*)fromArray
toArray:(NSArray*)toArray
indexPathSection:(NSUInteger)section {
NSMutableArray<NSIndexPath*>* indexPathsToInsert =
[[NSMutableArray alloc] init];
for (NSUInteger row = 0; row < toArray.count; row++) {
if (![fromArray containsObject:toArray[row]]) {
[indexPathsToInsert
addObject:[NSIndexPath indexPathForRow:row inSection:section]];
}
}
return indexPathsToInsert;
}
// Returns indexPaths that need to be deleted from the tableView.
- (NSArray<NSIndexPath*>*)indexPathsToDeleteFromArray:(NSArray*)fromArray
toArray:(NSArray*)toArray
indexPathSection:(NSUInteger)section {
NSMutableArray<NSIndexPath*>* indexPathsToDelete =
[[NSMutableArray alloc] init];
for (NSUInteger row = 0; row < fromArray.count; row++) {
if (![toArray containsObject:fromArray[row]]) {
[indexPathsToDelete
addObject:[NSIndexPath indexPathForRow:row inSection:section]];
}
}
return indexPathsToDelete;
}
@end