Как фильтровать NSFetchedResultsController (CoreData) с помощью UISearchDisplayController / UISearchBar
Я пытаюсь реализовать поисковый код в моем iPhone-приложении на базе CoreData. Я не уверен, как действовать дальше. В приложении уже есть NSFetchedResultsController с предикатом для извлечения данных для основного TableView. Я хочу убедиться, что я на правильном пути, прежде чем изменить слишком много кода. Я запутался, потому что многие из примеров основаны на массивах вместо CoreData.
Вот несколько вопросов:
-
Нужно ли иметь второй NSFetchedResultsController, который извлекает только соответствующие элементы, или я могу использовать тот же самый, что и основной TableView?
- В Vista Explorer, как я могу найти содержимое всех файлов в каталоге для слова?
- jqGrid: использование нескольких методов фильтрации данных
- Windows 8 не ищет / индексирует вторичные жесткие диски?
- Поиск строки в текстовом столбце в MySQL
- Как включить настраиваемые местоположения для индексирования в поиск в меню «Пуск»?
-
Если я использую один и тот же, это так же просто, как очистка кэша FRC, а затем изменение предиката в методе handleSearchForTerm: searchString? Должен ли предикат содержать исходный предикат, а также термины поиска или он помнит, что он использовал предикат для извлечения данных в первую очередь?
-
Как мне вернуться к исходным результатам? Я просто задал искомый предикат нулю? Не будет ли это убить исходный предикат, который был использован для получения результатов FRC в первую очередь?
Если у кого-нибудь есть примеры кода, использующего поиск в FRC, я был бы очень признателен!
- Excel: поиск списка строк в определенной строке с использованием формул массива?
- Firefox / Chrome отключает поиск в новой вкладке
- Поиск по регистру без учета регистра
- Двоичный поиск в массиве
- Outlook 2010 не разрешает поиск папок IMAP
- Универсальное поле поиска для jqgrid
- Поиск последовательности байтов в двоичном файле с помощью Java
- Множественный поиск по нескольким полям по умолчанию
Я на самом деле просто реализовал это в одном из моих проектов (ваш вопрос и другой неправильный ответ намекали, что делать). Я попробовал ответ Серджио, но имел проблемы с исключениями, когда на самом деле работал на устройстве.
Да, вы создаете два controllerа результатов выборки: один для обычного отображения, а другой для табличного представления UISearchBar.
Если вы используете только один FRC (NSFetchedResultsController), тогда исходный UITableView (а не вид таблицы поиска, который активен во время поиска), возможно, вызовет вызовы во время поиска и попытается неправильно использовать отфильтрованную версию вашего FRC, и вы увидите исключения выбрасывается неверное количество разделов или строк в разделах.
Вот что я сделал: у меня есть два FRC, доступных как свойства fetchedResultsController и searchFetchedResultsController. SearchFetchedResultsController не следует использовать, если нет поиска (при отмене поиска вы можете видеть ниже, что этот объект выпущен). Все методы UITableView должны выяснить, какой запрос таблицы он будет запрашивать, и какой применимый FRC вывести информацию. Методы делегатов FRC также должны выяснить, какой tableView необходимо обновить.
Удивительно, насколько это шаблонный код.
Соответствующие биты файла заголовка:
@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;
Соответствующие биты файла реализации:
@interface BlahViewController () @property (nonatomic, retain) NSFetchedResultsController *fetchedResultsController; @property (nonatomic, retain) NSFetchedResultsController *searchFetchedResultsController; @property (nonatomic, retain) UISearchDisplayController *mySearchDisplayController; @end
Я создал полезный метод для получения правильного FRC при работе со всеми методами 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; }
Делегировать методы для панели поиска:
#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; }
убедитесь, что вы используете правильное представление таблицы при получении обновлений из методов делегатов 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]; }
Другая информация о просмотре:
- (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:
- (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]; }
Некоторые отметили, что это можно сделать с помощью одного NSFetchedResultsController
. Вот что я сделал, и вот подробности. Это решение предполагает, что вы просто хотите отфильтровать таблицу и сохранить все остальные аспекты (порядок сортировки, расположение ячеек и т. Д.) Результатов поиска.
Сначала определите два свойства в подclassе UITableViewController
(с соответствующими @synthesize и dealloc, если применимо):
@property (nonatomic, retain) UISearchDisplayController *searchController; @property (nonatomic, retain) NSString *searchString;
Во-вторых, инициализируйте строку поиска в viewDidLoad:
ваш подclass 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];
В-третьих, реализуйте методы делегирования 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]; }
Наконец, в методе fetchedResultsController
измените NSPredicate
зависимости self.searchString
определения self.searchString
:
-(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; }
Мне потребовалось несколько попыток заставить это работать …
Мой ключ к пониманию заключался в понимании того, что здесь работают два tableViews. Один из них управляется моим controllerом view, а один управляется с помощью searchviewcontroller, а затем я могу проверить, кто из них активен и что делать правильно. Документация также была полезна:
Вот что я сделал –
Добавлен флаг 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;
Добавлен синтез в файле реализации.
Затем я добавил эти методы для поиска:
#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; }
Затем в controllerеWillChangeContent:
- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller { if ([self searchIsActive]) { [[[self searchDisplayController] searchResultsTableView] beginUpdates]; } else { [self.tableView beginUpdates]; } }
И controllerDidChangeContent:
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller { if ([self searchIsActive]) { [[[self searchDisplayController] searchResultsTableView] endUpdates]; } else { [self.tableView endUpdates]; } }
И удалите кеш при сбросе предиката.
Надеюсь это поможет.
Вы используете прямой поиск?
Если вы НЕ, вам, вероятно, нужен массив (или NSFetchedResultsController) с предыдущими поисками, которые вы использовали, когда пользователь нажимает «поиск», вы указываете своим FetchedResults изменить свой предикат.
В любом случае вам нужно будет каждый раз восстанавливать ваши FetchedResults. Я рекомендую использовать только один NSFetchedResultsController, так как вам придется дублировать свой код, и вам не нужно тратить память на то, что вы не показываете.
Просто убедитесь, что у вас есть переменная «SearchParameters» NSString, и ваш метод FetchedResults перестраивает ее для вас по мере необходимости, используя параметры поиска, если они доступны, вы должны просто сделать:
а) установить «searchParameters» на что-то (или нуль, если вы хотите получить все результаты).
b) отпустите и установите нулевой текущий объект NSFetchedResultsController.
c) перезагрузить данные таблицы.
Вот простой код:
- (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; }
Я столкнулся с той же задачей и нашел, что ПРОСТОЙ ПУТЬ ВОЗМОЖНО решить ее. В -fetchedResultsController
время вам нужно определить еще один метод, очень похожий на -fetchedResultsController
с пользовательским составным предикатом.
В моем личном случае мой -fetchedResultsController
выглядит так:
- (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; }
Как вы можете видеть, я получаю клиентов одного агентства, отфильтрованного по предикату agency.server_id
. В результате я tableView
свой контент в tableView
(все связанные с реализацией tableView
и fetchedResultsController
кода довольно стандартны). Чтобы реализовать searchField
я определяю метод делегата UISearchBarDelegate
. Я запускаю его с помощью метода поиска, например -reloadTableView
:
- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText { [self reloadTableView]; }
и, конечно, определение -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]; }
Этот кусок кода очень похож на первый, «стандартный» -fetchedResultsController
но внутри -fetchedResultsController
if-else:
+andPredicateWithSubpredicates:
– используя этот метод, мы можем установить предикат для сохранения результатов нашей основной первой выборки в tableView
+orPredicateWithSubpredicates
– используя этот метод, мы фильтруем существующую выборку по поисковому запросу из searchBar
В конце я устанавливаю массив предикатов как составной предикат для этой конкретной выборки. И для требуемых предикатов, ИЛИ для необязательных.
И это все! Вам не нужно ничего реализовывать. Счастливое кодирование!
Swift 3.0, UISearchController, NSFetchedResultsController и core data
Этот код будет работать на Swift 3.0 с Core Data
! Вам понадобится один метод делегата и несколько строк кода для фильтрации и поиска объектов из модели. Ничего не потребуется, если вы внедрили все FRC
и их методы delegate
а также searchController
.
Метод протокола 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 {} }
Это оно! Надеюсь, это поможет вам! благодаря
SWIFT 3.0
Используйте textField, UISearchDisplayController устарел с iOS 8, вам нужно будет использовать UISearchController. Вместо того, чтобы иметь дело с Search Controller, почему бы вам не создать свой собственный механизм поиска? Вы можете настроить его больше и иметь больше контроля над ним, и не нужно беспокоиться о том, что SearchController меняется и / или устарел.
Этот метод, который я использую, работает очень хорошо и не требует большого количества кода. Однако для этого требуется использовать Core Data и реализовать NSFetchedResultsController.
Сначала создайте TextField и зарегистрируйте его с помощью метода:
searchTextField?.addTarget(self, action: #selector(textFieldDidChange), for: UIControlEvents.editingChanged)
Затем создайте свой метод textFieldDidChange, описанный в селекторе, когда цель была добавлена:
func textFieldDidChange() { if let queryString = searchTextField.text { filterList(queryString) self.tableView.reloadData() } }
Затем вы хотите отфильтровать список в filterList()
используя предикат NSPredicate или NSCompound, если он более сложный. В моем методе filterList я фильтруюсь на основе имени сущности и имени объекта «subCategories» сущностей (отношение «один ко многим»).
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") } } } }
Я думаю, что у Луки есть лучший подход для этого. См. LargeDataSetSample и его причину
Он не использует FetchedResultsController
, но использует кеш при поиске, поэтому результаты поиска появляются намного быстрее, когда пользователь вводит больше в SearchBar
Я использовал его подход в своем приложении, и он работает нормально. Также помните, хотите ли вы работать с объектом Model, сделать его максимально простым, см. Мой ответ о setPropertiesToFetch
Вот способ обработки fetchedResults с несколькими наборами данных, которые являются как простыми, так и общими, чтобы применять практически в любом месте. Просто возьмите свои основные результаты в массив, когда какое-либо условие присутствует.
NSArray *results = [self.fetchedResultsController fetchedObjects];
Запросите массив, перейдя через него или что угодно, чтобы создать подмножество ваших основных получаемых результатов. И теперь вы можете либо использовать полный набор или подмножество, когда какое-либо условие присутствует.
Мне очень понравился подход @Josh O’Connor, где он не использует UISearchController
. Этот controller еще (Xcode 9) имеет ошибку макета, которую многие пытаются обходным путем.
Я вернулся к использованию UISearchBar
вместо UITextField
, и он работает очень хорошо. Мое требование для поиска / фильтра заключается в создании NSPredicate
. Это передается в 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
Наконец, подключите SearchBar к его делегату.
Надеюсь, это поможет другим
Простой подход для фильтрации существующего UITableView с использованием CoreData и который уже отсортирован по вашему желанию.
Это буквально тоже мне 5 минут, чтобы настроить и начать работать.
У меня был существующий UITableView
с использованием CoreData
заполненной данными из iCloud, и который имеет довольно сложные пользовательские взаимодействия, и я не хотел реплицировать все это для UISearchViewController
. Я смог просто добавить предикат к существующему FetchRequest
уже используемому FetchResultsController
и который фильтрует уже отсортированные данные.
-(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]; }