TableViewControllerを用いてToDoアプリを作る

Table View Controller を用いてToDoアプリの解説をしていきます。
個人的なメモとして書いていきますが、一方で誰かのお役に立てればと思います。

- Xcode 7.3.1
- Swift 2.2


<br>

#手順

1. 確認
2. Main.storyboardの設定
3. TableViewController.swift にコードを書いていく

<br>

#(1)始める前に
##TableViewController.swiftの作成
今回はプロジェクト作成時にあるViewControllerは使用しないので削除して、
File -> New -> File... から Subclass が **UITableViewController** のものを選択します。
ここでは、その作成したものを TableViewController.swift とします。
<br>
##Main.storyboardの確認事項
元のビューコントローラーを削除してから Table View Controller を配置します。
そして以下の確認を行います。

- Identity Inspector の Custom Class の Class が **TableViewController になっているか**
- 起動時の画面のビューコントローラーには必ず Attributes Inspector の **Is Initial View Controller にチェックを入れる**（今回は Navigation Controller を使用するのでそちらにチェックをいれる）

<br>

#(2) Main.storyboardでToDoの画面を作成

##Table View Cell の Identifier の設定をする
後に Cell に処理を加える際に Identifier が必要となるので、設定します。
今回は todoCell と名前を付けます。

![1.png](https://qiita-image-store.s3.amazonaws.com/0/121434/69af060b-0d4a-4b08-3bfd-939d50197c96.png)


<br>

##Navigation Controller と ＋ボタン を追加する
Editor -> Embed In -> Navigation Controller で追加すると
TableViewControllerの上にスペースが空きます。そこに Bar Button Item を追加します。
Attributes Inspector の System Item から Add を選択すると ＋ボタンになります。

![2.png](https://qiita-image-store.s3.amazonaws.com/0/121434/eb43ef62-e614-d1cb-12ad-35be91bd3500.png)


そして、これを最初に作成した TableViewController.swift とActionで繋げ、名前を tapAddButton にします。

![3.png](https://qiita-image-store.s3.amazonaws.com/0/121434/55761138-fde5-25e1-b496-ccff01fc4675.png)


<br>

#(2)ToDoのコードをかいていく

<br>

##アラート表示
＋ボタンを押したときに

1. 「ToDoを追加」というタイトル, 「ToDoを入力してください」というメッセージ
2. ToDoを入力するためのテキストエリア
3. OKボタン, キャンセルボタン

を生成します。

1はアラートダイアログで生成できます。
`let alertController = UIAlertController(title: "ToDoを追加", message: "ToDoを入力してください", preferredStyle: UIAlertControllerStyle.Alert)`

<br>

2は作ったアラートダイアログに追加する形で下記のようにします。
`alertController.addTextFieldWithConfigurationHandler(nil)`

<br>

3はOKが押されたときの処理をクロージャで記述します。
`let okAction = UIAlertAction(title: "OK", style: UIAlertActionStyle.Default) { (action: UIAlertAction) -> Void in ~OKが押されたときの処理~ }`

ここで、OKが押されたときの処理まとめると
1. todoListの配列に入力した値を先頭に挿入
2. テーブルに行が追加されたことをデーブルに通知
3. 保存処理
となります。

```swift
// OKボタンが押されたときの処理
            if let textField = alertController.textFields?.first {
                // todoListの配列に入力した値を先頭に挿入
                self.todoList.insert(textField.text!, atIndex: 0)
                
                // テーブルに行が追加されたことをデーブルに通知
                self.tableView.insertRowsAtIndexPaths(
                    [NSIndexPath(forRow: 0, inSection: 0)],
                    withRowAnimation: UITableViewRowAnimation.Right)
                
                //--------------------
                // 保存処理を追加
                //--------------------
                let userDefaults = NSUserDefaults.standardUserDefaults()
                userDefaults.setObject(self.todoList, forKey: "todoList")
                userDefaults.synchronize()
            }
```

<br>

また、入力をする際のポイントとして空白の場合は追加しないという処理を加えると良いです。

○前後の空白の文字を削除
`textField.text = textField.text!.stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceCharacterSet())`

○次にテキストが空白かどうかを確認します。
`if textField.text != "" { }`

<br>

またキャンセルボタンは UIAlertActionStyle を .Cancel にすればできます。
`let cancelAction = UIAlertAction(title: "キャンセル", style: UIAlertActionStyle.Cancel, handler: nil)`

<br>

OKボタン、キャンセルボタンなどを追加する場合は以下のように記述します。
`alertController.addAction()`

<br>

また、これらの設定を表示するために以下の一文を加えます。
`presentViewController(alertController, animated: true, completion: nil)`

<br>

##テーブルの行数を返却する
tableView を override します。return で帰ってきたToDoの長さを
例えばバッジを表示させたいときなどに使用することもできます。

```swift
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        // todoListの配列の長さを返却する
        return todoList.count
    }

```

<br>

##テーブルの行ごとのセルを返却する

再利用可能なセルを取得するために**storyboardで設定した識別子 todoCell をここで使用します。**
また、indexPath.rowでそれぞれ行番号を指定しています。
取得したToDoはセルのラベルにセットすることでTableViewのセルにToDoが表示されます。

```swift
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        // storyboardで指定したtodoCell識別子を利用して再利用可能なセルを取得する
        let cell = tableView.dequeueReusableCellWithIdentifier("todoCell", forIndexPath: indexPath)
        // 行番号に合ったToDoを取得
        let todoTitle = todoList[indexPath.row]
        // セルのラベルにToDoをセット
        cell.textLabel!.text = todoTitle
        return cell
    }
```

<br>

##セルの削除処理

削除可能かどうかの判定は下記のものを加えればできます。

```swift
override func tableView(tableView: UITableView, canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool {
        return true
    }
```

続いてセルの削除処理ですが、

1. 削除可能かどうかみる
2. ToDoリストから削除する
3. セルを削除する
4. 削除したことを保存する

を行います。

```swift
override func tableView(tableView: UITableView,
                            commitEditingStyle editingStyle: UITableViewCellEditingStyle,
                                               forRowAtIndexPath indexPath: NSIndexPath) {
        // 削除可能かどうか
        if editingStyle == .Delete {
            // ToDoリストから削除
            todoList.removeAtIndex(indexPath.row)
            // セルを削除
            tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
            //--------------------
            // 保存処理を追加
            //--------------------
            let userDefaults = NSUserDefaults.standardUserDefaults()
            userDefaults.setObject(self.todoList, forKey: "todoList")
            userDefaults.synchronize()
        }
    }
```


<br>

##ToDoの保存
今回はNSUserDefaultsを使用します。
文字列型配列の[String]に格納していきます。

|    保存する場合    |     参照する場合     |
|:-----------------|:------------------|
| setObject:forKey | stringArrayForKey |

保存処理を入れるタイミングは

- ＋ボタンでToDoを追加したとき
- セルを削除したとき

の２点です。

また、保存したToDoを参照するタイミングを起動時にしたいので viewDidLoad() にかきます。
以下の書き方で文字列型配列の todoList に追加する処理を書きます。
`todoList.appendContentsOf()`

<br>

##コード全体

こちらがコード全体になります。

```swift
import UIKit

class TableViewController: UITableViewController {
    
    // ToDoを格納した配列
    var todoList = [String]()
    
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        //--------------------
        // 読み込み処理を追加
        //--------------------
        let userDefaults = NSUserDefaults.standardUserDefaults()
        if let storedtodoList = userDefaults.arrayForKey("todoList") as? [String] {
            todoList.appendContentsOf(storedtodoList)
        }
    }
    
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }
    
    /**
     * ＋ボタンが押された際の処理
     */
    @IBAction func tapAddButton(sender: AnyObject) {
        // アラートダイアログ生成
        let alertController = UIAlertController(title: "ToDoを追加",
                                                message: "ToDoを入力してください",
                                                preferredStyle: UIAlertControllerStyle.Alert)
        // テキストエリアを追加
        alertController.addTextFieldWithConfigurationHandler(nil)
        // OKボタンを追加
        let okAction = UIAlertAction(title: "OK", style: UIAlertActionStyle.Default) {
            (action: UIAlertAction) -> Void in
            // OKボタンが押されたときの処理
            if let textField = alertController.textFields?.first {
                // 前後の空白文字を削除する要素を追加
                textField.text = textField.text!.stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceCharacterSet())
                // 空白文字かどうかを確認する処理
                if textField.text != "" {
                    // todoListの配列に入力した値を先頭に挿入
                    self.todoList.insert(textField.text!, atIndex: 0)
                    
                    // テーブルに行が追加されたことをデーブルに通知
                    self.tableView.insertRowsAtIndexPaths(
                        [NSIndexPath(forRow: 0, inSection: 0)],
                        withRowAnimation: UITableViewRowAnimation.Right)
                    
                    //--------------------
                    // 保存処理を追加
                    //--------------------
                    let userDefaults = NSUserDefaults.standardUserDefaults()
                    userDefaults.setObject(self.todoList, forKey: "todoList")
                    userDefaults.synchronize()
                }
            }
        }
        // OKボタンを追加
        alertController.addAction(okAction)
        
        // キャンセルボタンがタップされたときの処理
        let cancelAction = UIAlertAction(title: "キャンセル",
                                         style: UIAlertActionStyle.Cancel,
                                         handler: nil)
        // キャンセルボタンを追加
        alertController.addAction(cancelAction)
        
        // アラートダイアログを表示
        presentViewController(alertController, animated: true, completion: nil)
    }
    
    /**
     * テーブルの行数を返却する
     */
    override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        // todoListの配列の長さを返却する
        return todoList.count
    }
    
    /**
     * テーブルの行ごとのセルを返却する
     */
    override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        // storyboardで指定したtodoCell識別子を利用して再利用可能なセルを取得する
        let cell = tableView.dequeueReusableCellWithIdentifier("todoCell", forIndexPath: indexPath)
        // 行番号に合ったToDoを取得
        let todoTitle = todoList[indexPath.row]
        // セルのラベルにToDoをセット
        cell.textLabel!.text = todoTitle
        return cell
    }
    
    /**
     * セルが編集可能かどうかの判定処理
     */
    override func tableView(tableView: UITableView, canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool {
        return true
    }
    
    /**
     * セルを削除した際の処理
     */
    override func tableView(tableView: UITableView,
                            commitEditingStyle editingStyle: UITableViewCellEditingStyle,
                                               forRowAtIndexPath indexPath: NSIndexPath) {
        // 削除可能かどうか
        if editingStyle == .Delete {
            // ToDoリストから削除
            todoList.removeAtIndex(indexPath.row)
            // セルを削除
            tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
            //--------------------
            // 保存処理を追加
            //--------------------
            let userDefaults = NSUserDefaults.standardUserDefaults()
            userDefaults.setObject(self.todoList, forKey: "todoList")
            userDefaults.synchronize()
        }
    }

}
```

<br>

#最後に
まだまだ説明が不十分な点があるかと思いますが、誰かのお役に立てればと思います。
もっとこうしたほうがいい、ここ間違えてる という意見があればコメントいただけると嬉しいです。