[9]:SQLiteを使ってToDoリストアプリを作成(4)

今回がTodoリストの最後になります。SQLiteデータベースを使って、todoアイテムを追加・編集および、削除する詳細について扱って行きます。

以下の3つの記事を既に読まれている方を想定していますので、まだのかたは、先に以下の記事をご覧ください。

では、はじめましょう。

まず、編集、新規追加のためのボタンが必要なので、UIBarButtonItem の部品をNavigationBar に追加します。

RootViewController.mを開き、viewDidLoad メソッドに以下のコードを追加してください。

– (void)viewDidLoad {

self.title = @”Todo リスト;

self.navigationItem.leftBarButtonItem = self.editButtonItem;

UIBarButtonItem * btn = [[UIBarButtonItem alloc] initWithTitle:@”Add”

style:UIBarButtonItemStyleBordered

target:self action:@selector(addTodo:)];

self.navigationItem.rightBarButtonItem = btn;

}

追加した最初の行は、leftBarButtonItem を self.editButtonItem に追加しています。これで、自動的に”編集”ボタンがナビゲーションに追加されます。同時に、”編集”ボタンが押された時に、”削除”ボタンが表示されます。

この状態で、一度、ビルド&実行してみてください。以下の画面のようになると思います。

812-1

それでは、説明に戻りましょう。

次に、手動で、UIBarButtonItemを作成し、ナビゲーションに追加します。

この作業はInterface Builderで完結することも出来ますが、ここでは、作成から、アクションを割り当てるところまで手動で行います。

  • initWithTitle – ボタンに表示されるタイトルです。
  • style – ボタンの見た目です。
  • target – このボタンから送られたメッセージのハンドルクラスです。
  • action – ボタンが押された時に呼ばれるメソッドです。@selector を使い、呼びだすファンクション名を指定することが出来ます。

最後に、rightBarButtonItem にこのボタンを設定しています。この状態では、ナビゲーションにボタンを追加しただけですので、クリックされた時のアクションは未設定です。なので、このボタンをクリックするとエラーになると思います。

それでは、Todoオブジェクト内にメソッドを作成していきましょう。

Todo.hを開き、以下のコードを追加してください。

– (void)deleteFromDatabase;

+ (NSInteger)insertNewTodoIntoDatabase:(sqlite3 *)database;

insertNewTodoIntoDatabaseメソッドを追加しました。同時に、deleteFromDatabaseも追加していますね。これについては、データベースからのデータの削除を扱う時に実装します。

insertNewTodoIntoDatabaseメソッドの先頭についている”+”は、インスタンスメソッドではなく、クラスメソッドを表しています。クラスメソッドなので、インスタンス化せずに、呼び出すことが出来ます。

このメソッドを実装する前に、静的なsqlite3_statement をいくつか追加します。

Todo.m に以下を追加してください。

static sqlite3_stmt *delete_statment = nil;

static sqlite3_stmt *insert_statement = nil;

それでは、insertNewTodoIntoDatabaseを実装していきましょう。

+ (NSInteger)insertNewTodoIntoDatabase:(sqlite3 *)database {

if (insert_statement == nil) {

static char *sql = “INSERT INTO todo (text,priority,complete) VALUES(‘新規アイテム’,’3′,’0′)”;

if (sqlite3_prepare_v2(database, sql, –1, &insert_statement, NULL) != SQLITE_OK) {

NSAssert1(0, @”Error: failed to prepare statement with message ‘%s’.”, sqlite3_errmsg(database));

}

}

int success = sqlite3_step(insert_statement);

sqlite3_reset(insert_statement);

if (success != SQLITE_ERROR) {

return sqlite3_last_insert_rowid(database);

}

NSAssert1(0, @”Error: failed to insert into the database with message ‘%s’.”, sqlite3_errmsg(database));

return1;

}

更新する時のメソッドに似ていますね。デフォルト値をデータベースに挿入していることに注目してください。こうすることで、nullやnilを含むトラブルを避けることが出来ます。

このメソッドで最も大切なことは、新しく作成されたtodoオブジェクトのプライマリーキーを返すということです。

これは、”Add”ボタンが押された時に、即座にtodoに変遷するために、使われます。

todoオブジェクトに対して行う最後の作業は、todoテキストが変更された時に保存されるようにdehydrateメソッドを更新することです。

dehydrateメソッドを以下のように編集してください。

– (void) dehydrate {

if(dirty) {

if (dehydrate_statment == nil) {

const char *sql = “UPDATE todo SET text = ? , priority = ?,complete = ? WHERE pk=?”;

if (sqlite3_prepare_v2(database, sql, –1, &dehydrate_statment, NULL) != SQLITE_OK) {

NSAssert1(0, @”Error: failed to prepare statement with message ‘%s’.”, sqlite3_errmsg(database));

}

}

sqlite3_bind_int(dehydrate_statment, 4, self.primaryKey);

sqlite3_bind_int(dehydrate_statment, 3, self.status);

sqlite3_bind_int(dehydrate_statment, 2, self.priority);

sqlite3_bind_text(dehydrate_statment, 1, [self.text UTF8String], –1, SQLITE_TRANSIENT);

int success = sqlite3_step(dehydrate_statment);

if (success != SQLITE_DONE) {

NSAssert1(0, @”Error: failed to save priority with message ‘%s’.”, sqlite3_errmsg(database));

}

sqlite3_reset(dehydrate_statment);

dirty = NO;

}

}

細かい変更がいくつかあります。まず、“text = ?”という記述がsql文に追加されています。これは単純にtodoテキストを更新するものです。

他の変更点として、self.text property をsql文内の最初の”?”に関連づけています。

[self.text UTF8String] に注目してください。これは、sqlite3_bind_textが、char型をとるので、NSString を許容可能な形式に変換しています。

todoを追加するために、RootViewController内にメソッドを追加してきます。

ユーザが、”Add”ボタンを押した時に呼ばれるメソッドです。

RootViewController.m 内に以下のコードを追加してください。

– (void) addTodo:(id)sender {

todoAppDelegate *appDelegate = (todoAppDelegate *)[[UIApplication sharedApplication] delegate];

if(self.todoView == nil) {

TodoViewController *viewController = [[TodoViewController alloc]

initWithNibName:@”TodoViewController” bundle:[NSBundle mainBundle]];

self.todoView = viewController;

[viewController release];

}

Todo *todo = [appDelegate addTodo];

[self.navigationController pushViewController:self.todoView animated:YES];

self.todoView.todo = todo;

self.todoView.title = todo.text;

[self.todoView.todoText setText:todo.text];

}

まず、appDelegate オブジェクトへの参照を取得しています。理由としては、それ自身のaddTodo メソッドを呼び出す必要があるからです。

次に、TodoViewController がインスタンス化されていなければ、インスタンス化します。

この処理が必要な理由は、新しいtodoオブジェクトを作成した後に、viewを変遷させるためです。

これが完了したら、appDelegate の addTodo メソッドを呼びます。それは、新しいtodoオブジェクトを返し、todoオブジェクトの詳細を編集するために、詳細情報画面に移動します。

appDelegate 内にaddTodo メソッドを追加していきます。

todoAppDelegate.h を開いて、以下を追加してください。

-(Todo *)addTodo;

todoAppDelegate.m へ実装していきましょう。

-(Todo *) addTodo {

NSInteger primaryKey = [Todo insertNewTodoIntoDatabase:database];

Todo *newTodo = [[Todo alloc] initWithPrimaryKey:primaryKey database:database];

[todos addObject:newTodo];

return newTodo;

}

まず、TodoオブジェクトのinsertNewTodoIntoDatabase メソッドを呼び出しています。注目すべきは、todoオブジェクトを作成することなく、単にメソッドを呼び出している点です。これは、既に触れましたが、このメソッドは静的で、クラスのインスタンスを作成せずに呼ばれます。

次に、initWithPrimaryKey メソッドを呼ぶことで、todoオブジェクトを作成しています。これによって、新しいtodoオブジェクトへの参照を得ることが出来ます。

最後に、このtodoアイテムを、既存のtodo配列の最後に、追加しています。

UITableView は、この配列によって更新されるので、自動的に、新しいtodoオブジェクトも含まれます。最後の行は、このtodoオブジェクトを返しています。

前回のチュートリアルで、statusとpriorityが更新出来るようになりましたが、同じように、todoのテキストも更新出来るようにしましょう。

TodoViewController.h を開き、以下のコードを追加してください。

#import <UIKit/UIKit.h>

#import “Todo.h”

@interface TodoViewController : UIViewController {

IBOutlet UITextField *todoText;

IBOutlet UISegmentedControl *todoPriority;

IBOutlet UILabel *todoStatus;

IBOutlet UIButton *todoButton;

Todo *todo;

}

@property(nonatomic,retain) IBOutlet UITextField        *todoText;

@property(nonatomic,retain) IBOutlet UISegmentedControl *todoPriority;

@property(nonatomic,retain) IBOutlet UILabel            *todoStatus;

@property(nonatomic,retain) IBOutlet UIButton           *todoButton;

@property(nonatomic,retain) Todo *todo;

– (IBAction) updateStatus:(id) sender;

– (IBAction) updatePriority:(id) sender;

– (IBAction) updateText:(id) sender;

@end

todoText オブジェクトのためのUITextViewがなぜUITextFieldに変わりましたね。なぜでしょうか。

理由は、 UITextViewには、私たちの現在のデザインでテキストを保存するのに必要なメソッドがないからです。

また、後で、Interface Builder 内でもこの箇所のインターフェースを変更します。

ここで追加したメソッドは、updateText メソッドです。このIBAction はユーザが、todoテキストを設定した後に、キーボードの”Done”ボタンを押した時に呼ばれます。

それでは、このメソッドを追加していきましょう。

TodoViewController.m を開き、以下のコードを追加してください。

– (IBAction) updateText:(id) sender {

self.todo.text = self.todoText.text;

}

これが行っていることは、todoのテキストを、UITextField 内でユーザが入力したテキストへ更新することです。次に、UITextView を UITextField に置き換え、updateText メソッドと接続しましょう。

TodoViewController.xib ファイルをダブルクリックし、Interface Builder を起動します。起動したら、インターフェース上のUITextView をクリックし、削除します。そして、ライブラリから、UITextField をドラッグし、インターフェースに配置します。適当にサイズ調整してください。

812-2配置しただけでは、動作しないので、接続します。

Tool > Connections Inspector をクリックし、Connections Inspector を開きます。

”Did End On Exit” の横の○をFile’s Owner 上へドラッグします。「udpateText」 がポップアップされるので、それをクリックして、接続します。(udpateText が表示されない場合は、XCode上の変更が全て保存されているか確認してください。)

次に、“New Referencing Outlet” の横の○をFile’s Owner 上へドラッグします。「todoText」がポップアップされるので、それを選択します。以下の画像のようになればOKです。

812-3これを保存して、Interface Builder は終了します。これで、todoを追加することが出来るようになりました。

最後にすべきことは、todo をリストとデータベースから削除する機能を実装することです。

これはInterface Builderを使わず、XCode上のみで完了します。

appDelegate に、todo削除を扱うメソッドを追加します。todoAppDelegate.h を開き、以下のコードを追加します。

-(void)removeTodo:(Todo *)todo;

removeTodo メソッドを追加しました。以下のインポート文も追加してください。

#import “Todo.h”

これで、todoオブジェクトを扱うことが出来るようになります。

それでは、removeTodo メソッドを実装していきます。

todoAppDelegate.m を開き、以下のコードを追加してください。

-(void)removeTodo:(Todo *)todo {

NSUInteger index = [todos indexOfObject:todo];

if (index == NSNotFound) return;

[todo deleteFromDatabase];

[todos removeObject:todo];

}

最初の行は、todoのNSArray配列を検索します。これによって、削除されるtodoのインデックス番号が返されます。

そして、deleteFromDatabase メソッドを呼んで、該当オブジェクトをtodo配列から削除します。

UITableViewは、この配列を介して更新されているので、特別なコードを追加することなく、自動的に、ToDoを削除します。

todoオブジェクトにremoveTodo メソッドを作成します。

Todo.h に既にメソッドの宣言はしているので、Todo.m を開いて、コードを編集します。

-(void) deleteFromDatabase {

if (delete_statment == nil) {

const char *sql = “DELETE FROM todo WHERE pk=?”;

if (sqlite3_prepare_v2(database, sql, –1, &delete_statment, NULL) != SQLITE_OK) {

NSAssert1(0, @”Error: failed to prepare statement with message ‘%s’.”, sqlite3_errmsg(database));

}

}

sqlite3_bind_int(delete_statment, 1, self.primaryKey);

int success = sqlite3_step(delete_statment);

if (success != SQLITE_DONE) {

NSAssert1(0, @”Error: failed to save priority with message ‘%s’.”, sqlite3_errmsg(database));

}

sqlite3_reset(delete_statment);

}

delete_statement は、このチュートリアルの最初の方で宣言した、静的なsqlite3_stmt です。まず、これが、nil かどうか調べます。もし、そうなら、sqlite3_prepare 文を使いコンパイルします。

次に、sqlite3 内の”?” に、現在のtodoのプライマリーキーをあてはめます。

その後、実行し、リセットします。

データベースからtodo を削除するのに、最後にすることは、ユーザが”delete”ボタンを押したときの動作を特定することです。

RootViewController.m を開き、以下のコードを追加します。

// Override if you support editing the list

– (void)tableView:(UITableView *)tableView commitEditingStyle:

(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {

todoAppDelegate *appDelegate = (todoAppDelegate *)[[UIApplication sharedApplication] delegate];

Todo *todo = (Todo *)[appDelegate.todos objectAtIndex:indexPath.row];

if (editingStyle == UITableViewCellEditingStyleDelete) {

[appDelegate removeTodo:todo];

// Delete the row from the data source

[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:YES];

}

}

まず、appDelegate への参照を取得しています。次に、現在編集中かどうか調べます。もし、編集中なら、appDelegate 上の、removeTodo メソッドを呼びます。

次の行では、与えられたindexPath の列をUITableView から削除しています。

それでは、ビルド&実行してみてください。新規アイテムの追加、アイテムの編集、アイテムの削除が出来ると思います。

お疲れさまでした。

812-4

812-5

投稿日:
カテゴリー: 過去Blog

コメントする

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です