Dynamic Size Class

Si queremos incluir accesibilidad en nuestra app permitiendo que el tamaño del texto cambie si el usuario lo tiene así configurado (en Ajustes → Accesibilidad → Pantalla y tamaño del texto → Texto más grande), tenemos a nuestra disposición una variable de entorno (@Environment) llamada dynamicTypeSize.   

El dynamic type size es “Un tamaño de tipo dinámico, que especifica qué tan grande debe ser el contenido que ha sido definido como escalable”.

@Environment(\.dynamicTypeSize) private var dynamicTypeSize: DynamicTypeSize

Disponible a partir de iOS 15

De esta propiedad podemos obtener el tamaño de letra que tiene elegido el usuario y una propiedad tipo Bool isAccesibilitySize que nos indica si es un tamaño de accesibilidad grande, desde AX1 hasta AX5.

Con ello podemos adaptar nuestra vista intercambiando un HStack por un VStack según sea el caso. Tenemos un struct llamado AnyLayout

AnyLayout
Es una instancia que borra el tipo del protocolo Layout.

Disponible a partir de iOS 16

AnyLayout recibe un tipo de dato que se conforme con el protocolo Layout, como lo hacen HStackLayout, VStackLayout y ZStackLayout.

Layout
Es un protocolo que define la geometría de la colección de vistas.

Con el cual a través de AnyLayout podemos devolver tanto un HStackLayout, un VStackLayout o un ZStackLayout, cada uno con sus propios parámetros.

 var dynamicLayout: AnyLayout {<br>    dynamicTypeSize.isAccessibilitySize ?        AnyLayout(VStackLayout(alignment:.leading)) : AnyLayout(HStackLayout(alignment: .lastTextBaseline))<br> }

Si el usuario tiene seleccionado un tamaño accesible (Accessibility Size), podemos configurar nuestra vista para que se adapte mejor a la pantalla, pasando de un HStack a un VStack automáticamente.

//..
         VStack {
            Text("iOS 16")
            dynamicLayout {
                Label("Favourite", systemImage: "heart")
                    .foregroundStyle(.red)
                Label("Done", systemImage: "checkmark")
                    .foregroundStyle(.blue)
                Label("Tag", systemImage: "tag")
                    .foregroundStyle(.indigo)
            }
        }
        .labelStyle(.titleAndIcon)
//..

Para versiones con iOS 15 tenemos que construir nuestro propio AutoLayout.

He creado un struct que alterna VStack y HStack en función a un parámetro de tipo Bool.

struct AutoLayout15<Content: View>: View {
    @ViewBuilder let content: Content
    let vertical: Bool
    var hStackAlignment: VerticalAlignment
    var vStackAlignment: HorizontalAlignment
    
    init(_ vertical: Bool = true,
         hStackAlignment: VerticalAlignment = .top,
         vStackAlignment: HorizontalAlignment = .leading,
         @ViewBuilder content: () -> Content) {
        self.content = content()
        self.vertical = vertical
        self.hStackAlignment = hStackAlignment
        self.vStackAlignment = vStackAlignment
    }
    
    var body: some View {
        if vertical {
            VStack(alignment: vStackAlignment){
                content
            }
        } else {
            HStack(alignment: hStackAlignment) {
                content
            }
        }
    }
}

Lo utilizaremos con la misma variable calculada dynamicLayout de tipo Bool, que será uno de los parámetros de nuestro struct, en el closure del ViewBuilder añadiremos las vistas que necesitamos en ese caso los Label.

//..
       VStack {
            Text("iOS 15")
            AutoLayout15(dynamicLayout,
            hStackAlignment: .bottom, 
            vStackAlignment: .trailing) {
                Label("Favourite", systemImage: "heart")
                    .foregroundStyle(.red)
                Label("Done", systemImage: "checkmark")
                    .foregroundStyle(.blue)
                Label("Tag", systemImage: "tag")
                    .foregroundStyle(.indigo)
            }
        }
        .labelStyle(.titleAndIcon)
//..        

ScaleMetric

Otra propiedad que necesitaremos para la accesibilidad es @ScaleMetric, que es una propiedad dinámica que escala un valor Double.

 @ScaledMetric var imageWidth = 125.0

@ScaleMetric
Propiedad dinámica que escala un valor numérico.

Disponible desde iOS 14

Esta propiedad nos permite asignar un valor a un frame para, por ejemplo, cambiar el tamaño de una imagen, si es vectorial mucho mejor.

Definimos la propiedad y su tamaño por defecto y la añadimos como parámetro en el modificador frame

//..
             Image("lock")
                .resizable()
                .aspectRatio(contentMode: .fit)
                .frame(width: imageWidth)
//..

Cuando el tamaño del dynamic type cambie, la imagen se adaptará.

Vertical y Horizontal Size Class

¿Y que pasa si queremos también que nuestro contenido se adapte si la orientación es vertical u horizontal?

Para ello tenemos las propiedades de entorno verticalSizeClass y horizontalSizeClass de las cuales podemos obtener los valores regular y/o compact.

Las cuales podemos combinar para utilizar en nuestro AutoLayout15.

struct DynamicSize: View {
    @Environment(\.horizontalSizeClass) private var horizontal
    @Environment(\.verticalSizeClass) private var vertical
    var body: some View {
            AutoLayout15(horizontal == .compact && vertical == .regular) {
                DynamicSize15()
                DynamicSize16()
            }
    }
}

Para más información os recomiendo el vídeo de la WWDC24
Get started with Dynamic Type


¿Quieres recibir posts, cheatCodes, enlaces y katas en Swift para practicar?

Quincenalmente recibirás en tu correo electrónico la newsletter, solo hace falta tu correo electrónico.