Во-первых, это модель данных для бокового меню:
Код: Выделить всё
struct SideMenuOBJ: Identifiable, Hashable {
let id = UUID()
let image: UIImage
let label: String
var vc: UIViewController?
}
struct SideMenuRepository {
static func profile () -> [SideMenuOBJ] {
let base = [
SideMenuOBJ(image: UIImage(systemName: "person.circle")!, label: "Profile"),
SideMenuOBJ(image: UIImage(systemName: "list.clipboard")!, label: "Account Summary"),
SideMenuOBJ(image: UIImage(systemName: "fossil.shell")!, label: "History")
]
return base
}
}
Код: Выделить всё
public struct Drawer: View {
@Binding private var isOpened: Bool
private let menu: Menu
private let content: Content
public init(
isOpened: Binding,
@ViewBuilder menu: () -> Menu,
@ViewBuilder content: () -> Content
) {
_isOpened = isOpened
self.menu = menu()
self.content = content()
}
public var body: some View {
ZStack(alignment: .leading) {
content
if isOpened {
Color.clear
.contentShape(Rectangle())
.onTapGesture {
if isOpened {
isOpened.toggle()
}
}
menu
.transition(.move(edge: .leading))
.zIndex(1)
}
}
.animation(.spring(), value: isOpened)
.environment(\.drawerPresentationMode, $isOpened.mappedToDrawerPresentationMode())
}
}
public struct DrawerPresentationMode {
@Binding private var _isOpened: Bool
init(isOpened: Binding) {
__isOpened = isOpened
}
public var isOpened: Bool {
_isOpened
}
mutating func open() {
if !_isOpened {
_isOpened = true
}
}
mutating func close() {
if _isOpened {
_isOpened = false
}
}
}
extension Binding where Value == Bool {
func mappedToDrawerPresentationMode() -> Binding {
Binding(
get: {
DrawerPresentationMode(isOpened: self)
},
set: { newValue in
self.wrappedValue = newValue.isOpened
}
)
}
}
extension DrawerPresentationMode {
static var placeholder: DrawerPresentationMode {
DrawerPresentationMode(isOpened: .constant(false))
}
}
private struct DrawerPresentationModeKey: EnvironmentKey {
static var defaultValue: Binding = .constant(.placeholder)
}
extension EnvironmentValues {
public var drawerPresentationMode: Binding {
get { self[DrawerPresentationModeKey.self] }
set { self[DrawerPresentationModeKey.self] = newValue }
}
}
Код: Выделить всё
class SideMenuViewModel: ObservableObject {
@Published var profileOptions: [SideMenuOBJ] = SideMenuRepository.profile()
@ViewBuilder
func handleProfileOptionTap(option: SideMenuOBJ?) -> some View {
switch option?.label {
case "My Profile":
ProfileView()
case "Account":
AccountSummaryView()
case "My Takes History":
History()
default:
EmptyView()
}
}
}
Код: Выделить всё
struct SideMenu: View {
@ObservedObject var viewModel: SideMenuViewModel
@State private var selectedOption: SideMenuOBJ?
@State private var isNavigating: Bool = false
var body: some View {
NavigationStack {
ZStack {
ScrollView {
VStack(alignment: .leading, spacing: 16) {
VStack(alignment: .leading, spacing: 12) {
ForEach(viewModel.profileOptions) { option in
Button {
selectedOption = option
isNavigating.toggle()
} label: {
HStack {
Image(uiImage: option.image)
.renderingMode(.template)
.foregroundColor(.primary)
.frame(width: 24, height: 24)
Text(option.label)
.fontWeight(.bold)
.foregroundStyle(Color.primary)
.font(.body)
.padding(.vertical, 8)
.padding(.horizontal)
.cornerRadius(8)
}
.cornerRadius(8)
}
}
}
.padding(.horizontal)
}
}
.scrollIndicators(.never)
}
.navigationDestination(item: $selectedOption) { option in
viewModel.handleProfileOptionTap(option: option)
}
}
}
}
Код: Выделить всё
struct SideMenuIssueApp: App {
@State private var showSideMenu: Bool = false
var body: some Scene {
WindowGroup {
Drawer(isOpened: $showSideMenu) {
ZStack {
SideMenu(viewModel: SideMenuViewModel())
.frame(width: 270)
}
} content: {
contentView
}
}
}
var contentView: some View {
NavigationStack {
TabView {
ContentView()
.tabItem {
Label("Content View", systemImage: "tray")
}
}
.toolbar {
ToolbarItem(placement: .topBarLeading) {
Button {
showSideMenu.toggle()
} label: {
Image(systemName: "menucard")
}
}
}
}
}
}
Подробнее здесь: https://stackoverflow.com/questions/793 ... in-swiftui