SwiftUIチュートリアルをやってみた その1

samekard
Swift・iOSコラム
12 min readJun 5, 2019

--

SwiftUIというのが出ましたね。チュートリアルがあるそうなのでやってみます。これからチュートリアルを読む人が、負担が3割くらい少なくなることを目指して書きます。

Creating and Combining Views

次のようなアプリのレイアウトを作っていきます。観光地表示アプリとでも言いましょうか。地図と写真と観光地名と州名などを表示します。

観光地表示アプリ

やること

  • Xcodeで新規プロジェクト(SwiftUIを有効)を作成
  • プロジェクト作成後、初期状態をみてみる。ソースコードとビジュアル表示機能の関係を学ぶ
  • 各部品の配置と設定

感想&Tips

  • このチュートリアルを読むと、新しいOSやXcodeを入れなくてもSwiftUIの概要がわかるようになっている。
  • SwiftUIというのは、誤解を恐れながら言えば、UI設計の新しいやり方である。今までのやり方をかなりの勢いで捨てて新しいやり方に移行している。例えば、今まで主流だった、ViewControllerの中にViewがある、という構造が変わっている(プログラマから見える範囲では)。
  • SwiftUIを使うにはmacOS 10.15とXcode 11が必要。また、プロジェクトを作るときにUse SwiftUIにチェックを入れる。
  • プロジェクトを作ると、ContentViewというstructがある。これはViewプロトコルに適合。従来のViewControllerのviewにあたるメインのViewか? body{} の中(Swift文法的にはgetter)にUI部品を記述する。Viewプロトコルが要求するものは body のみ。
  • SwiftUIの部品を作成すると(上のもそうだが)、初期状態でstructが2つ作られている。ひとつはメインの処理を書くもの。もう一つはPreview(下で紹介)で表示するためにひとつめのstructのインスタンスを作成してPreview機能に渡すもの。こちらはPreviewProviderプロトコルに適合。
  • 従来のStoryboardに似た、アプリの画面を表示して視覚的に(色やフォントサイズを)操作できるPreviewというものがある。Preview内でマウスでポチポチと文字を赤色にする操作をすると、即座にソースコードに反映される。StoryboardとPreviewの根本的な違いとして、Storyboardはインスタンス生成のための情報を持つという性質があったが、Previewはソースコードを書くための補助ツールという位置づけになりインスタンス生成のための情報はソースコード側に集める。
  • Previewが表示されていないときは端っこのResumeを押す。
  • Preview表示内で部品をいじるのは、部品を選択して、cmd + クリック。
cmd + クリックしたところ
そこからインスペクタを押して詳細な設定をいじるところ
  • テキストのインスタンスを作ってフォントと色を指定するのは、づらづらとメソッドをつなげていく書き方をする。これらのメソッドはインスタンスを返すので、メソッドの連続が出来る。下の例でいうと、 .font(_).color(_) をやって返ってくるのがTextのインスタンスなので body の{}が some View を返すという要求を満たす。他のこの種のメソッドをやっても満たし続ける。
  • cmd + クリックで、操作のメニューを出すのは、ソースコード上でも行える。
  • レイアウトに関して。垂直方向と水平方向の部品の羅列が手軽に行える。垂直に VStack{} を使い、水平に HStack{} を使う。空白を入れる Spacer() や、部品の周りに余白を作る Padding() や、位置をずらす offset() メソッドがある。
  • body の後のgetterの{}は単一のビューを返す必要があるので、中身に複数の部品がある場合はそれらをまとめる必要がある。 VStack はそのまとめ役の働きもある。
  • VStack{} で囲むとき、ソースを手書きでもいいが、cmd + クリックで現れるメニューからEmbed in VStackを選ぶやり方もある。
  • Textを追加する。Xcode右上の+ボタンで部品箱を表示して、Text部品をソースコードの入れたいところにドラッグ。
  • Spacer は領域を最大に利用するような挙動
  • ここまで何度も見てきたような、インスタンス生成後に連なる、インスタンスの性質を決めるメソッドをmodifierメソッドというらしい
  • 四角い写真を丸く切り取る操作、その写真に輪郭をつける操作、シャドウをつける操作が簡単にできる
  • MKMapViewなどの従来のUI部品を使うことができる。structを作成し、欲しいUI部品の情報をその中に含む形になる。そのstructはUIViewRepresentableプロトコルに適合させる。これは必要なメソッドが2つあり、そこに欲しいUI部品の生成と挙動を書く。これらのメソッドはinit()と従来のviewDidLoad()の役割に近い。こうすることでSwiftUIの部品として扱える。
UIViewRepresentableプロトコルを使ってUIViewのサブクラスが利用可能
  • Previewはstaticモードとliveモードがある。staticモードはSwiftUIの部品のみ表示できる。UIViewのサブクラスである従来のUI部品を配置したときはliveモードに切り替えると表示する。
staticモードとliveモードの切り替えは右下
  • 最後に地図と写真とテキストを統合する。
  • MapViewインスタンスの作成時に縦しか指定してない。横は画面いっぱいに広がる。配置階層の親の値が適用されるということらしい。
  • safe areaを無視する edgesIgnoringSafeArea() がある
  • チュートリアルではところどころで理解度を測る問題が出てくる

Building Lists and Navigation

観光地の表示ページが出来たので、次に観光地リストを表示し、リストから選んだものを表示する処理を作ります。

やること

  • リスト表示の一項目を作成
  • 一項目が出来たらリスト作成
  • リストが出来たら要素をタップして詳細表示に移行する処理を作成
  • 観光地情報表示画面をタップした項目に従ったものにする

感想&Tips

  • まずAppleのチュートリアルページからプロジェクトをダウンロード。そこにはこのチャプターの開始状態プロジェクトと終了状態プロジェクトがある。開始状態プロジェクトをいじっていきましょう。何か変な不具合が出たときは終了状態プロジェクトと比べるといいでしょう。
真ん中のボタンですな
  • このチュートリアルでは観光地のデータはjson形式で与えられる。
  • ひとつの観光地情報を扱うstructがある。jsonデータを用いてこのstructインスタンスを作成して、それを表示部分から利用するという流れ。
  • リストの一項目(LandmarkRow)の設計で、landmardプロパティを追加したときに、LandmarkRow_Previewsでエラーが出るのは、インスタンス生成の情報が足りないから。LandmarkRow_Previews側で情報を足して解決。
  • PreviewProvider (SwiftUIの部品を作ったときに自動で出来る2つのstructのうち、Previewへの表示を担当するstruct。このstructはSwiftUIの部品を生成して返す。このstructをこの記事ではPreviewProviderということにする) は返すSwiftUI部品のサイズ指定が出来る。サイズ指定するとPreview上でそのサイズで表示する。
  • PreviewProviderは複数のSwiftUI部品を返すことが出来る。それにはGroup{}で部品を囲む。そうするとPreview上に複数表示される。今回は完成形がリストなので複数を軽くテスト表示させてみましょうという趣旨でここに組み込まれたと思う。
  • Group{}の外に書いたメソッドがGroup内の各要素に対して実行されるそうな
仕組みが難しそうですが、previewLayoutは各要素に適用されるようです
  • PreviewProviderに書いた内容はアプリに入らない。
  • リストを表示するには、bodyにList{}を書き、その中に項目を入れる。従来あったようなcellのidentifierの設定がいらない。
  • リスト生成時に配列を渡して表示するやりかたもある。チュートリアルではこれをdynamicallyなやり方と言っている。
  • このdynamicallyなやり方で実装するところは、一回で理解できなかったくらいややこしかった。ポイントは2つある。1つ目は、リスト生成時に渡すものは配列データとクロージャだということ。2つ目は、配列データの渡し方は、identifiedして渡すか、配列の要素がもともとIdentifiableプロトコルに適合しているものを渡すということ。
landmarkData配列の中身であるLandmarkにIdentifiableプロトコルをつけたのでlandmarkDataをそのまま渡せる
  • 次にリスト表示画面と観光地表示画面を行き来する処理を作る
  • NavigationView{}で囲むとナビゲーション機能がつく。
  • リストのそれぞれの項目(ソース上は一行だが)をNavigationButton(destination: LandmarkDetail()) {}で囲む。ボタンを押したときの移行先をdestinationに指定する
  • 観光地情報表示画面の表示内容がhard-cording(ソース上に直接書く)されているので、タップしたボタンに応じて内容を変えるようにする。情報受け渡し用のプロパティを追加し、エラーが出たインスタンス生成部分にパラメータを追加、を繰り返す。
  • アプリ立ち上げ時に表示するものを指定するのは 、SceneDelegate.swiftにある以下の行
let window = UIWindow(frame: UIScreen.main.bounds)
window.rootViewController = UIHostingController(rootView: ContentView())
self.window = window
window.makeKeyAndVisible()
  • Previewに表示される画面のサイズを変えるにはpreview providerで指定する

疑問

プロトコルについているsomeは何?

→ そのプロトコルに適合する何かの型を表す。型の具体的な情報を伏せるやり方(情報提供 mono さん)。

処理の効率は具体的な型を指定するやり方と同等。

bodyの後ろの{}はSwiftの文法的に何?

→ getterと思う。

VStack {} はSwiftの文法的に何?

VStack(何か、何か、何か)という形のインスタンス生成の命令であるが、その引数が、クロージャのみになっているため、丸かっこが省略され、波かっこがついている。そのクロージャは () -> Content というもの。Contentが何かわかり次第追記する。

Group {} はSwiftの文法的に何?

同上。

ForEach{} はSwiftの文法的に何?

同上。

landmarkData.identified(by: \.id)のバックスラッシュは何?

→ KeyPath (情報提供 mono さん)

--

--

samekard
Swift・iOSコラム

iOSアプリをいろいろ作りました。英語と中国語を勉強中。