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(alignment: .leading) { // Выравниваем 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") } } .frame(maxWidth: .infinity, maxHeight: .infinity) // Убедимся, что основной контент занимает все пространство .ignoresSafeArea(edges: .bottom) .navigationBarHidden(true) .sheet(item: $sheetType) { type in // ... sheet presentation logic } // Затемнение и закрытие по тапу if isSideMenuPresented { Color.black.opacity(0.4 * Double(((menuWidth + menuOffset) / menuWidth))) .ignoresSafeArea() .onTapGesture { withAnimation(.easeInOut) { isSideMenuPresented = false } } } // Боковое меню SideMenuView(isPresented: $isSideMenuPresented) .frame(width: menuWidth) .offset(x: -menuWidth + menuOffset) // Новая логика смещения .ignoresSafeArea(edges: .vertical) } .gesture( DragGesture() .onChanged { gesture in if !isSideMenuPresented && gesture.startLocation.x > 60 { return } let translation = gesture.translation.width if isSideMenuPresented { // При закрытии двигаем от 0 до -menuWidth self.menuOffset = max(0, menuWidth + translation) } else { // При открытии двигаем от 0 до menuWidth self.menuOffset = min(menuWidth, translation) } } .onEnded { gesture in if !isSideMenuPresented && gesture.startLocation.x > 60 { return } let threshold = menuWidth * 0.4 withAnimation(.easeInOut) { if self.menuOffset > threshold { isSideMenuPresented = true } else { isSideMenuPresented = false } // Сбрасываем menuOffset, так как isSideMenuPresented теперь главный источник истины self.menuOffset = isSideMenuPresented ? menuWidth : 0 } } ) } .navigationViewStyle(StackNavigationViewStyle()) .onChange(of: isSideMenuPresented) { presented in // Плавная анимация при нажатии на кнопку, а не только при жесте withAnimation(.easeInOut) { menuOffset = presented ? menuWidth : 0 } } } } struct MainView_Previews: PreviewProvider { static var previews: some View { let mockViewModel = LoginViewModel() MainView(viewModel: mockViewModel) } }