縁があってプログラミング教室で Swift で iOS のアプリを作る授業を行いました。
事前のヒアリング等は行わずとりあえず教室に行き授業が始まる数分前に
- どんなレベルなのか?
- どのようなことをやりたいのか?
- 興味が有ることは?
という質問を生徒さんにして Swift で簡単な iOS アプリを作ることに決めました。
生徒のスペック
- 高校生2年生
- タイピングが普通にできる
- 他の言語でプログラムを作ったことがある
- 配列、メソッド(関数)などの簡単な部分は理解できている
- mac を持っている
環境
- 生徒さん5人程度
- 教室内にはもっと沢山の生徒さんがいてその中の一角で行いました
- 先生は僕1人
- 授業は一コマ 1.5時間
- プロジェクターに自分の画面を映して説明しながら進める
やったこと
作るもの
題材としては TODO アプリ
を選びました。
準備時間がほぼゼロでしたので簡単なものを選びました。
また開始段階では生徒さんのレベルを正確に把握できていなかったので、このような簡単なものから初めて、余裕そうであれば徐々に難しい方向に持っていくということも舵取りもしやすそうだったのも選んだ理由の1つです。
今回の授業では以下の画像のように要素の追加と削除が行える状態まで完成しました。
- ボタンを押した時には固定文字列が追加されるだけなので次回があればここに好きな文字を入力できるようにしたいです(そうでないと TODO アプリとして成立しません)。
進め方(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 が表示されるようになります。
次に ViewController.swift
を開いて
class ViewController: UIViewController {
// なんかいろいろ書いてある...
}
となっている UIViewController
を UITableViewController
に書き換えます。
class ViewController: UITableViewController {
// なんかいろいろ書いてある...
}
次に再び Main.storyboard
を開いて storyboard 上の UITableViewController と ViewController.swift
の ViewController をひも付けします。
この状態でアプリを起動すると横線が並んでいるだけの空っぽの TableView が表示されると思います。
項目を表示する
今までは画面をポチポチしてただけですが、ここからは少しプログラミングっぽくなります。
自分の 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:
を書いてください。
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 を削除した位置の取得するところまで書きました。
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()
を使用してその間に項目に対する操作を書くとアニメーションが行われます。
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 のコードは授業中にこぼれ話として行ったものです)。
// + ボタンが押された時に実行されるメソッド
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 時間でした。
要素の削除まで進むかな?と思ったけど想像以上にサクサク進んで面白かったです。
面白かったのでまた次回も続きをやるか別の題材を取り扱うかしてなにかやりたいと思います。
このようなレベル感で生徒に教えてほしいという要望があれば声をかけていただければ幸いです。
最後に、今回使ったソースコードをすべて貼り付けておきます。
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()
}
}