プログラミング教室で Swift での TODO アプリの作り方を説明しました. その1

縁があってプログラミング教室で Swift で iOS のアプリを作る授業を行いました。

事前のヒアリング等は行わずとりあえず教室に行き授業が始まる数分前に

- どんなレベルなのか?
- どのようなことをやりたいのか？
- 興味が有ることは？

 という質問を生徒さんにして Swift で簡単な iOS アプリを作ることに決めました。

## 生徒のスペック

- 高校生2年生
- タイピングが普通にできる
- 他の言語でプログラムを作ったことがある
  - 配列、メソッド(関数)などの簡単な部分は理解できている
- mac を持っている

## 環境

- 生徒さん5人程度
  - 教室内にはもっと沢山の生徒さんがいてその中の一角で行いました
- 先生は僕１人
- 授業は一コマ 1.5時間
- プロジェクターに自分の画面を映して説明しながら進める

# やったこと

## 作るもの

題材としては `TODO アプリ` を選びました。 

準備時間がほぼゼロでしたので簡単なものを選びました。

また開始段階では生徒さんのレベルを正確に把握できていなかったので、このような簡単なものから初めて、余裕そうであれば徐々に難しい方向に持っていくということも舵取りもしやすそうだったのも選んだ理由の１つです。

今回の授業では以下の画像のように要素の追加と削除が行える状態まで完成しました。

+ ボタンを押した時には固定文字列が追加されるだけなので次回があればここに好きな文字を入力できるようにしたいです(そうでないと TODO アプリとして成立しません)。

![Simulator_Screen_Shot_2016_06_13_11_16_38.png](https://qiita-image-store.s3.amazonaws.com/0/17704/b5d1d990-5ac3-a708-92af-46d4217e961d.png)

## 進め方(1.5時間)

ここでは1.5時間の授業でどのように進めたかを紹介します。

基本的には限られた時間の中で動くものを作ることを優先したので変数や ViewController、 iOS アプリの仕組み、 Swift の文法などは教えていません。

とりあえず動くものを作って、そこから噛み砕いていけたら良いと考えました。

### プロジェクトの作成

Xcode を立ち上げて `Create a new Xcode project` -> `Single View Applicatoin` と進んで

`Product Name` に `TODO` など好きなプロジェクト名を入力します。

この時 `Language` を `Swift` に指定するのを忘れないで下さい。

この状態で適当な場所に `Create` すればプロジェクトの作成は完了です。

(詳細なプロジェクトの作成方法はググってください)

### 起動の確認

プロジェクトを作ったらとりあえず起動してみましょう。

画面左上の実行ボタン(▶)を押しておもむろに起動します。 この時シミュレーターを iPhone 5s などの画面サイズが小さいものに設定しておくことをおすすめします。

起動するとおそらく白い画面のアプリが立ち上がります。 ここでは起動することをとりあえず確認する事が目的なのでそれ以上は特に何もしません。

ここで起動しない場合はエラーメッセージなどを確認して解決する必要があります。

### TableView の表示

任意の項目をリスト上に並べるために iOS では UITableView を使用します。

今回は実装を簡単にするために UITableViewController を使用します。

`Main.storyboard` を開いて最初から用意されている ViewController を選択して delete キーを使って全て削除します(何もないまっさらな状態にします)。

次に以下の画像を参考にして `Navigation Controller` をドラッグします。

どういうわけか知りませんが Navigation Controller には UITableViewController がくっついてくるので別途 UITableViewController をドラッグする必要はありません。

そして Navigation Controller を選択して `Is Initial View Controller` にチェックを入れます。

こうすることでアプリが起動した時にこの ViewController が表示されるようになります。

<img width="1392" alt="貼り付けた画像_2016_06_13_11_43.png" src="https://qiita-image-store.s3.amazonaws.com/0/17704/6efc47fb-b32a-2147-982a-a2b206a4a6d1.png">


次に `ViewController.swift` を開いて

```swift:ViewController.swift
class ViewController: UIViewController {

  // なんかいろいろ書いてある...
}
```

となっている `UIViewController` を `UITableViewController` に書き換えます。

```swift:ViewController.swift
class ViewController: UITableViewController {

  // なんかいろいろ書いてある...
}
```


次に再び `Main.storyboard` を開いて storyboard 上の UITableViewController と `ViewController.swift` の ViewController をひも付けします。

<img width="765" alt="貼り付けた画像_2016_06_13_11_51.png" src="https://qiita-image-store.s3.amazonaws.com/0/17704/2d460295-4812-942c-cc76-b852d17d56a0.png">

この状態でアプリを起動すると横線が並んでいるだけの空っぽの TableView が表示されると思います。

<img width="300" alt="Simulator Screen Shot 2016.06.13 11.54.27.png" src="https://qiita-image-store.s3.amazonaws.com/0/17704/7162d04d-69fb-98dc-b30c-0a034c67ade7.png">

### 項目を表示する

今までは画面をポチポチしてただけですが、ここからは少しプログラミングっぽくなります。

自分の ViewController.swift と見比べながらコードを書いてみてください。

授業では順番に一緒に書きながら進めました。 メソッド名が長いので補完機能をうまく使用して書くようにアドバイスしました。

```swift:ViewController.swift
class ViewController: UITableViewController {

    var items = ["item 1", "item 2", "item 3"]

    override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return items.count
    }

    override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {

        let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath)

        cell.textLabel?.text = items[indexPath.row]

        return cell
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        self.tableView.registerClass(UITableViewCell.self, forCellReuseIdentifier: "cell")

    }

  // なんかいろいろ書いてある...

}
```

この状態で起動すると `item 1, item 2, item 3` が縦に並んで表示されると思います。

> `registerClass:forCellReuseIdentifier` を使用しているのは storyboard をプロジェクターに映すと文字が小さくて説明が大変なのでできるだけコードで書こうと思ったからです。

### 項目を追加する

項目の表示ができたので次は追加を行います。 画面右上に `+` ボタンを表示してそれを押したら何らかの要素が追加されるという機能を作ります。

スペースの都合で必要最小限のコードだけ載せていますが、こんな感じに `viewDidLoad` と `pushAddButton:` を書いてください。

```swift:ViewController.swift
class ViewController: UITableViewController {

  // なんかいろいろ書いてある...


    override func viewDidLoad() {
        super.viewDidLoad()
        self.tableView.registerClass(UITableViewCell.self, forCellReuseIdentifier: "cell")

        // + ボタンと `pushAddButton:` メソッドのひも付け
        navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .Add,
                                                            target: self,
                                                            action: #selector(ViewController.pushAddButton(_:)))
    }


    // + ボタンが押された時に実行されるメソッド
    func pushAddButton(sender: UIBarButtonItem) {

        items.append("追加したよん")
        tableView.reloadData()
    }

  // なんかいろいろ書いてある...

}
```

この状態で起動すると + ボタンを押す度に `追加したよん` という項目が追加されると思います。

`items.append("追加したよん")` の部分を `items.append("\(NSDate())") とやると現在時刻が追加されて面白いです(イギリスの時間になってしまう点だけ注意)。

授業の中では初めは `items.append` だけを行って要素を追加しても画面に反映されないことを確認した後に `tableView.reloadData() の行を追加して画面に反映されることを確認しました。

要素が変更されたことを tableView に伝える必要があることを教えました。

### 項目を削除する

項目の追加ができたので削除を行います。

今回は項目を左にスワイプしたら `Delete` ボタンが表示されてそれを押したら削除されるようにしました。

この機能に関しては追加の仕方を先ほど教えたので削除の仕方は自分たちで考えてもらうことにしました。

とはいっても何もヒント無しでは厳しいと思ったので、 `tableView:commitEditingStyle:forRowAtIndexPath` を書いて delete を削除した位置の取得するところまで書きました。

```swift:ViewController.swift
class ViewController: UITableViewController {

  // なんかいろいろ書いてある...

    // 削除する奴
    override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {

        // delete を押したところの場所
        let index = indexPath.row

    }

}
```

`items` の `removeAtIndex:` を呼ぶのが正解なのですが、 `removeAll` を呼んで全部消えちゃったりして面白かったです。

一つの例としては以下の様なコードを書いてあげると項目が消えます。

```
        // delete を押したところの場所
        let index = indexPath.row

        items.removeAtIndex(index)
        tableView.reloadData()
```

実行して消せることが確認できたら次へ進みます。

### アニメーションさせる

時間が少し余ったので追加の課題のして要素を追加・削除するときにアニメーションさせる方法を紹介しました。

さっきまでのコードだと削除した時には画面がぱっと更新されますが、アニメーションさせることでふわっとエフェクトを付けて消すことができます。

まずは削除時にアニメーションさせる方法です。

さっきのコードをちょろっといじることでアニメーションさせる事ができます。

`tableView.realoadData()` の代わりに `beginUpdates()` と `endUpdates()` を使用してその間に項目に対する操作を書くとアニメーションが行われます。

```swift
  override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {

        // delete を押したところの場所
        let index = indexPath.row

        tableView.beginUpdates()

        // まずは要素を削除
        items.removeAtIndex(index)

        // 削除した位置の情報を作る
        let indexPaths = NSIndexPath(forRow: indexPath.row, inSection: indexPath.section)
        // 要素が削除されたことを tableView に伝える
        tableView.deleteRowsAtIndexPaths([indexPaths], withRowAnimation: .Automatic)

        tableView.endUpdates()
    }
```

この状態で要素を削除するとエフェクトが付くと思います。


最後に追加時のアニメーションです。

削除する時よりちょっとだけ難易度が上がります。  折角なのでこれも追加した要素の index の取得方法だけ教えてアニメーションの処理はどのように行えばいいかを考えてもらいました。

コードの補完が出るので `tableView.add...` や `tableView.insert...` などでどのメソッドを使えばいいのか予測してもらいました。

一つの例としては以下の様なコードで追加時のアニメーションも行えます(時刻の formatter のコードは授業中にこぼれ話として行ったものです)。

```swift
    // + ボタンが押された時に実行されるメソッド
    func pushAddButton(sender: UIBarButtonItem) {

        let formatter = NSDateFormatter()
        formatter.dateStyle = .ShortStyle
        formatter.timeStyle = .FullStyle

        let item = formatter.stringFromDate(NSDate())

        items.append(item)

        tableView.beginUpdates()

        let index = items.indexOf(item)!

        let indexPaths = NSIndexPath(forRow: index, inSection: 0)

        tableView.insertRowsAtIndexPaths([indexPaths], withRowAnimation: .Automatic)

        tableView.endUpdates()
    }
```

ここまで出来たら実行して追加時にもアニメーションが起こることを確認します。

## おわり

これで大体 1.5 時間でした。

要素の削除まで進むかな？と思ったけど想像以上にサクサク進んで面白かったです。

面白かったのでまた次回も続きをやるか別の題材を取り扱うかしてなにかやりたいと思います。

このようなレベル感で生徒に教えてほしいという要望があれば声をかけていただければ幸いです。

最後に、今回使ったソースコードをすべて貼り付けておきます。

```swift:ViewController.swift

import UIKit

class ViewController: UITableViewController {

    var items = ["item 1", "item 2", "item 3"]

    override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return items.count
    }

    override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {

        let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath)

        cell.textLabel?.text = items[indexPath.row]

        return cell
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        self.tableView.registerClass(UITableViewCell.self, forCellReuseIdentifier: "cell")

        // + ボタンと `pushAddButton:` メソッドのひも付け
        navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .Add,
                                                            target: self,
                                                            action: #selector(ViewController.pushAddButton(_:)))
    }


    // + ボタンが押された時に実行されるメソッド
    func pushAddButton(sender: UIBarButtonItem) {

        let formatter = NSDateFormatter()
        formatter.dateStyle = .ShortStyle
        formatter.timeStyle = .FullStyle

        let item = formatter.stringFromDate(NSDate())

        items.append(item)

        tableView.beginUpdates()

        let index = items.indexOf(item)!

        let indexPaths = NSIndexPath(forRow: index, inSection: 0)

        tableView.insertRowsAtIndexPaths([indexPaths], withRowAnimation: .Automatic)

        tableView.endUpdates()
    }



    // 削除する奴
    override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {

        // delete を押したところの場所
        let index = indexPath.row

        tableView.beginUpdates()

        items.removeAtIndex(index)
        let indexPaths = NSIndexPath(forRow: indexPath.row, inSection: indexPath.section)
        tableView.deleteRowsAtIndexPaths([indexPaths], withRowAnimation: .Automatic)

        tableView.endUpdates()
    }

}

```

## 授業風景

![IMG_8499.jpg](https://qiita-image-store.s3.amazonaws.com/0/17704/0460caca-3ace-456e-3a53-d91cc591560f.jpeg)

![IMG_8498.jpg](https://qiita-image-store.s3.amazonaws.com/0/17704/dd0b8893-3d2a-6697-8d51-44353e13076a.jpeg)


## 開催場所

[TENTO さいたま](http://www.tento-net.com/school/tento-saitama)