Programación en Swift y SwiftUI para iOS Developers

Cómo desactivar el gesto de swipe en un TabView en SwiftUI

Introducción

SwiftUI ha revolucionado el desarrollo de aplicaciones para el ecosistema Apple gracias a su enfoque declarativo, su sintaxis limpia y su integración profunda con Xcode. Entre sus componentes más utilizados se encuentra TabView, una vista fundamental para construir interfaces basadas en pestañas, muy común en apps de iOS como redes sociales, reproductores multimedia, aplicaciones bancarias o plataformas educativas.

Por defecto, cuando utilizamos TabView con el estilo .page o incluso con el estilo de pestañas tradicional, SwiftUI habilita un gesto de deslizamiento (swipe) que permite al usuario cambiar de pestaña arrastrando la pantalla de izquierda a derecha o viceversa. Aunque esto es conveniente en muchos casos, hay situaciones donde este comportamiento no es deseable.

En este tutorial aprenderás, paso a paso, cómo desactivar el gesto de swipe en un TabView usando SwiftUI y Xcode, explorando diferentes enfoques, desde soluciones simples hasta técnicas más avanzadas utilizando UIKit cuando es necesario.


¿Por qué querrías desactivar el swipe en un TabView?

Antes de entrar en código, veamos algunos casos reales donde bloquear el gesto de swipe es una buena idea:

  1. Flujos guiados
    Si tu app presenta un proceso paso a paso (onboarding, tutorial, registro), no quieres que el usuario salte de una pestaña a otra sin seguir el flujo correcto.
  2. Evitar conflictos de gestos
    Si una de las vistas contiene un ScrollView, un mapa o un carrusel, el gesto de swipe puede interferir con la navegación entre pestañas.
  3. Control de navegación
    Puede que solo quieras permitir el cambio de pestaña mediante botones, validaciones o lógica específica.
  4. Apps educativas o de exámenes
    En apps de cuestionarios o evaluaciones, permitir que el usuario navegue libremente entre pestañas podría romper la lógica de la aplicación.

Cómo funciona TabView internamente

En SwiftUI, TabView es una abstracción que, en iOS, se apoya internamente en componentes de UIKit:

  • Para el estilo clásico de pestañas: UITabBarController
  • Para el estilo .pageUIPageViewController o un UIScrollView

Esto es importante porque el gesto de swipe está gestionado por estos componentes internos, no directamente por SwiftUI.

SwiftUI no proporciona actualmente una propiedad oficial como:

TabView {
   ...
}.disableSwipe(true) // ❌ No existe

Por eso debemos recurrir a técnicas alternativas.


Ejemplo base: TabView con swipe activo

Empezamos con un ejemplo básico para entender el comportamiento por defecto:

struct ContentView: View {
    var body: some View {
        TabView {
            Text("Pantalla 1")
                .font(.largeTitle)
                .tabItem {
                    Label("Uno", systemImage: "1.circle")
                }

            Text("Pantalla 2")
                .font(.largeTitle)
                .tabItem {
                    Label("Dos", systemImage: "2.circle")
                }

            Text("Pantalla 3")
                .font(.largeTitle)
                .tabItem {
                    Label("Tres", systemImage: "3.circle")
                }
        }
    }
}

Con esto, el usuario puede cambiar de pestaña tocando la TabBar o deslizando horizontalmente.

Nuestro objetivo es bloquear el gesto de deslizamiento.


Solución 1: Usar TabView con selección controlada

Una forma sencilla de evitar cambios de pestaña no deseados es controlar la selección con una variable @State.

struct ContentView: View {
    @State private var selectedTab = 0

    var body: some View {
        TabView(selection: $selectedTab) {
            Text("Pantalla 1")
                .tag(0)

            Text("Pantalla 2")
                .tag(1)

            Text("Pantalla 3")
                .tag(2)
        }
    }
}

Esto por sí solo no desactiva el swipe, pero nos permite saber cuándo el usuario intenta cambiar de pestaña.

Podemos usar esto para revertir el cambio:

.onChange(of: selectedTab) { newValue in
    selectedTab = 0 // Siempre vuelve a la pestaña 0
}

Esto crea la ilusión de que el swipe no funciona, pero la animación ocurre brevemente. No es ideal, pero puede servir en casos simples.


Solución 2: Usar .page y bloquear el gesto

Cuando TabView se usa con .tabViewStyle(.page), SwiftUI usa un UIScrollView por debajo.

TabView {
    Color.red
    Color.green
    Color.blue
}
.tabViewStyle(.page)

Aquí el swipe es aún más evidente.

La clave es desactivar el scroll del UIScrollView interno.

Para ello necesitamos usar UIKit mediante un UIViewRepresentable.


Creando un helper para desactivar el scroll

Creamos una vista invisible que busque el UIScrollView padre y desactive su scroll:

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) {}
}

Y un helper para buscar el UIScrollView:

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
    }
}

Aplicándolo al TabView

Ahora lo añadimos dentro del TabView:

struct ContentView: View {
    var body: some View {
        TabView {
            Text("Página 1")
            Text("Página 2")
            Text("Página 3")
        }
        .tabViewStyle(.page)
        .overlay(DisableSwipeTabView())
    }
}

Resultado:

  • Las páginas siguen existiendo
  • El gesto de swipe queda completamente bloqueado
  • Puedes controlar la navegación con botones

Añadiendo navegación por botones

Ahora creamos navegación manual:

struct ContentView: View {
    @State private var index = 0

    var body: some View {
        VStack {
            TabView(selection: $index) {
                Text("Paso 1").tag(0)
                Text("Paso 2").tag(1)
                Text("Paso 3").tag(2)
            }
            .tabViewStyle(.page)
            .overlay(DisableSwipeTabView())

            HStack {
                Button("Anterior") {
                    if index > 0 { index -= 1 }
                }

                Button("Siguiente") {
                    if index < 2 { index += 1 }
                }
            }
        }
    }
}

Ahora tienes un sistema de páginas sin swipe.


Ventajas de este enfoque

  • Control total de la navegación
  • No hay animaciones de swipe no deseadas
  • Compatible con iOS moderno
  • Ideal para onboarding, formularios o flujos guiados

Posibles problemas

  1. Cambios internos de SwiftUI
    Apple puede cambiar la estructura interna del TabView.
  2. Uso de UIKit
    Aunque SwiftUI es declarativo, algunas soluciones aún requieren UIKit.
  3. Testing
    Debes probar en varios dispositivos.

Casos de uso reales

  • Onboarding de apps
  • Tutoriales interactivos
  • Apps de cursos
  • Formularios multipaso
  • Juegos por niveles

Conclusión

Desactivar el gesto de swipe en un TabView en SwiftUI no es algo que Apple proporcione directamente, pero con un poco de creatividad y entendiendo cómo funciona SwiftUI internamente, podemos lograr un control total de la navegación.

Usando un pequeño puente hacia UIKit, podemos desactivar el UIScrollView que SwiftUI utiliza internamente y crear experiencias mucho más controladas, profesionales y adaptadas a nuestras necesidades.

Si estás construyendo una app en Xcode donde el flujo de navegación debe ser preciso, esta técnica te permitirá elevar la calidad de tu interfaz y evitar comportamientos no deseados.

Si tienes cualquier duda sobre este artículo, contacta conmigo y estaré encantado de ayudarte 🙂. Puedes contactar conmigo en mi perfil de X o en mi perfil de Instagram

Leave a Reply

Your email address will not be published. Required fields are marked *

Previous Article

Extensions en Swift - Tutorial con Ejemplos

Next Article

Guía Definitiva de Componentes en SwiftUI y sus librerías

Related Posts