While you navigate by swiping, the index of the at present chosen view most likely does not change till the brand new web page is partially seen. So on this case, the total animation is seen. Nevertheless, if you navigate by tapping the button, the index adjustments earlier than the brand new web page comes into view. This will clarify why plainly there is no such thing as a animation, as a result of it’s primarily occurring off-screen and subsequently not being seen.
There might also be different the reason why the animation is inconsistent, relying on how the TabView
pre-loads the view or retains it cached.
Since you’re utilizing a paged Tabview
and likewise hiding the web page indicators, you do not actually have to be utilizing a TabView
in any respect. You may need extra management of the animation in case you use a ScrollView
with sticky conduct as an alternative:
- The scroll view can include an
HStack
with.scrollTargetBehavior
. - The present web page is tracked utilizing
.scrollPosition
. - Sticky scrolling is achieved by utilizing
.scrollTargetBehavior
. I might advocate utilizing.viewAligned
as an alternative of.paging
, to keep away from points with protected space insets on units with main or trailing protected space insets (akin to an iPad, or an iPhone in panorama orientation). - To workaround the difficulty of the index altering earlier than the view seems, I might recommend basing the animation on whether or not the view is definitely close to the middle of the display screen or not. This may be detected utilizing an
.onGeometryChange
modifier. - When a view is altering from chosen to non-selected, do not scale it again up till it has disappeared off display screen. This implies utilizing a distinct threshold for the animation when a view is shifting into view, in comparison with when it’s shifting out of view.
- It is usually a good suggestion to clip every view to the web page body, in order that the off-screen scaled model does not overflow into the present model throughout animation.
Because the web page views shall be detecting their very own place, they want to have the ability to replace a devoted state variable. This implies factoring the web page view out right into a separate View
.
Whereas we’re at it, I might additionally recommend making the enum Identifiable
(and Hashable
). You possibly can then keep away from all use of array indices.
Right here is the totally up to date instance to indicate it working:
struct TabDemoView: View {
var onComplete: (() -> Void)? = nil
@State personal var currentPage: TabDemoPage? = .pageOne
var physique: some View {
VStack {
ScrollView(.horizontal) {
HStack(spacing: 0) {
ForEach(TabDemoPage.allCases) { web page in
PageView(web page: web page)
.containerRelativeFrame(.horizontal)
.clipped()
}
}
.scrollTargetLayout()
}
.scrollPosition(id: $currentPage, anchor: .middle)
.scrollTargetBehavior(.viewAligned(limitBehavior: .all the time))
.scrollIndicators(.hidden)
Spacer()
HStack(spacing: 12) {
ForEach(TabDemoPage.allCases) { web page in
let circleSize: CGFloat = currentPage == web page ? 12 : 8
let circleColor: Shade = currentPage == web page ? .blue : .grey.opacity(0.5)
Circle()
.fill(circleColor)
.body(width: circleSize, top: circleSize)
.animation(.spring(), worth: currentPage)
}
}
Group {
if currentPage != TabDemoPage.allCases.final {
Button("Subsequent") {
withAnimation(.spring()) {
currentPage = currentPage?.subsequent ?? .pageOne
}
}
} else {
Button("Get Began") { onComplete?() }
}
}
.padding()
.foregroundColor(.main)
}
.padding(.backside, 40)
}
}
struct PageView: View {
let web page: TabDemoPage
@State personal var proven = false
var physique: some View {
Textual content(web page.content material)
.body(maxWidth: .infinity, maxHeight: .infinity)
.opacity(proven ? 1 : 0)
.scaleEffect(proven ? 1 : 4)
.animation(.spring(), worth: proven)
.onGeometryChange(for: Bool.self) { proxy in
let midX = proxy.body(in: .scrollView).midX
let fullWidth = proxy.dimension.width
let halfWidth = fullWidth / 2
return abs(halfWidth - midX) < (proven ? fullWidth * 9 / 10 : halfWidth)
} motion: { isShown in
proven = isShown
}
}
}
enum TabDemoPage: Identifiable, Hashable, CaseIterable {
case pageOne
case pageTwo
case pageThree
case pageFour
case pageFive
case pageSix
var id: TabDemoPage {
self
}
var subsequent: TabDemoPage {
change self {
case .pageOne: .pageTwo
case .pageTwo: .pageThree
case .pageThree: .pageFour
case .pageFour: .pageFive
case .pageFive: .pageSix
case .pageSix: .pageSix
}
}
var content material: String {
change self {
case .pageOne: "Web page One"
case .pageTwo: "Web page Two"
case .pageThree: "Web page Three"
case .pageFour: "Web page 4"
case .pageFive: "Web page 5"
case .pageSix: "Web page Six"
}
}
}