CoreDataベースのiPhoneアプリに検索コードを実装しようとしています。どうすればいいかわかりません。アプリには、プライマリTableViewのデータを取得するための述語を持つNSFetchedResultsControllerが既にあります。あまりにも多くのコードを変更する前に、正しい道にいることを確認したいと思います。サンプルの多くがCoreDataではなく配列ベースであるため、私は混乱しています。
以下にいくつかの質問を示します。
一致するアイテムのみを取得する2番目のNSFetchedResultsControllerが必要ですか、それともプライマリTableViewと同じものを使用できますか?
同じものを使用する場合、FRCキャッシュをクリアし、handleSearchForTerm:searchStringメソッドの述語を変更するのと同じくらい簡単ですか?述語には最初の述語と検索語を含める必要がありますか、それとも述語を使用して最初にデータを取得したことを覚えていますか?
元の結果に戻すにはどうすればよいですか?検索述語をnilに設定するだけですか?そもそもFRCの結果を取得するために使用された元の述語を殺しませんか?
FRCで検索を使用するコードの例があれば、大歓迎です!
私は実際にこれを私のプロジェクトの1つに実装しました(あなたの質問と他の間違った答えは何をすべきかを示唆しました)。 Sergioの答えを試しましたが、実際にデバイスで実行しているときに例外の問題がありました。
はい、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];
}
これは、単一のNSFetchedResultsController
で実行できるとコメントしている人もいます。それが私がしたことであり、ここに詳細があります。このソリューションでは、テーブルをフィルタリングして、検索結果の他のすべての側面(並べ替え順序、セルレイアウトなど)を維持することを想定しています。
最初に、UITableViewController
サブクラスで2つのプロパティを定義します(該当する場合、適切な@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];
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
メソッドで、self.searchString
が定義されているかどうかに応じてNSPredicate
を変更します。
-(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;
}
これを機能させるために数回試行しました...
ここで理解するための鍵は、2つのtableViewが動作していることを理解することでした。 1つは私のviewcontrollerによって管理され、もう1つはsearchviewcontrollerによって管理され、その後、どれがアクティブであるかをテストして正しいことを行うことができました。ドキュメントも役に立ちました:
これが私がしたことです-
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];
}
}
そして、述語をリセットするときにキャッシュを削除します。
お役に立てれば。
私は同じ課題に直面し、それを解決するためにTHE SIMPLEST WAY POSSIBLEを見つけました。 短い:もう1つのメソッドを定義する必要があります。これは、カスタム複合述語を使用した-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
述語でフィルターされた1つの代理店のクライアントを取得しています。その結果、私はtableView
(tableView
およびfetchedResultsController
コードの実装に関連するすべてがかなり標準的)でもコンテンツを取得しています。 searchField
を実装するために、UISearchBarDelegate
デリゲートメソッドを定義しています。 -reloadTableView
と言う検索メソッドでトリガーしています:
- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText
{
[self reloadTableView];
}
そしてもちろん-reloadTableView
の定義:
- (void)reloadTableView
{
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:@"Client"
inManagedObjectContext:[[PTDataManager sharedManager] managedObjectContext]];
[fetchRequest setEntity:entity];
NSSortDescriptor *sortByName1Descriptor = [[NSSortDescriptor alloc] initWithKey:@"lastname" ascending:YES];
NSSortDescriptor *sortByName2Descriptor = [[NSSortDescriptor alloc] initWithKey:@"firstname" ascending:YES];
NSSortDescriptor *sortByName3Descriptor = [[NSSortDescriptor alloc] initWithKey:@"middlename" ascending:YES];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects: sortByName1Descriptor, sortByName2Descriptor, sortByName3Descriptor, nil];
fetchRequest.sortDescriptors = sortDescriptors;
NSPredicate *idPredicate = [NSPredicate predicateWithFormat:@"agency_server_id CONTAINS[cd] %@", agency.server_id];
NSString *searchString = self.searchBar.text;
if (searchString.length > 0)
{
NSPredicate *firstNamePredicate = [NSPredicate predicateWithFormat:@"firstname CONTAINS[cd] %@", searchString];
NSPredicate *lastNamePredicate = [NSPredicate predicateWithFormat:@"lastname CONTAINS[cd] %@", searchString];
NSPredicate *middleNamePredicate = [NSPredicate predicateWithFormat:@"middlename CONTAINS[cd] %@", searchString];
NSPredicate *orPredicate = [NSCompoundPredicate orPredicateWithSubpredicates:[NSArray arrayWithObjects:firstNamePredicate, lastNamePredicate, middleNamePredicate, nil]];
NSPredicate *andPredicate = [NSCompoundPredicate andPredicateWithSubpredicates:[NSArray arrayWithObjects:idPredicate, nil]];
NSPredicate *finalPred = [NSCompoundPredicate andPredicateWithSubpredicates:[NSArray arrayWithObjects:orPredicate, andPredicate, nil]];
[fetchRequest setPredicate:finalPred];
}
else
{
[fetchRequest setPredicate:idPredicate];
}
self.fetchedResultsController = [[NSFetchedResultsController alloc]initWithFetchRequest:fetchRequest managedObjectContext:[[PTDataManager sharedManager] managedObjectContext] sectionNameKeyPath:nil cacheName:nil];
self.fetchedResultsController.delegate = self;
NSError *error = nil;
if (![self.fetchedResultsController performFetch:&error])
{
NSLog(@"Unresolved error %@, %@", [error localizedDescription], [error localizedFailureReason]);
};
[self.clientsTableView reloadData];
}
この一連のコードは、最初の「標準」-fetchedResultsController
[〜#〜] but [〜#〜] if-elseステートメントの内部に非常に似ています。
+andPredicateWithSubpredicates:
-このメソッドを使用して、メインの最初のフェッチの結果をtableView
に保存するための述語を設定できます
+orPredicateWithSubpredicates
-このメソッドを使用して、searchBar
からの検索クエリによって既存のフェッチをフィルタリングします
最後に、この特定のフェッチの複合述語として述語の配列を設定しています。 AND必須の述語の場合、ORオプションの場合。
そして、それだけです!これ以上実装する必要はありません。ハッピーコーディング!
ライブ検索を使用していますか?
そうでない場合は、おそらく、以前に使用した検索の配列(または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;
}
Swift 3.0、UISearchController、NSFetchedResultsControllerおよびCore Data
このコードは、Swift 3.0 with Core Data
!モデルからオブジェクトをフィルタリングおよび検索するには、単一のデリゲートメソッドと数行のコードが必要です。すべてのFRC
メソッドとdelegate
メソッド、およびsearchController
メソッドを実装している場合、何も必要ありません。
UISearchResultsUpdating
プロトコルメソッド
func updateSearchResults(for searchController: UISearchController) {
let text = searchController.searchBar.text
if (text?.isEmpty)! {
// Do something
} else {
self.fetchedResultsController.fetchRequest.predicate = NSPredicate(format: "( someString contains[cd] %@ )", text!)
}
do {
try self.fetchedResultsController.performFetch()
self.tableView.reloadData()
} catch {}
}
それでおしまい!それがあなたを助けることを願っています!ありがとう
Swift 3.0
TextFieldを使用すると、UISearchDisplayControllerはiOS 8で非推奨になります。UISearchControllerを使用する必要があります。検索コントローラーを扱う代わりに、独自の検索メカニズムを作成してみませんか?これをさらにカスタマイズして、より細かく制御でき、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()
}
}
_
次に、NSPredicateまたはNSCompound述語がより複雑な場合は、NSPredicateを使用して、filterList()
メソッドでリストをフィルタリングします。 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")
}
}
}
}
_
ルカにはこれに対するより良いアプローチがあると思います。 LargeDataSetSample および その理由 を参照してください
彼はFetchedResultsController
を使用せず、検索時にキャッシュを使用するため、ユーザーがSearchBarにさらに入力すると、検索結果がはるかに速く表示されます。
私は自分のアプリで彼のアプローチを使用しましたが、うまくいきます。また、Modelオブジェクトを使用する場合は、できるだけ単純にすることを忘れないでください。 setPropertiesToFetch に関する私の答えを参照してください。
次に、複数のデータセットを持つfetchedResultsを処理する方法を示します。これは、ほぼどこにでも適用できるほど単純で一般的なものです。何らかの条件が存在する場合、単純にメインの結果を配列に取得します。
NSArray *results = [self.fetchedResultsController fetchedObjects];
メインのfetchedResultsのサブセットを作成するために、配列または必要なものをループして配列を照会します。そして、何らかの条件が存在する場合、フルセットまたはサブセットを使用できるようになりました。
彼がUISearchController
を使用しない@Josh O'Connorのアプローチが本当に気に入りました。このコントローラー(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をデリゲートに接続します。
これが他の人に役立つことを願っています
CoreDataを使用して既存のUITableViewをフィルター処理する簡単なアプローチで、既に希望する方法でソートされています。
これは文字通り、セットアップして作業するのに5分かかります。
ICloudのデータを入力したUITableView
を使用する既存の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];
}