プログラミング

【第2回】日本語版SwiftUIチュートリアル【リストとナビゲーション】

日本語版SwiftUIチュートリアル

日本語版SwiftUIチュートリアル

日本語版SwiftUIチュートリアル

本チュートリアルは,Appleが掲載している公式のSwiftUIチュートリアルを「日本語で」そして「詳細に」解説し直すことを目的とした記事です.

この手の記事は,他にも似たようなものがありますが,全てを網羅しているわけではなく,部分的に解説している記事が多数です.

そこで,SwiftUIを使ってアプリ開発をしたい方々のために,そして勉強している自分自身のために,日本語版SwiftUIチュートリアルの完全版を書きます.

なお,本記事の難易度としては,初級者も見様見真似で作れるようにはなっていますが,それでは物足りないという中級者のために,細かい文法的な解説も随所に入れていこうと思います

注意として,本記事はSwiftの言語解説はあまり含まないので,以下の記事を別タブで開いておいて,分からなければ逐一確認してみると良いと思います.

  1. Swiftを触ったことがないプログラマーのためのSwiftチュートリアル

 

ちなみに出来上がるアプリは,上図に示すような観光地アプリです.

各観光地の位置情報を確認したり,その観光地をお気に入りに追加したりできるアプリを目指します.

第1回は👇

日本語版SwiftUIチュートリアル

 

第2回:リストとナビゲーション ~動的なビュー生成~

第2回は,リストの生成とリンクナビゲーションの解説が主になります.

また,前回は静的なビューしか生成していませんでしたが,今回は複数のデータをもとにビューを動的に生成する,といったことも目指します.

ただ,ここで先に言っておきたいのは,Step 1とStep 2はSwiftUIのチュートリアルとしては,少しコアな話が多く,それをしっかり読み解くとなると難易度は高めです.

読んでいて少しでも「あ,挫折しそう!」となったら,諦めてコピペで済ましてしまうの良い手だと思います.

とりあえずプロジェクトを完成させる,これを第一目標にして頑張ってください.

Step 1. モデルを作る

アプリ開発においてモデル (Model) は重要なパーツです.

MVCモデル (Model View Controller) のMにあたります.

モデルは,その名の通りひとつのデータの集まりを示しており,データベースなどと連携して,ビューやコントローラ側がデータを扱いやすくする役割を担います.

今回作るアプリでは,観光地ひとつひとつを示すモデルを作りましょう.

まずはModelsディレクトリを作りましょう.

File > New > Group から作成できます.

そのあと,Modelsディレクトリに新しく「Landmark.swift」を作りましょう!

モデルは通常,構造体やクラスを用いて作成します.(モデルにプレビューは必要ないので消しちゃってください)

まだ空っぽですが,モデルを構築していきましょう.

まず,何がモデルのプロパティとして必要かを考えます.

とりあえず以下のようなプロパティを持ったモデルを考えてみることとします.

日本語版SwiftUIチュートリアル

とりあえず素直に実装してみます.

このとき,扱いやすくするために,座標とカテゴリは別で構造体,列挙型 (enum) として定義しておきます.

(ここ,本家だと case mountains= "Mountains" が無くて,そのまま進めると「Landmark.app may have crashed」するので気をつけてください!)

enumについている, CaseIterable はその名の通り,全ケースを Category.allCases で配列として取得できるようになる便利なプロトコルです.

そして,座標データをさらに扱いやすくするために, CoreLocation モジュールが提供する,経緯度座標系オブジェクト CLLocationCoordinate2D をプロパティとして追加しておきます.
(ここは深く理解しようとしなくて良いポイント)

このとき,もともとの座標データ coordinates は,外部ファイルから間違って呼び出しがされないように fileprivate アクセス修飾子をつけておきます.

また,画像も画像名だけだと,どうにもならないので, Image オブジェクトとして扱えるようなプロパティも追加しておきます.

ここでは, extension で Landmark 構造体に追加する形で定義してみましたが,別に最初から内部で定義しても特に変わらないはずです.

また,このときも画像名を示すプロパティも使わないので, fileprivate で隠蔽しましょう.

で,実はここで ImageStore.shared.image(...) が解決できなくて怒られます

チュートリアルでは,画像名からイメージオブジェクトに変換するクラス定義の解説を端折っているんですよね.

その話はこの後で説明しますが,まずはモデルの大枠を完成させましょう

本モデルはResources内にある以下のようなJSONデータからデータを受け取って,今作ったモデルをもとにオブジェクトを生成することを想定しています.

それを,コーディングしやすくするために,モデルにいくつかプロトコルを付与していきます.

Hashable は自動でハッシュ値を生成してくれるプロトコルで, Codable はJSONからオブジェクトを生成する際に役立つプロトコルです.

といっても,ピンとこないと思うのでこのチュートリアルでは,「そういうもんなんだな」と思っておいてください.

Srep 2. データ処理部

さて,ここは本家チュートリアルにはありませんが,重要な部分です.

コードが複雑なのと,SwiftUIのチュートリアルでは要らないと判断されたのか,本家では作ってあるファイルをポンと渡すだけでしたが,ここでは少しだけ解説をしてみようかなと思います.

まずはコード全貌を見せます.

とりあえず,Modelsディレクトリに,Data.swiftを新たに作成し,以上をコピペしてください

ちなみに,このファイルだけで以下を担っています.

  1. JSONからオブジェクトの生成
  2. 画像名からイメージオブジェクトの生成

...

さて,ここからはコードの解説になります,飛ばして構いません.(👉 飛ばす場合はこちら)

と言っても,全ては解説しきれませんが,知識に貪欲なみなさまに向けてちょっとだけ.

これは,JSONファイルを読み込んで, Landmark オブジェクト配列を作っていますね.

ただ,この load(...) は標準である関数ではないので,その下で定義しているわけですね.

随所にコメントを挿入してみました.

この関数の func load<T: Decodable> といった定義はジェネリクスと言い,この場合 Decodable なクラスや構造体を T という名前で置き換えていることになります.

ちなみに, Codable = Encodable + Decodable という定義になっていて,今回は T = Landmark となります.

その他,個々の標準であるものについては解説しませんが,やっている処理はなんとなく分かったかと思います.

細かく読んでみると,大した処理はしていませんね.

次は画像の処理部です,こちらの方が読むのは難しいです.

まずはプロパティから見てみましょう.

こちらもコメントで簡単に解説をつけました.

シングルトンについては,詳細は各自調べてください,ここでは深く解説しません.

次にメソッドです.

これも,コメントで簡単に解説をつけました.

コードを読む順番は関数上部のコメントを辿ってみてください.

SwiftUIには,いくつか画像を扱うクラスがありますが, CGImage は画像処理用, Image は加工済みで,かつビューで扱うためのもの,という認識で良いかと思います.

Step 3. リストビューを作る前に構成要素を作る

まずはじめに,「ContentView.swift」を「LandmarkDetail.swift」に変更しておきます.

このとき構造体名もそれに変えて欲しいのですが,Xcodeのリファクタリング機能を使いましょう.

変更したい物にカーソルを当てて (今回は struct ContentView ),Editor > Refactor > Rename で変更してください.

そうしたら,一旦それは置いておいて新たに「LandmarkRow.swift」を作成しましょう.

これは,リストビューの一つ一つの行データを構成する構造体を作るためです.

他プログラミング言語を普段使う人のための日本語版SwiftUIチュートリアル

そうしたら,先ほどJSONから読み込んで生成した,観光地モデル使うためのプロパティを定義します.

ただ,このままではプレビューが表示されません.

プレビューとしたら,「どの観光地データを使えば良いのかわからない」状態だからです.

なので,仮で一つ目のデータを渡しておいて,実装していきます.

これで,プレビューが表示されるようになったはずです.
※ここで「Landmark.app may have crashed (≒JSONからオブジェクトが作れないエラー)」したら,Models > Landmark.swiftに, case mountains = "Mountains" が無いことが考えられます

そうしたら早速,観光地データが反映されているか確認してみましょう.

日本語版SwiftUIチュートリアル

問題なさそうですね!

そうしたら,デザインを整えていきましょう!

日本語版SwiftUIチュートリアル

日本語版SwiftUIチュートリアル

理想的なデザインに仕上がりました.


Step 4. 複数のデータを使ったプレビュー表示

今,試しに一つ目の観光データのプレビューで,動作を確認していますが,他のデータでもうまく動くでしょうか?

ということで試してみましょう!

日本語版SwiftUIチュートリアル

問題はなさそうですが,ちょっと手間ですね.

そこで,複数のプレビューを同時に表示させる方法を紹介します.

まずは,こんなに広いキャンパスは必要ないので必要最低限の大きさにします.

日本語版SwiftUIチュートリアル

そうしたら, Group { } を使って複数のオブジェクトを一つのプレビューにまとめていきます

日本語版SwiftUIチュートリアル

様々なデータで,しっかりと理想通りの動作をしていることが一つのプレビューでわかるようになりました.

ただ,ちょっとコードが乱雑になってきましたね.

この .previewLayout(...) は View オブジェクトであれば適用可能であり, Group { } もその一つなので以下のようにまとめることができます.

とてもスッキリとしたコードになり,可読性も向上しましたね.

こういった小さなビューに限らず,プレビューを複数同時に描画させることで,作業効率の向上や,出来上がるアプリのイメージがしやすくなるので,上手く使っていきましょう.

Step 5. 観光地データをリストとして積み上げる

さて,第2回も後半戦です.

新たに,「LandmarkList.swift」を作成してください.

そうしたら,早速 List { } を使って,先ほど作った行データを積み上げてみましょう!

日本語版SwiftUIチュートリアル

このように, List { } も VStack { } のように垂直方向にビューを積み上げてくれる構造体ですが,こちらは予めリストデザインが決まっています.

またリストはスクロールが可能である点も一つの特徴です.

日本語版SwiftUIチュートリアル

そうしたら,今は観光地データを固定で2つ,お試しで渡しているだけなので,全ての観光地データを動的にリストビューとして表示させましょう

と,やりたいのですがこのままではダメです.

List(...) { } でオブジェクト配列を扱う時,各オブジェクトが常に識別可能である必要があります

なぜかというと,オブジェクトのプロパティが今後変更になった時,ビューの再描画をすぐに行わなければならないからです.

つまり,各オブジェクトは一意に定まるIDなどのキーが必要で,それを List(...) { } に教えてあげる必要があります.

今回は各観光地オブジェクトは id という固有の値を持っていますのでそれを利用したいのですが,以下のように書く必要があります.

しかし,これも可読性が悪くなるので,SwiftUIでは最初に書いた綺麗なコードでも動作するようにするためのプロトコル Identifiable  が用意されています.

これをモデルに付与します.

これで,この構造体は id が識別子として自動で認識されるようになります

すると,先のコードで理想的なプレビューが表示されるはずです.

日本語版SwiftUIチュートリアル

( List(landmarkData) { landmark in ... } の構文を見て「これなんだっけな」と思ったら,クロージャを見てください)


...

さて,ここでちょっとした補足を入れます.

先ほど,一瞬だけ List(landmarkData, id: \.id) が登場しましたが, \.id ←これは何でしょうか?

これはKey-Path Expressionと呼ばれる表記です.

これは一言で言い表すのであれば「プロパティへ動的にアクセスするための表記」です.

配列に対して,どのプロパティを見るかを決める時に使います.

例えば,年齢プロパティ age: Int を持つ User オブジェクトの配列, users があったとします.

それを,年齢でソートする時には以下のように表記します.

どのオブジェクトにおいても age プロパティを見る,という意味がなんとなく理解できるかと思います.

Step 6. リスト⇄詳細ビューのナビゲーション

次に,リスト (LandmarkList.swift) と各観光地データ (LandmarkDetail.swift)を繋げます.

これをナビゲーションと呼びます.

ビューをナビゲーションとする場合は, NavigationView { } でそのビューを囲みます.

そして,ナビゲーションビューとなった各要素 (今回はリストビュー) にはタイトルをつけることができます.

日本語版SwiftUIチュートリアル

このナビゲーションバータイトルの役割はすぐ後にわかります.

そうしたら,各行データをリンク付していきます.

リンク先は「LandmarkDetail.swift」です.

日本語版SwiftUIチュートリアル

しっかりと,ナビゲーションが動作していますね.

ここで,ナビゲーションバータイトルは,前ページに戻るリンクテキストとしても使われていますね.

とてもアプリっぽくなってきましたが,まだ全ての行先の観光地詳細が「Turtle Rock」のままなので次は,その部分を動的なビューになるようにしていきます.

Step 7. 各要素を動的なビューにしていく

まずは,「CircleImage.swift」から.

まだ,この構造体では画像が静的なので,プロパティを新たに持たせることで,引数によって描画するビューが変わるようにします.
(以降のコード修正をすると,本ステップ最後の操作が終わるまでプレビューは表示されません)

次は,「MapView.swift」です.

こちらも座標を動的に変えたいので,それをプロパティとします.

最後に,「LandmarkDetail.swift」.

そうしたら,アプリ立ち上げた際の最初のビューを「SceneDelegate.swift」で設定しましょう.

最後に,ナビゲーションを修正しましょう!

ここまでくると,全てのプレビューがまた上手く動作するようになるはずです.

日本語版SwiftUIチュートリアル

Step 8. プレビュー端末を変える

ここまでは,一種類の端末 (本チュートリアルではiPhone SE 2) で動作確認をしてきました.

しかし,実際にはユーザの端末は一意ではありません.

そこで,プレビューに使われる端末を複数指定して同時に描画することも可能です.

まずは一つの端末を設定してみます.

日本語版SwiftUIチュートリアル

そして2台以上の端末でプレビューを描画するには,以下のようにします.

日本語版SwiftUIチュートリアル

以上,第2回の内容はここまでとなります.

第2回:おわりに

お疲れ様でした!

第2回も,重要なSwiftUIの機能をたくさん紹介しました.

ここまで一緒に実装してきた方であれば,なんとなく仕様も分かってきて楽しくなってきているのではないでしょうか?

さて,次回はユーザ入力の扱い方について解説していきます.

日本語版SwiftUIチュートリアル

スポンサードリンク