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!