SwiftUI Tutorialsから学ぶSwiftUI〜その1〜

# はじめに
本記事は、Apple公式の[SwiftUI Tutorials](https://developer.apple.com/tutorials/swiftui)を参考として内容をまとめた記事です。
英語が苦手な方や、概要をさっと理解したい方を対象に本記事を執筆しております。

注釈コメントを入れたプロジェクトファイルは[こちら(GitHub)](https://github.com/b150005/SwiftUI)

***

# Creating and Combining Views
[Creating and Combining Views](https://developer.apple.com/tutorials/swiftui/creating-and-combining-views)

## 3つのコードエディタ
SwiftUIで記述するプログラムは、「**ソースエディタ**」「**キャンバス**」「**インスペクタ**」の3箇所から編集することができる。

### ソースエディタ(source editor)
![ソースエディタ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/673880/e843206f-4d21-e091-00e0-67810d934012.png)

### キャンバス(canvas)
![キャンバス.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/673880/201c34fd-2c69-ab35-40b4-66698b94b4e1.png)

### インスペクタ(inspector)
**インスペクタ**は、ソースエディタまたはキャンバスで、編集対象を`⌘(command) + ⌥(control)`を押したままクリックすると表示することができる。
![インスペクタ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/673880/610bbec9-b5e7-d638-8450-ef21da45d3bd.png)

## ビュー(view)
**ビュー**は、アプリケーション画面を構成する「画面部品」を指し、`View`プロトコルに準拠する構造体の内部で記述する。
また、ビュー毎に用意された**修飾子(modifier)**を付与することで、自由自在に変更することができる。

ただし、ビューがもつ`body`プロパティは一つのビューしか返せないため、複数ビューをもたせる場合は後述する[スタック]()でグループ化する必要がある。

基本的なビューと、その修飾子を以下に表す。

| ビュー(内容) | 修飾子 | 内容 |
|:--|:--|:--|
| 共通 | foregroundColor | 色 |
|   | frame | サイズ |
|   | padding | パディング |
| Text(ラベル) | font | フォント |
|   | lineLimit | 表示行数 |
|   | truncationMode | 省略位置 |
|   | fixedSize | 水平・垂直方向のサイズ固定 |
| Image(画像) | resizable | サイズの可変化 |
|   | aspectRatio | アスペクト比 |
|   | renderingMode | 色の可変化 |
|   | clipShape | フレームの図形 |
|   | overlay | 上乗せするビュー |
|   | shadow | 影の半径 |
| Button(ボタン) | border | 枠線 |
| Path(2次元図形) | stroke | 線色 |
|   | fill | 塗りつぶし色 |
| Spacer(スペーサー) |  |   |
| List(リスト) |   |   |
| Section(セクション) |   |   |
| NavigationView(ナビゲーションビュー) | navigationBarTitle | ナビゲーションバーのタイトル |
| NavigationLink(遷移先) |   |   |
| TabView(タブビュー) | tabItem | タブ |
| ScrollView(スクロールビュー) |   |   |
| TextField(テキストフィールド) | textFieldStyle | テキストフィールドのスタイル |

## スタック(Stack)
**スタック**は、複数のビューを**x, y, z方向**にグループ化する。

主なスタックは、以下の通り。

| スタック | 方向 | 内容 |
|:--|:-:|:--|
| HStack | x | **全てのビュー**を水平方向にグループ化して表示 |
| LazyHStack | x | **画面内のビュー**を水平方向にグループ化して表示 |
| VStack | y | **全てのビュー**を垂直方向にグループ化して表示 |
| LazyVStack | y | **画面内のビュー**を垂直方向にグループ化して表示 |
| ZStack | z | **全てのビュー**を垂直方向にグループ化して表示 |

## MapKit
**MapKit**は、ビューに**マップ**を埋め込むのに用いるフレームワークであり、マップを表す`Map`ビューと、マップに関連する各構造体を提供する。

### 座標(coordinate)
MapKitにおける座標は、**座標**を表す`CLLocationCoordinate2D`と、**縮尺**を表す`MKCoordinateSpan`で構成される`MKCoordinateRegion`で表現する。

### 値型データのバインド(`$`)
値型データを代入する場合、通常は変数の**値**が代入されるが、参照する値型データに`$`を付与することで、**変数**として代入できる。

***

# Building Lists and Navigation
[Building Lists and Navigation](https://developer.apple.com/tutorials/swiftui/building-lists-and-navigation)

## ビュー(view), モデル(model), リソース(resource)
プロジェクトファイルの管理において、ファイルの用途・機能によって以下のように分類するのが望ましい。

| 分類 | 用途・機能 |
|:--|:--|
| ビュー | **ビュー**を表すswiftファイル |
| モデル | ビューに反映する**データ構造**を記述するswiftファイル |
| リソース | **データ**を格納・記述するファイル(`json`や`XML`など) |

## JSON解析
JSON解析は、以下の手順で行う。

> 1. Bundleからファイルパス(=`URL`オブジェクト)を取得
> 2. Data型オブジェクトにJSONデータを格納
> 3. JSON解析を行う`JSONDecoder`クラスを用いたデコード処理の実施

ここで、JSONデータの解析結果を格納する構造体は、
`Decodable`と`Encodable`の2つの構造体を包含する`Codable`に準拠させることで、
JSONデータのキーと同名である構造体のプロパティに値を格納することができる。

また、JSONデータを格納した構造体リストを走査する場合は、`Hashable`・`Identifiable`プロトコルに準拠させる。

### 定義

```swift
// 1. Bundleからファイルパスを取得
Bundle.main.url(
    forResource name: String?, 
    withExtension ext: String?
) -> URL?
// パラメータ
// name: リソースファイル名
// ext: ファイル拡張子(通常はnil)
// -> extをnilに設定した場合は、拡張子も含めてファイル名を記述

// 2. Data型オブジェクトの生成・格納
// -> 全項目イニシャライザ(memberwise initializer)によって、optionsパラメータの記述は不要
Data(contentsOf url: URL, options: Data.ReadingOptions = []) throws
// パラメータ
// url: ファイルパスを表すURLオブジェクト

// 3. JSONDecoderを用いたデコード処理
JSONDecoder#decode<T>(_ type: T.Type, from data: Data) throws -> T where T : Decodable
// パラメータ
// type: 返却型
// -> 通常は"<返却型>.self"と記述し、<返却型>自身の型を指定
// data: JSONデータを格納するData型オブジェクト
```

### サンプルコード(定型文)

```swift:JSON解析
// JSON解析処理
func load<T: Decodable>(_ filename: String) -> T {
    let data: Data

    // ファイルパスの取得
    // -> プロジェクトファイルはBundleに格納される
    guard let file = Bundle.main.url(forResource: filename, withExtension: nil)
    else {
        fatalError("Couldn't find \(filename) in main bundle.")
    }

    // JSONファイル -> Data型 への変換
    do {
        data = try Data(contentsOf: file)
    } catch {
        fatalError("Couldn't load \(filename) from main bundle:\n\(error)")
    }

    // Data型 -> Codable型に準拠した構造体 への変換(JSON解析)
    // -> <返却型>リストの各プロパティに一対一で対応するJSONデータが代入
    do {
        let decoder = JSONDecoder()
        return try decoder.decode(T.self, from: data)
    } catch {
        fatalError("Couldn't parse \(filename) as \(T.self):\n\(error)")
    }
}
```

## 表示デバイスに応じた動的プレビュー
画面サイズの異なるデバイスに応じた動的プレビューを行う場合、ビューを描画するループ処理を行う`ForEach`構造体と、
キーパス(key path)の**基点となるインスタンス自身**を表す**アイデンティティキーパス**(`\.self`)を用いて、デバイス毎にビューを生成する。

### サンプルコード

```swift:デバイスに応じた動的プレビュー
struct LandmarkList: View { ... }

struct LandmarkList_Previews: PreviewProvider {
    static var previews: some View {
        // デバイスに応じた動的プレビュー
        // -> Identifiableに準拠していないリストを走査する場合は、キーパス式で記述
        // <= リスト・セット・辞書などのコレクションを走査する場合、各要素は"id"プロパティをもつ必要がある
        //    Identifiableに準拠しない場合は、アイデンティティキーパス(\.self)を用いてidを指定
        ForEach(["iPhone SE (2nd generation)", "iPhone XS Max"], id: \.self) { deviceName in
            LandmarkList()
                // プレビューするデバイスの指定
                .previewDevice(PreviewDevice(rawValue: deviceName))
                // プレビュー名の指定
                .previewDisplayName(deviceName)
        }
    }
}
```

# Handling User Input
[Handling User Input](https://developer.apple.com/tutorials/swiftui/handling-user-input)

## Combine
**Combine**は、**値の変更を監視**する`Publisher`、**値を更新**する`Operator`、**更新後の値を取得**する`Subscriber`を提供する
フレームワークであり、「値の変更」を**非同期イベント**として監視し、値の変更に伴う更新処理を自動で行う。

値の変更を監視するデータには`@Published`プロパティラッパを付与し、
監視データを保持するクラスを**final**クラスとして`ObservableObject`プロトコルに準拠させる。


## プロパティラッパ(property wrapper)
**プロパティラッパ**を用いることで、**値型データ**として扱われる構造体(ビュー)の内部で**プロパティの値にアクセス**することができる。

SwiftUIで用意されているプロパティラッパは、以下の通り。

| プロパティラッパ | データ型 | 読み書き | 管理ビュー |
|:--|:-:|:-:|:--|
| プロパティ | 値型 | 読み込み | - |
| @State | 値型 | 読み書き | ビュー自身 |
| @AppStorage | 値型 | 読み書き | アプリケーション自身 |
| @SceneStorage | 値型 | 読み書き | シーン自身 |
| @Binding | 値型 | 読み書き | 外部ビュー |
| @StateObject | 参照型 | - | ビュー自身 |
| @ObservedObject | 参照型 | - | 親ビュー |
| @EnvironmentObject | 参照型 | - | ビュー自身<br>外部ビュー |
| @Environment | 値型・参照型(環境値) | 読み込み | ビュー自身 |