2012年6月2日土曜日

Sqlite+FMDB+UITableViewで一覧→詳細アプリの作り方

fmdbを使うとADO.Netのような手順でDBを扱うことができるのでWindowsで開発していた人にとっては取っ付きやすいです。

■最終イメージ



サンプルソースは以下からどうぞ。


■手順

  • プロジェクトの作成
  • sqliteフレームワークの追加
  • fmdbライブラリの追加
  • サンプルDBの作成
  • 一覧画面の作成
  • 詳細画面の作成

■プロジェクトの作成

iPhoneSDK:5.1
Xcodeバージョン:4.3.2
iPhoneシュミレーター:5.0.1

新規プロジェクトを作成します。

■プロジェクトにsqliteフレームワークを追加する

xCodeのトップノードを選択し、Build PhasesタブのLink Binary with Libraries欄で+ボタンを押す。


libsqlite3.0.dylibを選択してAddボタンを押す。



■プロジェクトにFMDBライブラリを追加する

以下のページでZIPボタンを押してライブラリをダウンロードします。


ダウンロードしたファイルを解凍してプロジェクトの適当な場所にファイルをコピーします(ここではFMDBというグループを作成してそこにコピー)。


■サンプルDBの作成

新規にデータベースを作成します。ここではLitaを使用して作成しています。
テーブルを作成します。ここではArtistという名前のテーブルを作成しています。

データを入力しておきます。

データを入力したらCompact DBボタンを押して作業を保存します。

作成されたデータベースをプロジェクトに追加します。



■一覧画面の作成

さて準備ができたのでコーディングを進めていきます。
何もしていなければ以下のようにstoryboardが表示されます。
ここからViewControllerを削除します。ついでにViewController.hとViewController.mを削除します。
ViewController.hとViewController.mを削除するときに以下の確認が出ますが、Move To Trashを選んでください。
削除した後はこんな感じ。
storyboard上にNavigation Controllerを追加します。すると以下のようにNavigation ContrllerとTable View Contllerが対で追加されると思います。
Table View Contollerに対応するクラスファイルを追加します。

Subclass OfでUITableViewContollerを選択します。

Table View ContollerのクラスにMasterTableViewControllerを指定します。

ここでArtistのデータをメモリ上で格納するためArtistクラスを作成します。
Artist.h,Artist.mをそれぞれ編集します。
<Artist.h>
#import 

@interface Artist : NSObject
{
    NSString *id;
    NSString *name;
    NSString *genre;
}

@property(nonatomic,retain)NSString *id;
@property(nonatomic,retain)NSString *name;
@property(nonatomic,retain)NSString *genre;

@end

<Artist.m>
#import "Artist.h"

@implementation Artist
@synthesize id;
@synthesize name;
@synthesize genre;
@end
続いてTable View Controllerを記述していきます。

<MasterTableViewController.h>
#import "FMDatabase.h"

@interface MasterTableViewController : UITableViewController
{
    NSMutableArray *mArtists;
}
@end
<MasterTableViewController.m>
#import "MasterTableViewController.h"
#import "Artist.h"

@interface MasterTableViewController ()

@end

@implementation MasterTableViewController

- (id)initWithStyle:(UITableViewStyle)style
{
    self = [super initWithStyle:style];
    if (self) {
        // Custom initialization
    }
    return self;
}

- (void)viewDidLoad
{
    [super viewDidLoad];
    self.title = @"Master";

    NSFileManager *fileManager = [NSFileManager defaultManager]; 
 NSError *error;
 NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
 NSString *documentsDirectory = [paths objectAtIndex:0];
 NSString *writableDBPath = [documentsDirectory stringByAppendingPathComponent:@"Sample.db"];
 BOOL success = [fileManager fileExistsAtPath:writableDBPath];
 if(!success){ 
  NSString *defaultDBPath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:@"Sample.db"];
  success = [fileManager copyItemAtPath:defaultDBPath toPath:writableDBPath error:&error];
 }
 if(!success){
  NSAssert1(0, @"failed to create writable db file with message '%@'.", [error localizedDescription]);
 }

    FMDatabase* db = [FMDatabase databaseWithPath:writableDBPath];
    if(![db open])
    {
        NSLog(@"Err %d: %@", [db lastErrorCode], [db lastErrorMessage]);
    }
    
    [db setShouldCacheStatements:YES];

    NSString*   sql = @"SELECT * FROM Artist;";
    FMResultSet*    rs = [db executeQuery:sql];
    mArtists = [[NSMutableArray alloc] init];
    while( [rs next] )
    {
        Artist * artist = [[Artist alloc] init];
        artist.id = [rs intForColumn:@"ArtistID"];
        artist.name = [rs stringForColumn:@"ArtistName"];
        artist.genre = [rs stringForColumn:@"Genre"];
        artist.origin = [rs stringForColumn:@"Origin"];
        artist.yearsActive = [rs stringForColumn:@"YearsActive"];
        [mArtists addObject:artist];
        
    }
    
    [rs close];
    [db close];
}

- (void)viewDidUnload
{
    [super viewDidUnload];
    // Release any retained subviews of the main view.
    // e.g. self.myOutlet = nil;
}

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
    return (interfaceOrientation == UIInterfaceOrientationPortrait);
}

#pragma mark - Table view data source

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
    // Return the number of sections.
    return 1;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    // Return the number of rows in the section.
    return [mArtists count];
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *CellIdentifier = @"Cell";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell == nil) {
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier] ;
    }

    Artist *artist = [mArtists objectAtIndex:indexPath.row];
    cell.textLabel.text = artist.name;
    cell.detailTextLabel.text = artist.genre;
    cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;

    return cell;
}

/*
// Override to support conditional editing of the table view.
- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath
{
    // Return NO if you do not want the specified item to be editable.
    return YES;
}
*/

/*
// Override to support editing the table view.
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
    if (editingStyle == UITableViewCellEditingStyleDelete) {
        // Delete the row from the data source
        [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
    }   
    else if (editingStyle == UITableViewCellEditingStyleInsert) {
        // Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view
    }   
}
*/

/*
// Override to support rearranging the table view.
- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath
{
}
*/

/*
// Override to support conditional rearranging of the table view.
- (BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath
{
    // Return NO if you do not want the item to be re-orderable.
    return YES;
}
*/

#pragma mark - Table view delegate

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    // Navigation logic may go here. Create and push another view controller.
    /*
     <#DetailViewController#> *detailViewController = [[<#DetailViewController#> alloc] initWithNibName:@"<#Nib name#>" bundle:nil];
     // ...
     // Pass the selected object to the new view controller.
     [self.navigationController pushViewController:detailViewController animated:YES];
     */
}

@end

ここまでで実行すると以下のような画面が表示されると思います。
(まだdidSelectRowAtIndexPathメソッドは記述していません


■詳細画面の作成

storyboardに戻りView Controllerを一つ追加します。


DetailViewControllerとしてクラスファイルを一つ追加し、先ほど追加したView Controllerに割り当てます。


DetailViewControllerを実装していきます。
<DetailViewController.h>
#import "Artist.h"

@interface DetailViewController : UIViewController
{
    Artist *artist;
    UILabel *name;
    UILabel *genre;
    UILabel *origin;
    UILabel *yearsActive;
}
@property (nonatomic,retain) Artist *artist;
@property (nonatomic,retain) IBOutlet UILabel *name;
@property (nonatomic,retain) IBOutlet UILabel *genre;
@property (nonatomic,retain) IBOutlet UILabel *origin;
@property (nonatomic,retain) IBOutlet UILabel *yearsActive;
@end


<DetailViewController.m>
#import "DetailViewController.h"

@interface DetailViewController ()

@end

@implementation DetailViewController
@synthesize artist;
@synthesize name;
@synthesize genre;
@synthesize origin;
@synthesize yearsActive;

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
    if (self) {
        // Custom initialization
    }
    return self;
}

- (void)viewDidLoad
{
    [super viewDidLoad];
 
    name.text = artist.name;
    genre.text = artist.genre;
    origin.text = artist.origin;
    yearsActive.text = artist.yearsActive;
}

- (void)viewDidUnload
{
    [super viewDidUnload];
    // Release any retained subviews of the main view.
}

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
    return (interfaceOrientation == UIInterfaceOrientationPortrait);
}

@end

labelを配置し、コードのOutletとひもづけます。



DetailViewのIdentifierをDetailViewに修正します。


最後にMasterTableViewController側の呼び出し部分を記述します。

<MasterTableViewController.m>   以下の2点を追記するだけです。
#import "DetailViewController.h"

#pragma mark - Table view delegate

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    DetailViewController *detailViewController = [self.storyboard instantiateViewControllerWithIdentifier:@"detailView"];
    detailViewController.title = @"Detail";
    detailViewController.artist = [mArtists objectAtIndex:indexPath.row];
    [self.navigationController pushViewController:detailViewController animated:YES];
}
できあがり
サンプルソースは以下からどうぞ。

7 件のコメント:

  1. I am Inserting a data into SQLite (using FMDB) work on device! But when I update new data ,the device does not show the new data!
    Can you tell me how to solve it?
    thank

    返信削除
  2. You need to delete the application if you already installed your app in your device.
    Because the code above copy app.db when the app.db does not exist.
    Otherwise you can issue sql in appDelegate.

    Hope this helps.

    返信削除
  3. I know to delete the application before "RUN" the new data would be showed ! But if I don't want to delete application, how can I do ? Can you offer the source code~
    thanks

    返信削除
    返信
    1. Sorry for late reply.

      Just remove the code below and overwrite the db every time.

      ---------------------------------------------------------
      BOOL success = [fileManager fileExistsAtPath:writableDBPath];
      if(!success){
      ---------------------------------------------------------

      削除
  4. とてもわかりやすい解説で参考にさせて頂いている初心者です。
    手順どおりに作っていたところ、エラーが3点発生してしまいました、ご教授願えないでしょうか?

    よろしくお願い致します。
    MasterTableViewController.m

    artist.id = [rs intForColumn:@"ArtistID"]; エラー内容① implicit conversion of 'int' to NSString is disallowed with ARC

    artist.origin = [rs stringForColumn:@"Origin"];
    エラー内容② Property 'origin' not found on object of type 'Artist*'

    artist.yearsActive = [rs stringForColumn:@"YearsActive"];
    エラー内容③ Property 'yearsActive' not found on object of type 'Artist*'

    返信削除
    返信
    1. 上記全て誤植でした。本文にも書きましたがソースコードをGitHubにあげたので参考にしてみてください。
      https://github.com/tomonariokubo/SampleFMDB

      削除