ここではいくつかの変更があります。まず、NSintegerステータスを追加しました。これは、todoが完了したかどうかのステータスに関連づけられるプロパティです。さらに、それからpropertyを作成しています。 次に、dirtyと呼ばれるBOOL値があります。 このオブジェクトをtodoが変更されている場合を示すために使います。 これがどのような役割を果たすのかは、dehydrate メソッドを実装するときに分かります。 加えて、以下の3つのメソッドを宣言しています。 updateStatus – ステータスの値を更新したい時に呼び出すメソッドです。 updatePriority – 同じように、priorityを更新したい時に呼び出すメソッドです。 dehydrate – 基本的に、todoの状態をデータベースに保存する時に使います。プログラムの終了時に各Todo項目に対して、このメソッドを呼び出すことになります。 status変数をsynthesize行に追加するのを忘れないようにしてください。 以前行ったのと同様に、コンパイルされたdehydrationステートメントを保持するために、静的なsqlite3_stmt を作成する必要があります。 Todo.m を開いて、以下を追加してください。#import <Foundation/Foundation.h>
#import <sqlite3.h>
@interface Todo : NSObject {
sqlite3 *database;
NSString *text;
NSInteger primaryKey;
NSInteger priority;
NSInteger status;
BOOL dirty;
}
@property (assign, nonatomic, readonly) NSInteger primaryKey;
@property (nonatomic, retain) NSString *text;
@property (nonatomic) NSInteger priority;
@property (nonatomic) NSInteger status;
– (id)initWithPrimaryKey:(NSInteger)pk database:(sqlite3 *)db;
– (void)updateStatus:(NSInteger) newStatus;
– (void)updatePriority:(NSInteger) newPriority;
– (void)dehydrate;
@end
メソッドを実装していきましょう。static sqlite3_stmt *init_statement = nil;
static sqlite3_stmt *dehydrate_statment = nil;
@implementation Todo
@synthesize primaryKey,text,priority,status;
最初の二つのメソッド(udpateStatus と updatePriority)は、分かりやすいです。これらは、todoのpriorityとstatusを更新し、dirtyプロパティを 1 に設定します。 これは、todoが変更され、データベースに保存する必要があるという印です。 最後に、dehydrate メソッドです。プログラムの終了時に各todoにこのメソッドを呼び出します。dirty プロパティがYESなら、新しいデータをデータベースに登録する必要があります。 データベースのコードは、前のチュートリアルのコードに似ています。まず、dehydrate_statement が nil かどうか調べます。 これは、このメソッドが呼び出された最初の時だけに実行されます。 次に、update文を作成し、bind によって各”?”に変数をあてはめます。注意すべきは、数字が、左から右へ”?”の番号を表していることです。 最後に、sqlite3_stepを呼び出して、SQLiteステートメントを実行し、そして、ステートメントをリセットします。 Todo.m で最後にするのは、completeフィールドを取得するために、initWithPrimaryKey メソッド内のSELECT文を変更することです。 以下のようにコードを変更してください。– (void)updateStatus:(NSInteger)newStatus {
self.status = newStatus;
dirty = YES;
}
– (void)updatePriority:(NSInteger)newPriority {
self.priority = newPriority;
dirty = YES;
}
– (void) dehydrate {
if(dirty) {
if (dehydrate_statment == nil) {
const char *sql = “UPDATE todo SET 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, 3, self.primaryKey);
sqlite3_bind_int(dehydrate_statment, 2, self.status);
sqlite3_bind_int(dehydrate_statment, 1, self.priority);
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;
}
}
@end
ここはあまり変更点はありません。第一の変更は、synthesize行にstatusを追加したことです。次は、SQL文が、以下のように、データベースからcompleteフィールドを取得するように変更された点です。 SELECT text,priority,complete FROM todo WHERE pk=? 最後に、“self.status = sqlite3_column_in(init_statement,2);” という行があります。 これは、sqlデータ配列内の2番目のインデックスにstatusプロパティを割り当てています。このフィールドが使えるようになります。 そして、ナビゲーションが正しく機能するように、メインビューにタイトルを追加する必要があります。 rootViewController.m を開き、viewDidLoad メソッドに以下のように編集してください。@synthesize primaryKey,text,priority,status;
– (id)initWithPrimaryKey:(NSInteger)pk database:(sqlite3 *)db {
if (self = [super init]) {
primaryKey = pk;
database = db;
// Compile the query for retrieving book data. See insertNewBookIntoDatabase: for more detail.
if (init_statement == nil) {
// Note the ‘?’ at the end of the query. This is a parameter which can be replaced by a bound variable.
// This is a great way to optimize because frequently used queries can be compiled once, then with each
// use new variable values can be bound to placeholders.
const char *sql = “SELECT text,priority,complete FROM todo WHERE pk=?”;
if (sqlite3_prepare_v2(database, sql, –1, &init_statement, NULL) != SQLITE_OK) {
NSAssert1(0, @”Error: failed to prepare statement with message ‘%s’.”, sqlite3_errmsg(database));
}
}
// For this query, we bind the primary key to the first (and only) placeholder in the statement.
// Note that the parameters are numbered from 1, not from 0.
sqlite3_bind_int(init_statement, 1, primaryKey);
if (sqlite3_step(init_statement) == SQLITE_ROW) {
self.text = [NSString stringWithUTF8String🙁char *)sqlite3_column_text(init_statement, 0)];
self.priority = sqlite3_column_int(init_statement,1);
self.status = sqlite3_column_int(init_statement,2);
} else {
self.text = @”Nothing”;
}
// Reset the statement for future reuse.
sqlite3_reset(init_statement);
}
return self;
}
– (void)viewDidLoad {
// Add the following line if you want the list to be editable
// self.navigationItem.leftBarButtonItem = self.editButtonItem;
self.title = @”Todo Items”;
}
目次
Todo詳細情報viewの作成
UITableViewからtodoが選択された時に表示される詳細情報のviewを作成します。XCode上から、いずれかのxib(nib)ファイルをダブルクリックし、Interface Builder を起動します。ファイル > 新規作成 をクリックし、viewを選択します。 そして、作成されたviewに以下を追加します。- UITextView
- UISegmentedControl – これには、セグメントの数を「3」に設定する必要があります。また、このオプションの下にドロップダウンメニューが表示されています。各セグメントを選択し、タイトルにpriorityを記入してください。ここではスクリーンショットです。それぞれのpriority(低、中、高)にタイトルを設定してください。
- UILabel – これは status を表示するのに使います。
- UIButton – ユーザがstatusを更新するのにクリックするボタンです。(完了、未完了など)
TodoViewControllerクラスを作成
XCode上から ファイル > 新規ファイル をクリックし、UIViewController Subclass を選択します。 ファイル名は「TodoViewController」とし、”TodoViewController.h”ファイルも同時に作成にチェックします。 TodoViewController.h を開き、以下のように編集します。基本的に、Interface Builder アウトレットの各部品をXCodeと接続しています。 UIButtonがIBOutletを持っていることに注意してください。これは、todoが完了されているかどうかによって、ボタンの文字列を更新する必要があるからです。 また、updateStatus という名前のIBAction を設定しています。 これは後でボタンと接続されます。これはtodoアイテムのステータスの切り替えスイッチです(完了 or 未完了)。 最後に、updatePriority メソッドです。このメソッドは、UISegmentControl 上のpriority のセグメントがユーザによって選択された時に呼び出されます。 次に、TodoViewController.m を開いて、以下のコードを追加してください。#import <UIKit/UIKit.h>
#import “Todo.h”
@interface TodoViewController : UIViewController {
IBOutlet UITextView *todoText;
IBOutlet UISegmentedControl *todoPriority;
IBOutlet UILabel *todoStatus;
IBOutlet UIButton *todoButton;
Todo *todo;
}
@property(nonatomic,retain) IBOutlet UITextView *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;
@end
これは、各変数のgetterとsetterです。 これらのコードをインターフェースに接続する前に、ユーザがtodoが完了したと記録するためにボタンを押した時と、UISegmentedControl のセグメントが押された時に、呼ばれるメソッドを実装する必要があります。 先ほど追加したsynthesize行の下に以下のメソッドを追加してください。@synthesize todoText,todoPriority,todoStatus,todoButton,todo;
詳しく見て行きましょう。まず、updateStatus メソッドがあります。これはユーザがstatusを変更するボタンを押した時に呼ばれます。 基本的にtodoの現在のstatusをチェックし、それに従って、UIButtonのテキストが変更します。 todoが未完了な状態で、完了ボタンが押されると、ボタンに表示されている文字列が”完了に設定”から”未完了に設定”にかわります。 最後に、todo の updateStatus を呼び出し、新しい値(1 or 0)を渡します。 次に、updatePriority メソッドを見て行きましょう。 これは単純に、selectedSegmentIndex を呼び出すことによって、UISegmentedControlの値を読んでいます。 次の箇所は少し複雑ですね。 UISegmentedControlの値をこのメソッドに直接渡すことが出来ないからです。 UISegmentedControl は「1,2,3」のように昇順で並んでいますが、priority は(3 = 低, 2 = 中, 1 = 高)と降順にならんでいます。 これは、“2 – priority”の箇所です。 次は、UISegmented のコントロールは、「0」からはじまります。priority は「1」から始まるので、“+1″を追加する必要があります。 それでは、Interface Builder 上の UI Components をこのコードに接続しましょう。 TodoViewController.xib をダブルクリックし、Interface Builder を開きましょう。– (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
if (self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]) {
// Initialization code
}
return self;
}
– (IBAction) updateStatus:(id) sender {
if(self.todo.status == 0) {
[self.todoButton setTitle:@”Mark As In Progress” forState:UIControlStateNormal];
[self.todoButton setTitle:@”Mark As In Progress” forState:UIControlStateHighlighted];
[self.todoStatus setText:@”Complete”];
[self.todo updateStatus:1];
} else {
[self.todoButton setTitle:@”Mark As Complete” forState:UIControlStateNormal];
[self.todoButton setTitle:@”Mark As Complete” forState:UIControlStateHighlighted];
[self.todoStatus setText:@”In Progress”];
[self.todo updateStatus:0];
}
}
– (IBAction) updatePriority:(id)sender {
int priority = [self.todoPriority selectedSegmentIndex];
[self.todo updatePriority🙁2-priority+1)];
}
コードとUI部品を接続する
まず、先ほど作成したクラスとviewを関連づけます。Interface Builder 上のFile’s Owner オブジェクトをクリックします。次に、Tools > Identity Inspector を開きます。classという項目のところにドロップダウンがあります。このリストから「TodoViewController」を選択します。 これで、このクラスが関連づけられました。部品を接続していきましょう。viewウインドウをクリックします。どのUI部品も選択せずに、view自身が選択されているのを確認してください。Tools > Connection Inspector を開きます。“New Referencing Outlet”の横の○(円マーク)をドラッグし、File’s Owner の上で、放します。 “view”という文字がポップアップされるので、それをクリックします。 以下の画像を参考にしてください。 以下のようにviewとFile’s Ownerが接続されていればOKです。 この手順を繰り返し、各部品(UITextView, UISegmentedControl, UILabel, UIButton)をFile’s Owner に接続します。先ほどポップアップした”view”ではなく、それぞれ、相当する名称がポップアップされますので、そちらを選択してください。 例えば、UITextView では、File’s owner にドラッグすると、“todoText”という文字列がポップアップされて現れると思います。 updateStatus メソッドをUIButton に接続するために、UIButton を選択した状態で、“Touch up inside” の○をFile’s Owner にドラッグします。すると“updateStatus”というポップアップが出てくると思うので、それを選択します。 すると以下の画像のようになります。 Interface Builder で行う最後の作業は、UISegmentedControl を接続することです。viewの中からUISegmentedControlを選択し、Connections Inspectorを表示させます。 “Value Changed” メソッドの横の○をドラッグし、File’s Owner の上で放します。すると”updatePriority”というメソッドがポップアップしてきますので、それをクリックします。以下の画像のようになればOKです。 それでは、列が選択された時、このviewを表示させましょう。Interface Builder を終了し、RootViewController.h を開き、以下のコードを追加します。viewを変えるために、TodoViewController に変数を関連づけます。 RootViewController.m を開き、synthesizeを追加します。#import <UIKit/UIKit.h>
#import “TodoViewController.h”
@interface RootViewController : UITableViewController {
TodoViewController *todoView;
}
@property(nonatomic,retain) TodoViewController *todoView;
@end
@synthesize todoView;
最新の状態をUITableViewに反映させる
todo アイテム(status または、priority)が変更されるたびに、新しい変更が UITableView に反映される必要があります。 viewWillAppear を以下に編集します。[self.tableView reloadData] の行は、view が表示(非表示)されるたびに、テーブルデータを読み出します。これによって、常にテーブルが最新の情報を反映するようにします。 didSelectRowAtIndex メソッドを以下のように編集します。– (void)viewWillAppear:(BOOL)animated {
[self.tableView reloadData];
[super viewWillAppear:animated];
}
おなじみのコードがたくさん並んだ、とても長いメソッドですね。 まず、appDelegate へのハンドルと選択されたtodoオブジェクトを取得します。 次に、画面を推移させるために、Interface Builder で作成した todoView を viewController に配置します。 その後、view のいくつかのプロパティを設定します。 title は、todoのタイトル文字列を設定します。あまりに文字列が長い場合は、削られます。さらに、UITextView にも文字列がセットされます。 次に、UISegmentedView のインデックスに変換します。既に、この必要性については、説明しました。 setSelectedSegmentIndex メソッドを使って、UISegmentedControl のインデックスがセットされます。 最後に、ボタンの文字列とtodoのstatusがセットされます。 このチュートリアルの最後に、アプリケーションが閉じられる時、変更を保存するように設定します。 todoAppDelegate.m を開き、applicationWillTerminate メソッドを、以下のように追加してください。– (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
todoAppDelegate *appDelegate = (todoAppDelegate *)[[UIApplication sharedApplication] delegate];
Todo *todo = (Todo *)[appDelegate.todos objectAtIndex:indexPath.row];
if(self.todoView == nil) {
TodoViewController *viewController = [[TodoViewController alloc]
initWithNibName:@”TodoViewController” bundle:[NSBundle mainBundle]];
self.todoView = viewController;
[viewController release];
}
[self.navigationController pushViewController:self.todoView animated:YES];
self.todoView.todo = todo;
self.todoView.title = todo.text;
[self.todoView.todoText setText:todo.text];
NSInteger priority = todo.priority – 1;
if(priority > 2 || priority < 0) {
priority = 1;
}
priority = 2 – priority;
[self.todoView.todoPriority setSelectedSegmentIndex:priority];
if(todo.status == 1) {
[self.todoView.todoButton setTitle:@”Mark As In Progress” forState:UIControlStateNormal];
[self.todoView.todoButton setTitle:@”Mark As In Progress” forState:UIControlStateHighlighted];
[self.todoView.todoStatus setText:@”Complete”];
} else {
[self.todoView.todoButton setTitle:@”Mark As Complete” forState:UIControlStateNormal];
[self.todoView.todoButton setTitle:@”Mark As Complete” forState:UIControlStateHighlighted];
[self.todoView.todoStatus setText:@”In Progress”];
}
}
makeObjectsPerformSelector メソッドは、”NSArrayのメソッドに組み込まれています。 これは、配列内のすべてのオブジェクトに対し、渡したメソッドを繰り返します。 全てのtodoに対して、todo[x].dehydrate メソッドを呼び出すようにループします。 もし、todoが”dirty”なら変更されたということなので、データベースに保存します。そうでなければ、dehydrateメソッドは何もしません。 注意すべきは、アプリケーションを起動中にシミュレータを終了すると、このapplicationWillTerminateメソッドは呼ばれないということです。 これが正常に呼ばれていること(todoデータの変更が保存されたこと)を確認するためには、todoの内容を変更してから、シミュレータ上のホームボタンを押します。 これで今回のチュートリアルは終了ですので、ビルド&実行してみてください。 以下のような画面が表示されるはずです。– (void)applicationWillTerminate:(UIApplication *)application {
// Save data if appropriate
[todos makeObjectsPerformSelector:@selector(dehydrate)];
}
この投稿へのコメント