SwiftUI初学者がチーム向けに勉強会資料を作った Part1

## はじめに
　私が所属するチーム向けにSwiftUI勉強会を開催することとなったため、資料を記事として投稿します。SwiftUI初学者なので間違いなどあれば、教えていただきたいです！

## 目次
- [はじめに](#はじめに)
- [目次](#目次)
- [事前準備](#事前準備)
- [Part1のお品書き](#part1のお品書き)
    - [基本操作とレイアウト](#基本操作とレイアウト)
- [まとめ](#まとめ)


## 事前準備
　まずは、Xcodeで新しいプロジェクトを作成します。Xcodeを立ち上げ、```Create New Project```を選択してください。

<img src = "https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3939098/d76d712c-9e45-0bb6-24d1-af267285491a.png" width = 400>

　すると、次のような画面になるので、```iOS```の```App```を選択して```Next```を押してください。

<img src = "https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3939098/d7a6cb91-459f-69b0-6dcc-781545c3b89b.png">

　この画面では、次のように設定して```Next```を押します。

<img src = "https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3939098/71af8e84-082d-5185-20d7-0285401b48ac.png">




| 項目                    | 説明                                                       | 今回の設定                                | 
| ----------------------- | ---------------------------------------------------------- | ----------------------------------------- | 
| **Product Name**            | プロジェクト名、基本的には作成するアプリに関する名前を入力 | 任意                                      | 
| **Team**                    | Xcodeに登録済みのApple IDを選択                            | Apple IDを選択                            | 
| **Organization Identifier** | アプリを公開するときに必要となるID                         | 任意                                      | 
| **Bundle Identifier**       | アプリの一意のID                                           | Organization Identifierに合わせて自動入力 | 
| **Interface**               | StoryboardかSwiftUIを選択                                  | SwiftUI                                   | 
| **Language**                | 開発言語を選択                                             | Swift                                     | 
| **Storage**                 | アプリ内でデータを保存する手法を選択                       | None                                      | 
| **Include Test**            | テストコードを含むか                                       | チェックなし                              | 

　```Next``` を押し、任意のディレクトリにプロジェクトを保存すると、次のようにプロジェクトが立ち上がります。これで準備は完了です。

<img src = "https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3939098/0aff0da9-7d8e-287f-8fe4-0ff7f9663992.png">

## Part1のお品書き
- [基本操作とレイアウト](#基本操作とレイアウト)
    - [ContentViewについて](#contentviewについて)
    - [VStack・HStack・ZStack](#vstackhstackzstack)
    - [少し複雑なレイアウト](#少し複雑なレイアウト)
    - [課題](#課題)
        - [Level1](#level1)
        - [Level2](#level2)
        - [Level3](#level3)

### 基本操作とレイアウト
　作成したプロジェクトの画面では画面左側にContentView、右側にはiPhoneの画面が表示されていると思います。そのため、画面のレイアウトを作成したい場合、ContentViewのコードを変更すると、右側のiPhoneのプレビュー画面に即時反映されます。

<img src = "https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3939098/0aff0da9-7d8e-287f-8fe4-0ff7f9663992.png">

#### ContentViewについて

　コードを触る前に```ContentView.swift```を簡単に説明します。```ContentView.swift```はプロジェクト作成時にデフォルトで生成されているファイルで、中身は次のようになっています。

 ```swift:ContentView.swift
import SwiftUI

struct ContentView: View {
    var body: some View {
        VStack {
            Image(systemName: "globe")
                .imageScale(.large)
                .foregroundStyle(.tint)
            Text("Hello, world!")
        }
        .padding()
    }
}

#Preview {
    ContentView()
}
```

　```ContentView.swift```を確認すると、```body{}```内には```VStack{}```があり、その中にはさらに、```Image()```や```Text()```が確認できると思います。画面のレイアウトを作成する場合、基本的には```body{}```の中にコードを追加していくことになります。
　```body{}```内の```VStack{}```は```{}```内に記載された部品を縦方向に並べることができます。プレビュー画面と照らし合わせて見てみると、地球っぽいアイコン、Hello, Worldのテキストが縦方向に並んでいることが確認できます。
　```VStack{}```の後に```.padding()```がありますが、これは余白設定で、```VStack{}```の領域の外側に余白を追加しています。```.padding()```がある場合とない場合の領域（青枠）を比較すると、次のようになります。青枠を表示したい場合は、プレビュー画面の左下に3つ並んでいるアイコンの真ん中（Selectableボタン）を押し、コード内の```VStack```をクリックすると表示されます。

<details><summary>青枠を表示する方法</summary>

<img src = "https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3939098/31d52243-12b7-f508-9708-628ebdf0cdb3.png">

</details>
 
<img src = "https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3939098/c8577bf1-90bd-28c2-a416-5d9a1c025e5e.png" width = 300>

<img src = "https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3939098/e1196b8e-b614-2975-97db-9d970250d76a.png" width = 300>

　コード後半部には以下のような```#Preview```のブロックが記載されていることが確認できると思いますが、これは```SwiftUI```で作成したViewをプレビューしてくれます。これはソースコードのコンパイル時にコードを変換して部分的に生成し、Swiftで繰り返しコードを書く手間を省くことができる```Swift Macros```を用いています。マクロは新しいコードを追加しますが、既存のコードを変更したり修正することはないため、安全に使用できます。

```swift:ContentView.swift
#Preview {
    ContentView()
}
```

　```Swift Macros```が登場する前は```PreviewProvider```を継承した```struct```を定義して以下のように記載していたため、```Swift Macros```を用いたプレビューの定義の方が簡素になりました。

```swift:ContentView.swift
struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}
```

```ContentView.swift```の説明は以上になります。

では、実際に画面レイアウトを設計してみましょう。
はじめに、試しに```Hello, world!```を```こんにちは、世界！```に変更してみましょう。

```swift:ContentView.swift
import SwiftUI

struct ContentView: View {
    var body: some View {
        VStack {
            Image(systemName: "globe")
                .imageScale(.large)
                .foregroundStyle(.tint)
            Text("こんにちは、世界！")
        }
        .padding()
    }
}

#Preview {
    ContentView()
}
```

すると、プレビュー画面のテキストが```こんにちは、世界！```に変更されたと思います。

<img src = "https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3939098/0a648506-cbee-22bd-d114-3c40662cc837.png" width = 300>

また、```Image()```の```systemName```を```play.rectangle.fill```に変更してみましょう。

```swift:ContentView.swift
import SwiftUI

struct ContentView: View {
    var body: some View {
        VStack {
            Image(systemName: "play.rectangle.fill")
                .imageScale(.large)
                .foregroundStyle(.tint)
            Text("こんにちは、世界！")
        }
        .padding()
    }
}

#Preview {
    ContentView()
}
```

すると、色は違いますがYoutubeのようなアイコンが表示されたかと思います。

<img src = "https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3939098/44ec559c-31ae-37ad-a814-51cb425547b6.png" width = 300>

次の章からSwiftUIの基本である```VStack```、```HStack```、```ZStack```をそれぞれ説明します。

#### VStack・HStack・ZStack
##### VStack
　1つ前の章でも軽く触れましたが、```VStack```は```{}```内に記載した部品を縦方向に並べてくれます。```Stack```は```積み重ねる```の意味、```V```は```Vertical```の略で```垂直```の意味なので、```VStack{}```は**垂直（縦）方向に部品を積み重ねるための入れ物**と考えれば、イメージしやすいかもしれません。
　では、```VStack```を使って部品を並べてみましょう。テキストを縦方向に3つ並べて自己紹介文を作ってみます。

```swift:ContentView.swift
import SwiftUI

struct ContentView: View {
    var body: some View {
        VStack {
            Text("私は〜〜です")
            Text("趣味は〇〇です")
            Text("よろしくお願いします！")
        }
        .padding()
    }
}

#Preview {
    ContentView()
}
```

　すると、3つの文が縦方向に並んでいることが確認できると思います。

<img src = "https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3939098/90d670a6-373c-3be2-d0c0-8aa3b7d1681c.png" width = 300>

　また、SwiftUIでは基本図形として、四角形や円を使用できます。せっかくなので四角形と円を縦に並べてみましょう。四角形は```Rectangle()```、円は```Circle()```です。

```swift:ContentView.swift
import SwiftUI

struct ContentView: View {
    var body: some View {
        VStack {
            Rectangle()
                .frame(width: 200, height: 100)
                .foregroundStyle(.blue)
            Circle()
                .frame(width: 100, height: 100)
                .foregroundStyle(.red)
        }
        .padding()
    }
}

#Preview {
    ContentView()
}
```

```Rectangle()```や```Circle()```それぞれの次の行には```.frame()```や```.forgrondStyle()```が続いていますが、これらは**モディファイア**といい、Viewに表示される要素について設定を加えるための修飾子です。前述の```.padding()```などもモディファイアです。
　```.frame()```の引数に```width```と```height```を指定することで、幅や高さを調整することができます。```Rectangle()```については、指定した幅・高さ通りの図形になりますが、```Circle()```に```width```と```height```の値が異なる```.frame()```モディファイアを指定した場合、値の小さい方に合わせた図形サイズになります（例：width = 200、height = 100とした場合、円の直径は100になる）。
 　```.foregroundStyle()```は色を指定することができ、青色にしたい場合は```.blue```のように引数に記載してあげればOKです。

 プレビュー画面を確認すると次のように青色の長方形と赤色の円が縦方向に並んでいることが確認できると思います。

<img src = "https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3939098/e21529cb-5adc-058d-fd9a-5e093271e0cc.png" width = 300>

##### HSTack
　```HStack```は```{}```内に記載した部品を横方向に並べてくれます。```H```は```Horizontal```の略で```水平```の意味なので、前述の```VStack{}```と同様の捉え方をするならば、```HStack{}```は**水平（横）方向に部品を積み重ねるための入れ物**です。

　では、```HStack```を使って部品を並べてみましょう。前述の四角形と円を縦方向に並べるコードを```HStack```に変更してみましょう。

```swift:ContentView.swift
import SwiftUI

struct ContentView: View {
    var body: some View {
        HStack {
            Rectangle()
                .frame(width: 200, height: 100)
                .foregroundStyle(.blue)
            Circle()
                .frame(width: 100, height: 100)
                .foregroundStyle(.red)
        }
        .padding()
    }
}

#Preview {
    ContentView()
}
```

　先程、縦方向に並んでいた図形が横方向に変わったことが確認できると思います。

<img src = "https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3939098/cf3e34d8-339a-54b7-f715-e3a7106f0fa2.png" width = 300>

　前述のサンプルコードの場合、図形と図形の間に少し間隔が空いています。図形同士のスペースの大きさを数値で設定したい場合は、```HStack```の引数を次のように指定してあげるとスペースを設定することができ、```VStack```も同様の方法で実現できます。

```swift:ContentView.swift
import SwiftUI

struct ContentView: View {
    var body: some View {
        HStack(spacing: 0, content: {
            Rectangle()
                .frame(width: 200, height: 100)
                .foregroundStyle(.blue)
            Circle()
                .frame(width: 100, height: 100)
                .foregroundStyle(.red)
        })
    }
}

#Preview {
    ContentView()
}
```

　```HStack```の引数に```spacing```を指定することで、部品同士のスペースを指定することができ、引数```content```に並べたい部品を指定してあげることで、指定したスペースを保った状態で部品を並べることができます。
 今回のサンプルコードのように```spacing```の値を0にすることで、下記のスクリーンショットのように図形同士を横にピッタリ並べることができます。

<img src = "https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3939098/2cf16da9-3b4e-de57-982f-eb53a7449a2a.png" width = 300>

##### ZSTack
　```ZStack```は部品を重ねて配置し、画面に奥行きを表現することができます。```VStack```の```V（Vertical：垂直）```や```HStack```の```H（Horizontal：水平）```のように、```ZStack```の```Z```の意味を探しましたがわからなかったので、私は勝手に三次元のZ軸方向という解釈をしています。
 では、```ZStack```を使って部品を並べてみましょう。色と大きさの異なる円を3つ重ねてみます。

```swift:ContentView.swift
import SwiftUI

struct ContentView: View {
    var body: some View {
        ZStack {
            Circle()
                .frame(width: 300, height: 300)
                .foregroundStyle(.red)
            Circle()
                .frame(width: 200, height: 200)
                .foregroundStyle(.blue)
            Circle()
                .frame(width: 100, height: 100)
                .foregroundStyle(.yellow)
        }
    }
}

#Preview {
    ContentView()
}
```

 すると、直径の大きい円から順番に3つ重なっていることが確認できると思います。```ZStack```については、```VStack```や```HStack```のように引数```spacing```は指定できません。

 <img src = "https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3939098/ebd17d37-2e14-1e63-0860-a3b74aa58faa.png" width = 300>

##### 少し複雑なレイアウト
　これまでは、```HStack```、```VStack```、```ZStack```だけ用いたレイアウトを紹介しましたが、組み合わせることで少し複雑なレイアウトにも対応することができます。以下のようなコードを作成した場合、どのようなレイアウトになるか想像できますか？

```swift:ContentView.swift
import SwiftUI

struct ContentView: View {
    var body: some View {
        ZStack {
            Rectangle()
                .frame(width: 300, height: 300)
                .foregroundStyle(.black)
            VStack {
                Rectangle()
                    .frame(width: 200, height: 100)
                    .foregroundStyle(.red)
                HStack(spacing: 0, content: {
                    Rectangle()
                        .frame(width: 100, height: 100)
                        .foregroundStyle(.blue)
                    Circle()
                        .frame(width: 100, height: 100)
                        .foregroundStyle(.yellow)
                })
            }
        }
    }
}

#Preview {
    ContentView()
}
```

　上記のサンプルコードを実装してプレビュー画面で確認すると、次のようになります。

<img src = "https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3939098/5c3b7ea8-0132-fcee-8ab7-1acf90b59211.png" width = 300>

　少しだけ解説をすると、青色の正方形と黄色の円は```HStack```で横に並べ、赤色の長方形と```HStack```を```VStack```で縦に並べています。そして、黒色の正方形と```VStack```を```ZStack```積み重ねます。

<img src = "https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3939098/42646b90-9ad9-87d9-062c-c388d9ee5bcf.png" width = 300>

<img src = "https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3939098/b2b0d0a1-9b9c-6646-303a-ea6c579af3af.png" width = 300>

　このように、```HStack```、```VStack```、```ZStack```を用いることで、複雑なレイアウトも実現することができます。次節から課題を掲載しているので取り組んでみてください。

#### 課題
　課題のサンプルコードを記載していますが、サンプルコードと完全一致していなくても他の方法でレイアウトを作成できていればOKです。
##### Level1
　次のような自己紹介カードを作成してください。また、モディファイアを用いて以下のように各要素を設定してください。

- **各種設定**
- **「自己紹介カード」**
    - サイズ：```300x50```
    - 下線あり
    - フォントはタイトルサイズ
    - 文字は**太字**
    - 余白をつける（余白の数値設定は必要なし）

<p></p>

- **「名前」や「年齢」といった記入欄**
    - サイズ：```300x30```
    - 下線なし
    - フォントは本文サイズ
    - 文字は**太字**
    - 余白をつける（余白の数値設定は必要なし）

<p></p>

- **自己紹介カードの枠線**
    - 青色で太さは2.0

<p></p>

<img src = "https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3939098/4565ec98-8579-5442-7505-6d77380db7b3.png" width = 300>

<details><summary>サンプルコード</summary>

```swift:ContentView.swift
import SwiftUI

struct ContentView: View {
    var body: some View {
        VStack {
            SampleTitleText(text: "自己紹介カード")
                .underline()
                .padding()
            SampleBodyText(text: "名前：")
                .padding()
            SampleBodyText(text: "年齢：")
                .padding()
            SampleBodyText(text: "出身地：")
                .padding()
            SampleBodyText(text: "一言：")
                .padding()
        }
        .border(.blue, width: 2)
    }
}

struct SampleTitleText: View {
    var text: String?
    var body: some View {
        Text(text ?? "None Text")
            .font(.title)
            .bold()
            .frame(width: 300, height: 50, alignment: .center)
    }
}

struct SampleBodyText: View {
    var text: String?
    var body: some View {
        Text(text ?? "None Text")
            .font(.body)
            .bold()
            .frame(width: 300, height: 30, alignment: .leading)
    }
}

#Preview {
    ContentView()
}
```

</details>

##### Level2
　次のようなイラストを作成してください。部品は```VStack```、```ZStack```は用いずに```ZStack```のみでレイアウト実装してください。また、モディファイアを用いて以下のように各要素を設定してください。

- **各種設定**
    - 赤色の半円と白色の半円（元の円のサイズ：```200x200```）
        - 半円はモディファイアで実現可能
    - 黒色の細長い長方形（サイズ：```200x10```）
    - 中央に黒色と白色の円（黒色円のサイズ：```70x70```、白色円のサイズ：```50x50```）
    - 黒色の枠線（幅：```2.0```）

<img src = "https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3939098/5fa1a8d8-7a84-c06d-c365-f8ee403cb1c6.png" width = 300>


<details><summary>サンプルコード</summary>

```swift:ContentView.swift
import SwiftUI

struct ContentView: View {
    var body: some View {
        ZStack {
            Circle()
                .trim(from: 0.5, to: 1.0)
                .frame(width: 200, height: 200)
                .foregroundStyle(.red)
            Circle()
                .trim(from: 0.0, to: 0.5)
                .frame(width: 200, height: 200)
                .foregroundStyle(.white)
            Rectangle()
                .frame(width: 200, height: 10)
                .foregroundStyle(.black)
            Circle()
                .frame(width: 70, height: 70)
                .foregroundStyle(.black)
            Circle()
                .frame(width: 50, height: 50)
                .foregroundStyle(.white)
            Circle()
                .stroke(lineWidth: 2.0)
                .frame(width: 200, height: 200)
        }
    }
}

#Preview {
    ContentView()
}
```

</details>


##### Level3
　次のようなイラストを作成してください。また、モディファイア等を用いて以下のように各要素を設定してください。

- **各種設定**
    - 茶色の長方形（サイズ：```250x280```）
    - 赤色の長方形（サイズ：```260x40```）
    - 窓（色：シアン）
        - 1階の窓（サイズ：```40x70```）
        - 2階の窓（サイズ：```50x70```）
        - 幅```2.0```の枠をつける
    - 1階の窓とバルコニー、2階の窓とバルコニーの間にはそれぞれ縦方向に```20```のスペースを設けてください
    - 屋根（三角形）は```Rectangle()```や```Circle()```のように```Triangle()```はないので、```Path```を用いてカスタムShapeを自作してください（サイズ：```300x150```）

<details><summary>Pathを用いたカスタムShapeの作成手順</summary>

1. ```Shape```プロトコルを準拠した構造体を用意する
2. ```Path```を用いて図形を描画する

```swift:基本的な使い方
struct CustomShape: Shape {
    func path(in rect: CGRect) -> Path {
        Path { path in
            // pathに描画メソッドを追加して図形を描く
        }
    }
}
```

```swift:ContentView.swift
import SwiftUI

struct ContentView: View {
    var body: some View {
        VStack {
            CustomRectangle()
                .frame(width: 100, height: 100)
                .foregroundStyle(.blue)
            CustomRectangle()
                .stroke(lineWidth: 5.0)
                .frame(width: 200, height: 100)
                .foregroundStyle(.green)
        }
    }
}

#Preview {
    ContentView()
}

struct CustomRectangle: Shape {
    func path(in rect: CGRect) -> Path {
        Path { path in
            path.move(to: CGPoint(x: rect.minX, y: rect.minY)) // 開始位置
            path.addLine(to: CGPoint(x: rect.maxX, y: rect.minY)) // 開始位置から線を追加する
            path.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY)) // 上の行で追加した線の終点から、新たに線を追加する
            path.addLine(to: CGPoint(x: rect.minX, y: rect.maxY)) // 上の行で追加した線の終点から、新たに線を追加する
            path.addLine(to: CGPoint(x: rect.minX, y: rect.minY)) // 上の行で追加した線の終点から、新たに線を追加する
            path.closeSubpath() 
        }
    }
}
```

<img src = "https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3939098/374c0d60-0da1-0588-d1eb-d1e15749c25f.png" width = 200>


</details>

<img src = "https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3939098/9a1cb714-6267-a48d-7bae-20e7b006bbce.png" width = 300>


<details><summary>サンプルコード</summary>

```swift:ContentView.swift
import SwiftUI

struct ContentView2: View {
    var body: some View {
        VStack(spacing: 0, content: {
            Triangle()
                .frame(width: 300, height: 150)
                .foregroundStyle(.red)
            
            ZStack {
                Rectangle()
                    .frame(width: 250, height: 280)
                    .foregroundStyle(.brown)
                
                VStack {
                    HStack(spacing: 0, content: {
                        ZStack {
                            Window(width: 50, height: 70)
                        }
                        ZStack {
                            Window(width: 50, height: 70)
                        }
                    })
                    
                    Spacer().frame(height: 20)
                    
                    Rectangle()
                        .frame(width: 260, height: 40)
                        .foregroundStyle(.red)

                    Spacer().frame(height: 20)
                    
                    HStack(spacing: 0, content: {
                        ZStack {
                            Window(width: 40, height: 70)
                        }
                        ZStack {
                            Window(width: 40, height: 70)
                        }
                        ZStack {
                            Window(width: 40, height: 70)
                        }
                    })
                }
            }
        })
    }
}

struct Triangle: Shape {
    func path(in rect: CGRect) -> Path {
        Path { path in
            path.move(to: CGPoint(x: rect.midX, y: rect.minY))
            path.addLine(to: CGPoint(x: rect.minX, y: rect.maxY))
            path.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY))
            path.addLine(to: CGPoint(x: rect.midX, y: rect.minY))
            path.closeSubpath()
        }
    }
}

struct Window: View {
    var width: CGFloat?
    var height: CGFloat?
    
    var body: some View {
        Rectangle()
            .frame(width: width, height: height)
            .foregroundStyle(.cyan)
        Rectangle()
            .stroke(lineWidth: 2.0)
            .frame(width: width, height: height)
    }
}

#Preview {
    ContentView2()
}
```

</details>


## まとめ
　Part1の勉強会資料は以上となります。SwiftUIの勉強を始めてあまり月日が経っていないので、まだ初学者の段階ですが少し理解は深まったと思います。資料作成と並行して勉強をしないといけないので少し大変ですが、次回作（Part2）をお楽しみに！