d3f7
January 31, 2023, 9:23am
1
Hello all,
Could someone please help me figure out a wait to animate switching between the two views that are items in the TabView below? Ideally, I’d like the transition to be something natural, e.g. swiping left while in CurrentContentView
moves it to the left and drags PreviousContentView
from the right, and vice versa when swithing back.
import SwiftUI
struct ContentView: View {
@State private var selectedTab = 0
let numTabs = 2
let minDrag: CGFloat = 50
// MARK: - BODY
var body: some View {
let currentDay = Calendar.current.component(.day, from: Date())
TabView(selection: $selectedTab) {
Group{
CurrentContentView()
.tabItem {
VStack{
Image(systemName: "\(currentDay).circle.fill")
Text("Current content")
}
}
.tag(0)
.highPriorityGesture(
DragGesture()
.onEnded({
self.handleSwipe(translation: $0.translation.width)
})
)
PreviousContentView()
.tabItem {
VStack{
Image(systemName: "clock.arrow.circlepath")
Text("Previous content")
}
}
.tag(1)
.highPriorityGesture(
DragGesture()
.onEnded({
self.handleSwipe(translation: $0.translation.width)
})
)
}
.toolbar(.visible, for: .tabBar)
.toolbarBackground(Color.white, for: .tabBar)
}
}
private func handleSwipe(translation: CGFloat) {
if translation > minDrag && selectedTab > 0 {
selectedTab -= 1
edge = .leading
}
else if translation < -minDrag && selectedTab < numTabs - 1 {
selectedTab += 1
edge = .trailing
}
}
}
joash
January 31, 2023, 5:19pm
2
To animate the transition between two views in a TabView, you can add a custom transition using the transition
modifier and AnyTransition
type.
Here is an updated implementation:
struct ContentView: View {
@State private var selectedTab = 0
let numTabs = 2
let minDrag: CGFloat = 50
@State private var edge = Edge.leading
// MARK: - BODY
var body: some View {
let currentDay = Calendar.current.component(.day, from: Date())
TabView(selection: $selectedTab) {
Group{
CurrentContentView()
.tabItem {
VStack{
Image(systemName: "\(currentDay).circle.fill")
Text("Current content")
}
}
.tag(0)
.highPriorityGesture(
DragGesture()
.onEnded({
self.handleSwipe(translation: $0.translation.width)
})
)
.transition(AnyTransition.move(edge: edge).combined(with: .opacity))
PreviousContentView()
.tabItem {
VStack{
Image(systemName: "clock.arrow.circlepath")
Text("Previous content")
}
}
.tag(1)
.highPriorityGesture(
DragGesture()
.onEnded({
self.handleSwipe(translation: $0.translation.width)
})
)
.transition(AnyTransition.move(edge: edge).combined(with: .opacity))
}
.toolbar(.visible, for: .tabBar)
.toolbarBackground(Color.white, for: .tabBar)
}
}
private func handleSwipe(translation: CGFloat) {
if translation > minDrag && selectedTab > 0 {
selectedTab -= 1
edge = .leading
}
else if translation < -minDrag && selectedTab < numTabs - 1 {
selectedTab += 1
edge = .trailing
}
}
}
d3f7
January 31, 2023, 6:48pm
3
Hi @joash ,
Thank you for taking the time to help me. Try as I might, though, I couldn’t get your code to produce any sort of visible effect - and I tried both on the simulator and on my phone.
joash
February 1, 2023, 4:57am
4
Hi @d3f7
Can you confirm if this is the behaviour you’re trying to accomplish?
As a user I want to be able to swipe left and right to change the current tab view with the ability also to tap the tab icon to also change from current view to the next view as shown on the GIF attachment below.
I’m not able to run the code you shared so I assume that this is the behaviour you’re trying to achieve.
d3f7
February 2, 2023, 8:18am
5
Hi @joash ,
Here’s the effect I was trying to achieve (the sliding left-right between the child views):
Ultimatelly, I constructed a custom tab view and applied the transitions there. Here’s the code:
import SwiftUI
struct ContentView: View {
@State private var showTab = 0
let numTabs = 2
let minDrag: CGFloat = 50
let animationDuration = 0.2
let activeTint = Color("vpurple")
let inactiveTint = Color.primary
@State private var dailyTint: Color = Color("vpurple")
@State private var previousTint: Color = .primary
var body: some View {
let currentDay = Calendar.current.component(.day, from: Date())
VStack {
if showTab == 0 {
DailyAphorismView()
.transition(AnyTransition.asymmetric(insertion: .push(from: .leading), removal: .push(from: .trailing)).combined(with: .opacity))
.highPriorityGesture(
DragGesture()
.onEnded({
self.handleSwipe(translation: $0.translation.width)
})
)
}
else {
PreviousAphorismsView()
.transition(AnyTransition.asymmetric(insertion: .push(from: .trailing), removal: .push(from: .leading)).combined(with: .opacity))
.highPriorityGesture(
DragGesture()
.onEnded({
self.handleSwipe(translation: $0.translation.width)
})
)
}
Spacer()
HStack {
Button {
withAnimation(.easeIn(duration: animationDuration)) {
showTab = 0
previousTint = inactiveTint
dailyTint = activeTint
}
} label: {
VStack{
Image(systemName: "\(currentDay).circle.fill")
.font(.title2)
.padding(2)
Text("Афоризъм на деня")
.font(.caption)
}
}
.tint(dailyTint)
.padding(.horizontal, 20)
Spacer()
Button {
withAnimation(.easeIn(duration: animationDuration)) {
showTab = 1
dailyTint = inactiveTint
previousTint = activeTint
}
} label: {
VStack{
Image(systemName: "clock.arrow.circlepath")
.font(.title2)
.padding(2)
Text("Предишни афоризми")
.font(.caption)
}
}
.tint(previousTint)
.padding(.horizontal, 20)
}
.tint(.primary)
.padding(.vertical, 5)
.padding(.horizontal, 20)
}
}
private func handleSwipe(translation: CGFloat) {
withAnimation(.easeIn(duration: animationDuration)) {
if translation > minDrag && showTab > 0 {
showTab -= 1
previousTint = inactiveTint
dailyTint = activeTint
}
else if translation < -minDrag && showTab < numTabs - 1 {
showTab += 1
dailyTint = inactiveTint
previousTint = activeTint
}
}
}
}
joash
February 2, 2023, 10:11am
6
This is nice @d3f7
So just to confirm, your issue is now solve, is that correct?