Ya ha pasado algún tiempo desde que Apple presentara en la WWDC19 su framework para el desarrollo de la interfaz de usuario: SwiftUI.

Desde entonces, la información sobre el mismo ha crecido de manera exponencial (libros, blogs, podcasts…), lo que demuestra el gran interés que ha despertado en la comunidad y nos hace plantearnos a los desarrolladores si ha llegado el momento de realizar el cambio desde los actuales frameworks de iOS y macOS (UIKit y AppKit, respectivamente).

En este artículo vamos a hacer una introducción a SwiftUI y conocer las principales características de esta librería.

Programación imperativa vs. declarativa

Hace ya más de 30 años que NeXT, empresa creada en 1985 por el fundador de Apple, Steve Jobs, lanzó el código y las librerías necesarias para el desarrollo de apps y el uso de componentes gráficos en ordenadores. Pues bien, el uso de estas librerías y la forma en la que creamos apps hoy en día se basa en la misma arquitectura de desarrollo de entonces: arrastrar componentes gráficos al “Interface Builder” y conectarlos mediante outlets al código. Esta forma de trabajar es conocida como construcción de interfaces imperativas.

Mediante la programación imperativa le decimos al código cómo tiene que realizar cada uno de los pasos utilizando variables, sentencias y bucles. Es decir, construimos un algoritmo que se resolverá línea a línea.

El principal problema que nos encontramos en la programación imperativa es el control del “estado”. Si tenemos en cuenta que el estado es todo aquello que aparece en nuestra aplicación, y todas las posibilidades que tiene de cambiar junto con su entorno (por ejemplo para un botón, si está oculto o no, el color, el texto que aparece en él en función de otras propiedades, etc.) nos damos cuenta de la complejidad que aporta cada elemento que añadamos a la interfaz.

En cambio, mediante la programación declarativa, que es en la que se basa SwiftUI, se declara de forma abstracta las tareas que queremos realizar y el lenguaje se encarga de cómo hacerlo. Ahora, cualquier posible cambio de estado estará definido con anticipación, y la interfaz sabrá cómo responder ante dichos cambios. Todo lo demás será inmutable. Para hacerlo se apoya en la programación funcional.

Gracias a esta forma de trabajo, conseguimos hacer aplicaciones más compactas (escribiendo menos código), reutilizables y seguras.

Pero la programación declarativa no es algo nuevo, surgió en los años 90 con lenguajes de maquetación como el HTML. Por lo tanto, Apple no ha inventado nada, SwiftUI simplemente supone un cambio natural en el desarrollo de aplicaciones. De hecho, muchas librerías populares hoy en día (como Flutter o React) también utilizan interfaces declarativas.

Principales property wrappers en SwiftUI

Cuando se lanzó SwiftUI se incluyeron los llamados “property wrappers” en el lenguaje Swift, un tipo de propiedades que sirven para añadir alguna funcionalidad extra a la propiedad sobre la que actúan.

SwiftUI hace uso de varias de estas propiedades, veamos las principales:

@State

Indica al sistema que esta propiedad es un valor que cambia a lo largo del tiempo y que las vistas dependen de ese valor. Cualquier cambio en esta propiedad desencadena una recarga de la vista que la contiene para actualizar su estado. Además, esto provocará que se recarguen todas las vistas hijas de la principal (la que contiene la propiedad @State).

@State var isEnabled: Bool = true

@Binding

Indica al sistema que dicha propiedad tiene acceso de lectura y escritura al valor de la misma, pero sin ser su propietario.

@Binding var counter: Int

La vista padre pasará a la vista hija una referencia a dicha propiedad en una variable precedida con el símbolo “$”, lo que indicará que el valor será devuelto en el caso de sea modificado en la vista hija (binding).

import SwiftUI

struct ParentView: View {
    @State var counter: Int = 0

    var body: some View {
        ChildView(value: $counter)
    }
}

struct ChildView: View {
    @Binding var value: Int

    var body: some View {
        VStack {
            Text(“Value: \(value)”)
            Button(action: { self.value += 1 }) {
                Text(“Increase value”)
            }
        }
    }
}

struct ParentView_Previews: PreviewProvider {
    static var previews: some View {
        ParentView()
    }
}

Si lo ejecutamos en el Xcode, se mostrará en pantalla lo siguiente:

Como vemos, la propiedad @Binding de la vista hija tiene una referencia a la propiedad @State de la vista padre. De modo que cualquier cambio en la vista hija desencadenará a su vez un cambio y actualización del valor en la propiedad @State de la padre y, lo mismo a la inversa, un cambio en el padre supondrá el cambio del valor de la vista hija.


@ObservedObject

Utilizaremos esta propiedad cuando queramos crear nuestro propio modelo de datos y observar cambios en él. Para ello, crearemos una nueva clase que conformará el protocolo “ObservableObject”.

Aquella propiedad de la que queremos observar sus cambios, le añadimos el property wrapper @Published.

De vuelta a la vista, crearemos una propiedad con @ObservedObject, que será del tipo de la clase que hemos creado en el paso anterior y se le notificarán los cambios que se hayan producido en el modelo de datos.

import SwiftUI

final class SecondsCounter: ObservableObject {
    @Published var value: Int = 0
    let interval: TimeInterval = 1
    private var timer: Timer? = nil

    func start() {
        guard timer == nil else { return }
        timer = Timer.scheduledTimer(withTimeInterval: interval, repeats: true) { [weak self] _ in
            self?.value += 1
        }
    }

    func stop() {
        timer?.invalidate()
        timer = nil
    }

    deinit {
        stop()
    }
}

struct TimerView: View {
    @ObservedObject var counter: SecondsCounter = SecondsCounter()
    var body: some View {
        Text(“\(counter.value) seconds”)
            .onAppear { self.counter.start() }
            .onDisappear { self.counter.stop() }
    }
}

struct TimerView_Previews: PreviewProvider {
    static var previews: some View {
        TimerView()
    }
}

struct TimerView_Previews: PreviewProvider {
    static var previews: some View {
        TimerView()
    }
}

Si ejecutamos este código, se mostrará en pantalla un temporizador que va incrementando los segundos:

@Environment y @EnvironmentObject

Esta combinación de property wrappers se utiliza para observar valores u objetos del entorno. Es decir, cuando queremos tener un modelo o propiedad accesible desde cualquier lugar de la aplicación y estar informados de cuándo se produce un cambio en el mismo.

Por ejemplo, podemos utilizar una propiedad de este tipo para establecer los colores que utilizaremos por defecto en la aplicación, en función de si el dispositivo está configurado en modo oscuro o modo normal.

Entendiendo las vistas en SwiftUI

Para desarrollar con SwifUI, ya no utilizaremos el Interface Builder, el AutoLayout, ni las conexiones IBOutlet-IBAction. Los storyboard son reemplazados por código, lo que permite crear vistas de una forma más modular. Como consecuencia, por fin desaparecerán los conflictos que se producían al modificar los storyboards o ficheros .xib por distintos miembros del equipo de desarrollo al mismo tiempo.

Al contrario de lo que sucedía hasta ahora, que las vistas se creaban como clases, en SwiftUI las vistas se crean a partir de structs, es decir, son tipos por valor.

Cada vista tendrá una estructura en forma de árbol. Como elemento principal tendremos un elemento de tipo View, que devolverá una propiedad llamada “body”, donde iremos añadiendo los distintos componentes para crear nuestra interfaz de usuario:

struct ContentView: View {
    var body: some View {
        […]
    }
}

Como elemento principal podremos tener desde un componente aislado (como un texto), hasta un contenedor con otros elementos anidados.

Podremos ir dando forma a estos componentes aplicando modificadores. Por ejemplo, podemos añadir a un componente de texto (Text) un modificador para la letra en negrita (.bold()), otro para el color del fondo (.background) y así sucesivamente.

A la hora de añadir estos modificadores hay que tener en cuenta que el orden en el que lo hagamos es importante y afectará al resultado final. Esto se debe a que, cada vez que utilizamos un nuevo modificador, este es aplicado sobre la capa que conforman todos los anteriores sobre el componente principal.

Veámoslo con un ejemplo. Observa cómo afecta el orden en que aplicamos el modificador de padding, que añade unos márgenes internos a la vista, y el de background, que modifica el color de fondo:

import SwiftUI

struct ModifiersView: View {
    var body: some View {
        VStack {
            Text(“Bienvenido a SwiftUI”)
                .bold()
                .padding()
                .background(Color.green)

            Text(“Bienvenido a SwiftUI”)
                .italic()
                .background(Color.blue)
                .padding()
        }
    }
}

A partir de aquí, podemos crear pantallas más o menos complejas en función de los componentes que anidemos: contenedores horizontales (HStack), verticales (VStack) o superpuestos (ZStack), listas (List), campos de texto (TextFields), botones (Button)… por nombrar solo algunos de ellos.

Algunas ventajas frente a los frameworks actuales

Conclusiones

Si bien es cierto que SwiftUI está soportado a partir de la versión de iOS 13, no cabe duda que el futuro del desarrollo para las plataformas de Apple pasa por desenvolverse y trabajar con este nuevo framework.

Como ya pasó con el lanzamiento del lenguaje de programación Swift en el año 2014, todo requiere su tiempo, y hasta que el propio lenguaje alcanzó la estabilidad binaria han tenido que pasar años de adaptaciones, modificaciones y cambios. Sin embargo, esto no ha supuesto ningún impedimento para que, en los últimos años, la mayoría de aplicaciones se hayan implementado utilizando exclusivamente Swift como lenguaje de desarrollo.

Para ir empezando a dar nuestros primeros pasos, Apple nos proporciona una serie de tutoriales para familiarizarnos con el uso de esta potente librería que ha venido para quedarse.

Y lo mejor es que SwiftUI no sólo sirve para desarrollar aplicaciones de iOS, también podremos crear aplicaciones para todo el ecosistema de Apple: iPad, Mac, Watch y Apple TV.

Cuéntanos qué te parece.

Los comentarios serán moderados. Serán visibles si aportan un argumento constructivo. Si no estás de acuerdo con algún punto, por favor, muestra tus opiniones de manera educada.

Suscríbete

Estamos comprometidos.

Tecnología, personas e impacto positivo.