[解決済み] UISearchDisplayController/UISearchBarでNSFetchedResultsController(CoreData)をフィルタリングする方法
質問
CoreDataを使用したiPhoneアプリに検索コードを実装しようとしています。 どのように進めればいいのかわかりません。 このアプリには既にNSFetchedResultsControllerがあり、プライマリTableViewのデータを取得するための述語があります。 私は多くのコードを変更する前に、正しい道にいることを確認したいと思います。 多くの例がCoreDataの代わりに配列ベースなので、私は混乱しています。
以下は、いくつかの質問です。
-
一致する項目のみを取得する 2 つ目の NSFetchedResultsController を用意する必要がありますか、それともプライマリ TableView と同じものを使用できますか?
-
同じものを使用する場合、FRCキャッシュをクリアして、handleSearchForTerm:searchStringメソッドの述語を変更すればよいのでしょうか。 述語は検索語だけでなく、最初の述語も含まれていないといけないのか、そもそも述語を使ってデータを取得したことを記憶しているのか?
-
元の結果に戻るにはどうしたらよいですか? 検索述語をnilにすればいいのでしょうか? そうすると、そもそもFRCの結果を取得するために使われた元の述語が死んでしまうのでは?
FRCで検索を使ったコード例をご存知の方がいらっしゃいましたら、ぜひ教えてください。
どのように解決するのですか?
私は実際に私のプロジェクトの1つでこれを実装したばかりです(あなたの質問と他の間違った回答が何をすべきかのヒントとなりました)。 Sergioの答えを試しましたが、実際にデバイス上で実行すると例外的な問題が発生しました。
そうですね、2つの結果取得コントローラを作成します。1つは通常の表示用、もう1つはUISearchBarのテーブルビュー用です。
FRC (NSFetchedResultsController) を 1 つだけ使用すると、元の 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];
}
関連
-
[解決済み] UILongPressGestureRecognizerが押し下げ時に2回呼び出される
-
[解決済み】@try - Objective-Cのcatchブロック
-
[解決済み] iOSでHTMLをNSAttributedStringに変換する
-
[解決済み] プロダクションコードでNSLog()を使ってはいけないというのは本当ですか?
-
[解決済み] UITableViewHeaderFooterView。背景色を変更できない
-
[解決済み] インスタンスからクラス名を取得する
-
[解決済み] Xcodeで非推奨の警告を抑制する
-
[解決済み] UIViewのサブビューをセンタリングする方法
-
[解決済み] UIImageViewのコーナー半径の設定がうまくいかない
-
[解決済み] Objective-C Split()?
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
[解決済み] Cocoapodsの使用時にXcodeの警告を無視する
-
[解決済み】「推奨アイコンファイルがありません - バンドルには、ちょうど「120x120」ピクセル、.png 形式の iPhone / iPod Touch 用のアプリアイコンが含まれていません。
-
[解決済み] .xibファイルと.storyboardの違いは何ですか?
-
[解決済み] UIButton: 選択されたハイライト状態の画像を設定する
-
[解決済み] エラー : サービスは無効です
-
[解決済み] UITableViewHeaderFooterView。背景色を変更できない
-
[解決済み] cocoaアプリケーションのinfo plistにある「bundle display name」と「bundle name」の違いは何ですか?
-
[解決済み] UIWebViewの背景がクリアカラーに設定されているが、透明になっていない
-
[解決済み] UITextFieldの高さを設定するには?
-
[解決済み] iPhoneでテキスト入力のポップアップダイアログボックスを表示する簡単な方法は?