J'essaie d'implémenter le code de recherche dans mon application iPhone basée sur CoreData. Je ne sais pas comment procéder. L'application dispose déjà d'un NSFetchedResultsController avec un prédicat pour extraire les données de la table principale. Je veux m'assurer que je suis sur le bon chemin avant de changer trop de code. Je suis confus parce que beaucoup d'exemples sont basés sur des tableaux plutôt que sur CoreData.
Voici quelques questions:
Dois-je avoir un deuxième NSFetchedResultsController qui ne récupère que les éléments correspondants ou puis-je utiliser le même que le TableView principal?
Si j'utilise le même, est-ce aussi simple que d'effacer le cache FRC puis de modifier le prédicat dans la méthode handleSearchForTerm: searchString? Le prédicat doit-il contenir le prédicat initial ainsi que les termes de recherche ou se souvient-il qu'il utilisait un prédicat pour extraire des données?
Comment puis-je revenir aux résultats originaux? Dois-je simplement définir le prédicat de recherche sur nil? Cela ne va-t-il pas tuer le prédicat d'origine utilisé pour récupérer les résultats FRC en premier lieu?
Si quelqu'un a des exemples de code utilisant la recherche avec le FRC, je l'apprécierais grandement!
En fait, je viens de mettre cela en œuvre sur l'un de mes projets (votre question et l'autre réponse fausse suggéraient quoi faire). J'ai essayé la réponse de Sergio, mais j'ai rencontré des problèmes d'exception lors de l'exécution sur un périphérique.
Oui, vous créez deux contrôleurs de résultats d'extraction: l'un pour l'affichage normal et l'autre pour la vue Table de UISearchBar.
Si vous n'utilisez qu'un seul FRC (NSFetchedResultsController), la base UITableView d'origine (et non la vue de table de recherche active pendant la recherche) sera éventuellement rappelée lors de la recherche et tentera d'utiliser de manière incorrecte la version filtrée de votre FRC. jeté sur le nombre incorrect de sections ou de lignes dans les sections.
Voici ce que j'ai fait: J'ai deux CRF disponibles en tant que propriétés fetchedResultsController et searchFetchedResultsController. SearchFetchedResultsController ne doit pas être utilisé sauf en cas de recherche (lorsque la recherche est annulée, vous pouvez voir ci-dessous que cet objet est publié). Toutes les méthodes UITableView doivent déterminer la vue de la table à interroger et le FRC concerné à partir duquel extraire les informations. Les méthodes de délégué FRC doivent également déterminer quelle tableView doit être mise à jour.
Il est surprenant de constater à quel point cela est du code standard.
Bits pertinents du fichier d'en-tête:
@interface BlahViewController : UITableViewController <UISearchBarDelegate, NSFetchedResultsControllerDelegate, UISearchDisplayDelegate>
{
// other class ivars
// required ivars for this example
NSFetchedResultsController *fetchedResultsController_;
NSFetchedResultsController *searchFetchedResultsController_;
NSManagedObjectContext *managedObjectContext_;
// The saved state of the search UI if a memory warning removed the view.
NSString *savedSearchTerm_;
NSInteger savedScopeButtonIndex_;
BOOL searchWasActive_;
}
@property (nonatomic, retain) NSManagedObjectContext *managedObjectContext;
@property (nonatomic, retain, readonly) NSFetchedResultsController *fetchedResultsController;
@property (nonatomic, copy) NSString *savedSearchTerm;
@property (nonatomic) NSInteger savedScopeButtonIndex;
@property (nonatomic) BOOL searchWasActive;
bits pertinents du fichier d'implémentation:
@interface BlahViewController ()
@property (nonatomic, retain) NSFetchedResultsController *fetchedResultsController;
@property (nonatomic, retain) NSFetchedResultsController *searchFetchedResultsController;
@property (nonatomic, retain) UISearchDisplayController *mySearchDisplayController;
@end
J'ai créé une méthode utile pour récupérer le code FRC correct lorsque vous utilisez toutes les méthodes UITableViewDelegate/DataSource:
- (NSFetchedResultsController *)fetchedResultsControllerForTableView:(UITableView *)tableView
{
return tableView == self.tableView ? self.fetchedResultsController : self.searchFetchedResultsController;
}
- (void)fetchedResultsController:(NSFetchedResultsController *)fetchedResultsController configureCell:(UITableViewCell *)theCell atIndexPath:(NSIndexPath *)theIndexPath
{
// your cell guts here
}
- (UITableViewCell *)tableView:(UITableView *)theTableView cellForRowAtIndexPath:(NSIndexPath *)theIndexPath
{
CallTableCell *cell = (CallTableCell *)[theTableView dequeueReusableCellWithIdentifier:@"CallTableCell"];
if (cell == nil)
{
cell = [[[CallTableCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"CallTableCell"] autorelease];
}
[self fetchedResultsController:[self fetchedResultsControllerForTableView:theTableView] configureCell:cell atIndexPath:theIndexPath];
return cell;
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
NSInteger count = [[[self fetchedResultsControllerForTableView:tableView] sections] count];
return count;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
NSInteger numberOfRows = 0;
NSFetchedResultsController *fetchController = [self fetchedResultsControllerForTableView:tableView];
NSArray *sections = fetchController.sections;
if(sections.count > 0)
{
id <NSFetchedResultsSectionInfo> sectionInfo = [sections objectAtIndex:section];
numberOfRows = [sectionInfo numberOfObjects];
}
return numberOfRows;
}
Méthodes déléguées pour la barre de recherche:
#pragma mark -
#pragma mark Content Filtering
- (void)filterContentForSearchText:(NSString*)searchText scope:(NSInteger)scope
{
// update the filter, in this case just blow away the FRC and let lazy evaluation create another with the relevant search info
self.searchFetchedResultsController.delegate = nil;
self.searchFetchedResultsController = nil;
// if you care about the scope save off the index to be used by the serchFetchedResultsController
//self.savedScopeButtonIndex = scope;
}
#pragma mark -
#pragma mark Search Bar
- (void)searchDisplayController:(UISearchDisplayController *)controller willUnloadSearchResultsTableView:(UITableView *)tableView;
{
// search is done so get rid of the search FRC and reclaim memory
self.searchFetchedResultsController.delegate = nil;
self.searchFetchedResultsController = nil;
}
- (BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchString:(NSString *)searchString
{
[self filterContentForSearchText:searchString
scope:[self.searchDisplayController.searchBar selectedScopeButtonIndex]];
// Return YES to cause the search result table view to be reloaded.
return YES;
}
- (BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchScope:(NSInteger)searchOption
{
[self filterContentForSearchText:[self.searchDisplayController.searchBar text]
scope:[self.searchDisplayController.searchBar selectedScopeButtonIndex]];
// Return YES to cause the search result table view to be reloaded.
return YES;
}
assurez-vous que vous utilisez la vue de table correcte lors de l'obtention de mises à jour à partir des méthodes de délégation FRC:
- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller
{
UITableView *tableView = controller == self.fetchedResultsController ? self.tableView : self.searchDisplayController.searchResultsTableView;
[tableView beginUpdates];
}
- (void)controller:(NSFetchedResultsController *)controller
didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo
atIndex:(NSUInteger)sectionIndex
forChangeType:(NSFetchedResultsChangeType)type
{
UITableView *tableView = controller == self.fetchedResultsController ? self.tableView : self.searchDisplayController.searchResultsTableView;
switch(type)
{
case NSFetchedResultsChangeInsert:
[tableView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeDelete:
[tableView deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
break;
}
}
- (void)controller:(NSFetchedResultsController *)controller
didChangeObject:(id)anObject
atIndexPath:(NSIndexPath *)theIndexPath
forChangeType:(NSFetchedResultsChangeType)type
newIndexPath:(NSIndexPath *)newIndexPath
{
UITableView *tableView = controller == self.fetchedResultsController ? self.tableView : self.searchDisplayController.searchResultsTableView;
switch(type)
{
case NSFetchedResultsChangeInsert:
[tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeDelete:
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:theIndexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeUpdate:
[self fetchedResultsController:controller configureCell:[tableView cellForRowAtIndexPath:theIndexPath] atIndexPath:theIndexPath];
break;
case NSFetchedResultsChangeMove:
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:theIndexPath] withRowAnimation:UITableViewRowAnimationFade];
[tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath]withRowAnimation:UITableViewRowAnimationFade];
break;
}
}
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller
{
UITableView *tableView = controller == self.fetchedResultsController ? self.tableView : self.searchDisplayController.searchResultsTableView;
[tableView endUpdates];
}
Autres informations de vue:
- (void)loadView
{
[super loadView];
UISearchBar *searchBar = [[[UISearchBar alloc] initWithFrame:CGRectMake(0, 0, self.tableView.frame.size.width, 44.0)] autorelease];
searchBar.autoresizingMask = (UIViewAutoresizingFlexibleWidth);
searchBar.autocorrectionType = UITextAutocorrectionTypeNo;
self.tableView.tableHeaderView = searchBar;
self.mySearchDisplayController = [[[UISearchDisplayController alloc] initWithSearchBar:searchBar contentsController:self] autorelease];
self.mySearchDisplayController.delegate = self;
self.mySearchDisplayController.searchResultsDataSource = self;
self.mySearchDisplayController.searchResultsDelegate = self;
}
- (void)didReceiveMemoryWarning
{
self.searchWasActive = [self.searchDisplayController isActive];
self.savedSearchTerm = [self.searchDisplayController.searchBar text];
self.savedScopeButtonIndex = [self.searchDisplayController.searchBar selectedScopeButtonIndex];
fetchedResultsController_.delegate = nil;
[fetchedResultsController_ release];
fetchedResultsController_ = nil;
searchFetchedResultsController_.delegate = nil;
[searchFetchedResultsController_ release];
searchFetchedResultsController_ = nil;
[super didReceiveMemoryWarning];
}
- (void)viewDidDisappear:(BOOL)animated
{
// save the state of the search UI so that it can be restored if the view is re-created
self.searchWasActive = [self.searchDisplayController isActive];
self.savedSearchTerm = [self.searchDisplayController.searchBar text];
self.savedScopeButtonIndex = [self.searchDisplayController.searchBar selectedScopeButtonIndex];
}
- (void)viewDidLoad
{
// restore search settings if they were saved in didReceiveMemoryWarning.
if (self.savedSearchTerm)
{
[self.searchDisplayController setActive:self.searchWasActive];
[self.searchDisplayController.searchBar setSelectedScopeButtonIndex:self.savedScopeButtonIndex];
[self.searchDisplayController.searchBar setText:savedSearchTerm];
self.savedSearchTerm = nil;
}
}
Code de création FRC:
- (NSFetchedResultsController *)newFetchedResultsControllerWithSearch:(NSString *)searchString
{
NSArray *sortDescriptors = // your sort descriptors here
NSPredicate *filterPredicate = // your predicate here
/*
Set up the fetched results controller.
*/
// Create the fetch request for the entity.
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
// Edit the entity name as appropriate.
NSEntityDescription *callEntity = [MTCall entityInManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:callEntity];
NSMutableArray *predicateArray = [NSMutableArray array];
if(searchString.length)
{
// your search predicate(s) are added to this array
[predicateArray addObject:[NSPredicate predicateWithFormat:@"name CONTAINS[cd] %@", searchString]];
// finally add the filter predicate for this view
if(filterPredicate)
{
filterPredicate = [NSCompoundPredicate andPredicateWithSubpredicates:[NSArray arrayWithObjects:filterPredicate, [NSCompoundPredicate orPredicateWithSubpredicates:predicateArray], nil]];
}
else
{
filterPredicate = [NSCompoundPredicate orPredicateWithSubpredicates:predicateArray];
}
}
[fetchRequest setPredicate:filterPredicate];
// Set the batch size to a suitable number.
[fetchRequest setFetchBatchSize:20];
[fetchRequest setSortDescriptors:sortDescriptors];
// Edit the section name key path and cache name if appropriate.
// nil for section name key path means "no sections".
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
managedObjectContext:self.managedObjectContext
sectionNameKeyPath:nil
cacheName:nil];
aFetchedResultsController.delegate = self;
[fetchRequest release];
NSError *error = nil;
if (![aFetchedResultsController performFetch:&error])
{
/*
Replace this implementation with code to handle the error appropriately.
abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. If it is not possible to recover from the error, display an alert panel that instructs the user to quit the application by pressing the Home button.
*/
NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
abort();
}
return aFetchedResultsController;
}
- (NSFetchedResultsController *)fetchedResultsController
{
if (fetchedResultsController_ != nil)
{
return fetchedResultsController_;
}
fetchedResultsController_ = [self newFetchedResultsControllerWithSearch:nil];
return [[fetchedResultsController_ retain] autorelease];
}
- (NSFetchedResultsController *)searchFetchedResultsController
{
if (searchFetchedResultsController_ != nil)
{
return searchFetchedResultsController_;
}
searchFetchedResultsController_ = [self newFetchedResultsControllerWithSearch:self.searchDisplayController.searchBar.text];
return [[searchFetchedResultsController_ retain] autorelease];
}
Certains ont fait remarquer que cela peut être fait avec une seule NSFetchedResultsController
. C'est ce que j'ai fait et voici les détails. Cette solution suppose que vous souhaitiez simplement filtrer le tableau et conserver tous les autres aspects (ordre de tri, disposition des cellules, etc.) des résultats de la recherche.
Commencez par définir deux propriétés dans votre sous-classe UITableViewController
(avec les propriétés @synthesize et dealloc appropriées, le cas échéant):
@property (nonatomic, retain) UISearchDisplayController *searchController;
@property (nonatomic, retain) NSString *searchString;
Deuxièmement, initialisez la barre de recherche dans la méthode viewDidLoad:
de votre sous-classe UITableViewController
:
UISearchBar *searchBar = [[UISearchBar alloc] initWithFrame:CGRectMake(0,0,self.tableView.frame.size.width,44)];
searchBar.placeholder = @"Search";
searchBar.delegate = self;
self.searchController = [[[UISearchDisplayController alloc] initWithSearchBar:searchBar contentsController:self] autorelease];
self.searchController.delegate = self;
self.searchController.searchResultsDataSource = self;
self.searchController.searchResultsDelegate = self;
self.tableView.tableHeaderView = self.searchController.searchBar;
[searchBar release];
Troisièmement, implémentez les méthodes de délégué UISearchDisplayController
comme ceci:
// This gets called when you start typing text into the search bar
-(BOOL)searchDisplayController:(UISearchDisplayController *)_controller shouldReloadTableForSearchString:(NSString *)_searchString {
self.searchString = _searchString;
self.fetchedResultsController = nil;
return YES;
}
// This gets called when you cancel or close the search bar
-(void)searchDisplayController:(UISearchDisplayController *)controller willUnloadSearchResultsTableView:(UITableView *)tableView {
self.searchString = nil;
self.fetchedResultsController = nil;
[self.tableView reloadData];
}
Enfin, dans la méthode fetchedResultsController
, changez la valeur NSPredicate
en fonction de si self.searchString
est défini:
-(NSFetchedResultsController *)fetchedResultsController {
if (fetchedResultsController == nil) {
// removed for brevity
NSPredicate *predicate;
if (self.searchString) {
// predicate that uses searchString (used by UISearchDisplayController)
// e.g., [NSPredicate predicateWithFormat:@"name CONTAINS[cd] %@", self.searchString];
predicate = ...
} else {
predicate = ... // predicate without searchString (used by UITableViewController)
}
// removed for brevity
}
return fetchedResultsController;
}
Il m'a fallu quelques essais pour que cela fonctionne ...
Ma clé pour comprendre était de réaliser qu'il y a deux vues de table à l'oeuvre ici. L'une gérée par mon contrôleur de vue et l'autre par le searchviewcontroller, puis je pouvais tester pour voir ce qui est actif et faire le bon choix. La documentation était utile aussi:
Voici ce que j'ai fait -
Ajout du drapeau searchIsActive:
@interface ItemTableViewController : UITableViewController <NSFetchedResultsControllerDelegate, UISearchDisplayDelegate, UISearchBarDelegate> {
NSString *sectionNameKeyPath;
NSArray *sortDescriptors;
@private
NSFetchedResultsController *fetchedResultsController_;
NSManagedObjectContext *managedObjectContext_;
BOOL searchIsActive;
}
@property (nonatomic, retain) NSManagedObjectContext *managedObjectContext;
@property (nonatomic, retain) NSFetchedResultsController *fetchedResultsController;
@property (nonatomic, retain) NSString *sectionNameKeyPath;
@property (nonatomic, retain) NSArray *sortDescriptors;
@property (nonatomic) BOOL searchIsActive;
Ajout de la synthèse dans le fichier d'implémentation.
Ensuite, j'ai ajouté ces méthodes à la recherche:
#pragma mark -
#pragma mark Content Filtering
- (void)filterContentForSearchText:(NSString*)searchText scope:(NSString*)scope
{
NSFetchRequest *aRequest = [[self fetchedResultsController] fetchRequest];
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"name BEGINSWITH[cd] %@", searchText];
[aRequest setPredicate:predicate];
NSError *error = nil;
if (![[self fetchedResultsController] performFetch:&error]) {
// Handle error
NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
abort();
}
}
#pragma mark -
#pragma mark UISearchDisplayController Delegate Methods
- (BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchString:(NSString *)searchString
{
[self filterContentForSearchText:[self.searchDisplayController.searchBar text] scope:nil];
return YES;
}
/*
- (BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchScope:(NSInteger)searchOption
{
return YES;
}
*/
- (void)searchDisplayControllerWillBeginSearch:(UISearchDisplayController *)controller {
[self setSearchIsActive:YES];
return;
}
- (void)searchDisplayControllerDidEndSearch:(UISearchDisplayController *)controller
{
NSFetchRequest *aRequest = [[self fetchedResultsController] fetchRequest];
[aRequest setPredicate:nil];
NSError *error = nil;
if (![[self fetchedResultsController] performFetch:&error]) {
// Handle error
NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
abort();
}
[self setSearchIsActive:NO];
return;
}
Puis dans controllerWillChangeContent:
- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller
{
if ([self searchIsActive]) {
[[[self searchDisplayController] searchResultsTableView] beginUpdates];
}
else {
[self.tableView beginUpdates];
}
}
Et controllerDidChangeContent:
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller
{
if ([self searchIsActive]) {
[[[self searchDisplayController] searchResultsTableView] endUpdates];
}
else {
[self.tableView endUpdates];
}
}
Et supprimez le cache lors de la réinitialisation du prédicat.
J'espère que cela t'aides.
J’ai fait face à la même tâche et j’ai trouvé LA PLUS SIMPLE DES POSSIBLES la résoudre. En bref: vous devez définir une méthode supplémentaire, très similaire à -fetchedResultsController
avec un prédicat composé personnalisé.
Dans mon cas personnel, mon -fetchedResultsController
ressemble à ceci:
- (NSFetchedResultsController *) fetchedResultsController
{
if (fetchedResultsController != nil)
{
return fetchedResultsController;
}
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:@"Client"
inManagedObjectContext:[[PTDataManager sharedManager] managedObjectContext]];
[fetchRequest setEntity:entity];
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"agency_server_id == %@", agency.server_id];
fetchRequest.predicate = predicate;
NSSortDescriptor *sortByName1Descriptor = [[NSSortDescriptor alloc] initWithKey:@"lastname" ascending:YES];
NSSortDescriptor *sortByName2Descriptor = [[NSSortDescriptor alloc] initWithKey:@"firstname" ascending:YES];
NSSortDescriptor *sortByName3Descriptor = [[NSSortDescriptor alloc] initWithKey:@"middlename" ascending:YES];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects: sortByName1Descriptor, sortByName2Descriptor, sortByName3Descriptor, nil];
fetchRequest.sortDescriptors = sortDescriptors;
fetchedResultsController = [[NSFetchedResultsController alloc]initWithFetchRequest:fetchRequest managedObjectContext:[[PTDataManager sharedManager] managedObjectContext] sectionNameKeyPath:nil cacheName:nil];
fetchedResultsController.delegate = self;
return fetchedResultsController;
}
Comme vous pouvez le constater, je vais chercher les clients d’une agence filtrée par le prédicat agency.server_id
. En conséquence, je récupère mon contenu dans un tableView
(tout ce qui concerne l'implémentation du code tableView
et fetchedResultsController
est plutôt standard) . Pour implémenter searchField
, je définis une méthode déléguée UISearchBarDelegate
. Je le déclenche avec la méthode de recherche, disons -reloadTableView
:
- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText
{
[self reloadTableView];
}
et bien sûr la définition de -reloadTableView
:
- (void)reloadTableView
{
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:@"Client"
inManagedObjectContext:[[PTDataManager sharedManager] managedObjectContext]];
[fetchRequest setEntity:entity];
NSSortDescriptor *sortByName1Descriptor = [[NSSortDescriptor alloc] initWithKey:@"lastname" ascending:YES];
NSSortDescriptor *sortByName2Descriptor = [[NSSortDescriptor alloc] initWithKey:@"firstname" ascending:YES];
NSSortDescriptor *sortByName3Descriptor = [[NSSortDescriptor alloc] initWithKey:@"middlename" ascending:YES];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects: sortByName1Descriptor, sortByName2Descriptor, sortByName3Descriptor, nil];
fetchRequest.sortDescriptors = sortDescriptors;
NSPredicate *idPredicate = [NSPredicate predicateWithFormat:@"agency_server_id CONTAINS[cd] %@", agency.server_id];
NSString *searchString = self.searchBar.text;
if (searchString.length > 0)
{
NSPredicate *firstNamePredicate = [NSPredicate predicateWithFormat:@"firstname CONTAINS[cd] %@", searchString];
NSPredicate *lastNamePredicate = [NSPredicate predicateWithFormat:@"lastname CONTAINS[cd] %@", searchString];
NSPredicate *middleNamePredicate = [NSPredicate predicateWithFormat:@"middlename CONTAINS[cd] %@", searchString];
NSPredicate *orPredicate = [NSCompoundPredicate orPredicateWithSubpredicates:[NSArray arrayWithObjects:firstNamePredicate, lastNamePredicate, middleNamePredicate, nil]];
NSPredicate *andPredicate = [NSCompoundPredicate andPredicateWithSubpredicates:[NSArray arrayWithObjects:idPredicate, nil]];
NSPredicate *finalPred = [NSCompoundPredicate andPredicateWithSubpredicates:[NSArray arrayWithObjects:orPredicate, andPredicate, nil]];
[fetchRequest setPredicate:finalPred];
}
else
{
[fetchRequest setPredicate:idPredicate];
}
self.fetchedResultsController = [[NSFetchedResultsController alloc]initWithFetchRequest:fetchRequest managedObjectContext:[[PTDataManager sharedManager] managedObjectContext] sectionNameKeyPath:nil cacheName:nil];
self.fetchedResultsController.delegate = self;
NSError *error = nil;
if (![self.fetchedResultsController performFetch:&error])
{
NSLog(@"Unresolved error %@, %@", [error localizedDescription], [error localizedFailureReason]);
};
[self.clientsTableView reloadData];
}
Ce groupe de code est très similaire au premier, "standard" -fetchedResultsController
MAISà l'intérieur de la déclaration if-else est la suivante:
+andPredicateWithSubpredicates:
- en utilisant cette méthode, nous pouvons définir un prédicat pour enregistrer les résultats de notre première extraction dans la variable tableView
+orPredicateWithSubpredicates
- en utilisant cette méthode, nous filtrons l'extraction existante par requête de recherche de searchBar
À la fin, je configure un tableau de prédicats en tant que prédicat composé pour cette extraction particulière. ET pour les prédicats requis, OR pour facultatif.
Et c'est tout! Vous n'avez pas besoin de mettre en œuvre quoi que ce soit de plus… .. Bon codage!
Utilisez-vous une recherche en direct?
Si vous ne l'êtes PAS, vous voulez probablement un tableau (ou un NSFetchedResultsController) avec les recherches précédentes que vous avez utilisées. Lorsque l'utilisateur appuie sur "search", vous indiquez à votre FetchedResults de changer de prédicat.
Dans les deux cas, vous devrez reconstruire votre FetchedResults à chaque fois. Je recommande d'utiliser un seul NSFetchedResultsController, car vous devrez beaucoup dupliquer votre code et vous n'avez pas besoin de gaspiller de la mémoire dans quelque chose que vous ne montrez pas.
Assurez-vous simplement que vous avez une variable "searchParameters" NSString et que votre méthode FetchedResults la reconstruit au besoin, en utilisant les paramètres de recherche, le cas échéant, il vous suffit de faire:
a) définissez les "searchParameters" sur quelque chose (ou nil, si vous voulez tous les résultats).
b) relâchez et définissez sur nil l'objet NSFetchedResultsController actuel.
c) recharger les données de la table.
Voici un code simple:
- (void)searchString:(NSString*)s {
self.searchResults = s;
[fetchedResultsController release];
fetchedResultsController = nil;
[self.tableView reloadData];
}
-(NSFetchedResultsController *)fetchedResultsController {
if (fetchedResultsController != nil) {
return fetchedResultsController;
}
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:@"EntityName" inManagedObjectContext:self.context];
[fetchRequest setEntity:entity];
[fetchRequest setFetchBatchSize:20];
// searchResults is a NSString*
if (searchResults != nil) {
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"name LIKE %@",searchResults];
[fetchRequest setPredicate:predicate];
}
fetchedResultsController =
[[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
managedObjectContext:self.context sectionNameKeyPath:nil
cacheName:nil];
fetchedResultsController.delegate = self;
[fetchRequest release];
return fetchedResultsController;
}
Swift 3.0, UISearchController, NSFetchedResultsController et Core Data
Ce code fonctionnera sur Swift 3.0 avec Core Data
! Vous aurez besoin d'une seule méthode de délégué et de quelques lignes de code pour filtrer et rechercher des objets dans le modèle. Rien ne sera nécessaire si vous avez implémenté toutes les méthodes FRC
et delegate
ainsi que searchController
.
La méthode du protocole UISearchResultsUpdating
func updateSearchResults(for searchController: UISearchController) {
let text = searchController.searchBar.text
if (text?.isEmpty)! {
// Do something
} else {
self.fetchedResultsController.fetchRequest.predicate = NSPredicate(format: "( someString contains[cd] %@ )", text!)
}
do {
try self.fetchedResultsController.performFetch()
self.tableView.reloadData()
} catch {}
}
C'est tout! J'espère que ça vous aide! Merci
Swift 3.0
Utilisez un champ de texte, UISearchDisplayController étant obsolète à partir de iOS 8, vous devrez utiliser un UISearchController. Au lieu de traiter avec le contrôleur de recherche, pourquoi ne pas créer votre propre mécanisme de recherche? Vous pouvez le personnaliser davantage et le contrôler davantage sans avoir à vous soucier de la modification et/ou de l'obsolescence de SearchController.
Cette méthode que j’utilise fonctionne très bien et ne nécessite pas beaucoup de code. Cependant, vous devez utiliser Core Data et implémenter NSFetchedResultsController.
Commencez par créer un TextField et enregistrez-le avec une méthode:
searchTextField?.addTarget(self, action: #selector(textFieldDidChange), for: UIControlEvents.editingChanged)
Créez ensuite votre méthode textFieldDidChange, décrite dans le sélecteur lors de l'ajout de la cible:
func textFieldDidChange() {
if let queryString = searchTextField.text {
filterList(queryString)
self.tableView.reloadData()
}
}
Ensuite, vous souhaitez filtrer la liste dans la méthode filterList()
à l'aide du prédicat NSPredicate ou NSCompound s'il est plus complexe. Dans ma méthode filterList, je filtre en fonction du nom de l'entité et du nom de l'objet "subCategories" des entités (une relation de un à plusieurs).
func filterList(_ queryString: String) {
if let currentProjectID = Constants.userDefaults.string(forKey: Constants.CurrentSelectedProjectID) {
if let currentProject = ProjectDBFacade.getProjectWithID(currentProjectID) {
if (queryString != ""){
let categoryPredicate = NSPredicate(format: "name CONTAINS[c] %@ && project == %@", queryString, currentProject)
let subCategoryPredicate = NSPredicate(format: "subCategories.name CONTAINS[c] %@ && project == %@", queryString, currentProject)
let orPredicate = NSCompoundPredicate(type: .or, subpredicates: [categoryPredicate, subCategoryPredicate])
fetchedResultsController.fetchRequest.predicate = orPredicate
}else{
fetchedResultsController.fetchRequest.predicate = NSPredicate(format: "project == %@", currentProject)
}
do {
try fetchedResultsController.performFetch()
} catch {
print("Error: Could not fetch fetchedResultsController")
}
}
}
}
Je pense que Luka a une meilleure approche pour cela. Voir LargeDataSetSample et sa raison
Il n'utilise pas FetchedResultsController
, mais utilise le cache lors de la recherche. Les résultats de la recherche apparaissent donc beaucoup plus rapidement lorsque l'utilisateur tape davantage dans SearchBar.
J'ai utilisé son approche dans mon application et cela fonctionne bien. Rappelez-vous également que si vous souhaitez utiliser un objet Modèle, simplifiez-le au maximum. Voir ma réponse à propos de setPropertiesToFetch
Voici un moyen de gérer fetchedResults avec plusieurs ensembles de données à la fois assez simple et assez général pour s'appliquer presque n'importe où. Saisissez simplement vos principaux résultats dans un tableau lorsqu'une condition est présente.
NSArray *results = [self.fetchedResultsController fetchedObjects];
Interrogez le tableau en le parcourant ou tout ce que vous désirez afin de créer un sous-ensemble de vos propriétés fetchedResults. Et maintenant, vous pouvez utiliser l'ensemble complet ou un sous-ensemble lorsqu'une condition est présente.
J'ai vraiment aimé l'approche de @Josh O'Connor où il n'utilise pas de UISearchController
. Ce contrôleur toujours (Xcode 9) a un bogue de mise en page que beaucoup essaient de contourner.
Je me suis remis à utiliser UISearchBar
au lieu de UITextField
, et cela fonctionne assez bien. Mon exigence pour la recherche/filtre est de produire une NSPredicate
. Ceci est passé à la FRC:
class ViewController: UIViewController, UITableViewDelegate,
UITableViewDataSource, UISearchBarDelegate {
@IBOutlet var searchBar: UISearchBar!
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
shouldShowSearchResults = true
if let queryString = searchBar.text {
filterList(queryString)
fetchData()
}
}
func filterList(_ queryString: String) {
if (queryString == "") {
searchPredicate = nil
}
else {
let brandPredicate = NSPredicate(format: "brand CONTAINS[c] %@", queryString)
let modelPredicate = NSPredicate(format: "model CONTAINS[c] %@", queryString)
let orPredicate = NSCompoundPredicate(type: .or, subpredicates: [brandPredicate, modelPredicate])
searchPredicate = orPredicate
}
}
...
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
let request = NSFetchRequest<NSFetchRequestResult>(entityName: filmEntity)
request.returnsDistinctResults = true
request.propertiesToFetch = ["brand", "model"]
request.sortDescriptors = [sortDescriptor]
request.predicate = searchPredicate
Enfin, connectez le SearchBar à son délégué.
J'espère que cela aide les autres
Une approche simple pour filtrer UITableView existant à l'aide de CoreData et qui est déjà triée comme vous le souhaitez.
C’est littéralement trop 5 minutes pour installer et travailler.
J'avais une UITableView
existante en utilisant CoreData
remplie avec des données de iCloud et qui a des interactions utilisateur assez compliquées et je ne voulais pas avoir à répliquer tout cela pour une UISearchViewController
. J'ai pu simplement ajouter un prédicat à la FetchRequest
existante déjà utilisée par la FetchResultsController
et filtrant les données déjà triées.
-(void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText
{
NSPredicate *filterPredicate;
if(searchText != nil && searchText.length > 0)
filterPredicate = [NSPredicate predicateWithFormat:@"(someField CONTAINS[cd] %@) OR (someOtherField CONTAINS[cd] %@)", searchText, searchText];
else
filterPredicate = nil;
_fetchedResultsController.fetchRequest.predicate = filterPredicate;
NSError *error = nil;
[_fetchedResultsController performFetch:&error];
[self.tableView reloadData];
}