Skip to content

Cómo crear un sidebar en iPadOS y macOS utilizando SwiftUI

26 junio, 2020

Cuando diseñamos apps para pantallas más grandes, como por ejemplo iPad, Mac o algunos iPhone en modo «landscape», se debe aprovechar el espacio de la pantalla de una manera eficiente y mostrar más información en ella.

Para ello, lo más habitual es sustituir el TabBar por una pantalla con dos (o tres) columnas:

Cada columna suele recibir un nombre, que varía en función de si la pantalla tiene dos o tres columnas.

Para aquellas con tres columnas:

  • Primary: es la columna a la izquierda, la primera.
  • Supplementary: es la columna del medio
  • Secondary: es la columna a la derecha, la última.

En aquellas con solamente dos columnas, éstas reciben el nombre de Primary (la primera) y Secondary (la segunda).

En UIKit esto se puede hacer utilizando un UISplitViewController y utilizando delegados, pero… ¿Cómo se puede hacer en SwiftUI?

Crear un «SplitViewController» en SwiftUI:

En SwiftUI, para crear una interfaz con dos o tres columnas se hace añadiendo una segunda (o tercera) vista a un NavigationView.

Si queremos dos columnas:

struct ContentView: View {
    var body: some View {
        PrimaryView()
        SecondaryView()
    }
}

Si quieres que haya una tercera columna, basta con añadir otra vista más:

struct ContentView: View {
    var body: some View {
        PrimaryView()
        SupplementaryView()
        SecondaryView()
    }
}

Creando la Primary View: un sidebar en SwiftUI

Imagina que estamos creando la app de este blog, y queremos dividirla en tres columnas: categorías, posts pertenecientes a la categoría seleccionada, y por último el post seleccionado.

La primera columna debería ser, según las guías de estilo, una lista en formato sidebar. Para ello, basta con aplicarle el modificador .listStyle() al componente List de SwiftUI:

struct CategoSidebarrView: View {
    let categories = CategoryRepository.all
    
    var body: some View {
        List(categories) { category in
            // 1
            NavigationLink(
                destination: PostListView(posts: category.posts),
                label: {
                    CategoryRow(category: category)
                })
        }
        .listStyle(SidebarListStyle()) // 2
        .navigationBarTitle("Categories")
    }
}
  • // 1: Creamos un NavigationLink para navegar a la lista de artículos perteneciente a la categoría seleccionada
  • // 2: Aplicamos el modificador .listStyle() al componente de la lista, pasándole el estilo deseado: SidebarListStyle()

Esto hará que, dependiendo de la plataforma y del tamaño disponible de la pantalla, SwiftUI aplique el estilo correspondiente para los sidebars.

Creando la Supplementary y Secondary View:

Según las guías de estilo, si la supplementary view es una lista de elementos, el estilo ideal para esta vista es un PlainListStyle() (es el estilo por defecto, así que no hay que especificarlo explícitamente). En este ejemplo la supplementary view será una lista de Posts:

struct PostListView: View {
    let posts: [Post]
    
    var body: some View {
        List(posts) { post in
            NavigationLink(destination: URLView(url: post.url)) {
                Image(systemName: "safari")
                    .resizable()
                    .frame(width: 20, height: 20)

                Text(post.title)
                    .font(.body)
            }
        }
        .listStyle(PlainListStyle())
        .navigationBarTitle("Posts")
    }
}

Por último, la secondary view sería el propio artículo. Por simplificar el tutorial, simplemente mostraremos la URL del artículo sobre un color de fondo aleatorio:

struct URLView: View {
    let url: URL
    var body: some View {
        Label(url.absoluteString, systemImage: "heart.fill")
            .frame(maxWidth: .infinity, maxHeight: .infinity)
            .foregroundColor(.white)
            .font(.headline)
            .padding(20)
            .background(
                Color(
                    red: .random(in: 0.5...1), 
                    green: .random(in: 0.5...1), 
                    blue: .random(in: 0.5...1)
                )
            )
            .cornerRadius(8)
            .padding(20)
            .navigationBarTitle("URL")
    }
}

Combinando las tres vistas en un SidebarNavigation View:

Por último, es momento de crear la vista de la navegación para pantallas grandes. Crea un nuevo fichero llamado SidebarNavigation:

struct SidebarNavigation: View {
    var body: some View {
        NavigationView {
            CategorySidebarView()  // 1
            PostListView(posts: PostRepository.uiKitPosts)  // 2
            URLView(url: PostRepository.uiKitPosts[0].url)  // 3
        }
    }
}
  • // 1: Asignamos CategorySidebarView como la columna primary.
  • // 2: Asignamos PostListView como la columna supplementary. Al iniciar la app, se verá la lista de post de la categoría de UIKit.
  • // 3: Asignamos URLView como la columna secondary. Al iniciar la app, se verá la URL del primer post de UIKit.

Cambiar la vista principal de la app:

Abre la vista ContentView y utiliza el SidebarNavigation:

struct ContentView: View {

    var body: some View {
        SidebarNavigation()
    }
}

Ejecuta la en un simulador de iPad y verás las diferentes columnas:

Sidebar SwiftUI

Mejorando la usabilidad de la app:

La guía de estilo dice que en tamaños más pequeños de pantalla, por ejemplo iPhones en portrait o incluso cuando se utiliza el iPad en pantalla dividida, debemos sustituir la navegación de columnas por una navegación de tipo TabBar.

¿Cómo podemos hacer esto en SwiftUI?

Paso 1: Crear un TabNavigation View:

En este caso, vamos a sustituir la navegación en formato columna de las categorías y los posts por una navegación en formato TabBar. Para ello, crea un nuevo fichero TabNavigation:

struct TabNavigation: View {
    var body: some View {
        TabView {
            // Categories tab
            NavigationView {
                CategorySidebarView()
            }
            .tabItem {
                Image(systemName: "list.dash")
                Text("Categories")
            }

            // Posts tab            
            NavigationView {
                PostListView(posts: PostRepository.uiKitPosts)
            }
            .tabItem {
                Image(systemName: "pencil.slash")
                Text("Post")
            }
        }
    }
}

Paso 2: Cambiar el tipo de navegación cuando la pantalla sea más pequeña:

No se debe de diseñar la interfaz de la app basándose en si se va a utilizar en un iPad o en un iPhone, si no en el tamaño de pantalla disponible. Recuerda, el tamaño de pantalla disponible de una aplicación en un iPad puede cambiar cuando se utiliza en pantalla dividida.

Por ello, lo ideal es diseñarlo según el tamaño de pantalla disponible. Para poder «leer» el tamaño de pantalla, los desarrolladores utilizamos las SizeClasses. Existen dos tipos de SizeClasses:

  • regular para referirse al tamaño de pantalla «grande», con espacio suficiente para mostrar más información en la pantalla.
  • compact: para referirse al tamaño de pantalla «pequeño», aquel que no permite mostrar tanta información en la pantalla.

Para implementar esto en la app, tenemos que añadir una propiedad horizontalSizeClass a la vista ContentView:

struct ContentView: View {
    #if os(iOS)
    @Environment(\.horizontalSizeClass) private var horizontalSizeClass
    #endif

    var body: some View {
        SidebarNavigation()
    }
}

En macOS, no tenemos SizeClasses, así que utilizamos la macro #if os(iOS) para añadir esta propiedad solamente cuando se trate de aplicaciones iOS o iPadOS.

Ahora modificamos la propiedad body para comprobar el valor de esta propiedad, y devolver TabNavigation o SidebarNavigation cuando corresponda:

struct ContentView: View {
    #if os(iOS)
    @Environment(\.horizontalSizeClass) private var horizontalSizeClass
    #endif
    
    var body: some View {
        #if os(iOS)
        if horizontalSizeClass == .compact {
            return TabNavigation()
        } else {
            return SidebarNavigation()
        }
        #else // macOS
        return SidebarView()
        #endif
    }
}   

Por último, haremos uso de otro PropertyWrapper para evitar tener que utilizar la keyword return en SwiftUI: @ViewBuilder

struct ContentView: View {
    #if os(iOS)
    @Environment(\.horizontalSizeClass) private var horizontalSizeClass
    #endif
    
    @ViewBuilder var body: some View {
        #if os(iOS)
        if horizontalSizeClass == .compact {
            TabNavigation()
        } else {
            SidebarNavigation()
        }
        #else
        SidebarView()
        #endif
    }
}
Cambiar a TabView en compact size class
Atajos Xcode

Descarga GRATIS la chuleta de atajos de Xcode

He creado una chuleta en PDF con los 35 shortcuts de Xcode que todo iOS developer debe conocer.  Tenlos todos juntos y a mano para poder consultarlos en cualquier momento 🙂 

Déjame tus datos y te la enviaré a tu correo electrónico. 

Conclusión

Nunca había sido tan fácil crear aplicaciones universales, tanto para iOS, iPadOs y ahora también macOS. Está claro que SwiftUI es el futuro de la creación de interfaces para sistemas Apple, pero UIKit seguirá vivo durante muchos años todavía.

¿Te gustaría que escribiese el mismo tutorial pero utilizando UIKit?

¿Qué te ha parecido el artículo? Déjame tu feedback en los comentarios y lo leeré encantado 😃

👇👇👇


¿Me ayudas a compartir en redes sociales?