この講演では、よりSwiftらしい方法でテーブルビューコントローラを操作する方法をお話しします。
Table View Controllers in Swift (0:00)
みなさんこんにちは。 ここでは、テーブルビューコントローラを実装し、3つのことを行います。 最初にジェネリクスでそれを作成します。それからそれを編集するためにUndo機能を追加します。そして最後に複雑なイニシャライザを取り除き、構造体に配置します。
基本的なセットアップ (0:44)
final class MyTableViewController: UITableViewController {
let items: [Person]
init(items: [Person], style: UITableViewStyle) {
self.items = items
super.init(style:style)
}
}
テーブルビューコントローラを作成してみましょう。空っぽのテーブルビューコントローラがあります。そして、やろうとしている最初の事は、表示するために(そこに)配列を格納することです。我々は、複数のPerson
を保持する配列items
を作成し、同様にこの配列のためにイニシャライザを作成する必要があります。
イニシャライザは同様に配列itemsとstyle(UITableViewStyle
)を受け取ります。次に、これらのプロパティをセットする必要があります。最初にitems
をセットし super
をコールします。同様に、items
を渡す必要があります。
もう一つ行うことがあります。テーブルビューには別のイニシャライザが必要ですが、魔法のショートカット(Control+Option+Command+F
)を使うことができます。意味は「Fix All Issues」です。これは非常に便利です。
セルがまだ表示されていません。きっと誰でもそれを行う方法を知っています。一緒にやっていきます。最初にtableView(_:numberOfRowsInSection:)
の実装が必要です。
次に、tableView(_:cellForRowAtIndexPath:)
を追加して、セルを構成します。
セルを構成する (3:41)
サブタイトルスタイルを使用したい場合はどうしますか?セルスタイルという構成オプションを使用してこれができます。プロパティを追加して、イニシャライザでそれを渡します。
構成方法を変更したい場合はどうしますか?たとえば詳細なテキストラベルをセットしたいとします。cellForRowAtIndexPath
内で変更せず、構成オプションで実現できます。
2つのやり方があります。1つはプロトコルを使うことですが、より柔軟性が欲しいなら、関数を使用することができます。
オブジェクト指向プログラミングに慣れているなら、セルを構成し、セルとitemを渡すメソッドを追加したいでしょう。そうすると、他の人はサブクラス化して利用できます。
しかし、このように私はクラスをfinal
にしたいです。(クラス全体が表示され、最後にconfigureCellメセッドを利用する箇所が表示された状態)これをどのように実装しますか?メソッドの代わりにパラメータとしてこれを渡すことができます。ですので、cellとitemを渡す関数で構成します。
final class MyTableViewController: UITableViewController {
let items: [Person]
let configureCell: (cell: UITableViewCell, item: Person) -> ()
init(items: [Person], style: UITableViewStyle, configureCell: (UITableViewCell, Person) -> ()) {
self.items = items
self.configureCell = configureCell
super.init(style:style)
}
}
イニシャライザにセルを構成するcellとpersonを受け取る関数をパラメータとして渡す必要があります。
テーブルビューを作成する場所にスクロールし、末尾にクロージャを追加します。cellとitemが与えられるのでcell.textLabel?.textに渡し、cell.detailTextLabel?.textにも同じことをします。
let tableVC = MyTableViewController(items: people, style: .Plain) { cell, item in
cell.textLabel?.text = item.name
cell.detailTextLabel?.text = item.city
}
ジェネリックなモデル (7:12)
Person
ではなく、テーブルビューコントローラにポケモンを表示したい場合はどうしますか?これは、Person
を外部に取り出し、抽象化するためにジェネリクスを使用することができる場所です。それではやってみましょう。
ジェネリックパラメータを追加します。すべてのPersonの出現箇所をItemで置き換えます。 これはとても機械的な手順です。ジェネリッククラスを書いていると、とても機械的な手順になります。全てがまだ動作することを確認できます。
次に、Pokedexビューコントローラを作成しましょう。そしてpokedexを渡します。グループスタイルのdefault
を使用してセルを構成します。cellとitemを取得してからcell.textLabel?.text
にitemをセットしました。
ファンクショナルなUndo (9:08)
編集機能とUndo機能を追加しましょう。また、これを複雑になりかけているイニシャライザのパラメータとします。editable
はBoolです。そしてイニシャライザの下の方で編集ボタンを作成します。editable
がtrueであれば、UIBarButtonItemをナビゲーションバーに作成します。
edit
関数はAnyObject
であるsender
を受け取ります。そしてediting
をトグルで切り替えます。
Edit
ボタンをクリックすると、編集モードで表示されます。現時点では、編集機能の実装のうち、削除だけを実装します。テーブルビューをオーバーライドします。 tableView(_:commitEditingStyle:forRowAtIndexPath:)
。(editingStyleが)削除であるか確認し、そうでなければreturnします。そしてitems
から削除します。
それからモデルを変更後、テーブルを更新するようにする必要があります。手っ取り早い方法でテーブルビュー全体をリロードします。これで実際に削除ができるようになります。
次に、Undo機能を追加するために、Undo用の構造体を作成していきます。同様にジェネリックにします。そしてitemsの配列を初期値で初期化する必要があります。イニシャライザで、それをセットします。
struct UndoHistory<Item> {
let initialValue: [Item]
let history: [[Item]] = []
init(_ initialValue: [Item]) {
self.initialValue = initialValue
}
}
どのように履歴をを追跡しますか?シンプルにitemsの配列の配列を使用します。空の配列からはじめます。そして何かを変更するたびに、この配列にプッシュします。
var currentValue: [Item] {
return history.last ?? initialValue
}
currentValue
プロパティをitemsの配列として定義します。履歴の最後の項目をチェックし、nullであれば初期値を使用します。
UndoHistory構造体を使用するために、テーブルビューコントローラにプロパティとして追加します。そして、itemにアクセスするたびに、UndoHistoryのcurrentValueにいきます
削除のために行う最初のことは、currentValue用のセッターを追加することです。
var currentValue: [Item] {
get { return history.last ?? initialValue }
set { history.append(newValue) }
}
セッターは配列にプッシュします。また、 items 用のにセッターを追加する必要があります。
最後に、削除機能を動作させるために、履歴を変更するとき、テーブルビューをリロードするようにする必要があります。
次に、UNDO部分を追加しましょう。まず関数 undo を追加します。それは、履歴からitemを削除します。この関数は構造体を変更するので、mutating として示される必要があります。
関数 undo をコールする必要があるので、そのためにボタンを作成する必要があります。UIBarButtonItem
のUndoを作成します。targetはself、セレクターはundoです。
mutating func undo {
history.popLast()
}
セレクターを実装する必要があります。その中でhistory.undo()
をコールします。Ash、Natashaを削除し、それを元に戻すことができます。これが動作する理由は配列が構造体であるからです。それは本当に素晴らしいものです。
構造体の構成 (18:59)
次に、テーブルビュークラスを少しシンプルにします。そのために、最初はより複雑にするつもりです。すべてのイニシャライザ・パラメータを取得して、それらを構造体に入れるつもりです。私は、それを切り離して、TableViewConfiguration
という新しい構造体を作成するつもりです。それもジェネリックである必要があります。そして、我々はこれらの特性を作る必要があります。それで、それは多くのタイピングです。最後に、セルを構成します。
これを使うために、ビューコントローラのイニシャライザにテーブルビューの構成を渡します。そして、configuration.items、configuration.cellStyle、configuration.editableを使わなければなりません。
すべての構成を変数に格納することに集中できるので、コードをとてもより素晴らしくします。テスタビリティについて検討しているなら、これは素晴らしいです。これは単純な構造体です、そして、それが正しいことを簡単にテストすることができます。至る所でこれを再利用することができます、そして、1度テーブルビューコントローラをテストする必要があるだけです。
あなたができるのは、それをわずかに変更することです。この変数がライブラリにあると想像してみましょう。何かできることは、我々のアプリでそれを変えることです。理由はそれが構造体なので、簡単にコピーすることができます。そして、config.styleをGroupedにセットしました、そして、現在、それを使うことができます。
これは、私が何度も何度も使用する非常に強力なテクニックです。私は複雑なイニシャライザを持つクラスを作成し、それからイニシャライザの全てを取り出して構造体に格納します非常に再利用可能でテスト可能なコードを得ます。
コードベースでこれらのパターンを使用する場合、コード内でこれが利用可能なケースについて考えていただきたいです。具体的には、コードをきれいにするためにジェネリックと構造体を使用することができるかどうか見てください。それは、非常に有益です。これで色々試してみる場合は、移動機能を追加することをおすすめします。あなたはundo構造体で制限に遭遇するでしょう。しかし、私はあなたがそれを見つけ出して、fixすると確信します。ありがとう。
Q&A (22:57)
Q: ジェネリクスと構成を取り出すこのパターンを使った他の例をあげてください。
私の好きな例は、ネットワークのためにこうします。JSONとして価格など、ネットワークからいくつかのデータをロードし、人の値に変換する機能から始める場合、それを取り出し、APIのエンドポイントを記述する非常に単純な構造体になりました。すべての非同期のコードは、1つの一般的な場所に隠されていたので、その構造体は非常に簡単にテスト可能でした。
私はこの指令された小さいネットワークについてブログ投稿を書きました、しかし、これは大昔にありました。私はかなり前に Tiny Networking というブログ記事を書きました。それはもうコンパイルできませんが、何かヒントになるかもしれません。
Q: 修正可能なエラーを修正するために使用するキーボード ショートカットはなんでしたか?
Chris: Control + Option + Command + Fです。
エディタメニューで、インデントとエラー間の移動などと一緒にチェックしてみてください。
Q: どのように、あなたはXcodeのストーリーボードでこのようなパターンを使うでしょうか?
Chris: かつて、マックアプリのためにいたる所でストーリーボードとセグエを使いました、そして、私はそれが気に入っていました。ジェネリックではない汎用テーブルビューコントローラークラスのサブクラスを作成することによって、ストーリーボードでこれを使用することが可能です。
それでも、私はどんどんストーリーボードを使わなくなりました、そして、私はビューコントローラの外にすべてのロジックを移動するのが好きです。ビューコントローラはとても単純にになりえます。そして、ビューコントローラの外ですべてのナビゲーションを行うことができ、それはより再利用を可能にします。
そして、その有益性がストーリーボードを使用する利点を上回ると思います。将来、それが組み合わせやすくなることを望みます。ビューコントローラをより単純にできるすべてのことと、より再利用性を高めることはすばらしいです。
About the content
2017年3月のtry! Swift Tokyoの講演です。映像はRealmによって撮影・録音され、主催者の許可を得て公開しています。