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.