如何使用 UISearchDisplayController/UISearchBar 过滤 NSFetpedResultsController (CoreData)

我正在尝试在基于 CoreData 的 iPhone 应用程序中实现搜索代码。我不知道该怎么办。这个应用程序已经有了一个带谓词的 NSFetcheResultsController,用于检索主 TableView 的数据。在更改太多代码之前,我想确保自己处于正确的路径上。我很困惑,因为很多示例都是基于数组的,而不是基于 CoreData 的。

以下是一些问题:

  1. 我是否需要第二个 NSFetcheResultsController,它只检索匹配的项,还是可以使用与主 TableView 相同的一个?

  2. 如果我使用相同的方法,是否就像清除 FRC 缓存,然后在 handleSearchForTerm: searchString 方法中更改谓词一样简单?谓词是否必须包含初始谓词和搜索条件,或者它是否记得首先使用谓词来检索数据?

  3. 我怎样才能回到最初的结果?我是否只是将搜索谓词设置为 nil?这不会扼杀最初用于检索 FRC 结果的原始谓词吗?

如果任何人有任何使用 FRC 搜索代码的例子,我会非常感激!

54909 次浏览

你用的是实时搜索吗?

如果不是,那么您可能需要一个数组(或者 NSFetcheResultsController) ,其中包含您之前使用的搜索,当用户按下“搜索”键时,您将告诉 FetcheResults 更改其谓词。

无论哪种方式,您都需要每次都重新生成获取的结果。我建议只使用一个 NSFetchResultsController,因为您将不得不大量复制代码,而且不需要在没有显示的内存中浪费内存。

只要确保您有一个 NSString“ searchParameter”变量,并且 FetpedResults 方法根据需要为您重新构建它,如果可用的话,使用搜索参数,您应该这样做:

A)将“ searchParameter”设置为某个值(或者,如果您希望得到所有结果,则为 nil)。

B)释放并设置为当前 NSFetcheResultsController 对象为空。

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

我实际上只是在我的一个项目中实现了这个(你的问题和另一个错误的答案暗示了该怎么做)。我尝试了塞尔吉奥的回答,但实际上在设备上运行时有异常问题。

是的,您创建了两个提取结果控制器: 一个用于普通显示,另一个用于 UISearchBar 的表视图。

如果你只使用一个 FRC (NSFetchResultsController) ,那么原始的 UITableView (不是搜索时处于活动状态的搜索表视图)可能会在你搜索时调用回调函数,并试图错误地使用过滤后的 FRC 版本,你会看到因为错误的节数或节中的行数而抛出的异常。

下面是我所做的: 我有两个可用的 FRC,分别作为属性 fetchResultsController 和 searchFetchResultsController。只有在进行搜索(当搜索被取消时,您可以在下面看到这个对象被释放)时,才应该使用 searchFetcheResultsController。所有 UITableView 方法都必须确定它将查询哪个表视图,以及从哪个适用的 FRC 中提取信息。FRC 委托方法还必须确定要更新哪个 tableView。

令人惊讶的是,这些代码中有多少是样板代码。

头文件的相关部分:

@interface BlahViewController : UITableViewController <UISearchBarDelegate, NSFetchedResultsControllerDelegate, UISearchDisplayDelegate>
{
// other class ivars


// required ivars for this example
NSFetchedResultsController *fetchedResultsController_;
NSFetchedResultsController *searchFetchedResultsController_;
NSManagedObjectContext *managedObjectContext_;


// The saved state of the search UI if a memory warning removed the view.
NSString        *savedSearchTerm_;
NSInteger       savedScopeButtonIndex_;
BOOL            searchWasActive_;
}
@property (nonatomic, retain) NSManagedObjectContext *managedObjectContext;
@property (nonatomic, retain, readonly) NSFetchedResultsController *fetchedResultsController;


@property (nonatomic, copy) NSString *savedSearchTerm;
@property (nonatomic) NSInteger savedScopeButtonIndex;
@property (nonatomic) BOOL searchWasActive;

执行文件的相关部分:

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

我创建了一个有用的方法来检索正确的 FRC,当使用所有 UITableViewgenerate/DataSource 方法时:

- (NSFetchedResultsController *)fetchedResultsControllerForTableView:(UITableView *)tableView
{
return tableView == self.tableView ? self.fetchedResultsController : self.searchFetchedResultsController;
}


- (void)fetchedResultsController:(NSFetchedResultsController *)fetchedResultsController configureCell:(UITableViewCell *)theCell atIndexPath:(NSIndexPath *)theIndexPath
{
// your cell guts here
}


- (UITableViewCell *)tableView:(UITableView *)theTableView cellForRowAtIndexPath:(NSIndexPath *)theIndexPath
{
CallTableCell *cell = (CallTableCell *)[theTableView dequeueReusableCellWithIdentifier:@"CallTableCell"];
if (cell == nil)
{
cell = [[[CallTableCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"CallTableCell"] autorelease];
}


[self fetchedResultsController:[self fetchedResultsControllerForTableView:theTableView] configureCell:cell atIndexPath:theIndexPath];
return cell;
}


- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
NSInteger count = [[[self fetchedResultsControllerForTableView:tableView] sections] count];


return count;
}




- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
NSInteger numberOfRows = 0;
NSFetchedResultsController *fetchController = [self fetchedResultsControllerForTableView:tableView];
NSArray *sections = fetchController.sections;
if(sections.count > 0)
{
id <NSFetchedResultsSectionInfo> sectionInfo = [sections objectAtIndex:section];
numberOfRows = [sectionInfo numberOfObjects];
}


return numberOfRows;


}

搜索栏的委托方法:

#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 <NSFetchedResultsSectionInfo>)sectionInfo
atIndex:(NSUInteger)sectionIndex
forChangeType:(NSFetchedResultsChangeType)type
{
UITableView *tableView = controller == self.fetchedResultsController ? self.tableView : self.searchDisplayController.searchResultsTableView;


switch(type)
{
case NSFetchedResultsChangeInsert:
[tableView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
break;


case NSFetchedResultsChangeDelete:
[tableView deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
break;
}
}




- (void)controller:(NSFetchedResultsController *)controller
didChangeObject:(id)anObject
atIndexPath:(NSIndexPath *)theIndexPath
forChangeType:(NSFetchedResultsChangeType)type
newIndexPath:(NSIndexPath *)newIndexPath
{
UITableView *tableView = controller == self.fetchedResultsController ? self.tableView : self.searchDisplayController.searchResultsTableView;


switch(type)
{
case NSFetchedResultsChangeInsert:
[tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
break;


case NSFetchedResultsChangeDelete:
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:theIndexPath] withRowAnimation:UITableViewRowAnimationFade];
break;


case NSFetchedResultsChangeUpdate:
[self fetchedResultsController:controller configureCell:[tableView cellForRowAtIndexPath:theIndexPath] atIndexPath:theIndexPath];
break;


case NSFetchedResultsChangeMove:
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:theIndexPath] withRowAnimation:UITableViewRowAnimationFade];
[tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath]withRowAnimation:UITableViewRowAnimationFade];
break;
}
}




- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller
{
UITableView *tableView = controller == self.fetchedResultsController ? self.tableView : self.searchDisplayController.searchResultsTableView;
[tableView endUpdates];
}

其他观点信息:

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

我试了好几次才修好。

我理解的关键是意识到这里有两个 tableView 在工作。一个由我的 viewcontroller 管理,一个由 searchviewcontroller 管理,然后我可以测试哪个是活动的,并做正确的事情。这些文件也很有帮助:

Http://developer.apple.com/library/ios/#documentation/uikit/reference/uisearchdisplaycontroller_class/reference/reference.html

我是这么做的

添加了 searchIsActive 标志:

@interface ItemTableViewController : UITableViewController <NSFetchedResultsControllerDelegate, UISearchDisplayDelegate, UISearchBarDelegate> {


NSString *sectionNameKeyPath;
NSArray *sortDescriptors;




@private
NSFetchedResultsController *fetchedResultsController_;
NSManagedObjectContext *managedObjectContext_;


BOOL searchIsActive;


}


@property (nonatomic, retain) NSManagedObjectContext *managedObjectContext;
@property (nonatomic, retain) NSFetchedResultsController *fetchedResultsController;
@property (nonatomic, retain) NSString *sectionNameKeyPath;
@property (nonatomic, retain) NSArray *sortDescriptors;
@property (nonatomic) BOOL searchIsActive;

在实现文件中添加了合成。

然后我将这些方法添加到搜索中:

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

然后在控制器 WillChangeContent:

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

内容:

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

并在重置谓词时删除缓存。

希望这个能帮上忙。

有人评论说,这可以用一个 NSFetchedResultsController来完成。我就是这么做的,这是细节。这个解决方案假设您只是想过滤掉表,并维护搜索结果的所有其他方面(排序顺序、单元格布局等)。

首先,在 UITableViewController子类中定义两个属性(如果适用的话,使用适当的@synize 和 deloc) :

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

其次,在 UITableViewController子类的 viewDidLoad:方法中初始化搜索栏:

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的定义是:

-(NSFetchedResultsController *)fetchedResultsController {
if (fetchedResultsController == nil) {


// removed for brevity


NSPredicate *predicate;


if (self.searchString) {
// predicate that uses searchString (used by UISearchDisplayController)
// e.g., [NSPredicate predicateWithFormat:@"name CONTAINS[cd] %@", self.searchString];
predicate = ...
} else {
predicate = ... // predicate without searchString (used by UITableViewController)
}


// removed for brevity


}


return fetchedResultsController;
}

我面临着同样的任务,并找到了 最简单的方法来解决它。 简而言之: 您需要再定义一个方法,它非常类似于使用自定义复合谓词的 -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中检索我的内容(所有与 tableViewfetchedResultsController代码的实现相关的内容都是相当标准的)。 为了实现 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];
}

这组代码非常类似于 if-else 语句中的第一个“标准”-fetchedResultsController 但是:

使用这个方法,我们可以设置一个谓词来保存我们在 tableView中的第一次主要获取的结果

+orPredicateWithSubpredicates-使用这个方法,我们通过从 searchBar搜索查询来过滤现有的提取

最后,我将谓词数组设置为这个特定提取的复合谓词。AND 表示必需的谓词,OR 表示可选谓词。

就是这样! 你不需要再执行任何东西了。 编程愉快!

我认为卢卡有一个更好的方法。参见 LargeDataSetSample他的理由

他不使用 FetchedResultsController,而是在搜索时使用缓存,因此当用户在 SearchBar 中键入更多内容时,搜索结果会显示得更快

我在我的应用程序中使用了他的方法,效果还不错。还要记住,如果你想使用模型对象,使它尽可能简单,请参阅我对 SetPropertiesToFetch的回答

下面是一种处理具有多个数据集的 fetchResults 的方法,这种方法既简单又通用,几乎可以应用于任何地方。当出现某些条件时,只需将主要结果抓取到数组中。

NSArray *results = [self.fetchedResultsController fetchedObjects];

通过循环查询数组或其他方法来创建 main fetchResults 的子集。现在您可以使用完整集或者当某些条件存在时使用子集。

SWIFT 3.0

使用 textField,UISearchDisplayController 从 iOS8开始就不推荐使用,您必须使用 UISearchController。为什么不创建自己的搜索机制,而是使用 Search Controller?您可以对它进行更多的自定义,并对它进行更多的控制,而不必担心 SearchController 的更改和/或被弃用。

我使用的这个方法工作得很好,并且不需要很多代码。但是,它确实要求您使用核心数据并实现 NSFetchResultsController。

首先,创建一个 TextField 并用一个方法注册它:

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

然后创建 textFieldDidChange 方法,在添加目标时在选择器中进行描述:

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

然后,如果比较复杂的话,可以使用 NSPreate 或 NSComposite 谓词在 filterList()方法中筛选列表。在 filterList 方法中,基于实体的名称和实体“ subCategory”对象(一对多关系)的名称进行筛选。

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

Swift 3.0,UISearchController,NSFetchResults Controller 和核心数据

这段代码可以在 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 {}
}

就是这样! 希望对你有帮助! 谢谢

我真的很喜欢@Josh O’Connor 的方法,他没有使用 UISearchController。这个控制器(Xcode 9)仍然有一个布局错误,许多人试图解决这个问题。

我确实恢复使用 UISearchBar而不是 UITextField,它工作得很好。我对搜索/过滤器的要求是生成一个 NSPredicate。这是通过财务报告理事会:

   class ViewController: UIViewController, UITableViewDelegate,
UITableViewDataSource, UISearchBarDelegate {


@IBOutlet var searchBar: UISearchBar!


func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {


shouldShowSearchResults = true


if let queryString = searchBar.text {
filterList(queryString)


fetchData()
}
}






func filterList(_ queryString: String) {
if (queryString == "") {
searchPredicate = nil
}
else {
let brandPredicate = NSPredicate(format: "brand CONTAINS[c] %@", queryString)
let modelPredicate = NSPredicate(format: "model CONTAINS[c] %@", queryString)
let orPredicate = NSCompoundPredicate(type: .or, subpredicates: [brandPredicate, modelPredicate])
searchPredicate = orPredicate
}


}

...

let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
let request = NSFetchRequest<NSFetchRequestResult>(entityName: filmEntity)
request.returnsDistinctResults = true
request.propertiesToFetch = ["brand", "model"]


request.sortDescriptors = [sortDescriptor]
request.predicate = searchPredicate

最后,将 SearchBar 连接到它的委托。

我希望这能帮到其他人

使用 CoreData 筛选现有 UITableView 的简单方法,这些 UITableView 已经按照您希望的方式排序。

这真的是太我5分钟,设置和开始工作。

我有一个现有的 UITableView,使用的是从 iCloud 中填充的 Data 的 CoreData,它具有非常复杂的用户交互,我不想为 UISearchViewController复制所有这些。我能够简单地向 FetchResultsController已经使用的现有 FetchRequest添加一个谓词,该谓词过滤已经排序的数据。

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