SwiftUIのsheet（ModalView）を出し分ける

## SwiftUIでの画面遷移

SwiftUIは`View`にsheetファンクションが準備されており、そこから画面遷移を行います。

```swift
public func sheet<Content>(isPresented: Binding<Bool>, onDismiss: (() -> Void)? = nil, @ViewBuilder content: @escaping () -> Content) -> some View where Content : View
```

サンプルでは次のように`Binding<Bool>`を使った例が紹介されています。

```swift
struct ContentView: View {

    @State var isPresented: Bool = false

    var body: some View {
        VStack {
            Button("present") {
                self.isPresented.toggle()
            }
        }
        .sheet(isPresented: $isPresented) {
            Text("Presenting")
        }
    }
}
```

ただしこの方法には少し問題があり、複数画面に遷移したい場合に動作しなくなることです。

## SwiftUIの`sheet`の使い方

### `sheet`を繋げるとダメ

まず次の例では、`present 0`ボタンを押しても動作しなくなります。
`VStack`には`sheet`が2つ繋げられていますが`$isPresented0`は機能しなくなります。
`$isPresented1`の方に上書きされているためです。

```swift
struct ContentView: View {

    @State var isPresented0: Bool = false

    @State var isPresented1: Bool = false

    var body: some View {
        VStack {
            Button("present 0") {
                self.isPresented0.toggle()
            }
            Button("present 1") {
                self.isPresented1.toggle()
            }
        }
        .sheet(isPresented: $isPresented0) {
            Text("Presenting 0")
        }
        .sheet(isPresented: $isPresented1) {
            Text("Presenting 1")
        }
    }
}
```

### `Button`に`sheet`を繋げる

こうすることで`isPresented0` `isPresented1`はどちらも機能するようになります。

```swift
struct ContentView: View {

    @State var isPresented0: Bool = false

    @State var isPresented1: Bool = false

    var body: some View {
        VStack {
            Button("present 0") {
                self.isPresented0.toggle()
            }
            .sheet(isPresented: $isPresented0) {
                Text("Presenting 0")
            }

            Button("present 1") {
                self.isPresented1.toggle()
            }
            .sheet(isPresented: $isPresented1) {
                Text("Presenting 1")
            }
        }
    }
}
```

ではもっと、遷移先が多くなった場合はどうすればいいのか。

`@State var isPresented1: Bool`に頼るのは大変ですね。

### `sheet(item: Binding<Item?>)`を使う

`View`にはもう一つ`sheet`ファンクションが準備されています。

```swift
public func sheet<Item, Content>(item: Binding<Item?>, onDismiss: (() -> Void)? = nil, @ViewBuilder content: @escaping (Item) -> Content) -> some View where Item : Identifiable, Content : View
```

このファンクションを使うためにはまず、`Item`を`Identifiable`に準拠させる必要があります。
次ので例では`enum`を利用して`View`の出しわけを行っています。


```swift
extension Identifiable where Self: Hashable {
    typealias ID = Self
    var id: Self { self }
}

struct ContentView: View {

    enum Presentation: View, Hashable, Identifiable {
        case second
        case third
        var body: some View {
            switch self {
            case .second: return AnyView(SecondView())
            case .third: return AnyView(ThirdView())
            }
        }
    }

    @State var presentation: Presentation?

    var body: some View {
        VStack {
            Button("second") {
                self.presentation = .second
            }
            Button("third") {
                self.presentation = .third
            }
        }
        .sheet(item: self.$presentation) { $0 }
    }
}

```

### 引数を持って遷移したい時

```swift
extension Identifiable where Self: Hashable {
    typealias ID = Self
    var id: Self { self }
}

struct ContentView: View {

    enum Presentation: View, Hashable, Identifiable {
        case second(text: String)
        case third
        var body: some View {

            switch self {
            case .second(let text): return AnyView(SecondView(text: text))
            case .third: return AnyView(ThirdView())
            }
        }
    }

    @State var presentation: Presentation?

    var body: some View {
        VStack {
            Button("second") {
                self.presentation = .second(text: "SwiftUI")
            }
            Button("third") {
                self.presentation = .third
            }
        }
        .sheet(item: self.$presentation) { $0 }
    }
}

struct SecondView: View {
   
    @Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>

    var body: some View {
        Button("戻る") {
            self.presentationMode.wrappedValue.dismiss()
        }
    }
}
```

---

次の記事もオススメです。

- [SwiftUIでChildViewのFrameを取得する](https://qiita.com/1amageek/items/7dbb0d5000ed06923206)
- [SwiftUIの行儀の悪いお作法。](https://qiita.com/1amageek/items/8c583a00f2679dbf38df)