12
Help us understand the problem. What are the problem?

More than 1 year has passed since last update.

posted at

updated at

SwiftUI サイドメニューの実装(目の保養付き)

swiftui_layout_log

はじめに

サイドメニューを実装してみたので備忘録兼ねて残します。
今回はモチベーションアップ、目の保養の為にイメージ画像は綺麗なお姉ちゃんを使いました。

イメージ

side_menu_image

実装内容

まずメニュー項目のstructを作成

ForEachで使いたいのでIdentifiableに適合させておきます。
今回、画像はSFシンボル名で設定してImage(systemName:)で呼び出すようにしておきます。
メニュータップ時の遷移は実装していないので、こんな感じで終わりにします。

struct Menu: Identifiable {
    enum MenuType: String {
        case myAccount = "My Account"
        case setting = "Setting"
        case favorite = "Favorite"
        case faq = "FAQ"
        case signOut = "SignOut"

        var title: String { self.rawValue }

        var imageName: String {
            switch self {
            case .myAccount: return "person.crop.circle"
            case .setting: return "wrench"
            case .favorite: return "star.circle"
            case .faq: return "questionmark.circle"
            case .signOut: return "arrow.turn.up.left"
            }
        }
    }

    var id = UUID()
    var type: MenuType
}

MenuRowを作る

サイドメニューのリストに表示するRowの構造体を定義します。
Image(systemName:)で表示する画像はデフォルトでは、少々小さいのでimageScale(.large)にしたほうが良いようです。

struct MenuRow: View {

    var image = ""
    var text = ""

    var body: some View {
        VStack(alignment: .leading) {
            HStack {
                Image(systemName: image)
                    .imageScale(.large)
                    .frame(width: 32, height: 32)
                Text(text)
                    .font(.headline)
                Spacer()
            }
        }
    }
}

MenuViewを作る

実際にサイドメニューに表示させるViewを作ります。
名前とフォロー、フォロワーあたりは雑な作りなので、いつか綺麗にします。
先に以下、MenuViewの全体象を載せます。

struct MenuView: View {

    var menu = [Menu(type: .myAccount),
                Menu(type: .setting),
                Menu(type: .favorite),
                Menu(type: .faq),
                Menu(type: .signOut),]

    @Binding var show: Bool

    var body: some View {
        HStack {
            VStack(alignment: .center) {
                Image("female")
                    .resizable()
                    .scaleEffect(2)
                    .offset(x: 0, y: 20)
                    .aspectRatio(5/7, contentMode: .fit)
                    .frame(width: 100, height: 100)
                    .clipShape(Circle())
                    .overlay(
                        Circle().stroke(Color.white, lineWidth: 4))
                    .shadow(radius: 10)

                Text("Miranda Jeen")

                HStack {
                    VStack {
                        Text("follow")
                        Text("211")
                    }
                    .padding()

                    VStack {
                        Text("follower")
                        Text("324")
                    }
                    .padding()
                }
                .padding(.vertical)

                VStack(alignment: .leading) {
                    ForEach(menu) { item in
                        MenuRow(image: item.type.imageName, text: item.type.title)
                    }
                }
                Spacer()
            }
            .padding(.top, 20)
            .padding(30)
            .frame(width: ScreenSize.width/1.5)
            .background(Color.blue.opacity(0.4))
            .cornerRadius(20)
            .animation(.default)
            .offset(x: show ? 0 : -ScreenSize.width)
            .onTapGesture {
                self.show.toggle()
            }
            Spacer()
        }
        .padding(.top, statusBarHeight)
    }
}

少し解説

表示制御

showは外部からも制御するので@Bindingをつけて定義しておきます。

@Binding var show: Bool

Imageのレイアウト調節

               Image("female")
                    .resizable()
                    .scaleEffect(2)
                    .offset(x: 0, y: 20)
                    .aspectRatio(5/7, contentMode: .fit)
                    .frame(width: 100, height: 100)
                    .clipShape(Circle())
                    .overlay(
                        Circle().stroke(Color.white, lineWidth: 4))
                    .shadow(radius: 10)

イメージをリサイズする場合は、resizable()をつけます。
scaleEffectでスケールを少し拡大して、offsetで画像自体の表示位置を微調節します。
aspectRatio(5/7, contentMode: .fit)でアスペクト比を固定した上でframe(width: 100, height: 100)でイメージサイズを指定しています。

イメージに枠線を付ける

AppleのLandmarkのチュートリアルにあるような枠線で良かったので、そのまま使用しています。

                    .clipShape(Circle())
                    .overlay(
                        Circle().stroke(Color.white, lineWidth: 4))
                    .shadow(radius: 10)

ForEachでMenuRowを生成する

Identifiableに適合させたMenuをForEachで回して、MenuRowを生成します。

               VStack(alignment: .leading) {
                    ForEach(menu) { item in
                        MenuRow(image: item.type.imageName, text: item.type.title)
                    }
                }

サイドメニューの動作とMenuViewのレイアウトを調節する

最後にMenuView直下のHStackとその直下のVStackにレイアウトとサイドメニュー動作を追加します。

     var body: some View {
        HStack {
            VStack(alignment: .center) {

            }
            ....
            .padding(.top, 20)
            .padding(30)
            .frame(width: ScreenSize.width/1.5)
            .background(Color.blue.opacity(0.4))
            .cornerRadius(20)
            .animation(.default)
            .offset(x: show ? 0 : -ScreenSize.width)
            .onTapGesture {
                self.show.toggle()
            }
            Spacer()
        }
        .padding(.top, statusBarHeight)

.offset(x: show ? 0 : -ScreenSize.width)でサイドメニューが非表示状態になっている場合は、画面外の位置に待機させておくようにしています。
こうすることで出しわけることができます。

最後にHStackに対して.padding(.top, statusBarHeight)で余白をつけて、MenuViewは完成です。

親のViewで呼び出す

これでボタンをタップしたらサイドメニューが表示されるようになりました。

struct SideMenuView: View {
    @State var show = false

    var body: some View {
        ZStack {
            Group {
                Button(action: {
                    self.show = true
                }) {
                    Text("show side menu")
                }
            }
            .blur(radius: show ? 20 : 0)
            MenuView(show: $show)
        }
        .edgesIgnoringSafeArea(.all)
    }
}

サイドメニューを表示している時にメインのViewを濁したいのでblurを使っていますが、今回は、ボタンしかない真っ白名画面なので濁りませんが、つけています。

.blur(radius: show ? 20 : 0)

ソースコード

SwiftUIのレイアウト学習用のレポジトリです。
申し訳程度のものですが、ちょっとずつあげていきます。

さいごに

UIKitで実装するよりもシンプルな実装で作れました。
サイドメニューを表示するたびに綺麗なお姉さんが表示されて、癒されながら学習することができました。
こういうちょっとした目の保養って大事だなと改めて感じましたw

良かったら使ってみてください。

もっといい案あったり、ご指摘、アドバイスあればお待ちしております。
それでは最後までお読みいただきありがとうございました。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
12
Help us understand the problem. What are the problem?