【SwiftUI】sheetメソッドを使ってビューをモーダル表示にする

先日リリースした私のアプリに使用した技術の解説です。
私のアプリはこちら。

https://apps.apple.com/jp/app/id1553876980

# sheetメソッドとは

ビューをモーダル表示させるメソッド。下から出てきて画面の一番手前に表示される。

https://developer.apple.com/documentation/swiftui/scaledshape/sheet(ispresented:ondismiss:content:)

# 実際の動作

<iframe width="560" height="315" src="https://www.youtube.com/embed/DVBBx99nDqE" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>

「わかった！」ボタンを押すと下から答え合わせのビューが出てきて、画面の一番手前に表示されているのがわかるかと思います。
また、このシートは上下に動かすことができ、一定の位置まで下げて指を離すと閉じるようになっています。

# 基本の書き方

```Swift:sheetメソッド
.sheet(isPresented: ブール値, content: { 表示させたいビュー })
```

sheetメソッドはさまざまな引数を取りますが、一番シンプルな書き方はこのようになります。`isPresented`がtrueになったとき、`content`の{ }内のビューが表示されます。

# ソースコード

https://github.com/masayoshi0327/Sheet

上のサンプルアプリのコードの一部です。

```Swift:ContentView.swift
import SwiftUI

struct ContentView: View {
    
    @State var answer = ""
    @State var moveAnswer = false //シートを出すためのブール型の変数
    
    var body: some View {
        VStack{
            Text("なぞなぞ")
                .font(.title)
                .padding()
            Text("パンはパンでも白黒のかわいいパンは\nぱ〜んだ？")
            TextField("こたえを書いてね", text: $answer)
                .frame(width: 200)
                .padding()
                .border(Color.black)
            // シートを出すトリガーになるボタン
            Button(action: {
                moveAnswer = true //シートを出すためのアクション
            }, label: {
                Text("わかった！")
                    .padding()
                    .background(Color.green)
                    .foregroundColor(.white)
                    .cornerRadius(10)
            })
            //moveAnswerがtrueになったときに出てくるシート
            .sheet(isPresented: $moveAnswer, content: {
                Answer(answer: answer, moveAnswer: $moveAnswer)
            })
        }
    }
}

```

こちらのコードを部分ごとにわけて解説したいと思います。

# シートを出すための仕組み

まず、ContentViewのプロパティとしてブール型の変数を用意します。

```Swift
    @State var moveAnswer = false //シートを出すためのブール型の変数
```

アプリ起動時はシートが出ていない状態にしたいため、初期値にfalseを入れています。逆に言えば、初期値をtrueにすればアプリ起動時にシートを出すことができます。

次は、シートを出すトリガーになるボタンです。

```Swift
// シートを出すトリガーになるボタン
            Button(action: {
                moveAnswer = true //シートを出すためのアクション
            }, label: {
                Text("わかった！")
                    .padding()
                    .background(Color.green)
                    .foregroundColor(.white)
                    .cornerRadius(10)
            })
```

ボタンをタップするとmoveAnswerにtrueが代入されます。

そして、sheetメソッドを見ていきます。

```Swift
            .sheet(isPresented: $moveAnswer, content: {
                Answer(answer: answer, moveAnswer: $moveAnswer)
            })
```

ボタンのアクションによってmoveAnswerにtrueが入ると`isPresented`がtrueになり、`content`のAnswerビューが表示されます。

今回、AnswerビューはAnswer.swiftという別のファイルを作って書きました。そちらのビューも見てみましょう。

# 今回シートに表示したビュー

```Swift:Answer.swift
import SwiftUI

struct Answer: View {
    
    let answer: String //前の画面のなぞなぞの答え
    @Binding var moveAnswer: Bool //前の画面のmoveAnswerを反映させるための変数
    
    var body: some View {
        VStack{
            //なぞなぞの答えが正解かどうかで表示を変える
            if answer == "パンダ" {
                Text("せいかい！！\nすごい！天才だ！")
                    .font(.title)
                Image("correct")
                
            }else{
                Text("ちがうよ。もういちどもんだいをよく読んでみよう。")
                    .font(.title)
                Image("fault")
            }
            //シートを閉じるためのトリガーになるボタン
            Button(action: {
                moveAnswer = false //シートを閉じるためのアクション
            }, label: {
                Text("もどる")
                    .font(.title)
                    .padding()
            })
        }
    }
}
```

動画内でもやっていたように、わざわざボタンを作らなくてもシートを閉じる操作はできますが、ボタンがあった方が便利な場面もあるかと思います。このボタンの作り方について解説したいと思います。

# シートを閉じるためのボタンの作り方

シートを閉じるためには、__ContentViewのmoveAnswerがfalseになる必要があります__。そのため、まずはContentViewのmoveAnswerをAnswerビューに反映させます。

```Swift
   @Binding var moveAnswer: Bool //前の画面のmoveAnswerを反映させるための変数
```

`@Binding`をつけることで、このmoveAnswerに入れたブール値の状態を共有しています。つまり、__AnswerのmoveAnswerがfalseになると、ContentViewのmoveAnswerもfalseに変わる__ようにしています。

そしてこちらがシートを閉じるためのトリガーとなるボタンです。

```Swift
            //シートを閉じるためのトリガーになるボタン
            Button(action: {
                moveAnswer = false //シートを閉じるためのアクション
            }, label: {
                Text("もどる")
                    .font(.title)
                    .padding()
            })
```

このボタンを押すことでmoveAnswerにfalseが入り、シートを閉じることができます。