UILabel で文字を表示しようとして、センターに揃えるにはどうするんだ、というのを、いつものようにテキトーにタプしたら Xcode が補完してくれるので UITextA と打つと、UITextAlignmentCenter があるので、これだ!と思ったら、discontinue だよ、の警告。
代わりに何使うんだ?調べたら、NSTextAlignmentCenter とのことで、重複して作っちゃったから、元からある関数使え、と言うことなんですかね。
UILabel で文字を表示しようとして、センターに揃えるにはどうするんだ、というのを、いつものようにテキトーにタプしたら Xcode が補完してくれるので UITextA と打つと、UITextAlignmentCenter があるので、これだ!と思ったら、discontinue だよ、の警告。
代わりに何使うんだ?調べたら、NSTextAlignmentCenter とのことで、重複して作っちゃったから、元からある関数使え、と言うことなんですかね。
scope の範囲を広げたいので、global 変数にするか、あるいは property として定義するか、どっちがいいのか。効能はどちらも変わらないのでどうするか。
自分としての結論は、その class の変数にしたいなら property で、class 内でしか通用しなくていいなら global 変数でと言うことなんだなと。
class の変数にすると、class のインスタンスを生成後、property に値を付与できる余地が出てくる。class に対して直接的な interface を提供できる。
その必要がないなら global 変数で良い。実は、定義する際も global 変数の方が簡便。
分かってしまえばそんなのあたりまえなのですが、今までなんかぼんやりしていたので、自分の中で区別が明解になってよかった。
CoreData で entity を追加・変更したときの話。以前は、simulator で build したアプリをわざわざ削除してからでないと、エラーになっていたのね。また、ヘッダーファイルを更新するなどの操作を手動でやる必要があったりして、分かってれば手順に沿って忘れず実行すれば良いのだけれど、わかるまでが大変で、build したらエラーになるし、エラーの原因がわかんないしですごい苦労していた訳だ。
それが、最新の CporeData では automatic になったようで、Xcode の editor では相変わらずエラーのシンボルが出たりするけれども、clean したのちに build すると更新が確実にかかるようで、 error にならないし、build もちゃんとかかる。ついでに言うと
、古いアプリも削除してくれて新しい形で build してくれるので、忌まわしい、起動直後に error になって、なんじゃこりゃ?がなくなった。
初めからこうしておけよ>Apple
苦心は続くよどこまでも・・・
Detail に N の record を表示する方法は分かった。でも、これだけでは不十分で、追加、削除ができないとダメだねと。今日は削除する方法がわかったので書いておこう。
Detail に N record を表示する table view を配置し、record を左にスライドすると、削除の赤表示が出て削除できるはず、と思ったら出来ない。理由は、delegate method を実装していないから。以下を実装するとできるようになる:
- (void) tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle) editingStyle forRowAtIndexPath:(NSIndexPath *) indexPath
この method を実装しただけで、左にスライドすると削除の印が出る。でも、method の中身を書かないと何も起こらない。削除するためのコードは次のような感じ。
NSOrderedSet* savedItems = [[NSMutableOrderedSet alloc] initWithOrderedSet:_detailItem.toColors];
Colors* item = [savedItems objectAtIndex:indexPath.row];
[_detailItem removeToColorsObject:item];
// 削除処理のアニメーション。reloadData すると、アニメーションが消えてしまうので・・・
[tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
[tableView reloadRowsAtIndexPaths:[tableView indexPathsForVisibleRows]
withRowAnimation:UITableViewRowAnimationAutomatic];
まず、_datailItem.toColors を取出して NSOrderdSet に収納。次に、indexPath.row で選択した record の順番を指定し、record を到底する。ここで指定するのは N の table の型で、上記の例なら Colors。
次に、delete するための method は removeTo(table name)Object になる。これ、わかんないよね。Xcode で remove とタイプして、補完してくれたのでこれかな?と思って使ってみたらうまくいったという・・・あと、頭に指定するのは _detailItem であって、_detailItem.toColors ではないぞと。
その下の code は蛇足というか、決まり文句のようなもので、削除したことを視覚的にわかりやすくしたのと、削除したのち、reload して削除した record が確実に表示されないようにしたという訳。
うーん、マジ難しかったね。
いつもながらハマったね。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;
}
これで完璧。ふうぅ〜
iPhone でよく起きていたのですが通信環境が悪い電車の中とかで読み込めない、読み込めたと思ったらアクセスできなくなるという現象があり、キャッシュをクリアすると直ったりすることが多い。どうも、storage をローカルに保存されるようで、そこが壊れてしまうとアクセスができなくなるというのがこれまでにわかったところ。
自宅の MacBook Safari でも同じようにアクセス出来なくなり、あー iPhone と同じ〜と思ってキャッシュクリアしてもダメ。なにこれ。2-3 日放っていたら直ってた。不思議だ。
楽天で商品が表示されないという現象があったのですが、楽天は必要最小限の利用にとどめているので特に問題なかったんですがいつの間にか直っていた。不思議だ。
似たような話で、iTunes で Genius で play list を作ろうと思ったら使えない。共有 disk に保存している音楽ファイルが、共有が切れてしまったため Genius 用のファイルを再作成する必要があるからみたいで、その進捗は user に見えないからいつ復活するのかわからない。これ、 sleep したら共有 disk が意図せず切れてしまうのがそもそも悪いんだと思うけど。
放っておいたら治る、偉大だ。
UIImageView にカメラロールから選んだ画像をセットし、ボタンが押されたら色分解する。画像がセットされていなかったら、何もしない。
UIimageView に画像がセットされているかどうか判定するにはどうするか。正解は次の通り。
if ( _cookingImage.image == nil )
{
// NSLog(@"no image");
return;
}
つまり、比較演算子の == を使うと。これ、わかんないね。
property の image をつけてなくてまずはまった。次に isEqual で比較したらうまくいかない。isEqual ではなんでダメなのか。結局わからず仕舞いなのですが、やりたいことはできたのでよしとしよう。