Add left swipe gesture to show side menu in page tab view swiftui

Viewed 161

I was trying to follow a tutorial which implemented a clean swipe gesture to show a side menu making using of a hstack, offset, and drag gesture similar to how twitter currently has it set up. However, I wanted to modify it so that the tabs at the bottom can be swiped like a page tab view in swiftui but that breaks the swipe gesture so that it doesn't trigger the side menu when swiped left upon.

I was trying to make it work so that if I were in the left most tab, the swipe gesture should still trigger and show the side menu. However, it is not working. I was able to get it to work to show the side menu if the profile image was clicked and the swipe to get out of the side menu still works, so I was hoping someone could point me as to how I could solve this.

Here is the code for the BaseView.swift


import SwiftUI

struct BaseView: View {
    @State var showMenu: Bool = false
    
    // Hiding Native One...
    init(){
        UITabBar.appearance().isHidden = true
    }
    
    @State var currentTab = "Home"
    
    // Offset for Both Drag Gesuture and showing Menu...
    @State var offset: CGFloat = 0
    @State var lastStoredOffset: CGFloat = 0
    
    // GEsture Offset...
    @GestureState var gestureOffset: CGFloat = 0
    @State var isSearching = false
    var body: some View {
        
        let sideBarWidth = getRect().width - 90
        
        // Whole Navigation View....
        NavigationView{
            
            HStack(spacing: 0){
                
                // Side Menu...
                SideMenu(showMenu: $showMenu)
                
                // Main Tab View...
                VStack(spacing: 0){
                    
                    TabView(selection: $currentTab){
                        
                        Home(showMenu: $showMenu)
                            .navigationBarTitleDisplayMode(.inline)
                            .navigationBarHidden(true)
                            .tag("Home")
                            .gesture(isSearching ? DragGesture() : nil) 
                        
                        Text("Search")
                            .navigationBarTitleDisplayMode(.inline)
                            .navigationBarHidden(true)
                            .tag("Search")
                        
                        Text("Notifications")
                            .navigationBarTitleDisplayMode(.inline)
                            .navigationBarHidden(true)
                            .tag("Notifications")
                        
                        Text("Messages")
                            .navigationBarTitleDisplayMode(.inline)
                            .navigationBarHidden(true)
                            .tag("Message")
                    }.tabViewStyle(.page(indexDisplayMode: .never)) //Page Tab view modified here
                     .indexViewStyle(.page(backgroundDisplayMode: .never))
                    
                    // Custom Tab Bar....
                    VStack(spacing: 0){
                        
                        Divider()
                        
                        HStack(spacing: 0){
                            
                            // Tab Buttons...
                            TabButton(image: "Home")
                            
                            TabButton(image: "Search")
                            
                            TabButton(image: "Notifications")
                            
                            TabButton(image: "Message")
                        }
                        .padding(.top,15)
                        .padding(.bottom,safeArea().bottom == 0 ? 15 : 0)
                    }
                }
                .frame(width: getRect().width)
                // BG when menu is showing...
                .overlay(
                
                    Rectangle()
                        .fill(
                        
                            Color.primary
                                .opacity(Double((offset / sideBarWidth) / 5))
                        )
                        .ignoresSafeArea(.container, edges: .vertical)
                        .onTapGesture {
                            withAnimation{
                                showMenu.toggle()
                            }
                        }
                )
            }
            // max Size...
            .frame(width: getRect().width + sideBarWidth)
            .offset(x: -sideBarWidth / 2)
            .offset(x: offset > 0 ? offset : 0)
            // Gesture...
            .gesture(
            
                DragGesture()
                    .updating($gestureOffset, body: { value, out, _ in
                        
                        out = value.translation.width
                    })
                    .onEnded(onEnd(value:))
            )
            // No Nav bar title...
            // Hiding nav bar...
            .navigationBarTitleDisplayMode(.inline)
            .navigationBarHidden(true)
        }
        .animation(.easeOut, value: offset == 0)
        .onChange(of: showMenu) { newValue in
            
            // Preview issues...
            
            if showMenu && offset == 0{
                offset = sideBarWidth
                lastStoredOffset = offset
            }
            
            if !showMenu && offset == sideBarWidth{
                offset = 0
                lastStoredOffset = 0
            }
        }
        .onChange(of: gestureOffset) { newValue in
            onChange()
        }
    }
    
    func onChange(){
        let sideBarWidth = getRect().width - 90
        
        offset = (gestureOffset != 0) ? ((gestureOffset + lastStoredOffset) < sideBarWidth ? (gestureOffset + lastStoredOffset) : offset) : offset
        
        offset = (gestureOffset + lastStoredOffset) > 0 ? offset : 0
    }
    
    func onEnd(value: DragGesture.Value){
        
        let sideBarWidth = getRect().width - 90
        
        let translation = value.translation.width
        
        withAnimation{
            // Checking...
            if translation > 0{
                
                if translation > (sideBarWidth / 2){
                    // showing menu...
                    offset = sideBarWidth
                    showMenu = true
                }
                else{
                    
                    // Extra cases...
                    if offset == sideBarWidth || showMenu{
                        return
                    }
                    offset = 0
                    showMenu = false
                }
            }
            else{
                
                if -translation > (sideBarWidth / 2){
                    offset = 0
                    showMenu = false
                }
                else{
                    
                    if offset == 0 || !showMenu{
                        return
                    }
                    
                    offset = sideBarWidth
                    showMenu = true
                }
            }
        }
        
        // storing last offset...
        lastStoredOffset = offset
    }
    
    @ViewBuilder
    func TabButton(image: String)->some View{
        
        Button {
            withAnimation{currentTab = image}
        } label: {
            Image(image)
                .resizable()
                .renderingMode(.template)
                .aspectRatio(contentMode: .fit)
                .frame(width: 23, height: 22)
                .foregroundColor(currentTab == image ? .primary : .gray)
                .frame(maxWidth: .infinity)
        }

    }
}

struct BaseView_Previews: PreviewProvider {
    static var previews: some View {
        BaseView()
    }
}

Here is the code for Home.swift


import SwiftUI

struct Home: View {
    @Binding var showMenu: Bool
    var body: some View {
        
        VStack{
            
            VStack(spacing: 0){
                
                HStack{
                    
                    Button {
                        withAnimation{showMenu.toggle()}
                    } label: {
                        Image("Pic")
                            .resizable()
                            .aspectRatio(contentMode: .fill)
                            .frame(width: 35, height: 35)
                            .clipShape(Circle())
                    }
                    
                    Spacer()
                    
                    // Navigation Link...
                    NavigationLink {
                        
                        Text("Timeline View")
                            .navigationTitle("Timeline")
                        
                    } label: {
                        
                        Image("Sparkles")
                            .resizable()
                            .renderingMode(.template)
                            .aspectRatio(contentMode: .fit)
                            .frame(width: 22, height: 22)
                            .foregroundColor(.primary)
                    }

                }
                .padding(.horizontal)
                .padding(.vertical,10)
                
                Divider()
            }
            .overlay(
            
                Image("Logo")
                    .resizable()
                    .aspectRatio(contentMode: .fit)
                    .frame(width: 25, height: 25)
            )
            
            Spacer()
        }
    }
}

struct Home_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

The side menu is a simple view that shows some buttons and images. I wanted it so if I swipe left from the home menu, it still triggers the sidemenu to pop up and show along with the page tab view from the base. It works perfectly without the page tab view modification and I commented where the page tab view modification was made in the base view to clarify that. Thank you so much for all your help!

0 Answers
Related