Context
#The code
#GlassMenuItem.swift#
import SwiftUI
public struct GlassMenuItem: Identifiable {
// MARK: Subtypes
public enum LeadingItem {
case symbol(String)
case image(String)
}
// MARK: Properties
let title: String
let leadingItem: LeadingItem
let action: () -> Void
// MARK: Computed properties
public var id: String {
title
}
// MARK: Init
public init(
leadingItem: LeadingItem,
title: String,
action: @escaping () -> Void
) {
self.leadingItem = leadingItem
self.title = title
self.action = action
}
}
struct GlassMenuItemView: View {
// MARK: - Properties
private let namespace: Namespace.ID
let item: GlassMenuItem
@Binding private var isExpanded: Bool
// MARK: - Init
public init(
namespace: Namespace.ID,
item: GlassMenuItem,
isExpanded: Binding<Bool>
) {
self.namespace = namespace
self.item = item
self._isExpanded = isExpanded
}
// MARK: - Body
public var body: some View {
HStack(spacing: 4) {
Spacer()
if isExpanded {
Text(item.title)
.font(.system(size: 14, weight: .bold, design: .rounded))
.padding()
.glassEffect()
.glassEffectID(item.id, in: namespace)
}
Group {
switch item.leadingItem {
case .symbol(let name):
Image(systemName: name)
.frame(width: 60.0, height: 60.0)
.font(.system(size: 28, weight: .bold))
case .image(let name):
Image(name)
.frame(width: 60.0, height: 60.0)
.font(.system(size: 28, weight: .bold))
}
}
.glassEffect()
.glassEffectID(item.id, in: namespace)
.contentShape(Circle())
}
.bold()
.foregroundStyle(.primary)
.contentShape(Rectangle())
.onTapGesture {
withAnimation {
isExpanded.toggle()
}
item.action()
}
}
}
GlassExpandableMenu.swift
#import SwiftUI
public struct GlassExpandableMenu: View {
// MARK: - Namespace
@Namespace private var namespace
// MARK: - Properties
@State private var isExpanded: Bool = false
private let items: [GlassMenuItem]
// MARK: - Init
public init(items: [GlassMenuItem]) {
self.items = items
}
// MARK: - Body
public var body: some View {
VStack {
Spacer()
HStack {
Spacer()
GlassEffectContainer(spacing: 24) {
VStack(spacing: 24) {
if isExpanded {
ForEach(items) { item in
GlassMenuItemView(
namespace: namespace,
item: item,
isExpanded: $isExpanded
)
}
}
HStack {
Spacer()
Image(systemName: "plus")
.foregroundStyle(isExpanded ? .white : .primary)
.font(.system(size: 28, weight: .bold))
.frame(width: 60, height: 60)
.rotationEffect(.degrees(isExpanded ? 45 : 0))
.glassEffect(.regular.tint(isExpanded ? Color.red.opacity(0.2) : Color.systemBackground).interactive())
.glassEffectID("plus", in: namespace)
.contentShape(Circle())
.onTapGesture {
withAnimation {
isExpanded.toggle()
}
}
}
}
.ds_padding(.trailing, .twentyFour)
.ds_padding(.bottom, .thirtyTwo)
}
}
}
}
}
Demo
#Usage
##Preview {
GlassExpandableMenu(
items: [
.init(
leadingItem: .symbol("1.square"),
title: "One",
action: {
print("1")
}
),
.init(
leadingItem: .symbol("2.square"),
title: "Two",
action: {
print("2")
}
),
.init(
leadingItem: .symbol("3.square"),
title: "Three",
action: {
print("3")
}
),
.init(
leadingItem: .symbol("4.square"),
title: "Four",
action: {
print("4")
}
),
.init(
leadingItem: .symbol("5.square"),
title: "Five",
action: {
print("5")
}
),
.init(
leadingItem: .symbol("6.square"),
title: "Six",
action: {
print("6")
}
),
]
)
}
Result
#