I'm trying to create an equivalent to TabView, but with the difference that the 2nd View slides in over the top of the primary view.
Maybe there's a more elegant way of coding this (suggestions appreciated), but I've almost succeeded using the dragGesture. When a user swipes right to left the observed variable showTab2 is set to true, and the 2nd tab glides in over the top of tab 1 and displays 🥳.
The only problem is, that when a user happens to start the swipe over a button, the observed status (showTab2) does change as expected but the main view does not catch this change and does not display tab2. And that despite the showTab2 being an @Observable.
Any clues what I've missed? Or how to capture that the start of a swipe gesture starts over the top of a button and should be ignored.
According to the code in SwipeTabView this screenshot 👆 should never occur.
Here's the code:
@Observable
class myclass {
var showTab2 = false
}
struct SwipeTabView: View {
@State var myClass = myclass()
@State var dragAmount: CGSize = CGSize.zero
var body: some View {
VStack {
ZStack {
GeometryReader { geometryProxy in
VStack {
tab(tabID: 1, selectedTab: myClass.showTab2)
.zIndex(/*@START_MENU_TOKEN@*/1.0/*@END_MENU_TOKEN@*/)
.background(.black)
.transition(.identity)
.swipeable(stateOfViewAdded: $myClass.showTab2, dragAmount: $dragAmount, geometryProxy: geometryProxy, insertion: true)
}
if myClass.showTab2 || dragAmount.width != 0 {
tab(tabID: 2, selectedTab: myClass.showTab2)
.zIndex(2.0)
.drawingGroup()
.transition(.move(edge: .trailing))
.offset(x: dragAmount.width )
.swipeable(stateOfViewAdded: $myClass.showTab2, dragAmount: $dragAmount, geometryProxy: geometryProxy, insertion: false)
}
}
}
}
}
}
extension View {
func swipeable(stateOfViewAdded: Binding<Bool>,
dragAmount: Binding<CGSize>,
geometryProxy: GeometryProxy,
insertion: Bool) -> some View {
self.gesture(
DragGesture()
.onChanged { gesture in
// inserting must be minus, but removing must be positive - hence the multiplication.
if gesture.translation.width * (insertion ? 1 : -1 ) < 0 {
if insertion {
dragAmount.wrappedValue.width = geometryProxy.size.width + gesture.translation.width
} else {
dragAmount.wrappedValue.width = gesture.translation.width
}
}
}
.onEnded { gesture in
if abs(gesture.translation.width) > 100.0 && gesture.translation.width * (insertion ? 1 : -1 ) < 0 {
withAnimation(.easeOut.speed(Double(gesture.velocity.width))) {
stateOfViewAdded.wrappedValue = insertion
}
} else {
withAnimation(.easeOut.speed(Double(gesture.velocity.width))) {
stateOfViewAdded.wrappedValue = !insertion
}
}
withAnimation(.smooth) {
dragAmount.wrappedValue = CGSize.zero
}
}
)
}
}
struct tab: View {
var tabID: Int
var selectedTab: Bool
var body: some View {
ZStack {
Color(tabID == 1 ? .yellow : .orange)
VStack {
Text("Tab \(tabID) ").foregroundColor(.black)
Button(action: {
print("Tab2 should display - \(selectedTab.description)")
}, label: {
ZStack {
circle
label
}
})
Text("Tab2 should display - \(selectedTab.description)")
}
}
}
var circle: some View {
Circle()
.frame(width: 100, height: 100)
.foregroundColor(.red)
}
var label: some View {
Text("\(tabID == 1 ? ">>" : "<<")").font(.title).foregroundColor(.black)
}
}