【SwiftUI】親Viewから子Viewのシートに引数が渡らない問題と対策

タイトル通り親Viewから子Viewシートに引数が渡らない事象があったので、問題と対策を共有します。
ご指摘あればドシドシお願いします！

## 実行環境
Xcode 12.4
Swift 5.3.2

## 問題
親Viewから子Viewシートに引数が渡らない。
正確には１回目のシート表示のViewだけ、引数が渡っていない。

言葉だけだと意味分からないかもなので、コードと動画を載せます。
(カーソルが挙動不審ですが許してください😂)

![capture.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/426420/7f9d60ea-78ed-5349-9823-8e307eb1626c.gif)

```swift:test.swift
import SwiftUI

struct ContentView: View {
    @State private var inputText: String = ""
    @State private var showSheet = false
    
    var body: some View {
        VStack {
            TextField("入力してください", text: $inputText)
            Button(action: {
                showSheet = true
            }) {
                Text("Button")
            }
        }
        .sheet(isPresented: $showSheet, content: {
            SecondView(inputValue: inputText)
        })
    }
}

struct SecondView: View {
    let inputValue: String
    var body: some View {
        Text(inputValue)
    }
}
```

１回目に入力したキーワードが新しいシートに反映されていません。
２回目以降は反映されてるって状況です。


## 対策
ひとまず以下の方法を試したら、問題を解決しました。

### 対策1：onChangeを利用する

```swift:test1.swift
import SwiftUI

struct ContentView: View {
    @State private var inputText: String = ""
    @State private var showSheet = false
    
    var body: some View {
        VStack {
            TextField("入力してください", text: $inputText)
            Button(action: {
                showSheet = true
            }) {
                Text("Button")
            }
        }
        .sheet(isPresented: $showSheet, content: {
            SecondView(inputValue: inputText)
        })
//        追記した部分
        .onChange(of: showSheet, perform: { showSheet in
        })
    }
}

struct SecondView: View {
    let inputValue: String
    var body: some View {
        Text(inputValue)
    }
}
```


### 対策2：Bindingを利用する

```swift:test2.swift
import SwiftUI

struct ContentView: View {
    @State private var inputText: String = ""
    @State private var showSheet = false
    
    var body: some View {
        VStack {
            TextField("入力してください", text: $inputText)
            Button(action: {
                showSheet = true
            }) {
                Text("Button")
            }
        }
        .sheet(isPresented: $showSheet, content: {
//          変更した部分
            SecondView(inputValue: $inputText)
        })
    }
}

struct SecondView: View {
//  変更した部分
    @Binding var inputValue: String
    var body: some View {
        Text(inputValue)
    }
}
```


### 対策3：StateObjectを利用する
ObservedObjectでもOK。（試してないけどEnvironmentObjectも大丈夫だと思う）

```swift:test3.swift
import SwiftUI

struct ContentView: View {
//  @State var inputText: String = ""

// 追記したコード
    @StateObject private var model = Model()
    @State private var showSheet = false
    
    var body: some View {
        VStack {
// 変更したコード
            TextField("入力してください", text: $model.inputText)
            Button(action: {
                showSheet = true
            }) {
                Text("Button")
            }
        }
// 変更したコード
        .sheet(isPresented: $showSheet, content: {
            SecondView(inputValue: model.inputText)
        })
    }
}

struct SecondView: View {
    let inputValue: String
    var body: some View {
        Text(inputValue)
    }
}

// 追記したコード
class Model: ObservableObject {
    @Published var inputText = ""
}
```

## 最適な方法は？
実装するコードの規模、複雑さによるのかなと。あとはチームの方針など。

1つのViewで完結するしシンプルなのはonChangeを使う方法な気がするけど、Viewが増えたり複雑なアプリになるとBindingやプロパティラッパーを使った方法が便利な場面もありそう。

（他にも最適な対策はあるかもしれないが、SwiftUI側の問題であればそのうちバグが解消されて気にすることでも無くなりそう）


## 最後に（ぼやき）
正直この問題が自分のコードの問題なのか、SwiftUIの問題なのか定かではない！
対策も他にあるだろうし、何がベストかはっきり分からない。

とはいえ、同じような悩みを持った人の解決の一助、参考となれば嬉しいです！

ご指摘あればwelcomeです！