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

このチュートリアルは、タイトルの通り、SQLiteを使ってToDoリストアプリを作成の二回目の記事です。前回の記事を読んでない方は、そちらから読んでください。

今回は、SQLデータをUITableViewで表示するだけでなく、画像や文字列を使った複合的なカラムの作成を行います。今回のチュートリアルでは、以下の画像を使いますので、ダウンロードしてください。

high優先度:高
medium優先度:普通
low優先度:低

これらの画像は、優先度を表示するのに使います。

優先度に対応するために、Todo.hTodo.m にいくつかのコードを追加します。

Todo.hを開いてください。

@interface Todo : NSObject {

sqlite3 *database;

NSInteger primaryKey;

NSString *text;

NSInteger priority;

}

@property (assign,nonatomic,readonly)NSInteger primaryKey;

@property (nonatomic,retain)NSString *text;

@property (nonatomic) NSInteger priority;

NSInteger priority のプロパティを追加しました。与えられたtodoオブジェクトの優先度を取得・設定するのに使います。

次に、Todo.mを開いて、以下のように修正してください。

@synthesize primaryKey,text,priority;

-(id)initWithPrimaryKey:(NSInteger)pk database:(sqlite3 *)db {

if(self = [super init]){

primaryKey = pk;

database = db;

if (init_statement == nil) {

const char *sql = “SELECT text,priority 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));

}

}

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);

}else {

self.text = @”Nothing”;

}

sqlite3_reset(init_statement);

}

return self;

}

@end

最初のsynthesizeの行を変更しました。ゲッター&セッターメソッドを作成するために、priority プロパティを追加しました。

次に、SQL記述を少し変更しました。todoテーブルに優先度を加えるために、priorityを取得するように設定しました。最後に、todoテーブルから選択された優先順位の値に self.priority を設定します。

これはsqlite3_column_intメソッドを使用して行われます。

init_statement と 「1」を渡します。「1」は、優先度のデータが含まれているSQL配列のインデックスです。

先ほどダウンロードした優先度の画像をプロジェクトに追加します。

プロジェクト内のResourceフォルダを右クリックし、追加 > 既存のファイル をクリックします。すべての画像を選択し、追加します。以下のような画面で「デスティネーション…」にチェックして追加をクリックします。

7-1先ほどダウンロードした3つのファイルがRecourceフォルダに追加されました。

7-2

UITableView内の列のデータを表示するには、我々は、表示するデータの種類を定義する独自のセルクラスを作成する必要があります。

デフォルトでは、1つのテキスト列を表示することが出来る簡単なオブジェクトが提供されています。

これ自身はとても、有用なものではありますが、今回は、3列の構造をもったものが必要なので、独自のセルオブジェクトをつくります。

XCode上から、ファイル > 新規ファイル をクリックし、UITableView のサブクラスを作成します。

7-3このファイルは、TodoCellという名前にします。”同時に.hファイルも作成”をチェックします。

これで、基本的なメソッドが含まれたUITableViewオブジェクトの骨組みが出来ました。

TodoCell.hを開き、いくつかのプロパティを追加します。

#import <UIKit/UIKit.h>

#import “Todo.h”

@interface TodoCell : UITableViewController {

Todo *todo;

UILabel *todoTextLabel;

UILabel *todoPriorityLabel;

UIImageView *todoPriorityImageview;

}

@property (nonatomic, retain) UILabel *todoTextLabel;

@property (nonatomic, retain) UILabel *todoPriorityLabel;

@property (nonatomic, retain) UIImageView *todoPriorityImageView;

-(UIImage *)imageForPriority:(NSInteger)priority;

-(Todo *)todo;

-(void)setTodo:(Todo *)newTodo;

@end

まず、Todoオブジェクトが宣言されています。

各セルは、どのTodoオブジェクトが対応しているのかを知っているでしょう。

各セルのデータをアップデートする時に、これは役立ちます。

次に、2つのUILabel と UIImageViewがあります。これらの要素がどうして必要なのかを理解するには以下の画像を見てください。

7-4

水色のアイコン画像は、UIImageViewにより配置されています。「低」と「Todoリスト項目」の箇所は、それぞれ、UILabelです。

これらの宣言をした後、プロパティを設定しています。Todoオブジェクトについてのプロパティを作成していないことに注意してください。synthesizeも行いません。この理由としては、プライベートな変数にするためです。

その下にいくつかのメソッドが宣言されています。

最初は imageForPriority です。これのメソッドは、どの画像(赤、青、黄色)を表示するのかを指定するものです。

次は、Todoオブジェクトの getter & setter メソッドです。setter メソッドは、todoオブジェクトを割り当てること以外に、追加コードを含みます。

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

#import “TodoCell.h”

static UIImage *priority1Image = nil;

static UIImage *priority2Image = nil;

static UIImage *priority3Image = nil;

@interface TodoCell ()

-(UILabel *)newLabelWithPrimaryColor:(UIColor *)primaryColor selectedColor:(UIColor *)selectedColor fontSize:(CGFloat)fontSize bold:(BOOL)bold;

@end

@implementation TodoCell

@synthesize todoTextLabel,todoPriorityLabel,todoPriorityImageView;

+ (void)initialize{

priority1Image = [[UIImage imageNamed:@”high.png”] retain];

priority2Image = [[UIImage imageNamed:@”medium.png”] retain];

priority3Image = [[UIImage imageNamed:@”low.png”] retain];

}

いくつか新しいものが出てきています。まず、static UIImage が3つあります。これらは、三つの画像(赤、青、黄色)をそれぞれ扱います。

それらを一度だけ、割り当てる必要があるため、静的にします。

Staticの意味は、インスタンスではなく、クラスに関連づけられているということを意味します。

Todoセルは好きなだけ作ることが出来ますが、3つのUIImageだけが作成されます。

次の行では、プライベートなインターフェースを宣言しています。

これは、このクラスを除いて、使用することが出来ないプライベートメソッドを宣言しています。

次の、synthesize行は、todoオブジェクトではないことを再度注意してください。

initializeメソッドをみてください。ここで行われているのは、与えられた優先度に沿って、それぞれのUIImageをインスタンス化しています。

このinitializeメソッドは、最初のtodoセルクラスがビルドされたときに一回だけ呼ばれます。

次に、以下のコードを追加してください。

– (id)initWithFrame:(CGRect)frame reuseIdentifier:(NSString *)reuseIdentifier {

if (self = [super initWithFrame:frame reuseIdentifier:reuseIdentifier]) {

UIView *myContentView = self.contentView;

self.todoPriorityImageView = [[UIImageView alloc] initWithImage:priority1Image];

[myContentView addSubview:self.todoPriorityImageView];

[self.todoPriorityImageView release];

self.todoTextLabel = [self newLabelWithPrimaryColor:[UIColor blackColor]

selectedColor:[UIColor whiteColor] fontSize:14.0 bold:YES];

self.todoTextLabel.textAlignment = UITextAlignmentLeft; // default

[myContentView addSubview:self.todoTextLabel];

[self.todoTextLabel release];

self.todoPriorityLabel = [self newLabelWithPrimaryColor:[UIColor blackColor]

selectedColor:[UIColor whiteColor] fontSize:10.0 bold:YES];

self.todoPriorityLabel.textAlignment = UITextAlignmentRight;

[myContentView addSubview:self.todoPriorityLabel];

[self.todoPriorityLabel release];

// Position the todoPriorityImageView above all of the other views so

// it’s not obscured. It’s a transparent image, so any views

// that overlap it will still be visible.

[myContentView bringSubviewToFront:self.todoPriorityImageView];

}

return self;

}

これは、UITableViewCell の初期化メソッドです。まず、セルの基礎となるコンポーネントを適切にセットアップするために、親クラス(UITableViewCell)のinitWithFrame を呼び出して実行します。

次に、contentViewを参照します。contentView は各セルのviewです。すべてのUIコンポーネントにこのviewを追加します。

次の三行は、UIImageView を初期化し、それをviewに追加しています。priority1Image を関連づけていることに、注意してください。これは、更新されるまえのダミーのプレースホルダーです。

この後に、todoTextLabelを初期化しています。このラベルは、”ゴミを捨てに行く”などのto doを表示させます。

newLabelWithPrimaryColor というメソッドがあります。これについて、少し詳しくみていきましょう。これは、呼び出した時に、指定した属性を持つ新しいラベルを構築します。アップルの”Seismic XML”のサンプルコードで使われています。とてもお手軽です。これが呼ばれた後、viewに新しいラベルを追加し、todoPriorityLabelに対して繰り返し、このステップが行われます。

最後に、bringSubviewToFront メソッドが、priority の UIImageView に呼ばれています。このメソッドは、イメージの横に文字列を表示させるのに使われています。

これによって、文字列の上に画像を表示させています。階層になっているUIコンポーネントに使えます。

todoオブジェクトの“getter” と “setter”メソッドを追加していきます。

– (Todo *)todo

{

return self.todo;

}

– (void)setTodo:(Todo *)newTodo

{

todo = newTodo;

self.todoTextLabel.text = newTodo.text;

self.todoPriorityImageView.image = [self imageForPriority:newTodo.priority];

switch(newTodo.priority) {

case 2:

self.todoPriorityLabel.text = @”;

break;

case 3:

self.todoPriorityLabel.text = @”;

break;

default:

self.todoPriorityLabel.text = @”;

break;

}

[self setNeedsDisplay];

}

最初のメソッドは簡単ですね。todoオブジェクトを返すだけのものです。

setterの方のsetTodoメソッドは少し複雑ですね。

まず、新しく作られたnewTodoをtodo オブジェクトにセットします。次に、todoの詳細情報を表示するために、UITextLabelを更新します。この後に、imageforPriorityメソッドを呼び、UIImageViewの画像を設定します。これは与えられたプロパティに画像を返しています。

最後にswitch文があります。switchの用法は、Objective-Cでも、他の言語と同様です。もし、不安な方は、検索して調べてみてください。

newTodoの優先順位に基づいて、優先順位のラベルは3つの中の1つ(高、中、低)で更新されます。

[self setNeedsDisplay]によって、todoがセットされたら、セルに再表示させています。

さらに以下のコードを追加してください。

– (void)layoutSubviews {

#define LEFT_COLUMN_OFFSET 1

#define LEFT_COLUMN_WIDTH 50

#define RIGHT_COLUMN_OFFSET 75

#define RIGHT_COLUMN_WIDTH 240

#define UPPER_ROW_TOP 4

[super layoutSubviews];

CGRect contentRect = self.contentView.bounds;

if (!self.editing) {

CGFloat boundsX = contentRect.origin.x;

CGRect frame;

// Place the Text label.

frame = CGRectMake(boundsX +RIGHT_COLUMN_OFFSET , UPPER_ROW_TOP, RIGHT_COLUMN_WIDTH, 13);

frame.origin.y = 15;

self.todoTextLabel.frame = frame;

// Place the priority image.

UIImageView *imageView = self.todoPriorityImageView;

frame = [imageView frame];

frame.origin.x = boundsX + LEFT_COLUMN_OFFSET;

frame.origin.y = 10;

imageView.frame = frame;

// Place the priority label.

CGSize prioritySize = [self.todoPriorityLabel.text sizeWithFont:self.todoPriorityLabel.font

forWidth:RIGHT_COLUMN_WIDTH lineBreakMode:UILineBreakModeTailTruncation];

CGFloat priorityX = frame.origin.x + imageView.frame.size.width + 8.0;

frame = CGRectMake(priorityX, UPPER_ROW_TOP, prioritySize.width, prioritySize.height);

frame.origin.y = 15;

self.todoPriorityLabel.frame = frame;

}

}

このメソッドは、UITableViewCell が表示されると自動的に呼び出されます。これはUITableViewにどのように表示すべきかを教えています。

define文は、Cのdefineを同様です。

このようにコーディングした理由は、好みに合わせて表示を得るために、これらの変数を調整することができるようにするためです。

最初に、layoutSubviews の親クラスを呼びます。次に、contentView.bounds を参照します。この変数は、どれぐらいの描画領域があるのかを分かるようにし、オブジェクトを適切に整列出来るようにします。

if(!self.editing) の箇所は、必要ではありませんが、推奨します。セルを編集するために使います。まず、右端のカラムを宣言します。コンテントを保持するために、frameを作成しています。このカラムはtodoアイテムの文字列を扱います。

このコードのほとんどは位置設定です。これらの数値を変更し、どのような動作をするのか確認してみてください。

一旦、位置設定が完了したら、todo TextLabelのフレームは、この新しく作成されたフレームにセットされます。これは、各 UI components で行われます。

この設定例がすべてではありませんので、レイアウトはご自由に設定してください。

もう一つ、オーバーライドするメソッドがあります。それは、setSelectedメソッドです。以下のコードを追加してください。

– (void)setSelected:(BOOL)selected animated:(BOOL)animated {

[super setSelected:selected animated:animated];

UIColor *backgroundColor = nil;

if (selected) {

backgroundColor = [UIColor clearColor];

} else {

backgroundColor = [UIColor whiteColor];

}

self.todoTextLabel.backgroundColor = backgroundColor;

self.todoTextLabel.highlighted = selected;

self.todoTextLabel.opaque = !selected;

self.todoPriorityLabel.backgroundColor = backgroundColor;

self.todoPriorityLabel.highlighted = selected;

self.todoPriorityLabel.opaque = !selected;

}

このメソッドは、ユーザがセルをタップした時に呼び出されます。私たちは、セルがタップされた時の動作を設定する必要があります。このメソッドは、直感的で分かりやすいですね。まず、親クラスのsetSelected メソッドを呼びます。次に、セルが選択されているかどうかに応じて、背景色を更新します。

最後に、セルが選択されている時のラベル(文字?)を白にセットします。セルが選択されている時の青色に対してコンストラストさせるためです。

これから説明する最後の2つのメソッドは、コード内で使用したメソッドの補助的なメソッドです。

This last 2 methods that I want to talk about are the helper methods that we used earlier in the code.

以下のコードを追加してください。

– (UILabel *)newLabelWithPrimaryColor:(UIColor *)primaryColor

selectedColor:(UIColor *)selectedColor fontSize:(CGFloat)fontSize bold:(BOOL)bold

{

UIFont *font;

if (bold) {

font = [UIFont boldSystemFontOfSize:fontSize];

} else {

font = [UIFont systemFontOfSize:fontSize];

}

UILabel *newLabel = [[UILabel alloc] initWithFrame:CGRectZero];

newLabel.backgroundColor = [UIColor whiteColor];

newLabel.opaque = YES;

newLabel.textColor = primaryColor;

newLabel.highlightedTextColor = selectedColor;

newLabel.font = font;

return newLabel;

}

– (UIImage *)imageForPriority:(NSInteger)priority

{

switch (priority) {

case 2:

return priority2Image;

break;

case 3:

return priority3Image;

break;

default:

return priority1Image;

break;

}

return nil;

}

newLabelWithPrimaryColor – このメソッドは、UILabel が初期化された時に呼ばれました。これはいくつかのパラメータをもちます。コードを見ると、指定したサイズでfontが初期化されています。太字が設定されていた場合は、それにも対応しています。

次に、UILabel にいくつかのプロパティを与え、インスタンス化しています。

最後に、新しく作成されたUILabel が返されています。

imageForPriority – このメソッドは、とても単純です。priorityの値に対して、関連するpriorityのUIImageを返しています。

default節に注意してください。

I decided to handle it like this instead of doing “case 1″ to handle all other cases.  For whatever reason, if there is ever a priority that is not 1,2 or 3 it will, by default, have low priority.

UITableViewCell を作成しましたので、それをテーブルに表示する必要があります。

RootViewController.m を開き、以下のimport文を追加してください。これでTodoCellオブジェクトが扱えるようになります。

#import “TodoCell.h”

numberOfRowsInSection メソッドを探し以下のコードを追加してください。

– (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {

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

return appDelegate.todos.count;

}

これはフルーツの例とほとんど同じコードです。基本的にtodoアイテムの数を返しています。TodoCellが表示されるようにTodoCellを追加します。

cellForRowAtIndexPathメソッドを見つけてください、そして、以下のコードを加えてください。

– (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {

static NSString *MyIdentifier = @”MyIdentifier”;

TodoCell *cell = (TodoCell *)[tableView dequeueReusableCellWithIdentifier:MyIdentifier];

if (cell == nil) {

cell = [[[TodoCell alloc] initWithFrame:CGRectZero reuseIdentifier:MyIdentifier] autorelease];

}

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

Todo *td = [appDelegate.todos objectAtIndex:indexPath.row];

[cell setTodo:td];

// Set up the cell

return cell;

}

このコードは、アップルが与えてくれているコードにとても似ています。違いは、TodoCellオブジェクトをインスタンス化している点です。識別子を渡して、initWithFrameメソッドを使い、インスタンスを作成しています。

次の違いは、appDelegate を参照し、それを使って、渡されたインデックスのtodoアイテムを見つけています。最後に、我々は行のインデックス位置にあるのToDoアイテムにセルのToDoアイテムを設定し、セルを返します。

以上で完了です。

ビルド&実行してみてください。以下のような画面が表示されれば成功です。

703-1

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

2件のコメント

コメントする

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