SwiftUI - 上下左右から出てくるハーフモーダルの実装

SwiftUIでも上から下に向かうハーフモーダルの実装がなかったので、以下で実装
備忘録として残します。


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274108/03d61fdb-1153-f547-62da-b54e919537c0.png)


## 使い方
SideMenuを呼び出して利用

## 機能概要
・表示のアニメーション
・ハーフモーダル表示中は後ろのViewの色のトーンを落とす
・後ろのViewタップ時は閉じる


## 実装
```Swift
//ContentView.swift
import SwiftUI

struct ContentView: View {
    @State private var isOpen:Bool = false
    var body: some View {
        ZStack() {
            Button(action: {
                isOpen.toggle()
            }) {
                Text("OpenSideMenu")
                    .frame(width: 200, height: 50, alignment: .center)
                    .background(Color.black)
                    .foregroundColor(.white)
                    .cornerRadius(5.0)
            }
            
            SideMenu(view: MenuView(), width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height/2, menuType: .fromTop,isOpen: $isOpen)
        }.background(Color.green)
        .ignoresSafeArea(.all)
    }
}
```

```Swift
//MenuView.swift
import SwiftUI

struct MenuView: View {
    var body: some View {
        VStack() {
            Text("ハーフモーダル")
                .font(.title)
                .bold()
            Image("myImg")
                .resizable()
                .clipShape(Circle())
                .overlay(
                    Circle().stroke(Color.white, lineWidth: 2))
                .frame(width: 50, height: 50, alignment: .leading)
        }
    }
}
```

```Swift
//SideMenu.swift
import SwiftUI

//サイドメニュー
struct SideMenu<Content: View>: View {
    enum menutype {
        case fromLeft
        case fromRight
        case fromTop
        case fromBottom
    }

    let view: Content   //表示させるViewを指定
    let width: CGFloat  //メニューの幅を指定
    let height:CGFloat  //メニューの高さを指定
    let menuType: menutype //type指定(.fromLeft, .fromRight, .fromTop)
    @Binding var isOpen: Bool //BtnのBoolを引数とする
    
    var body: some View {
        ZStack {
            GeometryReader { _ in
                EmptyView()
            }
            .background(Color.gray.opacity(0.3))
            .opacity(self.isOpen ? 1.0 : 0.0)
            .animation(Animation.easeIn.delay(0.25))
            .onTapGesture {
                self.isOpen.toggle()
            }
            let displyView = view
                .frame(width: self.width, height: self.height)
                .background(Color.white)
                .animation(.default)

            switch self.menuType {
            case .fromLeft:
                HStack() {
                    displyView
                        .offset(x: self.isOpen ? 0 : -self.width)
                    Spacer()
                }
            case .fromRight:
                HStack() {
                    Spacer()
                    displyView
                        .offset(x: self.isOpen ? 0 : +self.width)
                }
            case .fromTop:
                VStack() {
                    displyView
                        .offset(y: self.isOpen ? 0 : -self.height)
                    Spacer()
                }
            case .fromBottom:
                VStack() {
                    Spacer()
                    displyView
                        .offset(y: self.isOpen ? 0 : +self.height)
                }
            }
        }.ignoresSafeArea(.all)
    }
}


```