CoreData と NSTableView に関して、いくつか書いたが、これらは練習。
NSTableView では entity 同士が relationship で繋がれたような階層的なデータは扱えないので、NSOutlineView というものが用意されている。
NSTableView の時と同様、Cell Based と View Based があり、挙動が違うようなんだが、イマイチ理解してない。
どちらが作成しやすいかといえば Cell Based 。
なお、デフォは View Based になっているようなので、NSOutlineView を貼り付けたら、右インスペクタを操作して Cell Based に設定しておく。
NSOutlineView
いろいろ調べたんだが、芯食った解説(特に Objective-C & MacOS 環境)に巡り合わなかったので、いきなり CoreDtata との連携を考えるのではなく NSOutlineView 単独で適当なデータを表示させる方向でいく。ここは慎重に。
で、作成。
クラス Person と NSMutableArray<Person *> *people を用意して *people を表示させたい。
階層がある場合には、セルクリックで展開できる。
まずは、ポイントを解説。
階層があるので、セルを表示するのに row と column では役不足。
そこで item という概念が登場する。
実態としては、この場合は *people を反映したなにかなんだが、多分、最初はなんのことかわからないと思う。
先に進もう。
NSOutlineDatasource プロトコルを適用した NSOutlineView を管理するクラスを作成する。
プロトコルを満たすために最低4つのメソッドを実装する必要がある。
なお、データを表示するだけなら、NSOutlineViewDatasource プロトコルを満たすだけでいい。delegate までは必要ない。
4つのメソッドは
-(NSInteger)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item;
-(BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item;
-(id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item;
-(id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item;
で、例えば、最初の outlineView: numberOfChildrenOfItem: は
-(NSInteger)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item{
//return !item ? [self.people count] :[[item children] count];
if(item){
return [[item children] count];
}else{
return [self.people count];
}
}
となる。
DataSource と item
まだ、公式ドキュメントで確認などはしていないのだが、動作から考えるに
・DataSource としてリンクさせたクラスのコレクションがテーブルで表示させる範囲
・コレクションに含まれるクラスが item
なのかなと推測しているし、例外は経験したことがない。
上の場合で言えば、NSMutableArray<Person *> *people 自体は要素ではないから、item ではない。
推測する、とぼかしたのは、コレクションすべてを試したわけではないから。
今のところ、取り扱いが簡単な NSMutableArray で試してうまく表示できたが、他の、例えば NSDictionary などは一切試していない。というか試したが、見事にコケた。書き方が悪いのか、それとも、仕様上そうなってないのかは不明。
チャレンジされた方がいたら、教えて欲しい。
また、「コレクションに含まれるクラスが item」とは書いたが、他のクラスを Person の子要素に含んでいても問題なく動く。
異なるクラスを 親-子-孫 の関係にした場合
「他のクラスを Person の子要素に含んでいても問題なく動く」とは書いたが、Person の子要素として Child クラスを設定し、さらにその子要素として GChild を設定した場合は、ダメっぽい。
NSString *className = NSStringFromClass([item class]);
NSLog(@"ClassName is: %@", className);
を適当な位置に入れたが、返されるのは Person のみ。
しかし、NSStringFromClass([item class])
という書き方は便利ですね。item のところにクラスがよくわからないインスタンスに置き換えれば、そのクラス名を返してくれます。
おそらく DataSorce = NSMutableArray<Person *> *people としたため、item のクラスとしては Person 以外取りようがないからだと思う。
しかし、DataSorce = NSMutableArray<NSObject *> *object と一般的にすると item は任意のクラスを取りうる。しかし、このやり方で3階層以上を実装するのは頭こんがらがりそう。
まあ、大雑把に表示はできたんですが。→自己解決。別記事にします。
NSOutlineView と CoreData を結びつける
結びつけること自体は、それほど難しくないと思う。
ただ、 One To Many の関係にある relationship の Many の側はことごとく NSSet になってしまうので、いまいち使いづらい。
いい方法ないだろうか?(→ NSSet 公式ページ)
クラスを生成した「後に」属性値を追加する
普通にできるっぽい。
.xcdatamodeled に attr を追加。該当するクラスに @property …. を追加する。
これができないと、ちょっとした変更をするたびに、クラスを生成する必要があるので、当然か。
実用的には、クラスと relationship をとりあえず決めておいて、細部は後で編集する、という使い方でいいかもしれない。
(続く)
気が早いかもしれないが、CoreData でデータも作成しておく。
いつものように Person – Child という親子関係の Entity を作成し、永続化させると sqlite には以下のように保存される。
対応するコードは以下の通り。
Child *child = [NSEntityDescription insertNewObjectForEntityForName:@"Child"
inManagedObjectContext:context];
child.name = @"taro";
Child *child2 = [NSEntityDescription insertNewObjectForEntityForName:@"Child"
inManagedObjectContext:context];
child2.name = @"kana";
Person *person = [NSEntityDescription insertNewObjectForEntityForName:@"Person"
inManagedObjectContext:context];
person.name = @"akiba";
NSSet *childs = [NSSet setWithObjects:child, child2, nil];
//[person addChildrenObject:child];
[person addChildren:childs];
Child のインスタンスを一度に複数登録する場合には、NSSet を使うので、そのように変更した。
アップル公式の参考情報
Xcode で NSOutlineView を使った際に右インスペクタに表示される参照情報。
気になったのでまとめた。
NSOutlineViewDataSource
Navigating Hierarchical Data Using Outline and Split Views
サンプル(↓)もあるのだが、立派すぎて初学者には参考にならないかな。
akiba