【第1回】日本語版SwiftUIチュートリアル【基本要素を学ぶ】
日本語版SwiftUIチュートリアル
本チュートリアルは,Appleが掲載している公式のSwiftUIチュートリアルを「日本語で」そして「詳細に」解説し直すことを目的とした記事です.
この手の記事は,他にも似たようなものがありますが,全てを網羅しているわけではなく,部分的に解説している記事が多数です.
そこで,SwiftUIを使ってアプリ開発をしたい方々のために,そして勉強している自分自身のために,日本語版SwiftUIチュートリアルの完全版を書きます.
なお,本記事の難易度としては,初級者も見様見真似で作れるようにはなっていますが,それでは物足りないという中級者のために,細かい文法的な解説も随所に入れていこうと思います.
注意として,本記事はSwiftの言語解説はあまり含まないので,以下の記事を別タブで開いておいて,分からなければ逐一確認してみると良いと思います.
ちなみに出来上がるアプリは,上図に示すような観光地アプリです.
各観光地の位置情報を確認したり,その観光地をお気に入りに追加したりできるアプリを目指します.
第1回:基本要素を学ぶ ~ビューを作成し,組み合わせる~
まずは,SwiftUIを使ってシンプルなビューから作成します.
今回学ぶものは,どのアプリでも必ずと言って良いほど使う超重要機能です.
Step 1. プロジェクトの作成
まずはプロジェクトを作成しましょう.
Xcodeを立ち上げて,
Create a new Xcode projectを選択します.
Single View Appを選択し,Next.
Product Name (プロジェクト名) を「Landmarks」としておきます.
Teamの部分は,Noneにしておきますが,もしその選択肢がなければ,"Add acount..."からログインしてください.
また,Organization NameとOrganization Identifierは何でも良いのですが,後者は固有のものにしたいのでドメイン名を逆で書くのが推奨されています.
私もそうしていますが,このアプリを公開するわけではないので,みなさんは適当で構いません.
ちなみに,Unit Test (単体テスト) やUI Testも,本チュートリアルでは取り上げないのでチェックを外しても良いです.
次に,保存場所を適当に決めて保存します.
ホームディレクトリなどに「SwiftProjects」や「XcodeProjects」といったディレクトリを作成するのがおすすめです.
さあ,Xcodeが立ち上がりました.
すでにいくつかファイルやコードが自動生成されていますね.
Step 2. 実行してみる
まずは実行してみましょう.
右上の "Resume" というボタンを押してみてください.
iPhoneによるプレビューが表示されるかと思います.
ここで,コード中のテキストを変更してみましょう.
1 2 3 4 5 | struct ContentView: View { var body: some View { Text("Hello, SwiftUI!") } } |
プレビューも変更されました.
このように,Xcode中のプレビューは,コードを書き換えると自動的に再描画してくれます,便利ですね.
...
さて,中級者の方に向け,そして初心者が中級者になるために,少しだけコードの解説をしてみます.
1 | import SwiftUI |
これは見ての通りなので,大丈夫ですね.
SwiftUI というフレームワークをインポートしています.
1 2 3 4 5 | struct ContentView: View { var body: some View { Text("Hello, SwiftUI!") } } |
これは,先ほど確認したように,ビューを司る構造体です.
SwiftUIの View というプロトコルを継承したものが,アプリではビューとして扱われます.
そして,プロパティとして body という名のComputed Propatyが定義されていますが,これがビューの核になります.
some キーワードは,同じプロトコル (型) であっても別型のように扱うことができるようにします.
1 2 3 4 5 | struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView() } } |
最後にこの部分.
ここでは,先ほど表示されていたプレビューを担当するコードが書かれています.
PreviewProvider プロトコルを継承するだけで,そういった扱いになります.
Step 3. テキストをカスタマイズする
次に,ビューの構成要素をカスタマイズしてみましょう.
とりあえず,先ほどのテキストを例にやってみます.
1 2 3 4 5 6 | struct ContentView: View { var body: some View { Text("Hello, SwiftUI!") .font(.title) } } |
1 2 3 4 5 6 7 | struct ContentView: View { var body: some View { Text("Hello, SwiftUI!") .font(.title) .foregroundColor(.green) } } |
とても直感的で分かりやすいですね.
もしくは,command ⌘を押しながら, Text( ... ) をクリックして,
Show SwiftUI Inspector... をクリックします.
すると,このようにコードを直接いじらなくとも,視覚的にカスタマイズも可能です.
...
さて,ここでもちょっと詳しく解説を入れます.
1 2 3 | Text("Hello, SwiftUI!") .font(.title) .foregroundColor(.green) |
の Text( ... ) はひとつの構造体です.
そして,その後にくっつく .font(.title) はメソッドに当たるわけですね.
各メソッドが Text 型を返しているので,特に不思議なことはないのですが, .title はちょっと不思議に思うかもしれません.
ドット . の前に何もインスタンスもないわけですから.
実は,Swiftでは型推論の柔軟性が高いです.
今回の場合, font() の引数は定義上では public func font(_ font: Font?) -> Text となっているため,コンパイラは .font(Font.title) と推論してくれるわけですね.
ちなみに今,さらっとでてきた, Font? について興味ある方はOptional型を見てみてください.
Step 4. Stackを使ったビューの組み合わせ
ここが第1回の大事な部分です.
ビューは一つではどうにもなりません.
先ほどのテキストにしても,複数のテキストや,画像の組み合わせで美しいビューを作り上げる必要があります.
そこで,SwiftUIではStackという機能があります.
実際に使ってみましょう.
とりあえず,ベースを以下のようにしておきます.
1 2 3 4 5 6 | struct ContentView: View { var body: some View { Text("Turtle Rock") .font(.title) } } |
ここの「Turtle Rock」というタイトル (観光地名) の下部に,場所をサブタイトルのような形でを入れてみます.
1 2 3 4 5 6 7 8 9 | struct ContentView: View { var body: some View { VStack{ Text("Turtle Rock") .font(.title) Text("Joshua Tree National Park") } } } |
テキストが垂直方向に積み上がりましたね.
そう, VStack { } はVertical Stackの略で,垂直歩行にオブジェクトを積み上げる構造体です.
他にも,Horizontal Stack (水平方向) の HStack { } ,手前方向 (z軸方向)の ZStack { } があります.
もう少し,ビューを整えてみましょう.
1 2 3 4 5 6 7 8 9 10 | struct ContentView: View { var body: some View { VStack(alignment: .leading) { Text("Turtle Rock") .font(.title) Text("Joshua Tree National Park") .font(.subheadline) } } } |
Stack系は,引数としてオブジェクトの左寄せ・右寄せが可能です.
.leadnig が左寄せ, .center が中央寄せ, .trailing が右寄せです.
そうしたら,他のStackも組み合わせてみましょう!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | struct ContentView: View { var body: some View { VStack(alignment: .leading) { Text("Turtle Rock") .font(.title) HStack { Text("Joshua Tree National Park") .font(.subheadline) Spacer() Text("California") .font(.subheadline) } } } } |
このように,Stackのネストも可能です.
ここで, Spacer() はスペースを動的に確保するオブジェクトです.
さらに,今のままだと余白がなくちょっとダサいので, VStack { } に対して余白を入れましょう.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | struct ContentView: View { var body: some View { VStack(alignment: .leading) { Text("Turtle Rock") .font(.title) HStack { Text("Joshua Tree National Park") .font(.subheadline) Spacer() Text("California") .font(.subheadline) } } .padding() } } |
余白でかなりデザインがよくなりました!
ここまでをまとめると,以下のようなイメージです.
...
次のステップに移る前に,ちょっと細かい話を挟みます.
今,使用しているStack系の構造体は少し特殊な形をしていることにお気づきでしょうか?
とても直感的に扱える一方で,Stackの { } 内は一体どのような処理が行われているのか気になりませんか?
そのような疑問は以下の記事で解説しているので,余力のある方は読んでみてください.
Step 5. イメージビューのカスタム
次に,画像をビューで扱う方法について解説していきます.
その前に本チュートリアルで扱う画像たちをダウンロードしてください.
???? Resources.zip
中には観光地の画像と,次回以降使う各観光地の情報が詰まったJSONファイルなども入っています.
これを File > Add Files to "Landmarks"... から追加してください.(command⌘ + option + Aでも可)
Added foldersは,「Create groups」にチェックしてください.
そうしたら,本題に入ります.
まず,「Resources」から「Assets.xcassets」に使う画像をドラッグします.
今回は「turtlerock.jpg」だけです.
アセットカタログにはアプリのアイコンなど固定画像を登録することができ,どこでも簡単に呼び出すことができます.
そうしたら,command⌘ + Nでファイルを新しく作成します.
名前は「CircleImage.swift」としておきます.
そうしたら,早速画像を読み込んで表示させてみましょう!
1 2 3 4 5 6 7 8 9 10 11 12 13 | import SwiftUI struct CircleImage: View { var body: some View { Image("turtlerock") } } struct CircleImage_Previews: PreviewProvider { static var previews: some View { CircleImage() } } |
画像が挿入できました!
そうしたら,加工していきましょう!
1 2 3 4 5 6 | struct CircleImage: View { var body: some View { Image("turtlerock") .clipShape(Circle()) } } |
1 2 3 4 5 6 7 8 9 10 | struct CircleImage: View { var body: some View { Image("turtlerock") .clipShape(Circle()) .overlay( Circle().stroke(Color.white, lineWidth: 4)) .shadow(radius: 10) } } |
お洒落に仕上がりました.
とりあえず画像の加工はこれで良いとして先に進みます.
使い方として,ここで作ったビューは別のビューで呼び出すので,このままにしておいてください.
Step 6. マップビューを作る
第1回目も後半戦です,後少し頑張りましょう.
次に,観光地の一座標からマップ情報を取得し,ビューに組み込むことを考えます.
新しく,「MapView.swift」を作成してください.
そうしたら,MapKitをインポートし,継承するプロトコルも変更します.
1 2 3 4 5 6 7 8 | import SwiftUI import MapKit struct MapView: UIViewRepresentable { var body: some View { Text("Hello, World!") } } |
次に,継承したプロトコルに則り,必要なメソッドを記述していきます.
この時点では,見様見真似で構いません.
1 2 3 4 5 | struct MapView: UIViewRepresentable { func makeUIView(context: Context) -> MKMapView { MKMapView(frame: .zero) } } |
あとひとつメソッドが必要です.
1 2 3 4 5 6 7 8 9 10 11 12 13 | struct MapView: UIViewRepresentable { func makeUIView(context: Context) -> MKMapView { MKMapView(frame: .zero) } func updateUIView(_ uiView: MKMapView, context: Context) { let coordinate = CLLocationCoordinate2D( latitude: 34.011286, longitude: -116.166868) let span = MKCoordinateSpan(latitudeDelta: 2.0, longitudeDelta: 2.0) let region = MKCoordinateRegion(center: coordinate, span: span) uiView.setRegion(region, animated: true) } } |
これでOKです.
プレビューで確認しますが,マップを立ち上げるには▶︎で実際に動作させる必要があります.
うまく動作していそうです.
実際にマップの動作を決定しているのは,
1 2 | let coordinate = CLLocationCoordinate2D( latitude: 34.011286, longitude: -116.166868) |
ですね.
緯度 (latitude) と経度 (longitude) からなる座標 (coordinate) データが核です.
Step 7. 各ビューを組み合わせる
第1回最後のセクションです.
先ほど作ったビューを組み合わせてみましょう.
「ContentView.swift」に戻ります.
マップビューとイメージビューを垂直方向に積み上げたいので,まずは大枠として VStack { } で囲みます.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | struct ContentView: View { var body: some View { VStack { VStack(alignment: .leading) { Text("Turtle Rock") .font(.title) HStack { Text("Joshua Tree National Park") .font(.subheadline) Spacer() Text("California") .font(.subheadline) } } .padding() } } } |
次に,マップビューを入れてみましょう.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | struct ContentView: View { var body: some View { VStack { MapView() .frame(height: 300) VStack(alignment: .leading) { Text("Turtle Rock") .font(.title) HStack { Text("Joshua Tree National Park") .font(.subheadline) Spacer() Text("California") .font(.subheadline) } } .padding() } } } |
さらにイメージビューも追加します.
※注 このとき,私の環境だと画像がはみ出てしまったので, アセットカタログ (Assets.xcassets) の画像を 1x から 2x に変更して対処しました.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | struct ContentView: View { var body: some View { VStack { MapView() .frame(height: 300) CircleImage() .offset(y: -130) .padding(.bottom, -130) VStack(alignment: .leading) { Text("Turtle Rock") .font(.title) HStack { Text("Joshua Tree National Park") .font(.subheadline) Spacer() Text("California") .font(.subheadline) } } .padding() } } } |
ちなみに
1 2 3 | CircleImage() .offset(y: -130) .padding(.bottom, -130) |
では,オフセットでy軸方向に130だけ上に表示をずらし,パディングで同じ分だけマイナスに余白を減らすことで,あたかも画像が単に上に移動しているように見せています.
そして最後に余白を調整しましょう!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | struct ContentView: View { var body: some View { VStack { MapView() .frame(height: 300) CircleImage() .offset(y: -130) .padding(.bottom, -130) VStack(alignment: .leading) { Text("Turtle Rock") .font(.title) HStack { Text("Joshua Tree National Park") .font(.subheadline) Spacer() Text("California") .font(.subheadline) } } .padding() Spacer() } } } |
よくなりましたね.
このままでも良いのですが,最上部のエリアも表示域として扱う場合は,以下のように指定してあげます.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | struct ContentView: View { var body: some View { VStack { MapView() .edgesIgnoringSafeArea(.top) .frame(height: 300) CircleImage() .offset(y: -130) .padding(.bottom, -130) VStack(alignment: .leading) { Text("Turtle Rock") .font(.title) HStack { Text("Joshua Tree National Park") .font(.subheadline) Spacer() Text("California") .font(.subheadline) } } .padding() Spacer() } } } |
とても見栄えがよくなりましたね.
これで,第1回は終了となります,お疲れ様でした!
第1回:おわりに
お疲れ様でした.
第1回も意外と長丁場でしたね.
本記事だけでもSwiftUIの基本は全て習得できたといっても良いでしょう.
しかし,まだまだ重要なSwiftUI機能はたくさんあるので,満足にアプリが作れるようになるには,もう少し知識が必要です.
さて次回はデータを動的に処理し,観光地のリストを作成していきます!
今回は,ひとつのデータに対してしか作れていなかったので,次回でよりアプリらしくなると思います.
頑張りましょう!