Cómo filtrar NSFetchedResultsController (CoreData) con UISearchDisplayController / UISearchBar

Estoy tratando de implementar el código de búsqueda en mi aplicación de iPhone basada en CoreData. No estoy seguro de cómo proceder. La aplicación ya tiene un NSFetchedResultsController con un predicado para recuperar los datos del TableView primario. Quiero asegurarme de estar en el camino correcto antes de cambiar demasiado código. Estoy confundido porque muchos de los ejemplos están basados ​​en matrices en lugar de CoreData.

Aquí hay algunas preguntas:

  1. ¿Debo tener un segundo NSFetchedResultsController que recupere solo los elementos coincidentes o puedo usar el mismo como TableView principal?

  2. Si uso el mismo, ¿es tan simple como borrar el caché FRC y luego cambiar el predicado en el método handleSearchForTerm: searchString? ¿El predicado debe contener el predicado inicial así como los términos de búsqueda o recuerda que utilizó un predicado para recuperar datos en primer lugar?

  3. ¿Cómo vuelvo a los resultados originales? ¿Acabo de establecer el predicado de búsqueda en nulo? ¿No matará eso el predicado original que se usó para recuperar los resultados de FRC en primer lugar?

Si alguien tiene algún ejemplo de código usando la búsqueda con el FRC, ¡lo agradecería enormemente!

De hecho, acabo de implementar esto en uno de mis proyectos (su pregunta y la otra respuesta incorrecta insinuaron qué hacer). Intenté la respuesta de Sergio, pero tuve problemas de excepción cuando realmente funcioné en un dispositivo.

Sí crea dos controladores de resultados de búsqueda: uno para la visualización normal y otro para la vista de tabla de UISearchBar.

Si solo usa un FRC (NSFetchedResultsController) entonces el UITableView original (no la vista de la tabla de búsqueda que está activa durante la búsqueda) posiblemente tendrá devoluciones de llamada mientras está buscando e intenta usar incorrectamente la versión filtrada de su FRC y verá excepciones arrojado sobre el número incorrecto de secciones o filas en las secciones.

Esto es lo que hice: tengo dos FRC disponibles como propiedades fetchedResultsController y searchFetchedResultsController. SearchFetchedResultsController no se debe utilizar a menos que haya una búsqueda (cuando se cancela la búsqueda, puede ver a continuación que este objeto está publicado). Todos los métodos de UITableView deben determinar qué vista de tabla consultará y de qué FRC aplicable extraer la información. Los métodos de delegado FRC también deben determinar qué tablaView actualizar.

Es sorprendente cuánto de este es un código repetitivo.

Pedacitos relevantes del archivo de encabezado:

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

partes relevantes del archivo de implementación:

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

Creé un método útil para recuperar el FRC correcto al trabajar con todos los métodos de 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  sectionInfo = [sections objectAtIndex:section]; numberOfRows = [sectionInfo numberOfObjects]; } return numberOfRows; } 

Delegar métodos para la barra de búsqueda:

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

asegúrese de utilizar la vista de tabla correcta al obtener actualizaciones de los métodos de delegado de FRC:

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

Otra información de vista:

 - (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 restred 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 { // restre 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; } } 

Código de creación de 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]; } 

Algunos han comentado que esto se puede hacer con un único NSFetchedResultsController . Eso es lo que hice, y aquí están los detalles. Esta solución asume que solo desea filtrar la tabla y mantener todos los otros aspectos (orden de clasificación, diseño de celda, etc.) de los resultados de búsqueda.

Primero, defina dos propiedades en su subclase UITableViewController (con el @synthesize apropiado y dealloc, si corresponde):

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

Segundo, inicialice la barra de búsqueda en el método viewDidLoad: de su subclase 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]; 

En tercer lugar, implemente los métodos de delegado UISearchDisplayController como este:

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

Finalmente, en el método fetchedResultsController , cambie NSPredicate dependiendo de si self.searchString está definido:

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

Me tomó varios bashs lograr que esto funcione …

Mi clave para entender fue darme cuenta de que hay dos tableViews trabajando aquí. Uno gestionado por mi viewcontroller y otro gestionado por el searchviewcontroller y luego podría probar para ver cuál está activo y hacer lo correcto. La documentación también fue útil:

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

Esto es lo que hice:

Se agregó el indicador searchIsActive:

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

Se agregó la sintetización en el archivo de implementación.

Luego agregué estos métodos para buscar:

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

Luego en controllerWillChangeContent:

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

Y controllerDidChangeContent:

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

Y elimine la caché al restablecer el predicado.

Espero que esto ayude.

¿Estás usando una búsqueda en vivo?

Si NO lo es, probablemente desee una matriz (o NSFetchedResultsController) con las búsquedas anteriores que usó, cuando el usuario presiona “buscar”, le dice a su FetchedResults que cambie su predicado.

De cualquier manera, tendrá que reconstruir sus FetchedResults todo el tiempo. Recomiendo usar solo un NSFetchedResultsController, ya que tendrás que duplicar mucho tu código y no necesitarás desperdiciar memoria en algo que no estás mostrando.

Solo asegúrate de tener una variable NSString “searchParameters” y tu método FetchedResults la reconstruye según sea necesario, usando los parámetros de búsqueda si está disponible, solo debes hacer:

a) establezca los “parámetros de búsqueda” en algo (o nulo, si desea todos los resultados).

b) suelte y establezca nulo el objeto NSFetchedResultsController actual.

c) volver a cargar los datos de la tabla.

Aquí hay un código 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; } 

Enfrenté la misma tarea y encontré LA MANERA MÁS SIMPLEMENTE POSIBLE para resolverlo. En breve: necesita definir un método más, muy similar a -fetchedResultsController con un predicado compuesto personalizado.

En mi caso personal mi -fetchedResultsController ve así:

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

Como puede ver, estoy buscando clientes de una agencia filtrados por el predicado agency.server_id . Como resultado, estoy recuperando mi contenido en un tableView (todos relacionados con la implementación de los tableView y fetchedResultsController es bastante estándar) también. Para implementar searchField , estoy definiendo un método delegado UISearchBarDelegate . Lo estoy activando con el método de búsqueda, digamos -reloadTableView :

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

y, por supuesto, la definición 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]; } 

Este grupo de código es muy similar al primero, “estándar” -fetchedResultsController PERO dentro de la instrucción if-else aquí es:

+andPredicateWithSubpredicates: utilizando este método podemos establecer un predicado para guardar los resultados de nuestra primera búsqueda principal en tableView

+orPredicateWithSubpredicates – con este método estamos filtrando la recuperación existente por consulta de búsqueda de searchBar

Al final estoy estableciendo una matriz de predicados como un predicado compuesto para esta búsqueda en particular. Y para los predicados requeridos, O para opcional.

¡Y eso es todo! No necesita implementar nada más. Feliz encoding!

Swift 3.0, UISearchController, NSFetchedResultsController y Core Data

¡Este código funcionará en Swift 3.0 con Core Data ! Necesitará un único método de delegado y unas pocas líneas de código para filtrar y buscar objetos del modelo. No se necesitará nada si ha implementado todos los FRC y sus métodos delegate , así como searchController .

El método de protocolo 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 {} } 

¡Eso es! Espero que te ayude! Gracias

SWIFT 3.0

Utilice un campo de texto, UISearchDisplayController está en desuso a partir de iOS 8, tendría que usar un UISearchController. En lugar de tratar con el controlador de búsqueda, ¿por qué no creas tu propio mecanismo de búsqueda? Puede personalizarlo más y tener más control sobre él, y no tener que preocuparse de que SearchController cambie y / o se desaproveche.

Este método que uso funciona muy bien y no requiere mucho código. Sin embargo, sí requiere que utilice Core Data e implemente NSFetchedResultsController.

Primero, crea un TextField y regístralo con un método:

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

Luego, cree su método textFieldDidChange, descrito en el selector cuando se agregó el objective:

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

Luego, desea filtrar la lista en el método filterList() utilizando el predicado NSPredicate o NSCompound si es más complejo. En mi método filterList, estoy filtrando en función del nombre de la entidad y el nombre del objeto “subCategories” de las entidades (una relación de uno a muchos).

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

Creo que Luka tiene un mejor enfoque para esto. Ver LargeDataSetSample y su razón

No usa FetchedResultsController , pero usa la caché cuando busca, por lo tanto, los resultados de búsqueda aparecen mucho más rápido cuando el usuario escribe más en SearchBar.

He utilizado su enfoque en mi aplicación y funciona bien. También recuerde que si desea trabajar con un objeto Model, hágalo lo más simple posible, vea mi respuesta sobre setPropertiesToFetch

Esta es una forma de manejar fetchedResults con múltiples conjuntos de datos que es tanto simple como general para aplicar en casi cualquier lugar. Simplemente toma tus resultados principales en una matriz cuando hay alguna condición presente.

 NSArray *results = [self.fetchedResultsController fetchedObjects]; 

Consulte la matriz mediante un bucle o lo que desee para crear un subconjunto de sus principales resultados de búsqueda. Y ahora puede usar el conjunto completo o subconjunto cuando hay alguna condición presente.

Realmente me gustó el enfoque de @Josh O’Connor, que no utiliza un UISearchController . Este controlador todavía (Xcode 9) tiene un error de diseño que muchos están tratando de resolver.

Volví a usar un UISearchBar lugar de un UITextField , y funciona muy bien. Mi requisito para la búsqueda / filtro es producir un NSPredicate . Esto se pasa al 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(entityName: filmEntity) request.returnsDistinctResults = true request.propertiesToFetch = ["brand", "model"] request.sortDescriptors = [sortDescriptor] request.predicate = searchPredicate 

Finalmente, conecte la barra de búsqueda a su delegado.

Espero que esto ayude a otros

Enfoque simple para filtrar UITableView existente usando CoreData y que ya está ordenado como lo desea.

Esto, literalmente, también me 5 minutos para configurar y trabajar.

Tenía un UITableView existente usando CoreData poblado con datos de iCloud y que tiene interacciones de usuario bastante complicadas y no quería tener que replicar todo eso para un UISearchViewController . Pude simplemente agregar un predicado a FetchRequest existente ya utilizado por FetchResultsController y que filtra los datos ya ordenados.

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