Swift初心者が年末年始を利用してSwiftUI Tutorialsをやってみた

# はじめに
今更感はありますが、年末年始で時間ができたので以前からずっと気になっていたSwiftUIを触ってみようと、Appleから提供されている[Tutorials](https://developer.apple.com/tutorials/swiftui/tutorials)をやってみました。（英語能力があれなのでGoogle先生にお世話になりながら...）
学生時代に研究でObjective-Cで一般未公開ながら[iPad向けアプリ](https://ci.nii.ac.jp/naid/170000086394)を作成したことがありましたが、Swiftはほとんど初心者だったため、Tutorialをやっているだけでも「Swiftってこんな書き方するんだ...」みたいな発見が多々ありました。
せっかくなのでSwift・SwiftUI含め詰まったことを自分用の備忘録として雑多にメモメモ。

## やったやつ
[Creating and Combining Views](https://developer.apple.com/tutorials/swiftui/creating-and-combining-views)
[Building Lists and Navigation](https://developer.apple.com/tutorials/swiftui/building-lists-and-navigation)
[Handling User Input](https://developer.apple.com/tutorials/swiftui/handling-user-input)

## structとclass
``` swift
struct ContentView: View {
    var body: some View {
        Text("Turtle Rock")
            .font(.title)
            .foregroundColor(.green)
    }
}
```
`struct`という文字から最初はC言語の構造体みたいな物なのかなと思いきや、関数も宣言できる。
他の言語同様`class`もあり、`struct`と`class`の違いとしては以下の通り。

* `struct`は継承ができない
* `class`は参照型、`struct`は値型

## PreviewProvider
Xcodeで作成中のViewをプレビューする内容を設定するやつ（雑。
`File > New > File...`で`Swift UIView`を選択してファイルを作成すると自動で作成される`struct`。
`previews`の中でViewの表示方法を定義できる。

``` swift
// 普通にContentViewを表示
struct ContentView_Preview: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}
```

ソースを直すとプレビューに即座に反映される。
プレビューからも部品のプロパティを弄れるし、ソースコード側に反映される。
まぁ、 EclipseとかIDEによっては同様な機能を持っている物はある。
面白いと思ったのは`PreviewProvider`での定義方法によって表示をある程度自在にできるところ。
例えば、複数の部品をグループ化して表示する。しかもサイズを設定する。

```swift
struct LandmarkRow_Previews: PreviewProvider {
    static var previews: some View {
        Group {
            LandmarkRow(landmark: landmarkData[0])
            LandmarkRow(landmark: landmarkData[1])
        }
        .previewLayout(.fixed(width: 300, height: 70))
    }
}
```
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/83511/bf0d9d86-36a1-22eb-5d44-7fe2d24125d0.png" width="300px"/>

例えば、端末別に表示をする等ができる。

```swift
struct LandmarkList_Previews: PreviewProvider {
    static var previews: some View {
        ForEach(["iPhone SE", "iPhone XS Max", "iPad Pro (12.9-inch)"], id: \.self) { deviceName in
            LandmarkList()
                .previewDevice(PreviewDevice(rawValue: deviceName))
                .previewDisplayName(deviceName)
        }
    }
}
```

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/83511/47bd2591-fcdb-3536-775b-611b09d785f9.png" width="250px"/>

## VStackとHStack

簡単にレイアウトできる。HTMLより楽。

* `VStack{}` - 垂直方向にコンポーネントを並べる
* `HStack{}` - 水平方向にコンポーネントを並べる

Stackは入れ子にもできる。
また、コンポーネント間の空白や余白も用意されている。

* `Spacer()` - 空白作る
* `Padding()` - 余白作る
* `Offset()` - 位置をずらす

```swift
            VStack(alignment: .leading) {
                Text("Turtle Rock")
                    .font(.title)
                HStack(alignment: .top) {
                    Text("Joshua Tree National Park")
                        .font(.subheadline)
                    Spacer()
                    Text("California")
                        .font(.subheadline)
                }
            }
```

## アプリ立ち上げ時の起動画面

アプリ立ち上げ時の起動画面（RootView）は`SceneDelegate.swift`の`scene`関数で設定する。
`window.rootViewController`にRootViewに設定したいViewをセットする。

```SceneDelegate.swift
    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {

        if let windowScene = scene as? UIWindowScene {
            let window = UIWindow(windowScene: windowScene)
            // UIHostingController()のrootViewに作成したViewを設定する。
            window.rootViewController 
                = UIHostingController(rootView: LandmarkList())
            self.window = window
            window.makeKeyAndVisible()
        }
    }
```

実際にアプリを実装する時、起動時に必要な初期処理(ファイル読み込みとか？)などはどこで書くのがベターなのかな？
画面と処理は分けた方が良さそうなので、RootViewに設定したViewの中で初期処理をやるのは何か違う気がする。
チュートリアルを進めたら出てくるのかな。

## protocol
Javaとかでいう`interface`的なアレ。
構造体でも使える。

```Landmark.swift
// HashableとCodableとIdentifiableを実装している。
struct Landmark: Hashable, Codable, Identifiable {
// 略
}
```

## ObservableObject
データを監視するためのオブジェクトを作成する。
`ObservableObject`プロトコルを実装する`class`を作成する。

変更を他クラスから参照できるよう、公開するプロパティには`@Published`属性を付ける。

```UserData.swift
import SwiftUI
import Combine

final class UserData: ObservableObject {
    @Published var showFavoritesOnly = false
    @Published var landmarks = landmarkData
}
```

参照する側ではObservableObjectを`@EnvironmentObject`属性を付けて宣言する。

```LandmarkList.swift
struct LandmarkList: View {
    // 監視するやーつ
    @EnvironmentObject var userData: UserData

    var body: some View {
        NavigationView {
            List {
                Toggle(isOn: $userData.showFavoritesOnly) {
                    Text("Favorites only")
                }

                ForEach(userData.landmarks) { landmark in
                    if !self.userData.showFavoritesOnly || landmark.isFavorite {
                        NavigationLink(destination: LandmarkDetail(landmark: landmark)) {
                            LandmarkRow(landmark: landmark)
                        }
                    }
                }
            }
            .navigationBarTitle(Text("Landmarks"))
        }
    }
}
```

場所によって参照先の記述方法が変わっているのはなんじゃろ？

* `$userData.showFavoritesOnly` - ドル記号はバインディングしていることを表している模様。値の変更を監視している。
* `ForEach(userData.landmarks) { landmark in` - 下記で`self`を付けてこちらに`self`を付けない理由はなんだろ？
* `!self.userData.showFavoritesOnly` - `self`はJavaとかでいう`this`のイメージ。


## 続く
Tutorialをやっただけですが、面白かったので多分続きます。

## 参考

[SwiftUIチュートリアルをやってみた その１](https://medium.com/swift-column/swiftui-tutorial-1-690de7cf5076)
[Swift では Protocol を積極的に使おう](https://qiita.com/mokumoku/items/2744c68fdb4bb53d0376)