今回は項目を実際に削除します。
なんですが…、その前に、ちょっと勉強。
この花は?アプリでは、投稿した人に投稿文と、それについたコメントを削除する権限を持たそうと思ってまして、そのため現在操作しているユーザが投稿した項目は削除可能、そうでない項目は削除不可としたいわけです。
つまり、こうしたいわけですわ。
って、どーやったのー。それー、それくださいって思った人、ラッキーです。
結構簡単に実装できるんですな。
というのは、UITableViewDataSourceの継承クラス(今回ならFirstViewControllerクラス)がtableView:canMoveRowAtIndexPath:メソッドを実装している場合、UITableViewはNSIndexPathで指定する項目は編集可能か問い合わせてくるんですな。
なので、編集可能にしたい項目だった時だけYESを返し、そうでなければNOを返せば上のように任意の項目だけ削除アイコンが出るようになるわけです。
今回は単純に
とだけやってます。indexPath.row % 2は項目インディックスを2で割った余りを求める計算。2で割ってるので余りは0か1、というわけでインディックス0の時は0、インディックス1の時は1、インディックス2の時は0…というように交互に0、1を繰り返しことになります。
ちゃんとスワイプにも対応してて、編集時に削除アイコンが出ない項目はスワイプでも「削除」ボタンが出ないようになってました。
いやいやすばらしい。
というわけで勉強終わり。
任意の項目が削除対象にできることもわかったので、いよいよ項目の削除。これは前回、空にしていたtableView:commitEditingStyle:forRowAtIndexPath:メソッドに処理を記述することで対応するんですわ。
作業は大別して2種類。
1、自身のデータの更新。
2、UITableViewの更新
です。
ここで気をつけることは、先に自分のデータの更新を終えておく事。
というのもdeleteRowsAtIndexPaths:withRowAnimation:メッセージは内部でUITableViewDataSourceインスタンス(今回ならFirstViewControllerインスタンス)にtableView:numberOfRowsInSection:メッセージを送りつけるから。
その場合、削除後の項目数を返す必要があるで、当然先に自身のデータを更新しておかないと駄目なんですな。
指定したインディックスの項目を削除するdeleteRowsAtIndexPaths:withRowAnimation:メソッドはNSArrayしか受け取らないので、単独項目削除でもNSArrayに埋め込んで渡してやらなければならない。
まあ、そのためにarrayWithObject:なんていう、単独のインスタンスだけのNSArrayを作るクラスメソッドがあるんですな。この場合initという前置詞(じゃない、「alloc」または「new」で始まる名前のメソッドや、「copy」を含む名前のメソッドやね、メンゴ)がつかないので、作成されたNSArrayは一時的なもの(retainはされていない)と考えられる。そのためrelease処理は不要。
これで、項目も削除されます。
うりゃっと実行。
ばっちり、アニメーション付きで削除されましたか?
「名前2」を削除したわけね。
で、もって現状は削除できるかどうかは項目インディックス依存なので、さっき削除できなかった「名前3」が、今度は削除可能になっている。ここらへんは、最終的には項目インディックスではなく項目を書き込んだユーザーと現在テーブルを編集しているユーザーが一致した場合という判断になる。
ま~、ここまでできたら、後はインサートですわ。これまでモーダルモードで利用していたNewFlowerViewControllerインスタンスから制御を戻す処理だけだったところに、項目追加作業を入れます。
とりあえず、NewFlowerViewControllerDelegateにはnewFlower:comment:メソッドしか用意してなかったので、キャンセルか登録か判別する方法として、inImageがnilだった場合はキャンセル、そうでなければ登録としました。
そういうのが嫌な人はnewFlower:comment:メソッドのパラメータにcanceled:(BOOL) canceledあたりを追加してもいいっすね。
私はiPhoneからの投稿は写真必須としたいので、inImageがnilだった場合はキャンセルていう取り決めでいきます。
で、実際の実装。
こっちもやっぱり同じように
1、自身のデータの更新。
2、UITableViewの更新
です。
新しい投稿は先頭の項目としてインサートしてみます。
おっと、NewFlowerViewControllerもちょっと変更せんと…
まず「キャンセル」ボタンの呼び出し先をendModalメソッドからcancelModalに変更。
ここで出てくるnewImageはUIImageインスタンスで、NewFlowerViewControllerのインスタンス変数として定義。imagePickerController:didFinishPickingImage:editingInfo:メソッドで渡されるimageを保持してます。
どうすか?
ちゃんと「登録する」ボタンで項目最上位に追加されましたか?
って、いかん、NewFlowerViewControllerインスタンス、2度目以降って前の写真が設定されたままだ。
「キャンセル」にせよ「登録する」にせよnewImageはnil、imageButtonのimageも解除しておかんと…、だいたい上の処理だと、写真を選び直すたびに前のnewImageがリークしちゃうじゃん。
あと、ナビバーの「登録する」ボタンも解除せんとね。
ですな。
あと、項目タップで提案ビューに移ると写真がはみ出してますな。
俺か?俺のせいか?
ここらへんは、以下次回!
------------
サンプルプロジェクト:konohana_test16.zip
なんですが…、その前に、ちょっと勉強。
この花は?アプリでは、投稿した人に投稿文と、それについたコメントを削除する権限を持たそうと思ってまして、そのため現在操作しているユーザが投稿した項目は削除可能、そうでない項目は削除不可としたいわけです。
つまり、こうしたいわけですわ。
って、どーやったのー。それー、それくださいって思った人、ラッキーです。
結構簡単に実装できるんですな。
というのは、UITableViewDataSourceの継承クラス(今回ならFirstViewControllerクラス)がtableView:canMoveRowAtIndexPath:メソッドを実装している場合、UITableViewはNSIndexPathで指定する項目は編集可能か問い合わせてくるんですな。
なので、編集可能にしたい項目だった時だけYESを返し、そうでなければNOを返せば上のように任意の項目だけ削除アイコンが出るようになるわけです。
今回は単純に
- (BOOL)tableView:(UITableView *)tableView
canEditRowAtIndexPath:(NSIndexPath *)indexPath {
return (indexPath.row % 2) == 1;
}
canEditRowAtIndexPath:(NSIndexPath *)indexPath {
return (indexPath.row % 2) == 1;
}
とだけやってます。indexPath.row % 2は項目インディックスを2で割った余りを求める計算。2で割ってるので余りは0か1、というわけでインディックス0の時は0、インディックス1の時は1、インディックス2の時は0…というように交互に0、1を繰り返しことになります。
実際には、これをデータベースから取り出した項目が、現在見ているユーザーが書き込んだ項目なのかそうでないのかを判断してYES、NOを返すんだけど、今回は動作確認のために適当です。
ちゃんとスワイプにも対応してて、編集時に削除アイコンが出ない項目はスワイプでも「削除」ボタンが出ないようになってました。
いやいやすばらしい。
というわけで勉強終わり。
任意の項目が削除対象にできることもわかったので、いよいよ項目の削除。これは前回、空にしていたtableView:commitEditingStyle:forRowAtIndexPath:メソッドに処理を記述することで対応するんですわ。
作業は大別して2種類。
1、自身のデータの更新。
2、UITableViewの更新
です。
ここで気をつけることは、先に自分のデータの更新を終えておく事。
というのもdeleteRowsAtIndexPaths:withRowAnimation:メッセージは内部でUITableViewDataSourceインスタンス(今回ならFirstViewControllerインスタンス)にtableView:numberOfRowsInSection:メッセージを送りつけるから。
その場合、削除後の項目数を返す必要があるで、当然先に自身のデータを更新しておかないと駄目なんですな。
- (void)tableView:(UITableView *)tableView
commitEditingStyle:(UITableViewCellEditingStyle)editingStyle
forRowAtIndexPath:(NSIndexPath *)indexPath
{
[result removeObjectAtIndex:indexPath.row];
[tableView deleteRowsAtIndexPaths:
[NSArray arrayWithObject:indexPath]
withRowAnimation:UITableViewRowAnimationTop];
}
commitEditingStyle:(UITableViewCellEditingStyle)editingStyle
forRowAtIndexPath:(NSIndexPath *)indexPath
{
[result removeObjectAtIndex:indexPath.row];
[tableView deleteRowsAtIndexPaths:
[NSArray arrayWithObject:indexPath]
withRowAnimation:UITableViewRowAnimationTop];
}
指定したインディックスの項目を削除するdeleteRowsAtIndexPaths:withRowAnimation:メソッドはNSArrayしか受け取らないので、単独項目削除でもNSArrayに埋め込んで渡してやらなければならない。
まあ、そのためにarrayWithObject:なんていう、単独のインスタンスだけのNSArrayを作るクラスメソッドがあるんですな。この場合
これで、項目も削除されます。
最終的にはresultではなくdbを更新して、そこから間接的にresultが更新される形にしなければならないけど、今回はGUIテスト用という事でそこまで実装しない。
うりゃっと実行。
ばっちり、アニメーション付きで削除されましたか?
「名前2」を削除したわけね。
で、もって現状は削除できるかどうかは項目インディックス依存なので、さっき削除できなかった「名前3」が、今度は削除可能になっている。ここらへんは、最終的には項目インディックスではなく項目を書き込んだユーザーと現在テーブルを編集しているユーザーが一致した場合という判断になる。
ま~、ここまでできたら、後はインサートですわ。これまでモーダルモードで利用していたNewFlowerViewControllerインスタンスから制御を戻す処理だけだったところに、項目追加作業を入れます。
とりあえず、NewFlowerViewControllerDelegateにはnewFlower:comment:メソッドしか用意してなかったので、キャンセルか登録か判別する方法として、inImageがnilだった場合はキャンセル、そうでなければ登録としました。
そういうのが嫌な人はnewFlower:comment:メソッドのパラメータにcanceled:(BOOL) canceledあたりを追加してもいいっすね。
私はiPhoneからの投稿は写真必須としたいので、inImageがnilだった場合はキャンセルていう取り決めでいきます。
で、実際の実装。
こっちもやっぱり同じように
1、自身のデータの更新。
2、UITableViewの更新
です。
新しい投稿は先頭の項目としてインサートしてみます。
ここも同じく、最終的にはresultではなくdbを更新して、そこから間接的にresultが更新される形にしなければならないけど、今回はGUIテスト用という事でそこまで実装しない。
-(void)newFlower:(UIImage*) inImage comment:(NSString*)inComment
{
if (inImage != nil) {
Contribute* c = [[[Contribute alloc] init] autorelease];
c.photo = inImage;
c.name = @"?";
c.comment = inComment;
c.contributeID = 0;
[result insertObject:c atIndex:0];
[self.tableView insertRowsAtIndexPaths:
[NSArray arrayWithObject:[NSIndexPath indexPathForRow:0 inSection:0]]
withRowAnimation:UITableViewRowAnimationTop];
}
[self.navigationController dismissModalViewControllerAnimated:YES];
}
{
if (inImage != nil) {
Contribute* c = [[[Contribute alloc] init] autorelease];
c.photo = inImage;
c.name = @"?";
c.comment = inComment;
c.contributeID = 0;
[result insertObject:c atIndex:0];
[self.tableView insertRowsAtIndexPaths:
[NSArray arrayWithObject:[NSIndexPath indexPathForRow:0 inSection:0]]
withRowAnimation:UITableViewRowAnimationTop];
}
[self.navigationController dismissModalViewControllerAnimated:YES];
}
おっと、NewFlowerViewControllerもちょっと変更せんと…
まず「キャンセル」ボタンの呼び出し先をendModalメソッドからcancelModalに変更。
// キャンセルなのでnewFlower:nil comment:nilとする。
- (void)cancelModal {
if ([newflowerDelegate respondsToSelector:@selector(newFlower:comment:)]) {
[newflowerDelegate newFlower:nil comment:nil];
}
}
// 投稿なので、newImageを委譲先に渡す。
- (void)endModal {
if ([newflowerDelegate respondsToSelector:@selector(newFlower:comment:)]) {
[newflowerDelegate newFlower:newImage comment:commentView.text];
}
}
- (void)cancelModal {
if ([newflowerDelegate respondsToSelector:@selector(newFlower:comment:)]) {
[newflowerDelegate newFlower:nil comment:nil];
}
}
// 投稿なので、newImageを委譲先に渡す。
- (void)endModal {
if ([newflowerDelegate respondsToSelector:@selector(newFlower:comment:)]) {
[newflowerDelegate newFlower:newImage comment:commentView.text];
}
}
ここで出てくるnewImageはUIImageインスタンスで、NewFlowerViewControllerのインスタンス変数として定義。imagePickerController:didFinishPickingImage:editingInfo:メソッドで渡されるimageを保持してます。
-(id)init {
if (self = [super init]) {
self.title = @"投稿";
newImage = nil;
}
return self;
}
・
・
- (void)imagePickerController:(UIImagePickerController *)picker
didFinishPickingImage:(UIImage *)image
editingInfo:(NSDictionary *)editingInfo {
newImage = [image retain]; // 登録時に渡すために保持する。
・
・
}
・
・
- (void)dealloc {
[newImage release];
[super dealloc];
}
if (self = [super init]) {
self.title = @"投稿";
newImage = nil;
}
return self;
}
・
・
- (void)imagePickerController:(UIImagePickerController *)picker
didFinishPickingImage:(UIImage *)image
editingInfo:(NSDictionary *)editingInfo {
newImage = [image retain]; // 登録時に渡すために保持する。
・
・
}
・
・
- (void)dealloc {
[newImage release];
[super dealloc];
}
どうすか?
ちゃんと「登録する」ボタンで項目最上位に追加されましたか?
って、いかん、NewFlowerViewControllerインスタンス、2度目以降って前の写真が設定されたままだ。
「キャンセル」にせよ「登録する」にせよnewImageはnil、imageButtonのimageも解除しておかんと…、だいたい上の処理だと、写真を選び直すたびに前のnewImageがリークしちゃうじゃん。
あと、ナビバーの「登録する」ボタンも解除せんとね。
- (void)imagePickerController:(UIImagePickerController *)picker
didFinishPickingImage:(UIImage *)image
editingInfo:(NSDictionary *)editingInfo {
[newImage release];
newImage = [image retain]; // 登録時に渡すために保持する。
・
・
}
- (void)resetImage {
[imageButton setImage:nil forState:UIControlStateNormal];
[newImage release];
newImage = nil;
self.navigationItem.rightBarButtonItem = nil;
}
// キャンセルなのでnewFlower:nil comment:nilとする。
- (void)cancelModal {
if ([newflowerDelegate respondsToSelector:@selector(newFlower:comment:)]) {
[newflowerDelegate newFlower:nil comment:nil];
}
[self resetImage];
}
// 投稿なので、newImageを委譲先に渡す。
- (void)endModal {
if ([newflowerDelegate respondsToSelector:@selector(newFlower:comment:)]) {
[newflowerDelegate newFlower:newImage comment:commentView.text];
}
[self resetImage];
}
didFinishPickingImage:(UIImage *)image
editingInfo:(NSDictionary *)editingInfo {
[newImage release];
newImage = [image retain]; // 登録時に渡すために保持する。
・
・
}
- (void)resetImage {
[imageButton setImage:nil forState:UIControlStateNormal];
[newImage release];
newImage = nil;
self.navigationItem.rightBarButtonItem = nil;
}
// キャンセルなのでnewFlower:nil comment:nilとする。
- (void)cancelModal {
if ([newflowerDelegate respondsToSelector:@selector(newFlower:comment:)]) {
[newflowerDelegate newFlower:nil comment:nil];
}
[self resetImage];
}
// 投稿なので、newImageを委譲先に渡す。
- (void)endModal {
if ([newflowerDelegate respondsToSelector:@selector(newFlower:comment:)]) {
[newflowerDelegate newFlower:newImage comment:commentView.text];
}
[self resetImage];
}
ですな。
newImageもself.navigationItem.rightBarButtonItem = nilのようにnilを代入するだけでrelease までやらしたければプロパティ宣言すればいい。ただしその場合、newImage =nilではなくself.newImage=nilとしなければならない。 「号外 propertyをちょっと調べてみた」と「Objective-C 2.0プログラミング言語」の「ドット構文」を参照。
あと、項目タップで提案ビューに移ると写真がはみ出してますな。
俺か?俺のせいか?
ここらへんは、以下次回!
------------
サンプルプロジェクト:konohana_test16.zip