Introduction
SwiftUI has revolutionized application development for the Apple ecosystem thanks to its declarative approach, clean syntax, and deep integration with Xcode. One of its most widely used components is TabView, a fundamental view for building tab-based interfaces, commonly found in iOS apps such as social networks, media players, banking apps, and educational platforms.
By default, when using TabView with the .page style—or even with the classic tab bar style—SwiftUI enables a swipe gesture that allows users to switch tabs by dragging left or right. While this is convenient in many cases, there are situations where this behavior is not desirable.
In this tutorial, you will learn step by step how to disable the swipe gesture in a TabView using SwiftUI and Xcode, exploring multiple approaches, from simple solutions to more advanced techniques that rely on UIKit when necessary.
Why Would You Want to Disable Swipe in a TabView?
Before diving into the code, let’s look at some real-world scenarios where disabling swipe is a good idea:
- Guided flows
If your app presents a step-by-step process (onboarding, tutorials, registration), you don’t want users skipping steps by swiping freely. - Gesture conflicts
If one of your tabs contains aScrollView, a map, or a horizontal carousel, swipe gestures may conflict with tab navigation. - Navigation control
You may want to allow tab switching only through buttons, validation, or custom logic. - Educational or exam apps
In quizzes or exams, allowing free navigation between tabs could break the app’s logic.
How TabView Works Internally
In SwiftUI, TabView is an abstraction that relies on UIKit under the hood:
- The classic tab style uses
UITabBarController - The
.pagestyle usesUIPageViewControlleror aUIScrollView
This is important because the swipe gesture is handled by these UIKit components, not directly by SwiftUI.
Currently, SwiftUI does not provide a native modifier like:
TabView {
...
}.disableSwipe(true) // ❌ This does not existSo we need alternative approaches.
Base Example: TabView with Swipe Enabled
Let’s start with a simple TabView example:
struct ContentView: View {
var body: some View {
TabView {
Text("Screen 1")
.font(.largeTitle)
.tabItem {
Label("One", systemImage: "1.circle")
}
Text("Screen 2")
.font(.largeTitle)
.tabItem {
Label("Two", systemImage: "2.circle")
}
Text("Screen 3")
.font(.largeTitle)
.tabItem {
Label("Three", systemImage: "3.circle")
}
}
}
}By default, users can switch tabs either by tapping the tab bar or by swiping horizontally.
Our goal is to prevent swiping.
Solution 1: Using Controlled Selection
A simple way to prevent unwanted tab changes is to control the selected tab using a @State variable:
struct ContentView: View {
@State private var selectedTab = 0
var body: some View {
TabView(selection: $selectedTab) {
Text("Screen 1").tag(0)
Text("Screen 2").tag(1)
Text("Screen 3").tag(2)
}
}
}This alone does not disable swipe, but it lets you detect when a tab change occurs. You can revert it:
.onChange(of: selectedTab) { _ in
selectedTab = 0
}This creates the illusion that swipe does not work, but there is still a brief animation. Not ideal, but useful in simple cases.
Solution 2: Using .page and Disabling the Scroll
When TabView uses .tabViewStyle(.page), it internally uses a UIScrollView:
TabView {
Color.red
Color.green
Color.blue
}
.tabViewStyle(.page)To truly disable swipe, we must disable the underlying scroll view.
This requires bridging SwiftUI with UIKit using UIViewRepresentable.
Creating a Helper to Disable Scrolling
We create a hidden view that searches for the parent UIScrollView and disables it:
import SwiftUI
struct DisableSwipeTabView: UIViewRepresentable {
func makeUIView(context: Context) -> UIView {
let view = UIView()
DispatchQueue.main.async {
if let scrollView = view.findScrollView() {
scrollView.isScrollEnabled = false
}
}
return view
}
func updateUIView(_ uiView: UIView, context: Context) {}
}And a helper extension:
extension UIView {
func findScrollView() -> UIScrollView? {
if let scroll = self.superview as? UIScrollView {
return scroll
}
for view in self.subviews {
if let found = view.findScrollView() {
return found
}
}
return nil
}
}Applying It to the TabView
Now we attach it to the TabView:
struct ContentView: View {
var body: some View {
TabView {
Text("Page 1")
Text("Page 2")
Text("Page 3")
}
.tabViewStyle(.page)
.overlay(DisableSwipeTabView())
}
}Result:
- Pages still exist
- Swipe gesture is completely disabled
- Navigation can be controlled manually
Adding Button-Based Navigation
Now let’s add manual navigation:
struct ContentView: View {
@State private var index = 0
var body: some View {
VStack {
TabView(selection: $index) {
Text("Step 1").tag(0)
Text("Step 2").tag(1)
Text("Step 3").tag(2)
}
.tabViewStyle(.page)
.overlay(DisableSwipeTabView())
HStack {
Button("Previous") {
if index > 0 { index -= 1 }
}
Button("Next") {
if index < 2 { index += 1 }
}
}
}
}
}Now you have a paged UI without swipe.
Advantages of This Approach
- Full control over navigation
- No unwanted swipe animations
- Works well for guided flows
- Perfect for onboarding, forms, and tutorials
Potential Issues
- Internal SwiftUI changes
Apple may change howTabViewis implemented internally. - UIKit dependency
Some SwiftUI solutions still require UIKit. - Testing
Always test on multiple devices and iOS versions.
Real-World Use Cases
- App onboarding
- Interactive tutorials
- Course apps
- Multi-step forms
- Games with level-based navigation
Conclusion
Disabling the swipe gesture in a TabView in SwiftUI is not officially supported, but by understanding how SwiftUI works under the hood, we can take full control of navigation.
By disabling the internal UIScrollView, we can create precise, professional, and user-friendly interfaces that behave exactly as we want—ideal for guided experiences and complex workflows.
If you have any questions about this article, please contact me and I will be happy to help you 🙂. You can contact me on my X profile or on my Instagram profile.