UISearchDisplayController / UISearchBarでNSFe​​tchedResultsController(CoreData)をフィルタリングする方法


146

CoreDataベースのiPhoneアプリに検索コードを実装しようとしています。どうすればいいのかわかりません。アプリには、プライマリTableViewのデータを取得するための述語を持つNSFetchedResultsControllerがすでにあります。あまりにも多くのコードを変更する前に、正しい方向に進んでいることを確認したいと思います。例の多くがCoreDataではなく配列ベースであるため、混乱しています。

ここにいくつかの質問があります:

  1. 一致するアイテムのみを取得する2番目のNSFetchedResultsControllerが必要ですか、それともプライマリTableViewと同じものを使用できますか?

  2. 同じものを使用する場合、FRCキャッシュをクリアしてから、handleSearchForTerm:searchStringメソッドの述語を変更するだけの簡単な方法ですか。述語には、最初の述語と検索語を含める必要がありますか、それとも、最初に述語を使用してデータを取得したことを覚えていますか?

  3. 元の結果に戻すにはどうすればよいですか?検索述語をnilに設定するだけですか?そもそも、FRCの結果を取得するために使用された元の述語を殺してしまうのではないでしょうか。

FRCでの検索を使用したコードの例を誰かが持っている場合、私はそれを大いに感謝します!


@ブレント、完璧な解決策は、私のために御馳走を働いた!
DetartrateD

回答:


193

私は実際にこれを私のプロジェクトの1つに実装しました(あなたの質問と他の間違った答えは何をすべきかを示唆しています)。私はセルジオの答えを試しましたが、実際にデバイスで実行しているときに例外の問題がありました。

はい、2つのフェッチ結果コントローラーを作成します。1つは通常の表示用で、もう1つはUISearchBarのテーブルビュー用です。

1つのFRC(NSFetchedResultsController)のみを使用している場合、検索中にアクティブな検索テーブルビューではなく、元のUITableViewで検索中にコールバックが呼び出され、FRCのフィルターバージョンを誤って使用しようとすると、例外が表示されます。正しくない数のセクションまたはセクション内の行についてスローされます。

これが私がしたことです:fetchedResultsControllerとsearchFetchedResultsControllerのプロパティとして利用可能な2つのFRCがあります。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];
}   

3
それは美しく機能するようです!ありがとう、ブレント!特にfetchedResultsControllerForTableView:メソッドが好きです。それはとても簡単です!
jschmidt 2010

2
途方もなく良いコード。jschmidtが言ったように、カスタムの「fetchedResultsControllerForTableView:」メソッドはプロセス全体を本当に単純化します。
Daniel Amitay、2011

ブレント。あなたは男です。しかし、ここにあなたのための新たな挑戦があります。バックグラウンド処理を使用したこのコードの実装。アプリの他の部分のマイナーなマルチスレッド化をいくつか実行しましたが、これは(少なくとも私にとっては)困難です。ユーザーエクスペリエンスが向上すると思います。勝負を受けて立つ?
jschmidt 2011

3
@BrentPriddyありがとう!検索テキストが変更されるたびにを設定するのではなくコードをリファクタリングしてフェッチ要求を変更しました。searchFetchedResultsControllernil
ma11hew28 2012年

2
あなたのcellForRowAtIndexPathではself.tableViewこの SOの質問で誰かが指摘したようにあなたは細胞を手に入れるべきではありませんか?これを行わないと、カスタムセルは表示されません。
amb

18

これは単一ので実行できるとコメントした人もいNSFetchedResultsControllerます。それが私がしたことであり、ここに詳細があります。このソリューションでは、テーブルをフィルタリングして、検索結果の他のすべての側面(ソート順、セルレイアウトなど)を維持することを想定しています。

最初に、UITableViewControllerサブクラスで2つのプロパティを定義します(該当する場合は、適切な@synthesizeおよびdeallocを使用)。

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

次に、サブクラスのviewDidLoad:メソッドで検索バーを初期化します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];

3番目に、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メソッドで次のNSPredicateif 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;
} 

1
このソリューションは私にとってはうまく機能し、はるかに簡単です。ありがとう!「if(self.searchString)」を「if(self.searchString.length)」に微調整することのみをお勧めします。これにより、検索を開始して検索バーから文字列を削除した後にテーブルビューをクリックした場合にクラッシュするのを防ぎます。
Guto Araujo 2013

17

これを機能させるために数回の試みが必要でした...

私が理解する鍵は、ここで2つのtableViewが機能していることを認識することでした。1つは私のviewcontrollerによって管理され、もう1つは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];
    }
}

そして、述語をリセットするときにキャッシュを削除します。

お役に立てれば。


まだわかりません。上の例は非常に良いですが、不完全ですが、推奨は機能しますが、機能しません...
Vladimir Stazhilov

あなただけの代わりにBOOLを使用してのアクティブなテーブルビューをチェックすることができ:if ( [self.tableView isEqual:self.searchDisplayController.searchResultsTableView] ) { ... }
rwyland

@rwyland-私のテストでは、検索がアクティブなときにself.tableviewがsearchdisplaycontroller.searchresultstableviewに設定されていないことが示されています。これらは決して等しくありません。
2013

5

私は同じタスクに直面し、それを解決するために最も簡単な方法が可能であることがわかりました。 まもなく:-fetchedResultsControllerカスタム複合述語と非常によく似た、もう1つのメソッドを定義する必要があります。

私の個人的なケースでは、私-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述語でフィルターされた1つの代理店のクライアントを取得しています。その結果、コンテンツも取得しています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 BUTと非常に似ています。

+andPredicateWithSubpredicates: -このメソッドを使用して、メインの最初のフェッチの結果を tableView

+orPredicateWithSubpredicates -この方法を使用して、検索クエリによる既存のフェッチをフィルタリングしています searchBar

最後に、この特定のフェッチの複合述語として述語の配列を設定しています。ANDは必須の述語、ORはオプションです。

そしてそれだけです!これ以上実装する必要はありません。幸せなコーディング!


5

ライブ検索を使用していますか?

そうでない場合は、おそらく以前に使用した検索を含む配列(またはNSFetchedResultsController)が必要です。ユーザーが「検索」を押すと、FetchedResultsにその述語を変更するように指示します。

どちらの方法でも、毎回FetchedResultsを再構築する必要があります。NSFetchedResultsControllerは1つだけ使用することをお勧めします。コードを何度も複製する必要があり、表示されていないものでメモリを無駄にする必要がないためです。

NSStringの「searchParameters」変数があることを確認し、FetchedResultsメソッドが必要に応じて変数を再構築します。可能な場合は検索パラメーターを使用して、次のようにします。

a)「searchParameters」を何か(または、すべての結果が必要な場合はnil)に設定します。

b)現在のNSFetchedResultsControllerオブジェクトを解放してnilに設定します。

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

とても興味深い!やってみます。
jschmidt 2010

これは機能しているようですが、テーブルにFRCが入力されると失敗します。searchTableViewは、使用するメインテーブルビューとは異なるテーブルです。FRCデリゲートメソッドは、検索時にメモリが少ないデバイス上にあり、メインのtableViewがセルをリロードする場合、いたるところに出現します。
ブレントプリディ

誰もがこのためのプロジェクトテンプレートへのリンクを持っていますか?何がどこにあるのかを理解するのは本当に難しいと思います。作業用のテンプレートをリファレンスとして用意しておくと非常に便利です。
RyeMAC3

@Brent、それがFRCデリゲートメソッドの変更を必要とする検索テーブルビューであるかどうかを確認する必要があります。FRCとUITableViewデリゲートのメソッドで正しいテーブルを更新すると、メインテーブルビューとテーブルビューを検索します。
kervich

@kervich上記の私の答えを説明していると思いますか、それとも1つのFRCでそれを実行できると言っていますか?
ブレントプリディ

5

Swift 3.0、UISearchController、NSFetchedResultsController、およびコアデータ

このコードは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 {}
}

それでおしまい!お役に立てば幸いです。ありがとう


1

SWIFT 3.0

textFieldを使用すると、UISearchDisplayControllerはiOS 8で非推奨になり、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」オブジェクト(1対多の関係)の名前に基づいてフィルタリングしています。

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

0

これにはルカの方が良いアプローチだと思います。LargeDataSetSampleその理由を参照してください

彼はを使用していませんFetchedResultsControllerが、検索時にキャッシュを使用しているため、ユーザーがSearchBarに入力する回数が多いほど検索結果がはるかに速く表示されます

私はアプリで彼のアプローチを使用しましたが、問題なく動作します。また、Modelオブジェクトを操作したい場合は、できるだけシンプルにしてください。setPropertiesToFetchに関する私の回答を参照してください


0

これは、ほぼすべての場所に適用するのに十分なほど単純で一般的である複数のデータセットを使用してfetchedResultsを処理する方法です。何らかの条件が存在する場合は、単純にメインの結果を配列に取得します。

NSArray *results = [self.fetchedResultsController fetchedObjects];

メインのfetchedResultsのサブセットを作成するために、配列または任意の配列をループしてクエリを実行します。そして、なんらかの条件が存在する場合は、フルセットまたはサブセットを使用できます。


0

私は@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をそのデリゲートに接続します。

これが他の人に役立つことを願っています


0

CoreDataを使用して既存のUITableViewをフィルターする簡単なアプローチ。

これは文字通り5分でセットアップして動作します。

iCloudからのデータが入力された既存のUITableViewusing CoreDataがあり、ユーザーの操作がかなり複雑で、ですべてを複製する必要はありませんでした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];
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.