【问题标题】:How to filter NSFetchedResultsController (CoreData) with UISearchDisplayController/UISearchBar如何使用 UISearchDisplayController/UISearchBar 过滤 NSFetchedResultsController (CoreData)
【发布时间】:2011-05-27 03:46:22
【问题描述】:

我正在尝试在基于 CoreData 的 iPhone 应用中实现搜索代码。我不确定如何进行。该应用程序已经有一个 NSFetchedResultsController 和一个谓词来检索主 TableView 的数据。在更改太多代码之前,我想确保自己走在正确的道路上。我很困惑,因为很多示例都是基于数组而不是 CoreData。

这里有一些问题:

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

  2. 如果我使用同一个,是不是像清空FRC缓存然后在handleSearchForTerm:searchString方法中改变谓词一样简单?谓词是否必须包含初始谓词以及搜索词,或者它是否记得它首先使用谓词来检索数据?

  3. 如何恢复原始结果?我只是将搜索谓词设置为 nil 吗?这不会首先杀死用于检索 FRC 结果的原始谓词吗?

如果有人有任何使用 FRC 搜索的代码示例,我将不胜感激!

【问题讨论】:

  • @Brent,完美的解决方案,对我来说是一种享受!

标签: iphone search core-data uisearchbar uisearchdisplaycontroller


【解决方案1】:

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

是的,您创建了两个获取结果控制器:一个用于正常显示,另一个用于 UISearchBar 的表格视图。

如果您只使用一个 FRC (NSFetchedResultsController),那么原始 UITableView(不是搜索时处于活动状态的搜索表视图)可能会在您搜索时调用回调,并尝试错误地使用 FRC 的过滤版本,而您将看到关于不正确的节数或节中的行数引发的异常。

这就是我所做的:我有两个 FRC 可用作属性 fetchedResultsController 和 searchFetchedResultsController。除非有搜索,否则不应使用 searchFetchedResultsController(当搜索被取消时,您可以在下面看到该对象已被释放)。所有 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

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

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

【讨论】:

  • 这似乎工作得很好!谢谢,布伦特!我特别喜欢 fetchedResultsControllerForTableView: 方法。这让它变得非常容易!
  • 非常好的代码。正如 jschmidt 所说,自定义的“fetchedResultsControllerForTableView:”方法确实简化了整个过程。
  • 布伦特。你是男人。但是,这对你来说是一个新的挑战。使用后台处理执行此代码。我已经对我的应用程序的其他部分进行了一些次要的多线程处理,但这很难(至少对我来说)。我认为它会增加更好的用户体验。接受挑战了吗?
  • @BrentPriddy 谢谢!我将您的代码重构为Modify the Fetch Request,而不是每次搜索文本更改时都将searchFetchedResultsController 设置为nil
  • 在您的cellForRowAtIndexPath 中,您不应该像有人在this SO 问题中指出的那样从self.tableView 获取单元格吗?如果您不这样做,则不会显示自定义单元格。
【解决方案2】:

有些人评论说这可以通过单个NSFetchedResultsController 来完成。这就是我所做的,这里是详细信息。此解决方案假定您只想过滤表格并维护搜索结果的所有其他方面(排序顺序、单元格布局等)。

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

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

【讨论】:

  • 这个解决方案对我来说效果很好,而且更简单。谢谢!我只建议将 'if (self.searchString)' 调整为 'if (self.searchString.length)。如果您在开始搜索并从搜索栏中删除字符串后单击表格视图,这可以防止它崩溃。
【解决方案3】:

我试了几次才搞定...

我理解的关键是意识到这里有两个 tableViews 在工作。一个由我的 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;
}

然后在controllerWillChangeContent:

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

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

希望这会有所帮助。

【讨论】:

  • 还是没看懂,上面的例子很好,但是不完整,但是你的推荐应该可以,但是不行……
  • 您可以只检查活动表视图而不是使用 BOOL:if ( [self.tableView isEqual:self.searchDisplayController.searchResultsTableView] ) { ... }
  • @rwyland - 我的测试表明当搜索处于活动状态时 self.tableview 未设置为 searchdisplaycontroller.searchresultstableview。这些永远不会相等。
【解决方案4】:

Swift 3.0、UISearchController、NSFetchedResultsController 和核心数据

此代码将在带有Core Data 的 Swift 3.0 上运行!您将需要一个委托方法和几行代码来过滤和搜索模型中的对象。如果您已经实现了所有 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 {}
}

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

【讨论】:

    【解决方案5】:

    我面临同样的任务,并找到了最简单的方法来解决它。 简而言之:您需要再定义一个方法,与带有自定义复合谓词的-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];
    }
    

    这一堆代码和第一个非常相似,“标准”-fetchedResultsController BUT 里面的 if-else 语句是:

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

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

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

    仅此而已!您无需再执行任何操作。 编码愉快!

    【讨论】:

      【解决方案6】:

      您是否使用实时搜索?

      如果你不是,你可能想要一个数组(或 NSFetchedResultsController),其中包含你之前使用的搜索,当用户按下“搜索”时,你告诉 FetchedResults 更改其谓词。

      无论哪种方式,您都需要每次都重新构建 FetchedResults。我建议只使用一个 NSFetchedResultsController,因为您将不得不大量重复您的代码,而且您不需要在未显示的内容上浪费内存。

      只要确保您有一个 NSString "searchParameters" 变量,并且您的 FetchedResults 方法会根据需要为您重建它,使用搜索参数(如果可用),您应该这样做:

      a) 将“searchParameters”设置为某个值(或 nil,如果您想要所有结果)。

      b) 释放并设置为 nil 当前的 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;    
      }
      

      【讨论】:

      • 非常有趣!我会试试看。
      • 这似乎有效,但是当您的表由 FRC 填充时它会失败,searchTableView 是与您使用的主表视图不同的表。 FRC 委托方法在搜索时在内存不足的设备上到处乱吐,并且主 tableView 想要重新加载单元格。
      • 谁有这个项目模板的链接?我发现很难弄清楚去哪里。有一个工作模板作为参考会非常好。
      • @Brent,您应该检查是否是需要更改 FRC 委托方法的搜索表视图 - 如果您这样做并更新 FRC 和 UITableView 委托方法中的正确表,那么使用时一切都应该没问题主表视图和搜索表视图的 FRC。
      • @kervich 我相信你在上面描述我的答案,或者你是说你可以只用一个 FRC 做到这一点?
      【解决方案7】:

      SWIFT 3.0

      使用 textField,从 iOS 8 开始不推荐使用 UISearchDisplayController,您必须使用 UISearchController。你为什么不创建自己的搜索机制,而不是处理搜索控制器?您可以对其进行更多自定义并对其进行更多控制,而不必担心 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")
                  }
              }
          }
      }
      

      【讨论】:

        【解决方案8】:

        我认为 Luka 对此有更好的方法。见LargeDataSetSamplehis reason

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

        我在我的应用程序中使用了他的方法,并且效果很好。还请记住,如果您想使用 Model 对象,请使其尽可能简单,请参阅我关于setPropertiesToFetch的回答

        【讨论】:

          【解决方案9】:

          这是一种处理包含多个数据集的 fetchedResults 的方法,该方法既简单又通用,几乎可以应用于任何地方。当某些条件存在时,只需将您的主要结果抓取到一个数组中。

          NSArray *results = [self.fetchedResultsController fetchedObjects];
          

          通过遍历数组或任何您想要的方式来查询数组,以便创建主要 fetchedResults 的子集。现在,当某些条件存在时,您可以使用完整集或子集。

          【讨论】:

            【解决方案10】:

            我真的很喜欢@Josh O'Connor 的方法,他不使用UISearchController。这个控制器(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<NSFetchRequestResult>(entityName: filmEntity)
                    request.returnsDistinctResults = true
                    request.propertiesToFetch = ["brand", "model"]
            
                    request.sortDescriptors = [sortDescriptor]
                    request.predicate = searchPredicate
            

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

            我希望这对其他人有所帮助

            【讨论】:

              【解决方案11】:

              使用 CoreData 过滤现有 UITableView 的简单方法,并且已经按您想要的方式排序。

              这确实让我花了 5 分钟来设置和开始工作。

              我有一个现有的UITableView,使用CoreData 填充来自iCloud 的数据,并且它具有相当复杂的用户交互,我不想为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];
              }
              

              【讨论】:

                猜你喜欢
                • 2012-08-21
                • 1970-01-01
                • 2011-01-12
                • 2013-09-14
                • 2014-04-07
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多