MagicalRecordとmogeneratorを利用したSwiftでのToDoアプリ作成


## はじめに
MagicalRecordとmogenerotorを利用したSwiftのToDoリストアプリ(プロジェクト名はToDoList)を説明していきます。MagicalRecordとはCoreDataを楽に使えるライブラリであり、mogeneratorとはCoreDataエンティティのクラスをつくるツールです。この２つを利用することで簡単にCoreDataを扱うことができます。

以下の図は実際に作成したものです。

![スクリーンショット 2016-06-04 10.31.13.png](https://qiita-image-store.s3.amazonaws.com/0/120465/ccfab7b8-6423-36cc-9cef-a510c218e8db.png "スクリーンショット 2016-06-04 10.31.13.png")



TableViewやStoryboardでの画面遷移、AutoLayoutなどの詳細な説明は省略します。詳細などは以下のチュートリアルを参考にしてもらえれば幸いです。

[Swiftで作るToDoアプリ開発チュートリアル](http://easyramble.com/swift-todo-app-tutorial.html
)




## 準備
準備として、Cocoapodsとmogeneratorのインストールが必要です。

Cocoapodsは以下のコマンドを実行することでインストールされます。

```
gem install cocoapods
```

mogeneratorのインストールの方法としては以下のコマンドを実行する

```
brew install mogenerator
```

もしくは以下のURLから直接ダウンロードしてくる方法があります。
https://rentzsch.github.io/mogenerator/

執筆時点ではCocoapodsのバージョンは0.39.0、mogeneratorのバージョンが1.28になっています。

## MagicalRecordのインストールとセットアップ
MagicalRecordのインストールはPodfileに以下を書きます。

```ruby:
pod 'MagicalRecord'
```

以下、コマンドを実行することによりMagicalRecordがインストールされます。

```ruby:
pod install
```
MagicalRecordはObjective-Cによって書かれているライブラリのためヘッダーファイルを作成し、そこに記述する必要があります。

```ObjectiveC:ToDoList-Bridging-Header.h
#import "MagicalRecord.h"
```
Objective-C Bridging-Headerに自身で作成したヘッダーファイルを記述するのを忘れないよう気をつけてください。

![スクリーンショット 2016-06-03 23.59.31.png](https://qiita-image-store.s3.amazonaws.com/0/120465/4e357bb5-4e84-8664-e31f-6138f9126be9.png "スクリーンショット 2016-06-03 23.59.31.png")


これでインストールは完了です。
次はセットアップです。セットアップは非常に簡単で、AppDelegate.swiftに下記のコードを1行追加するだけで完了です。

```swift:AppDelegate.swift
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
        
        MagicalRecord.setupCoreDataStackWithAutoMigratingSqliteStoreNamed("ToDoList.sqlite")
        
        return true
    }
```
MagicalRecordを使わずにCoreDataのセットアップを行う場合、管理オブジェクトであるNSManagedObjectContextや、永続性を保証するストアコーディネータと呼ばれるオブジェクトなどを利用し複数行にわたってセットアップを行わなければなりません。そのセットアップがたった1行で書けるという意味でMagicalRecordがいかに便利なライブラリであるかが分かります。

## ToDoListのモデルを作成
ToDoDataというCoreDataエンティティを作成します。title(タイトル)とtimestamp(時間)という属性をToDoDataエンティティが持つ設計になっています。

![スクリーンショット 2016-06-03 21.37.19.png](https://qiita-image-store.s3.amazonaws.com/0/120465/3b1e2042-b697-bec6-c020-9bdf4b4a4f51.png "スクリーンショット 2016-06-03 21.37.19.png")

右側のClass名を記述するのを忘れないように注意してください。Class名がないと次のmogeneratorを実行する際にエラーとなってしまいます。

## mogeneratorの利用
冒頭でも述べたように、mogeneratorはCoreDataエンティティクラスを自動生成してくれるツールです。新しいバージョンになってより利用しやすい形になっています。.xcdatamodeledがあるフォルダで以下のコマンドをたたくことによってカスタムクラスが生成されます。

```
 mogenerator --v2 --model ToDoList.xcdatamodeld --Swift
```

生成に成功すると以下のような表示になります。

```
1 machine files and 1 human files generated.
```
生成されるファイルにはmachine fileとhuman fileがあります。この2つのファイルをドラッグ＆ドロップでXcodeに結びつけます。
これだけでToDoDataエンティティを扱うことができるようになります。次の章では実際にToDoDateエンティティを扱うクラスを作成していきます。


## モデルクラスを扱うクラスの作成
データの挿入、削除、取り出し、保存といった機能を一つのクラスにまとめて定義してあげます。

モデルクラスを扱うには、まずはじめに管理オブジェクトであるコンテキストの指定をしてあげる必要があります。

```swift:ModelManager.swift
    func context() -> NSManagedObjectContext{
        return NSManagedObjectContext.MR_defaultContext()
    }
```

上記のようにして取得できるコンテキストは、アプリケーション起動直後(AppDelegate.swift)に最初にセットアップしたコンテキストになります。

データの挿入には、MR_createEntity()を利用します。これによってtitleとtimeStampが空のデータが1つ生成されます。そして、引数として、受け取ったNSStringとNSDateを挿入し、コンテキストを保存することでデータの挿入が完了します。MR_saveToPersistentStoreAndWait()はMagicalRecordの公式サイトでデータを保存するときに推奨しているため、利用しています。

```swift:ModelManager.swift
    func insertData(title : NSString , timestamp : NSDate){
        updateData(ToDoData.MR_createEntity()!, title: title, timestamp: timestamp)
    }
    
    func updateData(data : ToDoData , title : NSString , timestamp : NSDate){
        data.title = title as String
        data.timeStamp = timestamp
        context().MR_saveToPersistentStoreAndWait()
    }

```

データの消去にはMR_deleteEntitiy()を使用します。データを一つ受け取りそれを削除します。

```swift:ModelManager.swift
    func deleteData(data : ToDoData){
        data.MR_deleteEntity()
    }

```

データの取り出しにはMR_findAll()を利用します。
作成したToDodataエンティティの全てのデータをNSArray形式で返ってきます、

```swift:ModelManager.swift
    func MakeData() -> NSArray{
        return ToDoData.MR_findAll()!
    }

```

データの保存は、データの挿入の時と同様、MR_saveToPersistentStoreAndWait()を利用します。

```swift:ModelManager.swift
    func save(){
        context().MR_saveToPersistentStoreAndWait()
    }

```


最終的にモデルクラスを扱うクラスのコードは以下のようになります。

```swift:ModelManager.swift
class ModelManager{
    
    func context() -> NSManagedObjectContext{
        return NSManagedObjectContext.MR_defaultContext()
    }
    
    func insertData(title : NSString , timestamp : NSDate){
        updateData(ToDoData.MR_createEntity()!, title: title, timestamp: timestamp)
    }
    
    func updateData(data : ToDoData , title : NSString , timestamp : NSDate){
        data.title = title as String
        data.timeStamp = timestamp
        context().MR_saveToPersistentStoreAndWait()
    }
    
    func deleteData(data : ToDoData){
        data.MR_deleteEntity()
    }
    
   　func MakeData() -> NSArray{
        return ToDoData.MR_findAll()!
    }
    
    func save(){
        context().MR_saveToPersistentStoreAndWait()
    }
    
}
```

この作成したクラスを使い、後は実際に利用したいViewControllerで利用するだけです。

##データの取り出し
データの取り出しはメイン画面でviewWillAppearが呼ばれるたびに行います。viewWillAppearとは画面が呼ばれ、表示される直前に毎回呼ばれるメソッドです。
つまり、画面が呼ばれるたびに、ToDoDataエンティティの中にある全てのデータをdataという配列に入れるという操作を行います。
以下それを記したコードになります。

```swift:ViewController.swift

	var data = [] as! [ToDoData]

	override func viewWillAppear(animated: Bool) {
        super.viewWillAppear(animated)
        reloadData()
    }
    
    func reloadData(){
        data = ToDoData.MR_findAll() as! [ToDoData]
        tableView.reloadData()
    }
```

##データの表示

データの表示は、セルのメインテキストにタスクの内容(title)、サブのテキストに時間(timeStamp)が表示されるようにしています。timeStampはDate型であり、そのままテキストとして表示することが出来ないためString型にフォーマットして表示しています。

```swift:ViewController.swift

    func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        let cell: UITableViewCell = UITableViewCell(style: UITableViewCellStyle.Subtitle, reuseIdentifier: "Cell")
        cell.textLabel?.text = data[indexPath.row].title
        cell.detailTextLabel?.text = stringFromDate(data[indexPath.row].timeStamp!, format: "yyyy年MM月dd日 HH時mm分")
        return cell
    }
    
    func stringFromDate(date: NSDate, format: String) -> String {
        let formatter: NSDateFormatter = NSDateFormatter()
        formatter.dateFormat = format
        return formatter.stringFromDate(date)
    }

    
```

##データの挿入(新しいタスクの追加)
データの挿入は遷移先の画面で行うため、はじめに画面を遷移させます。
遷移させるための方法はいかに記します。今回は特に遷移先にデータを送るといった操作は行いません。

```swift:ViewController.swift
 	@IBAction func addingButton(sender: AnyObject) {
        self.performSegueWithIdentifier("PushData", sender: nil)
    }
    
 	override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
        if(segue.identifier == "PushData"){
        }
    }
```

遷移先の画面では実際にデータの挿入を行います。タスクであるtitleは下図の画面に設定してあるtextFieldから、時刻であるtimeStampは完了ボタンが押されたときの現在時刻をデータとして挿入します。最後にpopViewControllerAnimatedを配置することでデータの挿入後、遷移前の画面に戻るようにしています。

以下、遷移先の画面のコードと実際のStoryBoard上での画面になります。



```swift:NextViewController.swift
class NextViewController: UIViewController {
    
    @IBOutlet weak var textField: UITextField!
    
    @IBAction func insertNewObject(sender: AnyObject) {
        let model = ModelManager()
        let date = NSDate()
        model.insertData(textField.text!, timestamp: date)
        self.navigationController?.popViewControllerAnimated( true )
    }
    
}
```


![スクリーンショット 2016-06-04 10.27.57.png](https://qiita-image-store.s3.amazonaws.com/0/120465/9191cfed-884b-730c-74e7-a11e71423450.png "スクリーンショット 2016-06-04 10.27.57.png")



##データの削除(タスクの編集)
データの削除を行うときは、エンティティとして保存されているモデルのデータとTableViewに表示されているデータの２つを削除しなければなりません。以下コードです。Deleteのボタンが押されたとき(editingStyleがDelete状態のとき)に上記の順番でデータの削除を行っています。
TableViewに表示されているデータを先に消去してしまうと、インデックスが1行ずれてしまい、エラーの原因になるので気をつけてください。

```swift:ViewController.swift

	override func setEditing(editing: Bool, animated: Bool) {
        super.setEditing(editing, animated: animated)
        self.tableView.setEditing(editing, animated: animated)
    }

	func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
        if(editingStyle == UITableViewCellEditingStyle.Delete){
            let model = ModelManager()
            model.deleteData(data[indexPath.row])
            model.save()
            data.removeAtIndex(indexPath.row)
            tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: UITableViewRowAnimation.Fade)
        }
    }
```   

##終わりに
簡単な説明ではありましたがMagicalRecordとmogeneratorを使ったToDoリストアプリの作成を行いました。
まだまだ未熟者ですので、ご不明な点、ご指摘などありましたらどうぞよろしくお願いします。

今回のソースコードはGitHubにあげときました。ぜひ参考にしていただければと思います。

- https://github.com/hakkojapan/ToDoList.git