Maybe it's a silly question but I have a feeling that I can improve my project or at least make it more readable.
Here's the deal. I have a UITableView
populated with data from server. For everything I use MagicalRecord
and for dealing with UITableView
I use NSFetchedResultController
. And up to this point everything is fine. The problem occurs when I try to implement UISearchDisplayController
. Insted of useing NSFetchedResultController
I simply fetch data form Core Data and past it to the NSArray
. Here is the method which I use for sorting:
- (void)filterContentForSearchText:(NSString*)searchText scope:(NSString*)scope
{
NSPredicate *resultPredicate = [NSPredicate predicateWithFormat:@"SELF.name contains[c] %@", searchText];
searchResults = [Group MR_findAllSortedBy:@"caseInsensitiveName" ascending:YES withPredicate:resultPredicate];
}
An of course all UITableView
boilerplate code is stuffed with if
conditions checking type of the UITableView
.
I think I can use one NSFetchedResultController
to handle both tasks (populatind both UITableView
's) and ommit this searchResult
array but I'm not sure... So I would appreciate any suggestion, hint or whatever.
One more problem is when I use both UITableViews
populated from other sources they collide when I enable sections
. Simply, UISearchDisplayController.tableView
is covered by section
s from parent UITableView
.
Here is some code used for handling searching and populating UITableView
#pragma mark - Table View
-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
// Count sections in FRC
if (tableView == self.searchDisplayController.searchResultsTableView) {
return 1;
} else {
return [[self.groupsFRC sections] count];
}
}
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// Count groups in each section of FRC
if (tableView == self.searchDisplayController.searchResultsTableView) {
return [searchResults count];
} else {
return [[[self.groupsFRC sections] objectAtIndex:section] numberOfObjects];
}
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// Get reusable cell
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"GroupCell"];
// If there isn't any create new one
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"GroupCell"];
}
Group *group = [self determineGroupInTableViev:tableView atIndexPath:indexPath];
cell.textLabel.text = group.name;
// Checking if group has been selected earlier
if ([self.selectedGroups containsObject:@{@"name" : group.name, @"id" : group.cashID}]) {
[cell setAccessoryType:UITableViewCellAccessoryCheckmark];
} else {
[cell setAccessoryType:UITableViewCellAccessoryNone];
}
return cell;
}
// Adding checkmark to selected cell
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *selectedCell = [tableView cellForRowAtIndexPath:indexPath];
Group *group = [self determineGroupInTableViev:tableView atIndexPath:indexPath];
// Checking if selected cell has accessory view set to checkmark and add group to selected groups array
if (selectedCell.accessoryType == UITableViewCellAccessoryNone)
{
selectedCell.accessoryType = UITableViewCellAccessoryCheckmark;
[self.selectedGroups addObject:@{@"name" : group.name, @"id" : group.cashID}];
NSLog(@"%@", self.selectedGroups);
}
else if (selectedCell.accessoryType == UITableViewCellAccessoryCheckmark)
{
selectedCell.accessoryType = UITableViewCellAccessoryNone;
[self.selectedGroups removeObject:@{@"name" : group.name, @"id" : group.cashID}];
NSLog(@"%@", self.selectedGroups);
}
// Hiding selection with animation for nice and clean effect
[tableView deselectRowAtIndexPath:indexPath animated:YES];
}
#pragma mark - Filtering
- (void)filterContentForSearchText:(NSString*)searchText scope:(NSString*)scope
{
NSPredicate *resultPredicate = [NSPredicate predicateWithFormat:@"SELF.name contains[c] %@", searchText];
searchResults = [Group MR_findAllSortedBy:@"caseInsensitiveName" ascending:YES withPredicate:resultPredicate];
}
-(BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchString:(NSString *)searchString
{
[self filterContentForSearchText:searchString
scope:[[self.searchDisplayController.searchBar scopeButtonTitles] objectAtIndex:[self.searchDisplayController.searchBar selectedScopeButtonIndex]]];
return YES;
}
-(void)searchDisplayController:(UISearchDisplayController *)controller didHideSearchResultsTableView:(UITableView *)tableView
{
// Realoading main table view when search result did hide
[self.tableView reloadData];
}
I usually don't find UISearchDisplayController
very useful. This is because:
So, I usually have a single source of data for the table view and manipulate that source based on and entered search criteria.
This works for both FRC or simple Array. For the FRC, edit the fetch request (predicate) and performFetch:
, everything else is automatic. For the Array, use sortedArrayUsingDescriptors:
and reload (I have an original array of the source data, and another array reference used for the table methods. The second array either points to the original array instance or a new instance created from the sort).
In both cases I have a similar amount of methods to using a UISearchDisplayController
, but there are no if
statements in the table methods. Any if
statements and mutation of the data source and handled solely in the search methods (which are call much less frequently).
Add a property to the class:
@property (strong, nonatomic) NSPredicate *filterPredicate;
In the search text changed delegate:
if (text.length > 0) {
self.filterPredicate = [NSPredicate predicateWithFormat:...];
} else {
self.filterPredicate = nil;
}
[self refreshFRC];
And in the search cancel, just nil the predicate and refresh.
And the refresh:
- (void)refreshFRC {
NSFetchRequest *fetch = ...;
if (self.filterPredicate != nil) {
fetch.predicate = self.filterPredicate;
}
self.frc.fetchRequest = fetch;
[self.frc performFetch:...];
}