Study CoreData 7 ~親子三代奮闘記~

開発楽しんでますか?

iPhone/iPad&Macアプリ開発でのデータの扱いを便利に、
そして高速に効率よく使えるようにしてくれるフレームワークを学ぶためのこの『Studay CoreData』。

ついに今日で一週間が終了です。
パチパチパチパチ〜♬
 
 
みなさん、宿題はやってきましたか〜?
もしかしたら、ちょっと分からない部分もあったかもですね。

前回は、Eventのタイトルを入力するための[AddTitleViewController]を用意するところまでやりましたね。
で、それを[RootViewController]につないでおいてください。
ってのが宿題でした。

それじゃ、今日もXcodeで今までやってきた『TodoCore』プロジェクトを開いてはじめていくとしましょう!

注意:投稿者自身もCoraDataについて勉強中のため、このシリーズには誤りが含まれている可能性があります。もし、間違いに気付かれた方はコメント欄もしくはtwitterなどでご指摘いただけると幸いです<(_ _)>
また、開発環境はXcode3.2.3 iPhone SDK 4です。実機でのテストなどは自己責任でお願いいたします。

 
 
準備はいいですか?

では、はじまりはじまり〜♬
 
 
まずは宿題の答え合わせから!

っとは言っても、以下で説明するコードと全く同じになっていなくても構いません。
要は結果的に同じ処理になっていれば良いですから。

 
 

1. 編集用ビューコントローラとの適切な関係(たぶん)

まず、Root/AddTitleViewControllerを繋げるために変更する場所は、当然、[addButton]のアクションである[insertNewObject]ですよね。
宿題前はこんな感じになっていたはずです。

前回までで色々あったように、ここで
『新規データオブジェクトの作成と保存』
が行われているってことは分かりましたね。
つまり、現時点でのCoreDataのキモのような処理がされている事になります。

で、ここでちょっとした疑問が出てくるわけです。

[newManagedObject]は次のViewControllerへ渡すとして…
『保存の処理はどうしたらいいの?』

ここでちょっと悩んでしまった方もいらっしゃるんじゃないでしょうか?

この『保存をどうするか?』ですが、実はいくつかの方法があります。
簡単に考えても以下の3つ以上はあるかと。

A. 保存してから[AddTitleViewController]に渡す
B. 保存せずに渡して、[AddTitleViewController]側で保存する。
C. 保存せずに渡して、[AddTitleViewController]で値を入れたら、そのデータを返してもらって[RootViewController]で保存する。

大ざっぱに言うと
『[newManagedObject]が変更された状態が保存されればいい』
ので、どの方法も間違いではないと思います。
 
 
ただ、Aに関しては
[データオブジェクト作成時]、[変更時]と二回の保存作業が必要になり、効率的に疑問が残ります。
このデータモデルは[キャンセル]される可能性もあるので
『何もされずにキャンセルされるかもしれないデータをいちいち保存する』
のはどうなのかな?と。

次にB、これがシンプルで良さそうなんですが、
今後、色々な編集用のコントローラを追加していった場合のコトを考えると、各コントローラで保存したりしなかったりという管理方法は、混乱を招きやすいような気がします。
それと、今回の[sectionNumber]のように、[fetchedResultsController]から情報を取得する必要がある場合、編集用コントローラ側にもそれを渡さなければいけないので大変そうですね。

で、Cの場合はちょっと構造が複雑になる可能性もありそうな気もしますが、『RootViewControllerで一元管理できる』という点で使いやすそうな気がします。
 
 
そう考えるとC. の方法がいいですかね。
(Appleの『CoreDataBooks』(サンプルコード)もこの形のようです。)

ってことで[AddTitleViewController]の表示時には、
まずは何もせずに空のデータオブジェクトを作って、それを渡せば
とりあえずはOKです。
 
 

2. 実際のつなぎかた。(宿題の解答)

結果的に、以下のような新しい[AddTitleViewController表示用のメソッド]を作ることにしました。
(念のため[insertNewObject]はそのまま残してあります。)

データオブジェクトの作成の部分はそのまま流用してます。

そして値は何も入れずに[addTitleViewController]に渡して、一応タイトルも入れて。
あとは、
新たにナビコントローラを作って[addTitleViewController]を入れて
モーダルビューとして設定して表示
。って感じですね。

そしたら忘れずに[addButton]のアクションを[showModalAddTitleView]に変更しておきます。
まずはこれで、AddTitleViewControllerが繋がりましたね。
(心配な方はビルドして確認してみてください。)
 
 
それじゃ、保存の処理のことは一旦後回しにして、[AddTitleViewController.m]での作業をしていきましょう!
 

3. 人生にはムダなこともある。

まずは[textFieldのアクション]ですが…結論から言いましょう!

何もしなくてオッケー牧場っ!

ここでヘタに

[self.managedObject setValue:textField.text forKey:@"title"];

みたいな処理をさせてしまうと、
テキストが一文字入力/削除される度にデータオブジェクトの値が変更されてムダな処理が増えます。
データオブジェクトへの値の入力は、保存される前に一度だけされれば何の問題もないですし、最終的にキャンセルになれば一度も入力しなくて済むわけです。

なので牧場っ!!
で、

[textField addTarget:self action:nil 
	forControlEvents:UIControlEventAllEditingEvents];

は、まるごと削除しちまいましょ。
 
 
そうなると残りは肝心の『保存とキャンセル』ですね。
これは両方とも[RootViewController]でやるって決めました。
つまり、どちらかのボタンが押された後に[RootViewController]へデータを渡さなきゃいけないってこと。

でも今の状態では、子の[AddTitleViewController]から親の[RootViewController]にメッセージを送るはできない(親子が逆なら出来ますが)…。

ってことで[デリゲート]の出番です。
 

4. 子供が親に文句を言える権限 (デリゲート)

デリゲートについて『まだ、よく分からない…』って方もいるかもしれないので、簡単に説明しておきますね。
『そんなの知ってるYo!』な方はスルーしてください<(_ _)>

 
デリゲートをこれまたおおざっぱに説明すると、上に書いたタイトルそのまんま。
『子供が親に文句を言う権利!!』みたいなものです。
(UIViewControllerとUIView、UIButtonを例にして説明します。)
 
 
つまり、どういうことかと言うと、

親である[UIViewController]内で作った子[UIView]があるとします。
この場合、
親コントローラーは子ビューのサイズや色、大きさなどを設定できますよね。
(親が子に対して『あ〜しろ、こうしろっ!』って命令してる感じ)

これに対して
子ビューは親コントローラの色や形などを変えることは出来ない
(子供は文句が言えないし、言っても怒られる(エラーが返される)だけ。>o<;)

では今度は、
そのViewが[drawRect:]内などでビューの子となる[UIButton]を作った
とします。

すると今度はさっきとは違って、子ボタンは親ビューに

『オレ、今タップされたから、オヤジの色変えてくれよ』

って言えるうえに、Viewは実際に言われた通りに色を変えちゃったりするわけです。

これは別に[UIViewController]が頑固オヤジで[UIView]が気弱な子供、[UIButton]は反抗期の孫。ってコトではなくて…

ここがポイント!

[button ..... target:self action....];

ここで、アクションを送るターゲットとして[self]をセットしている事で、
親(self)に子(button)が文句を言う許可をしてあげてるってわけ。
 
 
で、この場合はターゲットですが、
デリゲートも同じように機能させることができるんです。
デリゲートは以前に紹介した[プロトコル](メソッドの集合体)なので、デリゲートの機能を持たせるにはまず、そのメソッドを定めた[ほにゃららデリゲート]というプロトコルを作る必要があります。

では、それを用意してデリゲートを使えるようにしましょう。
 

5. デリゲートを用意する。

今回の場合は[RootViewController]が[AddTitleViewController]を作る時

addViewController.delegate = self;

と出来るようにしたいので、

[AddTitleViewController]にデリゲートプロパティを持たせて、
[RootViewController]は[AddTitleViewControllerのデリゲートメソッド]に対応させます。

まずは、デリゲート名を
[AddTitleViewControllerDelegate]として、
デリゲートメソッドは
[- (void)didEditWithViewController:(UIViewController *)controller saved:(BOOL)saved]
としましょう。

つまり、
[controller]で編集が終わったから保存かキャンセル処理([saved])してね』
って言えるようにします。

引数を[NSManagedObject]ではなく[UIViewController]にしたのは、汎用性を考えてという理由と、managedObjectは、

[(AddTitleViewController *)controller managedObject]

とすれば取得できるからです。
 
 
すると、AddTitleViewController.hはこんな感じになります。

まず、プロトコルの宣言をして、
デリゲート用のインスタンス変数プロパティ(assign属性)を追加。

そして[interfaceブロック]の@endの後にデリゲートプロトコルを追加します。
(プロトコルの宣言をしているところを消して、そこに下のデリゲートプロトコルを移動させてもいいのですが、見た目的にちょっとアレなので…)
 
 
AddTitleViewController.mでは、@synthesizeを追加(例の書き方で)して
[dealloc]で self.delegate = nilするだけです。
デリゲートメソッドの実装は、そのメソッドを使うクラス側(この場合はRootViewController)で行います。
 
 
で、RootViewController.h

ここでの追加は3つですね。

1. AddTitleViewController.hのインポート
 (これをしないとデリゲートが読み込めません)
2. デリゲートの追加。
3. デリゲートメソッドの宣言。

 
 
で、実装ですが、
これは[insertNewObject]での保存処理を真似ればいいんですが、
キャンセルされた場合ってどうすればいいんですかね?
 
 
『ま、保存しなきゃいいだけじゃない?』

細かいことは気にせず、とりあえず書いてみましょう。

つまり、
controllerからmanagedObjectを取得して
保存する場合だけ保存処理して、モーダルビューを隠すだけ。

簡単なので、やってみてくださいね。
 
 
で、できたのがこちら。(RootViewController.m)

それと[showModalAddTitleView]のところで、[addViewController]の初期化の直後

addViewController.delegate = self;

ってしとけばOK!
 
 
とこれで、あとは各ボタンのアクションからこのメソッドを呼ぶだけですね。
ですが、それをsave/cancelButtonの処理として別々に書くのは面倒です。
なので、各ボタンにタグをつけて同じメソッドで処理させちゃいましょう!

ではみなさんもやってみてください。

カチャカチャカチャカチャ……(タイピングの音)
 

6. 似たような処理をひとつにまとめるの術

 
出来ましたか?

こんなんでいいかと。

まず、分かりやすいように#defineでタグを設定しておいて、それをそれぞれのボタンに割り当ててます。

で、両方のボタンから呼ばれる[didEditManagedObject]の中で、
[sender]がsaveボタンならseved = YESにします。
あとは、テキストフィールドの中にテキストがあれば[title]属性の値を入れて、デリゲートメソッドに送ります。
(なにも値を入れなければ勝手にデフォルト値が入れられます。)
 
 
これでとりあえず完了っ!
ってことで、早速ビルドしてみましょう!
 

7. ビルドしてみる


どうです?
うまくいきましたか〜!?

『やったぁ〜〜〜\(^♢^)/ 完璧っっ!!』

と思ったそこのアナタっ!

それはヌカヨロコビデス…ハイ…
 

8. …けども…!?(・_・;?

またこのビルドでもエラーがあります。
もしかしたらコーディングしてる途中で気付いてたよっていう、感が良い方もいるかもですね^^;

まだ『????エラー?』な方は、
一度追加ボタンを押してそのままキャンセル。でもう1回追加ボタンを押して保存
してみてください。
 
 
…ね?落ちたでしょ。^^;

そのうえエラーがごちゃごちゃと書き出されてますが、ここを見ると

“The operation couldn’t be completed. (Cocoa error 1570.)”

って書いてますね。
『オペレーションがちゃんと完了できてないよ』ってコトみたい。
 
 
でも、追加して保存したり、行やセクションを削除したりしてる限りはエラーは出ない。

原因は…?
 
 
そう!
このエラーは必ず、『キャンセル処理をした後に保存』しようとすると発生するんです。
(試しに3回キャンセルしてから保存しようとすると、3回分のエラーが出ます)
つまり、キャンセルの処理の仕方に問題があるみたい。ってコトが分かります。
 
 
でも、そもそも
『キャンセルした時は保存も何もしない』って処理(?)でしたよね。
何にもしてないのにエラーになるの?って思うんですが、
実際は何かしなくちゃいけないんです。

その『何か』の答えは、実は今まで見てきたコードの中に書かれていたりします。

憶えてますか?
セルが削除された時の処理です。
ここでは
『コンテキストから、削除されたセルの元データも削除して保存』
してるんでしたね。

そう。キャンセルとは言ってもすでに空のデータオブジェクトは存在するので、そいつを削除してからコンテキストを保存してあげないといけないんですね。
じゃないとその空のデータが浮遊霊のようにさまよい続けることに…
(キャ〜〜〜〜っ!!)
 
 
なので、その通りにします。

まず[saved]じゃなかった場合の処理として、
コンテキストから[editedObject]を削除します。
ただ、

[[editedObject managedObjectContext] deleteObject:editedObject];

だとなんか気持ち悪いので[self.managedObjectContext]に変更。

で、[saved]がYESだろうとNOだろうと、コンテキストは保存するので
[if/elase]の外に出して、こちらもコンテキストの呼び出しを変更しました。

では、これでリビルドしてみてください。

今度こそ上手くいくはずです。^^;
これで、いくらキャンセルしてもエラーは出なくなりましたね。

めでたしめでたし♪
 
 
ってことで、キリがいいので今日はここまで!
おつかれさまっしたーっ!

また、あしたもがんばりまっしょい
 

広告

Study CoreData 7 ~親子三代奮闘記~」への2件のフィードバック

  1. いつも楽しく拝見させて頂いています。

    文中の文字の誤りがありましたので、ご連絡致します。

    >それと[showModalAddTitleView]のところで、[addViewController]の初期化の直後に

    showModelAddTitleView
    ご確認よろしくお願いします。^^

    • ぷみらさん、はじめまして!
      コメントありがとうございます<(_ _)>

      ご指摘の点ですが、ここは
      “AddTitleView”をモーダルビューとして表示するメソッドなので
      [showModalAddTitleView]でOKです。

      でも改めて見るとちょっと分かりづらい名前ですね^^;
      また、気付いた点があればご遠慮なくコメント頂けると嬉しいです。

コメントを残す

以下に詳細を記入するか、アイコンをクリックしてログインしてください。

WordPress.com ロゴ

WordPress.com アカウントを使ってコメントしています。 ログアウト / 変更 )

Twitter 画像

Twitter アカウントを使ってコメントしています。 ログアウト / 変更 )

Facebook の写真

Facebook アカウントを使ってコメントしています。 ログアウト / 変更 )

Google+ フォト

Google+ アカウントを使ってコメントしています。 ログアウト / 変更 )

%s と連携中