SwiftUI – Cómo hacer una presentación modal

Qué es una presentación modal

Las presentaciones modales en iOS son usadas para mostrar pantallas que están fuera del contexto actual de pantallas que estamos mostrando. Para entender bien esto debemos saber que en iOS existen dos formas de mostrar una vista:

  • Apilada en el stack de navegación de NavigationView.
  • Presentada fuera del contexto de navegación (presentación modal).

Cada tipo de presentación tiene un propósito:

  • Cuando usamos un NavigationView todas las vistas sobre las que naveguemos tendrán un flujo definido que deberán pertenecer al mismo contexto. Un ejemplo sencillo puede ser cuando mostramos una aplicación de noticias. Podemos presentar una pantalla de categorías de noticias que dará paso a un listado, y éste a su vez permitirá ver la noticia completa en una nueva pantalla. Este tipo de navegación la realizaremos con un NavigationView.
  • Por otro lado, existen flujos de navegación que son independientes o que no forman parte del contexto en el que nos encontramos. Estas pantallas las presentaremos de forma modal con los modificadores sheet o fullScreenCover y nos permitirá saltar a un nuevo contexto que contenga su propia navegación si fuera necesario. Siguiendo con el ejemplo de noticias del caso anterior, podemos incluir un nuevo flujo que sugiera al usuario a realizar login antes de leer las noticias. Está pantalla de login estaría fuera del contexto de noticias por lo que debería ser presentada para generar un nuevo flujo y al terminar el login podamos continuar por donde lo dejamos.

En UIKit las presentaciones modales se realizaban con el método present que podíamos encontrar en cualquier UIViewController. En estas presentaciones también se podían modificar el tipo de la misma, lo que afectaba en la forma en la que se presentaban las pantallas. En SwiftUI este flujo ha cambiado y está más limitado, pudiendo sólo realizar dos tipos de presentación: sheet y fullScreenCover.

sheet

Es un modificador de View y permite presentar un nuevo View. Este tipo de presentación cambia su aspecto dependiendo del contexto, pero su función es presentar una vista sin estar a pantalla completa.

fullScreenCover (iOS 14)

Es un modificador de View y permite presentar un nuevo View. Este tipo de presentación presenta una vista a pantalla completa.

Cómo usamos sheet y fullScreenCover

Ambas implementaciones son exactamente iguales, simplemente tenemos que cambiar el modificador dependiendo del que queramos usar en cada caso.

Para mostrar una pantalla con estos modificadores de View tenemos que usarlo sobre una vista de nuestra pantalla. Estos modificadores necesitan una variable de estado de tipo Bool, que será la encargada de activar o desactivar la presentación.

@State var showingDetail = false

...

.sheet(isPresented: $showingDetail) {
    SheetDetailView(showingDetail: $showingDetail)
}

...

.fullScreenCover(isPresented: $showingDetail) {
    FullDetailView(showingDetail: $showingDetail)
}

Es importante pasar como parámetro la variable de estado encargada de realizar la presentación (showingDetail) para que podamos modificarla cuando queramos volver atrás.

Ejemplo completo usando sheet

struct ContentView: View {
    @State var showingDetail = false
    
    var body: some View {
        VStack(spacing: 15) {
            Text("Main Screen")
            Button(action: {
                showingDetail.toggle()
            }) {
                Text("Show Detail")
            }.sheet(isPresented: $showingDetail) {
                SheetDetailView(showingDetail: $showingDetail)
            }
        }
        .frame(maxWidth: .infinity, maxHeight: .infinity)
        .background(Color.yellow.opacity(0.3))
    }
}

struct SheetDetailView: View {
    @Binding var showingDetail: Bool
    
    var body: some View {
        VStack(spacing: 15) {
            Text("Detail Screen")
            Button(action: {
                showingDetail.toggle()
            }) {
                Text("Back")
            }
        }
        .frame(maxWidth: .infinity, maxHeight: .infinity)
        .background(Color.red.opacity(0.3))
    }
}

Ejemplo completo usando fullScreenCover

struct ContentView: View {
    @State var showingDetail = false
    
    var body: some View {
        VStack(spacing: 15) {
            Text("Main Screen")
            Button(action: {
                showingDetail.toggle()
            }) {
                Text("Show Detail")
            }.fullScreenCover(isPresented: $showingDetail) {
                FullDetailView(showingDetail: $showingDetail)
            }
        }
        .frame(maxWidth: .infinity, maxHeight: .infinity)
        .background(Color.yellow.opacity(0.3))
    }
}

struct FullDetailView: View {
    @Binding var showingDetail: Bool
    
    var body: some View {
        VStack(spacing: 15) {
            Text("Detail Screen")
            Button(action: {
                showingDetail.toggle()
            }) {
                Text("Back")
            }
        }
        .frame(maxWidth: .infinity, maxHeight: .infinity)
        .background(Color.red.opacity(0.3))
    }
}

En ambos ejemplos estamos pasando a la pantalla de detalle la variable de estado showingDetail que inicia la presentación para poder hacer el dismiss de la misma. Podríamos usar la variable @Environment(.presentationMode) para realizar esta misma acción y no necesitaríamos pasar la variable showingDetailMás información.

Ejemplo

Puedes encontrar este ejemplo en el Github de SDOS bajo el apartado Present.

Rafael Fernández,
iOS Tech Lider