LoginSignup
6
5

[SwiftUI] SideMenuViewを作りたい

Posted at

はじめに

こんにちは!
アプリ開発が好きで、Swiftの勉強をしている大学生です。
夏休みに入ったので、アウトプット頑張っていきたいと思っています。
温かい目で見ていただけると幸いです。

やりたいこと

以下のように、あるボタンを押したらサイドバーが現れるようにしたいです。

実装イメージ

  1. HogeViewとSideMenuView(サイドーバーのViewのこと)の2つのViewを用意します
  2. 上記二つのViewをHStackで囲み、そのOffsetを変更する

イメージはこんな感じです。

実装

まずはHogeViewを用意します。

public struct HogeView: View {
    // sideMenuViewを表示・非表示を決めるフラグ
    @Binding var isSideMenu: Bool
    
    public init(isSideMenu: Binding<Bool>) {
        self._isSideMenu = isSideMenu
    }
    // 今回は簡易的なViewだけを用意
    public var body: some View {
        VStack {
            Button {
                // ボタンのタップに応じてtoggleします
                isSideMenu.toggle()
            } label: {
                Text("open SideMenu")
            }
        }
        .frame(maxHeight: .infinity)
    }
}

次にSideMenuViewを用意します。


public struct SideMenuView: View {
    // HogeViewと同様のフラグ
    @Binding var isSideMenu: Bool
    
    public init(isSideMenu: Binding<Bool>) {
        self._isSideMenu = isSideMenu
    }

    // 簡易的なViewを用意
    public var body: some View {
        VStack(alignment: .leading, spacing: 0) {
            ScrollView {
                VStack(spacing: 15) {
                    Text("HogeHoge~~~")
                        .font(.system(size: 24).bold())
                    
                    Text("Side Menu")
                }
                .padding(.top, 20)
            }
        }
        // 画面の約5/4まで表示したいのでoffsetをframeをいじります
        .frame(width: getScreenWidth() - 90)
        .background {
            Color.primary
                .opacity(0.04)
                .ignoresSafeArea(.container, edges: .vertical)
        }
        .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .leading)
    }
}

上記のgetScreenWidth()ですが下記のようなextensionで画面の幅を取得しています

public extension View {
    func getScreenWidth() -> CGFloat {
        return UIScreen.main.bounds.width
    }
}

最後にこのHogeViewとSideMenuViewを繋げるConnecterView(もっといい命名ありそう)というものを作ります。


public struct ConnecterView: View {
    @State private var isSideMenu = false
    @State private var offset: CGFloat = 0

    public init() {}
    
    public var body: some View {
        let sideBarWidth = getScreenWidth() - 90
        HStack(spacing: 0) {
            SideMenuView(isSideMenu: $isSideMenu)
            
            HogeView(isSideMenu: $isSideMenu)
                .frame(width: getScreenWidth())
                .overlay {
                    Rectangle()
                        .fill(
                            Color.primary
                                .opacity(Double((offset / sideBarWidth) / 5))
                        )
                        .ignoresSafeArea(.container, edges: .vertical)
                        .onTapGesture {
                            isSideMenu.toggle()
                        }
                }
        }
        // これでHStackを大きな一つのViewとして捉え、offsetを動かす
        .frame(width: getScreenWidth() + sideBarWidth)
        .offset(x: -sideBarWidth / 2)
        .offset(x: offset)
        .onChange(of: isSideMenu) { _ in
            // 下記条件に応じてoffsetをいじる
            if isSideMenu && offset == 0 {
                withAnimation {
                    offset = sideBarWidth
                }
            }
            
            if !isSideMenu && offset == sideBarWidth {
                withAnimation {
                    offset = 0
                }
            }
        }
    }
}

これで実装できました

Tip: これだけでは物足りない

現状のコードだけでは下記のような機能がなく物足りないです。

  • Dragに応じてSideMenuViewを表示する
  • SideMenuViewからNavigationLink等で画面遷移した時に、遷移先のViewがSideMenuViewの幅のまま表示されています。

後ほど、これも解決していきたいと思います。

終わりに

誰かの役に立つことができていれば幸いです。
アウトプットを頑張ろうと思っているので温かい目で見ていただけると嬉しいです。

6
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
5