UITableViewの並び替えをデータベース(SQLite)に反映し並び順を保持する

##概要
下記のようなSQLiteのテーブルからデータを表示しているUITableViewに
セルの並び替えを実装し、その状態を保持する方法を考えました。

```objc:TableViewController.m
#import "TableViewController.h"
#import "FMDatabase.h"

@interface TableViewController () {
    NSMutableArray *subject;
    NSMutableArray *subjectID;
    NSMutableArray *sequence;
    NSArray *paths;
    NSString *dir;
    FMDatabase *db;
}
@end

@implementation TableViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // データベース,テーブルを作成
    paths = NSSearchPathForDirectoriesInDomains( NSDocumentDirectory, NSUserDomainMask, YES );
    dir   = [paths objectAtIndex:0];
    db    = [FMDatabase databaseWithPath:[dir stringByAppendingPathComponent:@"app.db"]];
    NSString *create = @"CREATE TABLE IF NOT EXISTS tr_sample (subject_id INTEGER PRIMARY KEY AUTOINCREMENT, subject TEXT);";
    [db open];
    [db executeUpdate:create];
    [db close];
    
    // インサート
    NSString *insert = @"INSERT INTO tr_sample (subject) VALUES (?)";
    [db open];
    [db executeUpdate:insert, @"あ"];
    [db executeUpdate:insert, @"い"];
    [db executeUpdate:insert, @"う"];
    [db executeUpdate:insert, @"え"];
    [db executeUpdate:insert, @"お"];
    [db close];
    
    // セレクト
    subject = [[NSMutableArray alloc] init];
    subjectID = [[NSMutableArray alloc] init];
    sequence = [[NSMutableArray alloc] init];
    
    NSString *select = [[NSString alloc] initWithFormat:@"SELECT subject_id, subject FROM tr_sample ORDER BY subject_id"];
    [db open];
    FMResultSet *results = [db executeQuery:select];
    while ([results next]) {
        NSDictionary *dic = [results resultDictionary];
        [subject addObject:dic[@"subject"]];
        [subjectID addObject:dic[@"subject_id"]];
        [sequence addObject:dic[@"sequence"]];
    }
    [results close];
    [db close];
}

// TableViewの行数
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return subject.count;
}

// セルに表示
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    
    static NSString *CellIdentifier = @"Cell";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (!cell) {
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle
                                      reuseIdentifier:CellIdentifier];
    }
    // ラベルにデータを表示する
    cell.textLabel.text = subject[indexPath.row];
    return cell;
}

@end
```
<img width="487" alt="スクリーンショット 2016-07-24 16.50.44.png" src="https://qiita-image-store.s3.amazonaws.com/0/133786/927d8306-4ddc-94e7-02c8-681ec4deb3ab.png">


##セルの並び替えを実装する
viewDidLoadなどに編集ボタンを実装する。

```objc:TableViewController.m
self.navigationItem.rightBarButtonItem = [self editButtonItem];
```

編集モード時に並び替えを行えるようにする。

```objc:TableViewController.m
- (BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath {
    return YES;
}
```
```objc:TableViewController.m
- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath {
}
```
これで編集ボタンを押すとセルの並び替えができるようになります。
セルを移動させた時に`fromIndexPath`と`toIndexPath`でそれぞれ移動前、移動後のIndexPathが取得できます。
<img width="487" alt="スクリーンショット 2016-07-24 19.33.51.png" src="https://qiita-image-store.s3.amazonaws.com/0/133786/742c8698-236b-610c-94a5-8ed2dc8ed95c.png">



##並び替えた順番をデータベースに保持する
テーブル作成時に並び順を保存しておくためのカラムを作る。

```objc:TableViewController.m
NSString *create = @"CREATE TABLE IF NOT EXISTS tr_sample (subject_id INTEGER PRIMARY KEY AUTOINCREMENT, subject TEXT, sequence INTEGER);";
```

セレクト文のORDER BYで参照するカラムを変更する。

```objc:TableViewController.m
NSString *select = [[NSString alloc] initWithFormat:@"SELECT subject_id, subject, sequence FROM tr_sample ORDER BY sequence"];
```


セルの並び替え時に並び順を更新する。
並び替えを実装した時に書いたメソッドにさらに書き加えていきます。

```objc:TableViewController.m
// セルの並び替え時に実行される
- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath {
    NSInteger fromIndex = fromIndexPath.row;
    NSInteger toIndex = toIndexPath.row;
    
    // 移動先と移動元の間にあるセルの並び順を１つずつスライドさせる
    // 下に移動させた場合
    if(fromIndex < toIndex){
        while (fromIndex < toIndex) {
            NSString *update = @"UPDATE tr_sample SET sequence = ? WHERE subject_id = ?";
            [db open];
            [db executeUpdate:update,[NSNumber numberWithInteger:fromIndex],subjectID[fromIndex+1]];
            [db close];
            fromIndex++;
        }
    // 上に移動させた場合
    }else if (fromIndex > toIndex){
        while (fromIndex > toIndex) {
            NSString *update = @"UPDATE tr_sample SET sequence = ? WHERE subject_id = ?";
            [db open];
            [db executeUpdate:update,[NSNumber numberWithInteger:fromIndex],subjectID[fromIndex-1]];
            [db close];
            fromIndex--;
        }
    }

    // 移動させたセルの並び順を更新
    NSString *update = @"UPDATE tr_sample SET sequence = ? WHERE subject_id = ?";
    [db open];
    [db executeUpdate:update,[NSNumber numberWithInteger:toIndex],subjectID[fromIndexPath.row]];
    [db close];
    NSString *select = [[NSString alloc] initWithFormat:@"SELECT subject_id, subject, sequence FROM tr_sample ORDER BY sequence"];
    [db open];
    FMResultSet *results = [db executeQuery:select];

    // 更新したデータをセレクト
    subject = [[NSMutableArray alloc] init];
    subjectID = [[NSMutableArray alloc] init];
    sequence = [[NSMutableArray alloc] init];
    while ([results next]) {
        NSDictionary *dic = [results resultDictionary];
        [subject addObject:dic[@"subject"]];
        [subjectID addObject:dic[@"subject_id"]];
        [sequence addObject:dic[@"sequence"]];
    }
    [results close];
    [db close];

}
```

これで並び替えた状態を保持できるようになりました。
![スクリーンショット 2016-07-24 19.42.26.png](https://qiita-image-store.s3.amazonaws.com/0/133786/f530e816-5ddc-a14b-171a-3e6ec650461b.png)
![スクリーンショット 2016-07-24 19.42.37.png](https://qiita-image-store.s3.amazonaws.com/0/133786/c2250601-c683-335b-ef37-82c6ff988e59.png)

##まとめ
いろいろツッコミどころはあると思いますが、
とりあえずこんな感じで目的は果たせたと思います。
他にもっといい方法がありそうな気がしていますが……