[SwiftUI]sheetで遷移するView間で値を引き渡す

# はじめに
この記事は[ジーズアカデミーAdvent Calendar 2019](https://qiita.com/advent-calendar/2019/gsacademy?fbclid=IwAR2AtQw-j9Mln-KaSfmX_E2gNZeg2vbesJcbARuzccOFm_70Nqd9hfWnSEU) 3日目の記事です。
こんにちは！Dev（週末コース）8期の@Ryu0823といいます。
ジーズではWebコースで、最後のDemo dayも参加できなかったのですが、卒業してからもいろいろな言語をかじり、最近はSwiftUIでアプリを制作しています。
SwiftUIはSwiftより簡単かつわかりやすいと感じていますが、まだ新しい言語のためできないこともあったり、情報も少ないのが現状です。
そこで今回は、sheetで遷移する場合にView間で値を渡す方法を探すのに苦労したので、備忘録的に書いてみます。
完成形は↓
![Nov-24-2019 21-50-32.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/152778/fdd87327-7776-0483-238a-811b7df0021a.gif)
# やることと初期状態
FirstViewからsheetとしてSecondViewを表示し、SecondViewで入力された値をFirstViewに表示します。

```swift
struct FirstView: View {
    var body: some View {
        VStack {
            Text("Hello") //　<-SecondViewで入力された値を表示する
            Button(action: {
                print("Button tapped.") // <-SecondViewへ遷移する
            }) {
                Text("Button")
            }
        }
    }
}

struct SecondView: View {
    @State var userName = ""
    
    var body: some View {
        NavigationView {
            Form {
                Section(header: Text("What is your name?")) {
                    TextField("Name", text: $userName) // <-ここで入力された値をFirstViewへ戻す
                }
            }
            .navigationBarTitle("SecondView", displayMode: .inline)
            // FirstViewへ戻る処理が必要
        }
    }
}
```

# FirstViewからsheetとしてSecondViewを表示
```swift

struct FirstView: View {
    // SecondViewの表示/非表示のState
    @State var showSecondView = false

    var body: some View {
        VStack {
            Text("Hello") //　<-SecondViewで入力された値を表示する
            
            Button(action: {
                // SecondViewの表示/非表示の切り替え
                self.showSecondView.toggle()
            }) {
                Text("Tell your name")
                } 
            }
            .sheet(isPresented: self.$showSecondView) {
                // SecondViewを表示
                SecondView(isPresent: self.$showSecondView)
            }
        }
    }
}

struct SecondView: View {
    @State var userName = ""
    // SecondViewの表示/非表示
    @Binding var isPresent: Bool
    
    var body: some View {
        NavigationView {
            Form {
                Section(header: Text("What is your name?")) {
                    TextField("Name", text: $userName) // <-ここで入力された値をFirstViewへ戻す
                }
            }
            .navigationBarTitle("SecondView", displayMode: .inline)
            // FirstViewへ戻る
            .navigationBarItems(trailing:
                Button(action: {
                    self.isPresent = false
                }) {
                    Text("Done")
                }
            )
        }
    }
}
```
# View間で値を連携する
## ObservableObjectを定義
View間で連携したい変数を、ObservableObject classを継承したclassのpropertyとして定義

```swift

final class ViewModel: ObservableObject {
    @Published var name = ""
}
```

## FirstViewにObservedObjectを追加

```swift

struct FirstView: View {
    @State var showSecondView = false
    // ViewModelをObservedObjectとして初期化
    @ObservedObject var userName = ViewModel()
    
    var body: some View {
        VStack {
            // ObservedObjectのname propertyを表示(SecondViewの値を表示したい)
            Text("Hello \(self.userName.name)")
            
            Button(action: {
                self.showSecondView.toggle()
            }) {
                Text("Tell your name")
                } 
            }
            .sheet(isPresented: self.$showSecondView) {
                // SecondViewを表示
                SecondView(isPresent: self.$showSecondView)
            }
        }
    }
}
```

## FirstViewで初期化したObservedObjectをSecondViewと共有する
SecondViewではFirstViewと同じObservedObjectを見る必要があります。
そのため、**SecondViewではViewModel classを指定するだけで、初期化はしません。**
初期化すると別のObservedObjectが作成され、View間で値の共有ができません。
（私はここでつまった💦）

```swift

struct SecondView: View {
    @Binding var isPresent: Bool
    // FirstViewから呼び出す際にFirstViewで初期化したViewModel変数を指定
    @ObservedObject var userName: ViewModel
    
    var body: some View {
        NavigationView {
            Form {
                Section(header: Text("What is your name?")) {
                    TextField("Name", text: $userName.name)
                }
            }
            .navigationBarTitle("SecondView", displayMode: .inline)
            .navigationBarItems(trailing:
                Button(action: {
                    self.isPresent = false
                }) {
                    Text("Done")
                }
            )
        }
    }
}
```

## 最後にFirstView内のSecondView初期化を修正
SecondViewで定義したuserName propertyにFirstViewのuserNameが入ります。
これでSecondViewのTextFieldに入力した値がFirstViewに連携されます。

```swift

SecondView(isPresent: self.$showSecondView, userName: self.userName)
```

# 終わりに & SwiftUI参考リンク
まだまだ勉強中ですが、この記事が少しでも役に立てば幸いです！
SwiftUIやSwiftそのほかモバイル向け言語やってる方いたらお気軽にSNSで繋がりましょう！

最後にSwiftUI学習の参考サイトを載せておきます。
・[100 days of SwiftUI - Hacking with Swift](https://www.hackingwithswift.com/100/swiftui)
[#100daysOfSwiftUI](https://twitter.com/hashtag/100DaysOfSwiftUI?src=hashtag_click)でTwitterに毎日勉強したことを投稿してます。
・[The SwiftUI Lab](https://swiftui-lab.com/)
SwiftUIはまだ出来たばかりなので、ドキュメントが未整備な部分もあるのですが、このサイトではかなり詳しく解説してくれています。