下部から出現するカード風スライドメニューを作る

Published by @SoNiceInfo at 5/26/2020


カード式スライドメニューの画像

マップアプリやGoogleアプリで採用されている、画面下部にいるカード式スライドメニューをSwiftUIで実装する方法を紹介します。
自分で実装したものに加え、Githubで公開されているパッケージも紹介します。こちらはXcodeの「Swift Packages」に追加すれば簡単に使うことができます。
今回はマップの上にカード式スライドメニューを表示するアプリを作ります。

ContentViewを準備する

まずはマップを表示するViewを作成します。
地図を表示するを参考に作成します。
作成したらContentViewから呼び出します。ここでは次に実装するカード式スライドメニューCardModalViewも呼び出しておきます。

import SwiftUI

struct ContentView: View {
    var body: some View {
        ZStack() {
            MapView()
            CardModalView()
        }
    }
}

カード式スライドメニューを実装する

今回のカード式スライドメニューは、上部、中部、下部と3段階に止まるようになっています。
あらかじめoffsets.zeroで宣言しておいてonAppear内でGeometryReaderから取得で画面サイズに合わせて適合できるように再定義しています。
DragGestureonChangedでは移動中は上部、下部の領域を出ないように制限しつつスライドできるようにしています。
onEndedでは上部、中部、下部の近い領域にカード式スライドメニューがセットされるように場合分けして書いています。

import SwiftUI

struct CardModalView: View {
    @State private var offsets = (top: CGFloat.zero, middle: CGFloat.zero, bottom: CGFloat.zero)
    @State private var offset: CGFloat = .zero
    @State private var lastOffset: CGFloat = .zero
    
    var body: some View {
        GeometryReader { geometry in
            VStack (spacing: 30) {
                RoundedRectangle(cornerRadius: 5)
                    .foregroundColor(.gray)
                    .frame(width: 100, height: 10)
                Text("CardModal")
                    .font(.largeTitle)
                Spacer()
            }
            .padding()
            .frame(width: geometry.size.width, height: geometry.size.height)
            .background(Color.yellow)
            .clipShape(RoundedRectangle(cornerRadius: min(self.offset, 20) ))
            .animation(.interactiveSpring())
            .onAppear {
                self.offsets = (
                    top: .zero,
                    middle: geometry.size.height / 2,
                    bottom: geometry.size.height * 3 / 4
                )
                self.offset = self.offsets.bottom
                self.lastOffset = self.offset
            }
            .offset(y: self.offset)
            .gesture(DragGesture(minimumDistance: 5)
                .onChanged { v in
                    let newOffset = self.lastOffset + v.translation.height
                    if (newOffset > self.offsets.top && newOffset < self.offsets.bottom) {
                        self.offset = newOffset
                    }
                }
                .onEnded{ v in
                    if (self.lastOffset == self.offsets.top && v.translation.height > 0) {
                        if (v.translation.height < geometry.size.height / 2) {
                            self.offset = self.offsets.middle
                        } else {
                            self.offset = self.offsets.bottom
                        }
                    } else if (self.lastOffset == self.offsets.middle) {
                        if (v.translation.height < 0) {
                            self.offset = self.offsets.top
                        } else {
                            self.offset = self.offsets.bottom
                        }
                    } else if (self.lastOffset == self.offsets.bottom && v.translation.height < 0) {
                        if (abs(v.translation.height) > geometry.size.height / 2) {
                            self.offset = self.offsets.top
                        } else {
                            self.offset = self.offsets.middle
                        }
                    }
                    self.lastOffset = self.offset
                }
            )
        }
        .edgesIgnoringSafeArea(.all)
    }
}

struct CardModalView_Previews: PreviewProvider {
    static var previews: some View {
        CardModalView()
    }
}

有志が作成したパッケージを使う

自分で実装しなくても融資が作成したパッケージを使うことで瞬時に自分のアプリに機能を組み込むことができます。
今回のカード式スライドメニューでいうとhttps://github.com/moifort/swiftUI-slide-over-cardがとても便利そうです。 Xcode上で「Project」「プロジェクト名」と進んでいき、「Swift Packages」の「+」で上記URLを追加すれば準備完了です。 これを使って上記と近いものを作ろうとするとこうなります。

import SwiftUI
import SlideOverCard

struct ContentView: View {
    @State private var position = CardPosition.bottom
    @State private var background = BackgroundStyle.solid

    var body: some View {
        ZStack() {
            MapView()
            SlideOverCard($position, backgroundStyle: $background) {
                VStack {
                    Text("Slide Over Card").font(.title)
                    Spacer()
                }
            }
        }
    }
}

参考

moifort/swiftUI-slide-over-card: Slide over modal/card for SwiftUI

その他

上下左右から出現するスライドメニューを作りたい場合はこちら。
上部左右から出現するメニューを作る



    アプリをリリースしました!

    ToDoアプリ

    iCloudを利用したデバイス間データ共有、ダークモードに対応しています。

    リリースしたToDoアプリのスクリーンショット

    IPアドレス履歴保存アプリ

    取得したIPアドレスを確認、位置情報とともに保存できます。

    リリースしたIPアドレス保存アプリのスクリーンショット