Edited 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のレイアウト学習用のレポジトリです。

申し訳程度のものですが、ちょっとずつあげていきます。

https://github.com/kazy-dev/SwiftUI-Layout-Practice


さいごに

UIKitで実装するよりもシンプルな実装で作れました。

サイドメニューを表示するたびに綺麗なお姉さんが表示されて、癒されながら学習することができました。

こういうちょっとした目の保養って大事だなと改めて感じましたw

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

もっといい案あったり、ご指摘、アドバイスあればお待ちしております。

それでは最後までお読みいただきありがとうございました。