SwiftUI Page TabViewStyle视图动画失效及预加载问题求助
onAppear & Animation Early This is a super common gotcha with SwiftUI's PageTabViewStyle — it preloads the adjacent pages (one on each side of the current visible page) by default. That's why when you swipe to Page Two, Page Three's onAppear fires immediately, so by the time you actually swipe to Page Three, the animation is already done.
Here are two solid solutions to fix this:
Solution 1: Use selection Binding to Trigger Animation Only When Page is Active
Instead of relying on onAppear, we'll track the currently selected page and only trigger the animation when the page is actually visible to the user. This works with the default TabView behavior, no need to disable preloading.
Updated code:
struct ContentView: View { let pages = ["Page One", "Page Two", "Page Three", "Page Four", "Page Five", "Page Six"] @State private var selectedPage: String = "Page One" var body: some View { TabView(selection: $selectedPage) { ForEach(pages, id: \.self) { pageTitle in PageView(title: pageTitle, isActive: selectedPage == pageTitle) .tag(pageTitle) } } .tabViewStyle(.page) } } struct PageView: View { @State private var isAnimating: Bool = false let title: String let isActive: Bool var body: some View { Text(title) .font(.title) .scaleEffect(isAnimating ? 0.2 : 1.0) .onChange(of: isActive) { _, becomesActive in // Only trigger animation when the page becomes the active one guard becomesActive else { return } print("Animation started for - \(title)") isAnimating = true withAnimation(.easeOut(duration: 1.0)) { isAnimating = false } } .onDisappear { print("onDisappear - \(title)") // Reset animation state so it works again if the page comes back isAnimating = false } } }
How it works:
- We bind the TabView's selection to a state variable
selectedPage, and tag each page with its title - The
PageViewreceives anisActiveflag that checks if it's the currently selected page - We use
onChange(of: isActive)to listen for when the page becomes visible — that's when we fire the animation - We reset the animation state in
onDisappearto make sure it runs correctly if the user swipes back to the page later
Solution 2: Build Custom Pagination with ScrollViewReader (No Preloading)
If you really want to eliminate preloading entirely, you can ditch the default PageTabViewStyle and build your own pagination with ScrollView and ScrollViewReader. This gives you full control over when pages load and animate.
Example code:
struct ContentView: View { let pages = ["Page One", "Page Two", "Page Three", "Page Four", "Page Five", "Page Six"] @State private var currentPageIndex = 0 private let pageWidth = UIScreen.main.bounds.width var body: some View { ScrollView(.horizontal, showsIndicators: false) { HStack(spacing: 0) { ForEach(Array(pages.enumerated()), id: \.element) { index, pageTitle in PageView(title: pageTitle, shouldAnimate: index == currentPageIndex) .frame(width: pageWidth) .id(index) } } } .scrollTargetLayout() .scrollPosition(id: $currentPageIndex) .scrollIndicators(.hidden) .ignoresSafeArea() } } struct PageView: View { @State private var isAnimating: Bool = false let title: String let shouldAnimate: Bool var body: some View { Text(title) .font(.title) .scaleEffect(isAnimating ? 0.2 : 1.0) .onChange(of: shouldAnimate) { _, shouldAnimateNow in guard shouldAnimateNow else { return } print("Animation started for - \(title)") isAnimating = true withAnimation(.easeOut(duration: 1.0)) { isAnimating = false } } .onDisappear { isAnimating = false } } }
How it works:
- We use a horizontal
ScrollViewwith each page set to the full screen width, creating a paginated effect scrollPositiontracks the index of the currently visible page- The
shouldAnimateflag is only true when the page's index matches the current scroll position, so the animation only fires when the page is actually visible
Quick Note
There's no public API to disable the default preloading behavior of PageTabViewStyle, so Solution 1 is usually the most straightforward and maintainable approach. Test both options and see which fits your use case best!
内容的提问来源于stack exchange,提问作者Wolfer

