はじめてのSwift UI②

# はじめに

[はじめてのSwift UI①](https://qiita.com/atsu123456789/items/3c736254e9d7c9a1951e)

前回の続きです。
@StateによるViewの更新から画像の差し込みあたりの話までやります。

# ForEach
- UI部分を繰り返し生成するためのもの

``` ContentView.swift
import SwiftUI

struct Human: Identifiable {
    let id = UUID()
    let name: String
}

struct ArrayContentView: View {
    
    let humans = [
        Human(name: "田中"),
        Human(name: "鈴木"),
        Human(name: "佐藤"),
    ]
    
    var body: some View {
        List {
            ForEach(humans) { human in
                Text("\(human.name)さん、こんにちは")
            }
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ArrayContentView()
    }
}
```

↓こんな感じ
![スクリーンショット 2023-06-21 22.26.28.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1193085/a02692b2-7896-3f63-a200-98065a0a25d8.png)

# @State
- Viewの構造体のなかでプロパティを変更できるようにするもの
- 構造体では@Stateをつけることでプロパティを変更できる
- @Stateをつけたプロパティが変わったときにViewが更新される

``` ContentView.swift
import SwiftUI

struct ContentView: View {
    @State var lastName = "田中"
    let firstName = "太郎"
    
    var body: some View {
        VStack {
            Button(action: {self.lastName="島田"}) {
                Text("名字を変える")
            }
            Text(lastName + firstName)
        }
    }
}

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

↓こんな感じ。ボタンを押すと、苗字が田中から島田に代わる
![スクリーンショット 2023-06-21 22.37.22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1193085/cd465e66-e2c2-1ba6-0ae9-87238981103e.png)

# @ObservedObject
- インスタンス版の@State
- @ObservedObjectが付いたインスタンスのプロパティが変更されたときにViewを更新
- インスタンスの基となったクラスで@publishedがついているプロパティだけ
- 基となるクラスにObservedObjectプロトコルをつける
- 変更されたときにViewが更新されるようにしたいプロパティには@Publishedをつける
- ObservedObjectプロトコルがついたクラスを使ってインスタンスを作るときには@ObservedObjectをつける
- @ObservedObjectには複数のViewで使う場合、それぞれ独立したプロパティが使われる
- Viewすべてで共通のプロパティを使いたい場合は@EnvironmentObjectを使う

``` ContentView.swift
import SwiftUI

struct ContentView: View {
    @ObservedObject var userData = UserData(name: "鈴木", age: 20)
    
    var body: some View {
        VStack {
            Button(action: {
                userData.name = "佐藤"
            }) {
                Text("名前を変える")
                    .padding()
            }
            
            Button(action: {
                userData.age += 1
            }) {
                Text("歳を増やす")
                    .padding()
            }
            
            Text("\(userData.name)の年齢は「\(userData.age)」歳")
        }
    }
}

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

``` UserData.swift
import SwiftUI

class UserData: ObservableObject {
    @Published var name: String
    @Published var age: Int
    
    init(name: String, age: Int) {
        self.name = name
        self.age = age
    }
}
```

↓こんな感じ
「名前を変える」を押すと、名前が鈴木から佐藤に、歳を増やすを押すと、歳が増えていく。

![スクリーンショット 2023-06-21 23.07.47.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1193085/7f499a97-ab0d-f5e5-8201-ce1e2294a89a.png)

# @EnvironmentObject
- @ObservedObjectをそれぞれのViewで共通のプロパティを使えるようにしたもの
- @ObservedObjectとの共通点
    - インスタンスの基となるクラスはObservableObjectプロトコル準拠
    - 変更されたときにViewが更新されるようにしたいプロパティは@Publishedをつける

- @ObservedObjectと違うのはインスタンスの作り方

@EnvironmentObjectを理解するためにまず@observedObjectを2つ並べたケースを考えてみる

``` ContentView.swift
import SwiftUI

struct ContentView: View {
    
    @ObservedObject var userData = UserData()
    
    var body: some View {
        VStack {
            Button(action: {
                userData.age += 1
            })
            {
                Text("年齢を増やす")
            }
            
            Text("ContentView: \(userData.name)の年齢は\(userData.age)")
                .padding()
            
            AnotherContentView()
        }
    }
}

struct AnotherContentView: View {
    
    @ObservedObject var userData = UserData()
    
    var body: some View {
        Text("AnotherContentView: \(userData.name)の年齢は\(userData.age)")
    }
}

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

``` UserData.swift
import SwiftUI

class UserData: ObservableObject {
    @Published var name = "田中"
    @Published var age = 25
}
```

これだと↓こんな感じ
![スクリーンショット 2023-06-21 23.25.08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1193085/b336fee1-a63e-aafc-3aa4-0cb34e06916b.png)

UserDataクラスから@ObservedObjectを使ってそれぞれインスタンス化しているため、それぞれのインスタンスは別物として扱われる。
なのでContentView内で定義したButtonを押すと、片方の年齢だけが増加する。

次に@environmentObjectを使ってみる

``` ContentView.swift
import SwiftUI

struct ContentView: View {
    
    @EnvironmentObject var userData: UserData
    
    var body: some View {
        VStack {
            Button(action: {
                userData.age += 1
            })
            {
                Text("年齢を増やす")
            }
            
            Text("ContentView: \(userData.name)の年齢は\(userData.age)")
                .padding()
            
            AnotherContentView()
        }
    }
}

struct AnotherContentView: View {
    
    @EnvironmentObject var userData: UserData
    
    var body: some View {
        Text("AnotherContentView: \(userData.name)の年齢は\(userData.age)")
    }
}

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

``` UserData.swift
import SwiftUI

class UserData: ObservableObject {
    @Published var name = "田中"
    @Published var age = 25
}
```

``` TestApp.swift
import SwiftUI

@main
struct ChangeTextColorApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
                .environmentObject(UserData())
        }
    }
}
```

これだと↓のようになる

![スクリーンショット 2023-06-21 23.31.58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1193085/c0314632-3016-7194-28f6-aa94c0ea372c.png)

このように、共通のプロパティとして扱われるため、「年齢を増やす」ボタンを押すと、両方の年齢が増加する。

今回は「TestApp.swift」(作成したアプリケーション名が入る)でも
``` swift
ContentView()
    .environmentObject(UserData())
```
を記述したが、これは実機やシミュレータなどでも@environmentObjectを適用させるためのものである。

# TextField
- 文字を入力するためのUI
- 入力された文字は@Stateのついた変数に格納される

``` ContentView.swift
struct ContentView: View {
    
    @EnvironmentObject var userData: UserData
    
    @State var favoriteAnimal = ""
    
    var body: some View {
        VStack {
            TextField("好きな動物を入力してください", text:
            $favoriteAnimal)
            Text("すきな動物は\(favoriteAnimal)")
                .padding()
        }
    }
}


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

↓こんな感じ。文字を打ち込めて、その値によって表示が変わる

![スクリーンショット 2023-06-22 19.32.25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1193085/9673178c-5d65-4e02-9b87-8c8215f93e61.png)

# TextFieldでリターンキーを押したときの処理
- onSubmitというモディファイアを使う

``` ContentView.swift
import SwiftUI

struct ContentView: View {
    
    @EnvironmentObject var userData: UserData
    
    @State var favoriteAnimal = ""
    
    var body: some View {
        VStack {
            TextField("好きな動物を入力してください", text:
            $favoriteAnimal)
            .onSubmit {
                favoriteAnimal = ""
            }
            Text("すきな動物は\(favoriteAnimal)")
                .padding()
        }
    }
}


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

↓こんな感じ。TextFieldでリターンを押すと、favoriteAnimalが空になる。


![スクリーンショット 2023-06-22 19.32.25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1193085/b4b3be13-a5b8-ecf5-5710-5a4c862ff4d8.png)

# Image
- 画像を表示するためのUI部品

``` ContentView.swift
import SwiftUI

struct ContentView: View {
    
    @EnvironmentObject var userData: UserData
    
    @State var favoriteAnimal = ""
    
    var body: some View {
        Image("tiikawa")
    }
}


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

↓こんな感じ。プロジェクトには「tiikawa」という画像ファイルを取り込んであります。

![スクリーンショット 2023-06-22 20.05.53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1193085/21eaf1e8-4dac-24c3-d8ea-e7d5db89de43.png)

ただ、このままだと画像サイズが合っていないのでサイズを変更する必要があります。

## 画像サイズの変更
- resizable
    - 画像のサイズを変更できるようにするモディファイア
    - これがないとサイズを指定しても変更できない
- frame
    - 実際に画像のサイズを変更するモディファイア
    - 縦幅と横幅を指定する

``` ContentView.swift
import SwiftUI

struct ContentView: View {
    
    @EnvironmentObject var userData: UserData
    
    @State var favoriteAnimal = ""
    
    var body: some View {
        Image("tiikawa")
            .resizable()
            .frame(width: 200, height: 100, alignment: .center)
    }
}


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

↓こんな感じ。画像のサイズを変更できたことが分かる

![スクリーンショット 2023-06-22 20.12.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1193085/c88e84d9-dca2-c1c3-6e60-c46850462b2e.png)

# さいごに
長くなりそうなので、今回はここまで。
次回で最後にするよ～

# 参考文献

https://www.udemy.com/course/swiftui-ios-iphone/?utm_source=adwords&utm_medium=udemyads&utm_campaign=DSA_WebIndex_la.JA_cc.JP&utm_content=deal4584&utm_term=_._ag_89101504074_._ad_624699263400_._de_c_._dm__._pl__._ti_dsa-454026085647_._li_1009050_._pd__._&gclid=CjwKCAjwv8qkBhAnEiwAkY-ahmilHuWoL7OQRFZ3dMIu6-F-zFWlsRj16En4BE4ckq0Ga_OA7eK-RBoC2AMQAvD_BwE