SwiftUIで開閉式メニューの作り方
SwiftUIで下からニュッと表示する開閉式メニューの作り方を解説します。
この記事は私が2020年12月22日に発表した資料、
「SwiftUIで作る開閉式メニュー」を記事化したものです。
動作環境
この記事は以下の環境で動作を確認しています。
- Xcode 12.2
- iOS 14.2
開閉式メニューを作ろう
今回作る開閉式メニューの動きをgifでみてみましょう。
親Viewを作る
まずは親Viewを作りましょう。
struct ContentView: View {
@State var isShowMenu = false
var body: some View {
ZStack {
Button(action: {
withAnimation {
isShowMenu.toggle()
}
}, label: {
Text("show menu")
})
.frame(width: UIScreen.main.bounds.width)
}
}
}
ZStack
でButton
が真ん中に表示されるViewです。
isShowMenu
プロパティでメニューを開閉するかどうかを判断します。
ボタンをタップしたらisShowMenu
をtoggle
メソッドで反転させています。
withAnimation
で囲うことでアニメーションつきで更新させます。
現状のキャプチャです。まだボタンが真ん中にあるだけの状態です。
MenuViewを作る
続いて子ViewとしてMenuView
を作ります。
struct MenuView: View {
@Binding var isShowMenu: Bool
var body: some View {
VStack {
Spacer()
HStack {
Spacer()
Button(action: {
withAnimation {
isShowMenu = false
}
}, label: {
Image(systemName: "checkmark")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 20, height: 20)
.padding()
})
}
.background(Color.black.opacity(0.8))
.foregroundColor(.white)
.offset(x: 0, y: isShowMenu ? 0 : 300)
}
}
}
VStack
でSpacer
とHStack
を縦に並べます。
HStack
では右端にチェックマークをつけたボタンを配置しています。
ボタンを押すと親Viewから渡れたisShowMenu
をfalseにします。
isShowMenu
の値によってoffset
でHStack
の位置を変えています。この例ですと、falseだとy軸から300下の位置に移動させています。
HStack
の高さは300よりも小さいので、300下に移動すると画面から見えなくなり閉じられて見えるという寸法です。
300の数値は今のところマジックナンバーで特に意味はありません。
HStack
の高さよりも大きければ何でも良いです。
後で適切なものに変更します。
さて、親ViewにMenuView
を追加してみましょう。
ZStack {
Button(action: {
withAnimation {
isShowMenu.toggle()
}
}, label: {
Text("show menu")
})
.frame(width: UIScreen.main.bounds.width)
// デフォルトではコンテンツはセーフエリア内にとどまる
MenuView(isShowMenu: $isShowMenu)
}
表示はこのようになります。
下の隙間が気になりますね。
SwiftUIはデフォルトではSafe Area内でコンテンツが収まります。
これをなんとかしましょう。
Safe Areaを無視してみる
単純な解決方法としてSafe Areaを無視してみましょう。
ZStack {
Button(action: {
withAnimation {
isShowMenu.toggle()
}
}, label: {
Text("show menu")
})
.frame(width: UIScreen.main.bounds.width)
MenuView(isShowMenu: $isShowMenu)
.ignoresSafeArea(edges: .bottom) // Safe Areaを無視する
}
親ViewからMenuView
にignoresSafeArea
をつけてSafe Areaを無視してみます。
表示はどうなるでしょうか?
指定どおりにSafe Areaが無視されました。
しかしユーザーがタップできる領域がSafe Area外に表示されてしまっています。
操作性が悪くなるのでなんとか避けたいです。
これを解決するため、Safe Area外には背景コンテンツを配置しましょう。
Safe Area外には背景コンテンツを配置
ではその方法です。
子Viewとして新たにMenuViewWithinSafeArea
を作ります。
struct MenuViewWithinSafeArea: View {
@Binding var isShowMenu: Bool
let bottomSafeAreaInsets: CGFloat
var body: some View {
GeometryReader { geometry in // Viewの高さを取得するため
VStack(spacing: 0) {
Spacer()
HStack {...}
...
.offset(x: 0, y: isShowMenu ? 0 : geometry.size.height) // Viewの高さ分移動させる
// safe area外の背景コンテンツ
Rectangle()
.foregroundColor(Color.red.opacity(0.8)) // 分かりやすいように赤色背景にする
.frame(height: bottomSafeAreaInsets) // 高さをbottom Safe Area Insetsに合わせる
.edgesIgnoringSafeArea(.bottom) // Safe Areaを無視する
.offset(x: 0, y: isShowMenu ? 0 : geometry.size.height) // Viewの高さ分移動させる。
}
}
}
}
MenuViewWithinSafeArea
はMenuView
と異なりbottomSafeAreaInsets
プロパティがあります。これは親Viewから渡されるデータです。
またGeometryReader
を追加しています。
これはViewの高さを取得するためです。
HStack
のoffset
にgeometry.size.height
を指定して、Viewの高さ分移動するようにしています。先程のMenuView
は300というマジックナンバーを使っていましたが、この例では意味ある値になっています。
そしてSafe Area外の背景コンテンツとしてRectangle
を配置しています。
高さが親Viewから渡されたBottom Safe Area Insetsと同じ値を指定し、edgesIgnoringSafeArea
でSafe Areaを無視しています。そしてHStack
と同じように.offset
でisShowMenu
の値で表示位置を変更しています。
親ViewにMenuViewWithinSafeArea
を適応してみましょう。
GeometryReader { geometry in
ZStack {
Button(action: {
withAnimation {
isShowMenu.toggle()
}
}, label: {
Text("show menu")
})
.frame(width: UIScreen.main.bounds.width)
MenuViewWithinSafeArea(isShowMenu: $isShowMenu,
bottomSafeAreaInsets: geometry.safeAreaInsets.bottom)
.ignoresSafeArea(edges: .bottom)
}
}
親ViewにもGeometryReader
を追加します。
これはMenuViewWithinSafeArea
にgeometry.safeAreaInsets.bottom
を渡したいからです。親Viewからでないとgeometry.safeAreaInsets.bottom
が適切に取得できません。
そしてMenuViewWithinSafeArea
自体にもignoresSafeArea
をしてSafe Areaを無視しています。
表示をみてみましょう。
無事にSafe Area外に背景コンテンツを配置できました。
GeometryReader
を使って親ViewからsafeAreaInsets.bottom
を渡し、その分の高さのViewを作るのがミソです。
これで開閉式メニューの解説を終わりたいと思います。
サンプルコード
https://gist.github.com/SatoTakeshiX/4c0aed5430f2a272d33ebcfd8192b5d6
宣伝
インプレスR&D社より、「1人でアプリを作る人を支えるSwiftUI開発レシピ」発売中です。
「SwiftUIでアプリを作る!」をコンセプトにSwiftUI自体の解説とそれを組み合わせた豊富なサンプルアプリでどんな風にアプリ実装すればいいかが理解できる本となっています。
iOS 14対応、Widgetの作成も一章まるまるハンズオンで解説しています。
SwiftUIを学びたい方、ぜひこちらのリンクをチェックしてください!