fetchedResultsControllerのdynamicな変更


CoreDataを使ったUITableViewControllerでfetchedResultsControllerを何らかの操作に応じてダイナミックに変更して[tableView reloadData]で都度テーブル表示を変えるということは普通に考えるのではないかと思うのだけれど、なぜかググってもサンプルがなかなか出てこない。
それだけではなく、fetchedResultsControllerをdynamicに変更してはいけないと書いているサイトも多い。例えば以下の2つ
Dynamically Updating FetchedResultsController?
iphone – Changing the sortDescriptor of a FetchedResultsController dynamically. Ideas? – Stack Overflow
“Important: You must not modify the fetch request. For example, you must not change its predicate or the sort orderings.”
と書いてある。しかし、一方で(ようやく見つけた)以下のようなダイナミックに変更するためのメソッドを提供しているページもある。
Method to change to NSFetchedResultsController dynamically — Gist
やってはいけないというのは僕の読み違えだったのだろうか?
さらに、これを見ると、animationの処理の部分を除けば変更のためにやっていることは単に以下の初期化処理だけだ。

self.fetchedResultsController.delegate = nil;
self.fetchedResultsController = nil;
[fetchedResultsController release];

して、最後に [self.tableView reloadData]; するだけのことである。むしろ簡単すぎてサンプルがなかったのかもな。
僕の場合、ソートではなく self.condition に条件を入れて、self.conditionの内容の差し替えしてfetchedResultsController内でその変更を反映したかっただけなので、以下のように書ける。
なお、

@synthesize fetchedResultsController = fetchedResultsController_;
@synthesize managedObjectContext = managedObjectContext_;

である。それで、

– (void) reloadMyTable
{
 self.fetchedResultsController.delegate = nil;
 self.fetchedResultsController = nil;
 [fetchedResultsController_ release];
 fetchedResultsController_ = nil;
 if (![[self fetchedResultsController] performFetch:&error]) {
  NSLog(@”Unresolved error %@, %@”, error, [error userInfo]);
  exit(-1); // Fail
 }
  [self.tableView reloadData];
}

というのを作って、
おなじみの fetchedResultsController には、条件検索を追加して以下のようにする。

– (NSFetchedResultsController *)fetchedResultsController {
// Set up the fetched results controller if needed.
if (fetchedResultsController_ == nil) {
// Create the fetch request for the entity.
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
// Edit the entity name as appropriate.
NSEntityDescription *entity = [NSEntityDescription entityForName:@”Notes”
inManagedObjectContext:managedObjectContext_];
[fetchRequest setEntity:entity];

//ここに条件検索を追加
NSMutableString *predicateCommand = [[NSMutableString alloc] initWithString: @”condition1 = “];
[predicateCommand appendFormat:@”‘%@'”, self.condition];
NSPredicate *predicate = [NSPredicate predicateWithFormat: predicateCommand];
[fetchRequest setPredicate: predicate];
[predicateCommand release];

// Edit the sort key as appropriate.
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc]
initWithKey:@”displayOrder”
ascending:NO];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];

[fetchRequest setSortDescriptors:sortDescriptors];

[NSFetchedResultsController deleteCacheWithName:@”Root”];

// 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:managedObjectContext_
sectionNameKeyPath:nil cacheName:@”Root” ];
aFetchedResultsController.delegate = self;
self.fetchedResultsController = aFetchedResultsController;

[aFetchedResultsController release];
[fetchRequest release];
[sortDescriptor release];
[sortDescriptors release];
}

return fetchedResultsController_;
}

self.conditionを適当に設定してからreloadMyTableを呼ぶ。
一応、これでうまく行った。でも、これでいいのか不安である。

※補足 2011年8月19日
その後、fetchedResultsController の中身でフィルタリングを変えるのではなく、例えば
-(void) filterContentForSearchText:(NSString*)searchText
のようなメソッドを追加して、この中で、
NSFetchRequest *aRequest = [fetchedResultsController_ fetchRequest];
して、このaRequestに新しいpredicateを以下のようにセットし
[aRequest setPredicate:predicate];
[[self fetchedResultsController] performFetch:&error]
して、そのままfetchedResultsControllerを使うという方法があることを知った。
この方法の方がよいのかもしれない。
詳しくはこのメモサイトではなく本番サイトの http://www.smallmake.com にそのうち書こうと思う。

広告

UITextField が UIKeyboardWillShowNotification を拾わない件


ひとつのUIViewの中にUITextFieldとUITextViewがあります。UITextFieldがタイトルで、UITextViewが本文という感じです。UITextViewのほうはキーボードを表示したときに、キーボード高さ分縮めてやらないと、最後の行まで編集したときにスクロールせずカーソルがキーボードの下にかくれてしまいます。ですからUIKeyboardWillShowNotificationで判定します。そしてUIKeyboardWillShowNotification検知すると同時に、「done」ボタンをナビゲーションバーとかに表示させます(「add」ボタンを「done」に切り替える)。「done」をタップするとキーボードを片付けてUITextViewのサイズをもとにもどします。
タイトルを表示しているUITextFieldでも同じことをしようと思い、UITextFieldをタップして編集が始まりUIKeyboardWillShowNotificationを検知したら「done」ボタンを表示さようと思いました。しかし、なぜかこの場合は、UIKeyboardWillShowNotificationが来ません。なぜでしょう?
同じような問題について以下に解決方法があります。
Won’t receive UIKeyboardWillShowNotification – iPhone Dev SDK Forum
本人は、Add toolbar on top of keyboard – iPhone Dev SDK Forum よりa more clean way.だといっていますが、僕には高度すぎます。
よく考えたらキーボードのreturnキーをdoneボタンにしているのを見たことがあります。無理にナビゲーションバーにdoneボタンを置かなくても、TextFieldなのでこの方法でよいはず。
delegateをちゃんと設定して
– (void)textFieldDidBeginEditing:(UITextField *)textField
{
textField.returnKeyType = UIReturnKeyDone;
}
– (BOOL)textFieldShouldReturn:(UITextField *)textField
{
[textField resignFirstResponder];
return YES;
}
でよいこととしました。

self.tableView.frame = CGRectMake(….)はできないと知る


モーダルを表示。そのモーダルはUITableViewControllerでできている。そして、頭にToolBarをつけたいと思った。それで、viewDidLoad で
toolbar = [[[UIToolbar alloc] init] autorelease]; して
[self.view addSubview:toolbar] して
toolbar.frame = CGRectMake(….) でToolBarを頭に持ってくる。
そして、
self.tableView.frame = CGRectMake(….) でToolBar分の空きを頭に作る。
完璧!と思ったら、うまく行かない。
self.tableView.frame への設定が全く効かないだけでなく、どうも、そもそも self.view とはイコール self.tableView であるらしい。ToolBarはTableViewの中に食い込んでしまっている。
どうする?

[toolbar sizeToFit];
[self.tableView setTableHeaderView:toolbar];
(つまり、 self.tableView.tableHeaderView = toolbar; )
で解決。前の[toolbar sizeToFit];はtoolbarのサイズを正しく得るために必要なようだ。これで、CGRectMakeのための計算の全く必要ない。上記だけでよい。
また、デバイスを回転する場合は、
– (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
[toolbar sizeToFit];
}
とするだけで、ちゃんとランドスケープの時に幅の細いToolBarにしてくれる。

あっ。でも、これだとテーブルをスクロールしたときにToolBarも一緒に動いてしまう!だめだ。ToolBarは固定したいのだった。やはり、その場合はUIViewControllerを使ってTooBarとTabelViewを自分で載せるしかないのかもしれない。

なお、参考にしたのは下記サイトでした。この例のようにSearchBarの場合はこれでいいが、ToolBarはこれではまずかった!
Adding a Search Bar to a UITableView – Objective C – Snipplr Social Snippet Repository