SwiftUIでサイドメニューの実装

SwiftUIでサイドメニューを再現してみました。
ドラッグやタップで表示、非表示にできます。
# 環境
- xcode12.1
- Swift 5

![slidemenu.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/272984/bac92972-f12f-3ae5-20f0-f05eec734ea9.gif)

# イメージ
ピンク　 メニュー
青　　  ホーム
黒枠　  見えているところ
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/272984/47a4d208-6893-5631-a86c-b82fe1d0ded7.png" width="300"> <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/272984/9899bd26-3480-4282-9ebc-c824a7a5fbd3.png" width="300">

ドラッグに合わせてメニューとホームの両方のオフセットをずらしています。

# コード全文

```swift

import SwiftUI

struct HomeView: View {
    
    @State var showMenu: Bool = false

    let menuWidth = UIScreen.main.bounds.width * 0.7
    
    /// homeのx座標(青いやつ)
    @State var xPosition: CGFloat = 0
    @State var isDrag: Bool = false
    
    var drag: some Gesture {
        DragGesture()
            .onChanged{ value in
                isDrag = true
           
                // value.location.x - value.startLocation.x がドラッグしている距離
                // 移動させすぎないようにmaxとminで最大値と最小値の設定
                if showMenu {
                    xPosition = max(min(menuWidth + value.location.x - value.startLocation.x, menuWidth), 0)
                } else {
                    xPosition = max(min(value.location.x - value.startLocation.x, menuWidth), 0)
                }
            }
            .onEnded{ value in
                
                // Dragが終了したタイミングで開くか、閉じるかを判定したい
                if value.location.x - value.startLocation.x >= menuWidth / 3 {
                    showMenu = true
                } else if -(value.location.x - value.startLocation.x) >= menuWidth / 3 {
                    showMenu = false
                }
                isDrag = false
            }
    }
    
    var body: some View {
        
        GeometryReader { geometry in
            VStack {
                HStack(spacing: 60) {
                    AvatarView(showMenu: $showMenu)
                    Spacer()
                }
                .padding(EdgeInsets.init(top: 8, leading: 8, bottom: 8, trailing: 8))
                Spacer()
            }
            .frame(width: geometry.size.width, height: geometry.size.height)
            .offset(x: isDrag ? xPosition : (showMenu ? menuWidth : 0))
            .animation(.easeInOut(duration: 0.2))
            .background(Color.blue)
            .onTapGesture {
                if showMenu {
                    showMenu.toggle()
                }
            }
            .gesture(drag)

            SlideMenuView(showMenu: $showMenu)
                .frame(width: menuWidth, height: geometry.size.height)
                .offset(x: isDrag ? -menuWidth + xPosition : (showMenu ? 0 : -menuWidth))
                .animation(.easeInOut(duration: 0.2))
                .gesture(drag)

        }
    }
}

struct AvatarView: View {
    @Binding var showMenu: Bool
    
    var body: some View {
        Button(action: {
            self.showMenu.toggle()
            
        }) {
            Color.yellow
                .frame(width: 44, height: 44)
                .clipShape(Circle())
        }
    }
}


struct SlideMenuView: View {
    
    @Binding var showMenu: Bool
    
    var body: some View {
        VStack(spacing: 16) {
            Spacer()
            MenuRow(showMenu: $showMenu, title: "Account", icon: "gear")
            MenuRow(showMenu: $showMenu, title: "Billing", icon: "creditcard")
            MenuRow(showMenu: $showMenu, title: "Sign out", icon: "person.crop.circle")
            Spacer()

        }
        .background(Color.white)
    }
}

struct MenuRow: View {
    @Binding var showMenu: Bool
    
    var title: String
    var icon: String
    
    var body: some View {
        
        Button(action: {
            self.showMenu.toggle()
            
        }) {
            HStack(spacing: 16) {
                
                Image(systemName: icon)
                    .font(.system(size: 20, weight: .light))
                    .imageScale(.large)
                    .frame(width: 32, height: 32)
                
                Text(title)
                    .font(.system(size: 20, weight: .bold, design: .default))
                    .frame(width: 120, alignment: .leading)
                Spacer()
            }
            .padding(.horizontal, 30)

        }
    }
}



struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        HomeView()
    }
}
```

# 感想
もうちょっと綺麗な実装できたかも。。。
それにしてもSwiftUIって書きやすいですね。