Wie man NSFetchedResultsController (CoreData) mit UISearchDisplayController / UISearchBar filtert

Ich versuche, Suchcode in meiner CoreData-basierten iPhone App zu implementieren. Ich bin mir nicht sicher, wie ich vorgehen soll. Die App verfügt bereits über einen NSFetchedResultsController mit einem Prädikat zum Abrufen der Daten für das primäre TableView. Ich möchte sicherstellen, dass ich auf dem richtigen Weg bin, bevor ich zu viel Code ändere. Ich bin verwirrt, weil so viele der Beispiele Array-basierte anstelle von CoreData sind.

Hier sind einige Fragen:

  1. Benötige ich einen zweiten NSFetchedResultsController, der nur die übereinstimmenden Elemente abruft, oder kann ich denselben als primäres TableView verwenden?

  2. Wenn ich das gleiche verwende, ist es so einfach wie das Löschen des FRC-Caches und dann das Prädikat in der Methode handleSearchForTerm: searchString zu ändern? Muss das Prädikat das ursprüngliche Prädikat sowie die Suchbegriffe enthalten oder erinnert es sich daran, dass es ein Prädikat verwendet hat, um überhaupt Daten abzurufen?

  3. Wie komme ich zu den ursprünglichen Ergebnissen zurück? Setzen Sie das Suchprädikat einfach auf null? Wird damit das ursprüngliche Prädikat, das zum Abrufen der FRC-Ergebnisse verwendet wurde, nicht gelöscht?

Wenn jemand Beispiele für Code hat, der die Suche mit dem FRC verwendet, würde ich das sehr schätzen!

Ich habe das gerade bei einem meiner Projekte implementiert (Ihre Frage und die andere falsche Antwort haben angedeutet, was zu tun ist). Ich habe Sergio’s Antwort ausprobiert, hatte aber Ausnahmeerrors, wenn ich auf einem Gerät lief.

Ja, Sie erstellen zwei Abrufergebnis-Controller: einen für die normale Anzeige und einen weiteren für die Tabellenansicht der UISearchBar.

Wenn Sie nur einen FRC (NSFetchedResultsController) verwenden, wird das ursprüngliche UITableView (nicht die während der Suche aktive Suchtabelle) möglicherweise während der Suche Callbacks enthalten und versuchen, die gefilterte Version Ihres FRC falsch zu verwenden, und Sie sehen Ausnahmen um falsche Anzahl von Abschnitten oder Zeilen in Abschnitten geworfen.

Hier ist, was ich getan habe: Ich habe zwei FRCs als Eigenschaften abgerufeneResultsController und searchFetchedResultsController. Der searchFetchedResultsController sollte nur bei einer Suche verwendet werden (wenn die Suche abgebrochen wird, können Sie unten sehen, dass dieses Objekt freigegeben wird). Alle UITableView-Methoden müssen herausfinden, welche Tabellenansicht abgefragt wird und auf welche FRC die Informationen übertragen werden sollen. Die FRC-Delegatmethoden müssen auch herausfinden, welche tableView aktualisiert werden soll.

Es ist überraschend, wie viel davon der Code ist.

Relevante Bits der Header-Datei:

@interface BlahViewController : UITableViewController  { // 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; 

Relevante Bits der Implementierungsdatei:

 @interface BlahViewController () @property (nonatomic, retain) NSFetchedResultsController *fetchedResultsController; @property (nonatomic, retain) NSFetchedResultsController *searchFetchedResultsController; @property (nonatomic, retain) UISearchDisplayController *mySearchDisplayController; @end 

Ich habe eine hilfreiche Methode zum Abrufen der richtigen FRC erstellt, wenn Sie mit allen UITableViewDelegate / DataSource-Methoden arbeiten:

 - (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  sectionInfo = [sections objectAtIndex:section]; numberOfRows = [sectionInfo numberOfObjects]; } return numberOfRows; } 

Methoden für die Suchleiste delegieren:

 #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; } 

Stellen Sie sicher, dass Sie die richtige Tabellenansicht verwenden, wenn Sie Updates von den FRC-Delegatmethoden erhalten:

 - (void)controllerWillChangeContent:(NSFetchedResultsController *)controller { UITableView *tableView = controller == self.fetchedResultsController ? self.tableView : self.searchDisplayController.searchResultsTableView; [tableView beginUpdates]; } - (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id )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]; } 

Weitere Informationen zur Ansicht:

 - (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; } } 

FRC-Erstellungscode:

 - (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]; } 

Einige haben NSFetchedResultsController dass dies mit einem einzelnen NSFetchedResultsController getan werden NSFetchedResultsController . Das habe ich gemacht, und hier sind die Details. Bei dieser Lösung wird davon ausgegangen, dass Sie nur die Tabelle filtern und alle anderen Aspekte (Sortierreihenfolge, Zellenlayout usw.) der Suchergebnisse beibehalten möchten.

Definieren Sie zuerst zwei Eigenschaften in Ihrer UITableViewController Unterklasse (mit den entsprechenden @ synthesize und dealloc, falls zutreffend):

 @property (nonatomic, retain) UISearchDisplayController *searchController; @property (nonatomic, retain) NSString *searchString; 

Zweitens, initialisiere die Suchleiste in der viewDidLoad: Methode deiner UITableViewController Unterklasse:

 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]; 

Drittens implementieren Sie die UISearchDisplayController wie UISearchDisplayController :

 // 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]; } 

Schließlich ändern NSPredicate in der Methode ” fetchedResultsController ” das NSPredicate abhängig davon, ob self.searchString definiert ist:

 -(NSFetchedResultsController *)fetchedResultsController { if (fetchedResultsController == nil) { // removed for brevity NSPredicate *predicate; if (self.searchString) { // predicate that uses searchString (used by UISearchDisplayController) // eg, [NSPredicate predicateWithFormat:@"name CONTAINS[cd] %@", self.searchString]; predicate = ... } else { predicate = ... // predicate without searchString (used by UITableViewController) } // removed for brevity } return fetchedResultsController; } 

Ich brauchte ein paar Versuche, damit das funktioniert …

Mein Schlüssel zum Verständnis war zu erkennen, dass hier zwei TableViews arbeiten. Eine wurde von meinem Viewcontroller verwaltet und eine von der searchviewcontroller verwaltet und dann konnte ich testen, welche aktiv ist und das Richtige tun. Die Dokumentation war auch hilfreich:

http://developer.apple.com/library/ios/#documentation/uikit/reference/UISearchDisplayController_Class/Reference/Reference.html

Folgendes habe ich getan –

Die searchIsActive-Flagge hinzugefügt:

 @interface ItemTableViewController : UITableViewController  { 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; 

Die Synthese wurde in der Implementierungsdatei hinzugefügt.

Dann habe ich diese Methoden zur Suche hinzugefügt:

 #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; } 

Dann in controllerWillChangeContent:

 - (void)controllerWillChangeContent:(NSFetchedResultsController *)controller { if ([self searchIsActive]) { [[[self searchDisplayController] searchResultsTableView] beginUpdates]; } else { [self.tableView beginUpdates]; } } 

Und controllerDidChangeContent:

 - (void)controllerDidChangeContent:(NSFetchedResultsController *)controller { if ([self searchIsActive]) { [[[self searchDisplayController] searchResultsTableView] endUpdates]; } else { [self.tableView endUpdates]; } } 

Löschen Sie den Cache, wenn Sie das Prädikat zurücksetzen.

Hoffe das hilft.

Verwenden Sie eine Live-Suche?

Wenn Sie NICHT sind, möchten Sie wahrscheinlich ein Array (oder einen NSFetchedResultsController) mit den vorherigen Suchanfragen, die Sie verwendet haben, wenn der Benutzer auf “Suchen” klickt, geben Sie Ihren FetchedResults an, ihr Prädikat zu ändern.

In jedem Fall müssen Sie Ihre FetchedResults jedes Mal neu erstellen. Ich empfehle, nur einen NSFetchedResultsController zu verwenden, da Sie Ihren Code sehr oft duplizieren müssen und Sie keinen Speicher in etwas verschwenden müssen, das Sie nicht zeigen.

Stellen Sie nur sicher, dass Sie eine NSString “searchParameters” -Variable haben und die FetchedResults-Methode diese für Sie nach Bedarf neu erstellt. Verwenden Sie dazu die Suchparameter, falls verfügbar, sollten Sie Folgendes tun:

a) setze die “searchParameters” auf etwas (oder nil, wenn du alle Ergebnisse willst).

b) Geben Sie das aktuelle NSFetchedResultsController-Objekt frei und setzen Sie es auf null.

c) Laden Sie die Tabellendaten neu.

Hier ist ein einfacher Code:

 - (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; } 

Ich stand vor der gleichen Aufgabe und fand DIE EINFACHE MÖGLICHKEIT , sie zu lösen. Kurz -fetchedResultsController : Sie müssen eine weitere Methode definieren, die sehr ähnlich zu -fetchedResultsController mit einem benutzerdefinierten zusammengesetzten Prädikat ist.

In meinem persönlichen Fall sieht my- -fetchedResultsController aus:

 - (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; } 

Wie Sie sehen können, agency.server_id ich Kunden von einer Agentur, gefiltert nach agency.server_id Prädikat. Als Ergebnis tableView ich meinen Inhalt in einem tableView (alles was mit der Implementierung von tableView und fetchedResultsController Code von tableView fetchedResultsController , ist ziemlich Standard). Zur Implementierung von searchField definiere ich eine UISearchBarDelegate Delegate-Methode. Ich -reloadTableView es mit der Suchmethode aus, sagen wir -reloadTableView :

 - (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText { [self reloadTableView]; } 

und natürlich die Definition von -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]; } 

Dieser Code-Haufen ist dem ersten, “standard” -fetchedResultsController sehr ähnlich, ABER in der if-else-statement ist hier:

+andPredicateWithSubpredicates: – Mit dieser Methode können wir ein Prädikat setzen, um Ergebnisse unseres ersten tableView in der tableView

+orPredicateWithSubpredicates – Mit dieser Methode filtern wir den bestehenden Abruf nach Suchabfragen von searchBar

Am Ende setze ich Array von Prädikaten als zusammengesetztes Prädikat für diesen bestimmten Abruf. UND für erforderliche Prädikate, ODER für optional.

Und das ist alles! Sie müssen nichts mehr implementieren. Glückliche Kodierung!

Swift 3.0, UISearchController, NSFetchedResultsController und Core Data

Dieser Code funktioniert mit Swift 3.0 mit Core Data ! Sie benötigen eine einzelne Delegate-Methode und einige Codezeilen zum Filtern und Suchen von Objekten aus dem Modell. Nichts wird benötigt, wenn Sie alle FRC und ihre delegate sowie searchController .

Die Protokollmethode 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 {} } 

Das ist es! Hoffe es hilft dir! Vielen Dank

SWIFT 3.0

Verwenden Sie ein textField, UISearchDisplayController ist ab iOS 8 veraltet, Sie müssten einen UISearchController verwenden. Anstatt sich mit dem Such-Controller zu beschäftigen, warum erstellen Sie nicht Ihren eigenen Suchmechanismus? Sie können es mehr anpassen und mehr Kontrolle darüber haben. Sie müssen sich keine Sorgen darüber machen, dass sich SearchController ändert und / oder veraltet ist.

Diese Methode, die ich verwende, funktioniert sehr gut und erfordert nicht viel Code. Sie müssen jedoch Core Data verwenden und NSFetchedResultsController implementieren.

Erstellen Sie zunächst ein TextField und registrieren Sie es mit einer Methode:

 searchTextField?.addTarget(self, action: #selector(textFieldDidChange), for: UIControlEvents.editingChanged) 

Dann erstellen Sie Ihre textFieldDidChange-Methode, die im Selektor beschrieben wird, als das Ziel hinzugefügt wurde:

 func textFieldDidChange() { if let queryString = searchTextField.text { filterList(queryString) self.tableView.reloadData() } } 

Dann möchten Sie die Liste in der filterList() -Methode mit NSPredicate oder NSCompound Prädikat filtern, wenn es komplexer ist. In meiner filterList-Methode filtere ich anhand des Namens der Entität und des Namens des Objekts “subCategories” der Entitäten (eine Eins-zu-viele-Beziehung).

 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") } } } } 

Ich denke, Luka hat einen besseren Ansatz dafür. Siehe LargeDataSetSample und seinen Grund

Er verwendet nicht den FetchedResultsController , sondern verwendet den Cache beim Suchen, daher erscheinen die Suchergebnisse viel schneller, wenn der Benutzer in SearchBar mehr eingibt

Ich habe seinen Ansatz in meiner App verwendet und es funktioniert OK. Denken Sie auch daran, wenn Sie mit Model-Objekt arbeiten möchten, machen Sie es so einfach wie möglich, sehen Sie sich meine Antwort zu setPropertiesToFetch an

Hier ist eine Möglichkeit, abgehandelte Ergebnisse mit mehreren Datensätzen zu behandeln, die sowohl einfach als auch allgemein genug sind, um fast überall angewendet zu werden. Greife einfach deine Hauptergebnisse zu einem Array, wenn eine Bedingung vorliegt.

 NSArray *results = [self.fetchedResultsController fetchedObjects]; 

Fragen Sie das Array ab, indem Sie es durchlaufen oder was immer Sie wünschen, um eine Teilmenge Ihrer wichtigsten abgerufenen Ergebnisse zu erstellen. Und jetzt können Sie entweder die gesamte Menge oder Teilmenge verwenden, wenn eine Bedingung vorliegt.

Ich mochte den Ansatz von @Josh O’Connor, bei dem er keinen UISearchController . Dieser Controller (Xcode 9) hat einen Layout-Fehler, den viele zu umgehen versuchen.

Ich habe wieder eine UISearchBar anstelle eines UITextField , und es funktioniert ganz gut. Meine Anforderung für die Suche / den Filter besteht darin, ein NSPredicate zu erzeugen. Dies wird an den FRC weitergegeben:

  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(entityName: filmEntity) request.returnsDistinctResults = true request.propertiesToFetch = ["brand", "model"] request.sortDescriptors = [sortDescriptor] request.predicate = searchPredicate 

Verbinden Sie schließlich die SearchBar mit ihrem Delegaten.

Ich hoffe, das hilft anderen

Simple Approach to Filter existing UITableView using CoreData and which is already sorted how you want.

This literally too me 5 minutes to setup and get working.

I had an existing UITableView using CoreData populated with Data from iCloud and which has pretty complicated user interactions and I didn’t want to have to replicate all that for a UISearchViewController . I was able to simply add a predicate to the existing FetchRequest already used by the FetchResultsController and which filters the already sorted data.

 -(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]; }