When I use UISearchBar
and write something as a search string this whole searching thing gets a little laggy. My guess is that I'm messing UI stuff and Core Data in the main thread but I might be wrong. What is more, I use one entity for all of this stuff so there are no relations etc. there are 3321 objects in this table and this app is consuming approximately 12 to 14 MB of RAM what you can see on the screenshot below:
I thought that it'll be more efficent as 3321 objects isn't so much. For all Core Data stuff I use MagicalReacord. I operate on one instance of NSFetchedResultController
but switching NSPredicate
between main table view alnd search table view.
But nothing will be more valuable that source code so here you go:
#import "GroupsViewController.h"
#import "CashURLs.h"
#import "Group.h"
#import <AFNetworking.h>
@interface GroupsViewController ()
{
NSPredicate *resultPredicate;
UIRefreshControl *refreshControl;
}
@property (strong, nonatomic) NSMutableArray *selectedGroups;
@property (strong, nonatomic) NSFetchedResultsController *groupsFRC;
@end
@implementation GroupsViewController
-(void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
// Getting array of selected groups
self.selectedGroups = [NSMutableArray arrayWithArray:[[NSUserDefaults standardUserDefaults] objectForKey:@"SelectedGroups"]];
}
- (void)viewDidLoad
{
[super viewDidLoad];
resultPredicate = nil;
// Initializing pull to refresh
refreshControl = [[UIRefreshControl alloc] init];
[refreshControl addTarget:self action:@selector(refreshData) forControlEvents:UIControlEventValueChanged];
[self.tableView addSubview:refreshControl];
// Check if there is at least one Group entity in persistat store
if (![Group MR_hasAtLeastOneEntity]) {
[self refreshData];
} else {
[self refreshFRC];
[self.tableView reloadData];
}
}
#pragma mark - Downloading
-(void)refreshData
{
// Show refresh control
[refreshControl beginRefreshing];
// On refresh delete all previous groups (To avoid duplicates and ghost-groups)
[Group MR_truncateAll];
[[AFHTTPRequestOperationManager manager] GET:ALL_GROUPS parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
// For each group from downloaded JSON...
for (id group in responseObject) {
// ... Create entity and filll it with data
Group *groupEntity = [Group MR_createEntity];
groupEntity.name = [group valueForKey:@"name"];
groupEntity.cashID = [group valueForKey:@"id"];
groupEntity.sectionLetter = [[[group valueForKey:@"name"] substringToIndex:1] uppercaseString];
groupEntity.caseInsensitiveName = [[group valueForKey:@"name"] lowercaseString];
}
// Save Groups to persistent store
[[NSManagedObjectContext MR_defaultContext] MR_saveToPersistentStoreAndWait];
[self refreshFRC];
[self.tableView reloadData];
[refreshControl endRefreshing];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(@"Failed to load data: %@", [error localizedDescription]);
// End refreshing
[refreshControl endRefreshing];
// Show alert with info about internet connection
UIAlertView *internetAlert = [[UIAlertView alloc] initWithTitle:@"Ups!" message:@"Wygląda na to, że nie masz połączenia z internetem" delegate:self cancelButtonTitle:@"OK" otherButtonTitles:nil, nil];
[internetAlert show];
}];
}
#pragma mark - Table View
-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
// Count sections in FRC
return [[self.groupsFRC sections] count];
}
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// Count groups in each section of FRC
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.groupsFRC objectAtIndexPath: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.groupsFRC objectAtIndexPath: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/Searching
// Seting searching predicate if there are any characters in search bar
-(void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText
{
if (searchText.length > 0) {
resultPredicate = [NSPredicate predicateWithFormat:@"SELF.caseInsensitiveName CONTAINS[c] %@", searchText];
} else {
resultPredicate = nil;
}
[self refreshFRC];
}
-(void)searchBarCancelButtonClicked:(UISearchBar *)searchBar
{
// If user cancels searching we set predicate to nil
resultPredicate = nil;
[self refreshFRC];
}
// Refreshing NSFetchedResultController
- (void)refreshFRC
{
self.groupsFRC = [Group MR_fetchAllSortedBy:@"caseInsensitiveName"
ascending:YES
withPredicate:resultPredicate
groupBy:@"sectionLetter"
delegate:self];
}
I read in one thread that CONTAINS
could be resource consuming but I really have no idea how to implement it other way. My other guess was to put this searching in another queue
and do it asynchronously, separate it from the UI... It isn't so laggy that UI will have to wait for ages for reloaded table view. But is it the right way? I have to improve preformance of this UISearchBar
because I don't want to have unhappy customers.
I hope you could proide me with some ideas or you have any improvements for my code