下部から出現するカード風スライドメニューを作る
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から取得で画面サイズに合わせて適合できるように再定義しています。DragGesture
のonChanged
では移動中は上部、下部の領域を出ないように制限しつつスライドできるようにしています。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その他
上下左右から出現するスライドメニューを作りたい場合はこちら。
上部左右から出現するメニューを作る