CoreData: detal に tableView を表示する方法

いつもながらハマったね。CoreData で 1 対 N をやろうとするといつもハマるので、この手のプログラムを書くときはかなり萎える。

あらかじめよい点も記載しておくと、以前は CoreData で管理する table とか field を変更すると、その都度、header ファイルを更新する必要があったりして、すんごいめんどくさかったんだけど、最新の CoreData では、変更を加えても automatic に内部で更新されるので、手間が格段に減っている。革命的に良くなったにせよ、相変わらず 1 対 N の、特に、N 側の record を操作しようとするとハマるのだ。

ようやくうまくいったので、ここらで最短の手順で N 側の表示ができるようになるまでの方法を記しておこう。

そもそもハマるのは、Master 側は template が充実しているので、template に沿って必要な変更を加えればよいのだけれど、Detail の template は全く不足していて、色々こちらで指定してやる必要があるから。要領が分かっていれば何てことは無いのだけれど、素で作ろうとするとかなり厄介。さて、その方法は以下の通り。

tableView を Detail に配置。 次に、tableView を first responder に接続、dataSource を選択する。また、同じく tableView を first responder に接続し、delegate を選択。この 2 つを設定したということは、protocol の指定も必要になる。.h ファイルで、UITableViewDataSource, UITableViewDelegate を protocol として指定しておく。
この時点で起動すると Detail に遷移した時点で落ちる。理由は、tableView 必須の method を実装していないから。必須メソッドを実装すると落ちなくなる。必須の method は次の通り。必要最小限の実装にしてある。
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return 1;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
return cell;
}
cell の定義が済んでいないので、多分これでも落ちる。気にせず先に進もう。

Master の prepareForSegue で detailItem object が渡ってきているので、Detail で使用できる。detailItem とは、編集対象としている record と考えるとわかりやすい。relationship の指定がしてあるなら、N の record も一緒に来ている。
試しに、add ボタンを押したら 1 側のレコードを変更するようにしてみる。configureView に、template マンマで timestamp を表示する記述があえるので、真似て 1 のフイールド title を設定してみよう。string 型としたので 単に、
self.thisRecordTitle = tself.detailitem.title
にすれば大丈夫。ちなみに、UILabel の outlet 名を title としたら、UIViewController の予約語dだったためエラー発生。thisRecordTitle に変更したら問題なくなった。初歩的ながら罠にはまらないよう注意。

save は AppDelegate に記載があるので、それを呼ぶようにする。
具体的には次の通り。
AppDelegate* appDelegate = (AppDelegate*)[[UIApplication sharedApplication] delegate];
[appDelegate saveContext];
detail のヘッダーで、AppDelegate.h が include されていないので、そこは自分で補完しよう。

さて、1 側の保存方法は分かったとして、N 側の保存はどうやるのか。master から detail に遷移した段で、N の 1 record が編集対象になっているのか? と疑問を感じることもあるのだけれど、そうではなくて、1 の record から事象を見ていると考えると理解が早い。detailViewController に segue で遷移するときに、detailItem が引数として渡ってきていて、detailViewController に property として定義してあるので、detailItem を使えば N の追加が可能なのだ。

まず、編集中の 1 の record に N の record は幾つ関連づけられているか数を確認してみよう。やり方は、configureView で次のように書くとできる。
long hoge = [self.detailItem.relationship_name count];
count method を使うこの書き方は、array の書き方なんだけど、Set である relationship でも問題なく機能する。追加 method を記述していない今は、動作させたところで当然 0 になる。それでは、N に追加する method を実装しよう。
ボタンを追加し、action も追加しておく。
この actioon に対応する method に次の記述をする。
[self.detailItem addTo(N の table 名)Object:(N 型 record)];
わかりにくいですが、detailItem に対し、addTo(table name)Object:new method を実行するということ。coredata のバージョンが低いと、insertObject を使え、などと書いてある記事もあるけど、今の CoreData では、addTo(table 名)Object method になるので、いろいろ検索して調べてもわからないことが多いのだ。
ここまで書いてもまだ分かりにくい。Table 名が Colors だったとすると、addToColorsObject: method を使うことになる。

ここまでくると、Master から Detail に画面遷移すると、先ほど count していた数に変化が起こっているはず。先ほどは 0 だったのに、数が増えているはずだ。それにもかかわらず、まだ tableView に表示がされない訳だが、必須 method に表示のための記述をしてやればよい。

その前に cell の中身はいつも custumCell を使っているので今回もそうする。new でファイルを追加する。追加するのは UITableViewCell の sub class となるようにし、XIB ファイルを別途に生成することを忘れないように。この custom cell クラスに表示するための object を layout しておく。また、detailViewController には、今追加した class を include しよう。

前準備はこれで大体済んだ。cell の表示は先ほどの必須 method であった、 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath で指定する。custom cell を考慮した記述は次のようになる。
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString* CellIdentifier = @"favoriteColorNumeric";
UINib* nib = [UINib nibWithNibName:@"STTableViewCell" bundle:nil];
[tableView registerNib:nib forCellReuseIdentifier:CellIdentifier];
STTableViewCell* cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
NSMutableOrderedSet* savedItems = [[NSMutableOrderedSet alloc] initWithOrderedSet:_detailItem.toColors];
Colors* item = [savedItems objectAtIndex:indexPath.row];
cell.colorRed.text = [NSString stringWithFormat:@"%f", item.colorRed];
return cell;
}
肝となるのは、2 点ある。custom cell を使うので、class 名を指定してやる必要があること。identifier も指定すること。また、initWithOrderedSet で _detailItem.relationship 名を指定するのですが、ここで警告が出るようだと、CoreData の設定が間違っている。ordered の check を入れるとうまくいく(ってか、自動で check して欲しいのですが・・・)。
結局のところ、relationship の先にある record は、_detailItem.relationship 名.fielld 名でアクセス可能。これを知らないとマジハマる。
もう一つの必須 method も直しておこう。
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
long arraySize = [_detailItem.toColors count];
return arraySize;
}
これで完璧。ふうぅ〜