Como filtrar NSFetchedResultsController (CoreData) com UISearchDisplayController / UISearchBar

Estou tentando implementar o código de pesquisa no meu aplicativo para iPhone baseado em CoreData. Não sei como proceder. O aplicativo já tem um NSFetchedResultsController com um predicado para recuperar os dados do TableView primário. Eu quero ter certeza de que estou no caminho certo antes de alterar muito código. Estou confuso porque muitos dos exemplos são baseados em array em vez de CoreData.

Aqui estão algumas perguntas:

  1. Preciso ter um segundo NSFetchedResultsController que recupera apenas os itens correspondentes ou posso usar o mesmo que o TableView primário?

  2. Se eu usar o mesmo, é tão simples quanto limpar o cache do FRC e, em seguida, alterar o predicado no método handleSearchForTerm: searchString? O predicado precisa conter o predicado inicial, bem como os termos de pesquisa, ou lembra que usou um predicado para recuperar dados em primeiro lugar?

  3. Como eu volto aos resultados originais? Eu apenas configuro o predicado de pesquisa como nulo? Isso não vai matar o predicado original que foi usado para recuperar os resultados do FRC em primeiro lugar?

Se alguém tiver algum exemplo de código usando a pesquisa com o FRC, eu agradeceria muito!

Na verdade, acabei de implementar isso em um dos meus projetos (sua pergunta e a outra resposta errada sugeriam o que fazer). Eu tentei a resposta de Sergio, mas tive problemas de exceção quando realmente rodando em um dispositivo.

Sim, você cria dois controladores de resultados de busca: um para a exibição normal e outro para a exibição de tabela da UISearchBar.

Se você usar apenas um FRC (NSFetchedResultsController), o UITableView original (e não a exibição da tabela de pesquisa que está ativa durante a pesquisa) possivelmente terá callbacks chamados enquanto você estiver pesquisando e tentar usar incorretamente a versão filtrada do FRC e você verá exceções jogado sobre o número incorreto de seções ou linhas nas seções.

Aqui está o que eu fiz: Eu tenho dois FRCs disponíveis como propriedades fetchedResultsController e searchFetchedResultsController. O searchFetchedResultsController não deve ser usado a menos que haja uma pesquisa (quando a pesquisa é cancelada, você pode ver abaixo que este object é liberado). Todos os methods UITableView devem descobrir qual visualização de tabela será consultada e qual FRC aplicável obterá as informações. Os methods delegates FRC também devem descobrir qual tableView para atualizar.

É surpreendente o quanto disso é código clichê.

Bits relevantes do arquivo de header:

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

bits relevantes do arquivo de implementação:

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

Eu criei um método útil para recuperar o FRC correto ao trabalhar com todos os methods 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 methods para a barra de pesquisa:

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

Certifique-se de usar a exibição de tabela correta ao obter atualizações dos methods de delegação do 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]; } 

Outras informações de visualização:

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

Código de criação 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]; } 

Alguns comentaram que isso pode ser feito com um único NSFetchedResultsController . Foi o que fiz e aqui estão os detalhes. Essa solução pressupõe que você deseja filtrar a tabela e manter todos os outros aspectos (ordem de sorting, layout da célula, etc.) dos resultados da pesquisa.

Primeiro, defina duas propriedades em sua subclass UITableViewController (com o @synthesize e dealloc apropriado, se aplicável):

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

Em segundo lugar, inicialize a barra de pesquisa no método viewDidLoad: da sua subclass 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]; 

Em terceiro lugar, implemente os methods 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, no método fetchedResultsController , altere o NSPredicate dependendo se self.searchString estiver 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; } 

Levei algumas tentativas para fazer isso funcionar …

Minha chave para entender foi perceber que há dois tableViews em funcionamento aqui. Um gerenciado pelo meu viewcontroller e um gerenciado pelo searchviewcontroller e então eu pude testar para ver qual está ativo e fazer a coisa certa. A documentação também foi útil:

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

Aqui está o que eu fiz –

Adicionado o sinalizador 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; 

Adicionado o sintetizar no arquivo de implementação.

Então eu adicionei esses methods para pesquisar:

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

Em seguida, em controllerWillChangeContent:

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

E controllerDidChangeContent:

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

E exclua o cache ao redefinir o predicado.

Espero que isto ajude.

Você está usando uma pesquisa ao vivo?

Se você não é, você provavelmente quer um array (ou um NSFetchedResultsController) com as pesquisas anteriores que você usou, quando o usuário pressiona “search”, você diz ao seu FetchedResults para mudar seu predicado.

De qualquer forma, você precisará reconstruir seus FetchedResults todas as vezes. Eu recomendo usar apenas um NSFetchedResultsController, já que você terá que duplicar muito seu código e você não precisará perder memory em algo que não está mostrando.

Apenas certifique-se de ter uma variável “searchParameters” do NSString e seu método FetchedResults a reconstruirá para você conforme necessário, usando os parâmetros de pesquisa, se disponíveis, você deve fazer apenas:

a) defina o “searchParameters” para algo (ou nulo, se você quiser todos os resultados).

b) libere e defina para nil o object atual NSFetchedResultsController.

c) recarregar os dados da tabela.

Aqui está um código simples:

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

Enfrentei a mesma tarefa e encontrei o MAIS SIMPLES MODO possível para resolvê-lo. Logo: você precisa definir mais um método, muito semelhante ao -fetchedResultsController com um predicado composto personalizado.

No meu caso pessoal my- -fetchedResultsController é assim:

 - (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 você pode ver, estou buscando clientes de uma agência filtrada por predicado agency.server_id . Como resultado, estou recuperando meu conteúdo em um tableView (todos relacionados à implementação do tableView e o código fetchedResultsController é bastante padrão) também. Para implementar searchField eu estou definindo um método delegado UISearchBarDelegate . Estou acionando com o método de pesquisa, digamos -reloadTableView :

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

e, claro, a definição 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 monte de código é muito parecido com o primeiro, “padrão” -fetchedResultsController mas dentro da declaração if-else aqui está:

+andPredicateWithSubpredicates: – usando este método, podemos definir um predicado para salvar os resultados de nossa primeira busca principal no tableView

+orPredicateWithSubpredicates – usando este método, +orPredicateWithSubpredicates a busca existente pela consulta de pesquisa do searchBar

No final, estou definindo a matriz de predicados como um predicado composto para essa busca específica. E para predicados obrigatórios OU para opcional.

E isso é tudo! Você não precisa implementar nada mais. Codificação feliz!

Swift 3.0, UISearchController, NSFetchedResultsController e Core Data

Este código funcionará no Swift 3.0 com Core Data ! Você precisará de um único método delegado e algumas linhas de código para filtrar e pesquisar objects do modelo. Nada será necessário se você tiver implementado todos os FRC e seus methods delegate , bem como o searchController .

O 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 {} } 

É isso aí! Espero que ajude você! obrigado

SWIFT 3.0

Use um textField, UISearchDisplayController está obsoleto a partir do iOS 8, você teria que usar um UISearchController. Em vez de lidar com o Search Controller, por que você não cria seu próprio mecanismo de pesquisa? Você pode personalizá-lo mais e ter mais controle sobre ele, e não precisa se preocupar com o SearchController mudar e / ou ser preterido.

Esse método que uso funciona muito bem e não requer muito código. Isso requer que você use o Core Data e implemente o NSFetchedResultsController.

Primeiro, crie um TextField e registre-o com um método:

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

Em seguida, crie seu método textFieldDidChange, descrito no seletor quando o destino foi adicionado:

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

Em seguida, você deseja filtrar a lista no método filterList() usando o predicado NSPredicate ou NSCompound, se for mais complexo. No meu método filterList, estou filtrando com base no nome da entidade e no nome do object “subCategories” de entidades (um para muitos relacionamentos).

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

Eu acho que Luka tem uma abordagem melhor para isso. Veja LargeDataSetSample e sua razão

Ele não usa o FetchedResultsController , mas usa o cache ao pesquisar, portanto, os resultados da pesquisa aparecem muito mais rapidamente quando o usuário digita mais no SearchBar

Eu usei sua abordagem no meu aplicativo e funciona bem. Lembre-se também se você deseja trabalhar com o object Model, torná-lo o mais simples possível, veja minha resposta sobre setPropertiesToFetch

Aqui está uma maneira de lidar com os fetchedResults com vários conjuntos de dados que são simples e gerais o suficiente para serem aplicados em praticamente qualquer lugar. Simplesmente pegue seus resultados principais em uma matriz quando alguma condição estiver presente.

 NSArray *results = [self.fetchedResultsController fetchedObjects]; 

Consultar o array fazendo um loop ou o que você desejar para criar um subconjunto de seus principais fetchedResults. E agora você pode usar o conjunto completo ou subconjunto quando alguma condição estiver presente.

Eu realmente gostei da abordagem do @Josh O’Connor onde ele não usa um UISearchController . Este controlador ainda (Xcode 9) tem um bug de layout que muitos estão tentando solucionar.

Eu voltei a usar um UISearchBar vez de um UITextField , e funciona muito bem. Minha exigência para a pesquisa / filtro é produzir um NSPredicate . Isso é passado para o 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 o SearchBar ao seu delegado.

Espero que isso ajude outros

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