import SwiftUI struct MainView: View { @ObservedObject var viewModel: LoginViewModel @State private var selectedTab: Int = 0 @StateObject private var newHomeTabViewModel = NewHomeTabViewModel() // Состояния для TopBarView @State private var selectedAccount = "@user1" @State private var accounts = ["@user1", "@user2", "@user3"] @State private var sheetType: ProfileTab.SheetType? = nil // Состояния для бокового меню @State private var isSideMenuPresented = false @State private var menuOffset: CGFloat = 0 private var tabTitle: String { switch selectedTab { case 0: return "Home" case 1: return "Search" case 2: return "Chats" case 3: return "Profile" default: return "Home" } } private var menuWidth: CGFloat { UIScreen.main.bounds.width * 0.8 } var body: some View { NavigationView { ZStack { // Основной контент VStack(spacing: 0) { TopBarView( title: tabTitle, selectedAccount: $selectedAccount, sheetType: $sheetType, accounts: accounts, viewModel: viewModel, isSideMenuPresented: $isSideMenuPresented ) ZStack { NewHomeTab(viewModel: newHomeTabViewModel) .opacity(selectedTab == 0 ? 1 : 0) SearchTab() .opacity(selectedTab == 1 ? 1 : 0) ChatsTab() .opacity(selectedTab == 2 ? 1 : 0) ProfileTab(viewModel: viewModel, sheetType: $sheetType, selectedAccount: $selectedAccount, accounts: $accounts, onScroll: { _ in }) .opacity(selectedTab == 3 ? 1 : 0) } .frame(maxWidth: .infinity, maxHeight: .infinity) CustomTabBar(selectedTab: $selectedTab) { print("Create button tapped") } } .ignoresSafeArea(edges: .bottom) .navigationBarHidden(true) .sheet(item: $sheetType) { type in // ... sheet presentation logic } // Затемнение и закрытие по тапу if isSideMenuPresented { Color.black.opacity(0.4 + Double((menuOffset / menuWidth))) .ignoresSafeArea() .onTapGesture { withAnimation(.easeInOut) { isSideMenuPresented = false } } } // Боковое меню SideMenuView(isPresented: $isSideMenuPresented) .frame(width: menuWidth) .offset(x: (isSideMenuPresented ? 0 : -menuWidth) + menuOffset) .ignoresSafeArea(edges: .vertical) .zIndex(1) } .gesture( DragGesture() .onChanged { gesture in // Разрешаем открывать только свайпом от левого края, а закрывать — откуда угодно if !isSideMenuPresented && gesture.startLocation.x > 60 { return } let translation = gesture.translation.width if isSideMenuPresented { // Если меню открыто, позволяем двигать его влево до полного закрытия let newOffset = max(-menuWidth, translation) self.menuOffset = min(0, newOffset) } else { // Если меню закрыто, позволяем двигать его вправо до полного открытия self.menuOffset = max(0, translation) } } .onEnded { gesture in // Если свайп был не от края (для закрытого меню), ничего не делаем if !isSideMenuPresented && gesture.startLocation.x > 60 { return } let threshold = menuWidth * 0.4 withAnimation(.easeInOut) { if isSideMenuPresented { // Если меню было открыто и сдвинуто влево больше, чем на `threshold` if menuOffset < -threshold { isSideMenuPresented = false } } else { // Если меню было закрыто и сдвинуто вправо больше, чем на `threshold` if menuOffset > threshold { isSideMenuPresented = true } } // Возвращаем временное смещение в 0, основное состояние `isSideMenuPresented` сделает остальное self.menuOffset = 0 } } ) } .navigationViewStyle(StackNavigationViewStyle()) } } struct MainView_Previews: PreviewProvider { static var previews: some View { let mockViewModel = LoginViewModel() MainView(viewModel: mockViewModel) } }