Hace unos días comencé a trabajar en un proyecto personal que, por motivos que comentare en otro post, espero, decidí desarrollarlo de forma nativa para iOS (ademas de su versión web, claro)
Por suerte para mí, en 2019, Apple anuncio SwiftUI, un framework que, en palabras de Apple:
"Ofrece vistas, controles y estructuras de diseño para declarar la interface de tus applicaciones".
Una definición, que para los que venimos del mundo web, nos puede sonar a la de React (y bueno, de varios otros frameworks tambien).
Lo cierto es que SwiftUI toma mucho de React y, en esencia, es muy parecido. Claro que, adaptado al ecosistema Apple y ofreciendo una experiencia muy similar a lo que, en el mundo web frontend, seria similar a usar React + Mobx + Storybook + algun "design system" (en este caso, el design system de Apple).
Comenzando
Comenzar con SwiftUI es bastante simple (obviamente, es requerimiento contar con macOS, a diferencia de la web, el ecosistema de Apple no esta pensado para ser abierto):
- Abrimos Xcode
- Seleccionamos "Create a new Xcode project" / "Crear nuevo proyecto de Xcode"
- Elegimos como template "Single View App"
- Llenamos un par de datos y, muy importante, seleccionamos "User Interface: SwiftUI"
Xcode inicializa el proyecto y vemos su pantalla principal y varios archivos creados. Por defecto, tendremos abierto "ContentView.swift", nuestra primera vista de SwiftUI.
import SwiftUI
struct ContentView: View {
var body: some View {
Text("Hello, World!")
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Esto ya podemos compilarlo y correrlo en un simulador o incluso en un dispositivo iOS/iPadOS/macOS. Ademas veremos a la derecha o debajo del editor, dependiendo del tamaño de la ventana, un preview de nuestra aplicación/vista (seguramente haya que hacer click en "resume" para comenzar a ver algo)
Pero veamos un poco que esta pasando acá.
La primer linea import SwiftUI
es bastante clara, incluye dicha librería/framework.
Luego vemos un struct
llamado ContentView
que implementa un protocol
de nombre View
:
protocolos, clases estructuras, referencias, etc
Vamos por lo más sencillo: un protocol
es, ni más ni menos, lo que en Typescript o Java es una interface
. Es decir, un contrato, estamos diciendo que nuestra struct
tiene una serie de atributos específicos. Javascript no posee tipos, por tanto no tenemos un equivalente directo más allá de usar un objeto y "confiar" (o chequearlo en runtime) que tendrá un determinado método o propiedad.
Sigamos por el struct
: Esto equivale a una class
en Javascript y una instancia de este struct
equivaldría a un objeto. Pero hay una pequeña trampa.
En Swift las instancias de structs
Siempre se pasan por valor.
¿Que quiere decir eso? que si pasamos nuestro objeto, mediante una llamada a una función o una asignación, este objeto se copiará y la función recibirá una nueva copia del mismo.
En Javascript, los objetos siempre se pasan por referencia, es decir, lo que pasamos en realidad es un puntero al espacio de memoria del objeto y no el objeto en sí.
Veamos:
let user = {
name: "Pablo"
}
let anotherUser = user
anotherUser.name = "Juan"
console.log(user.name) // "Juan"
console.log(anotherUser.name) // "Juan"
Mientras que en Swift:
struct User {
var name: String
}
var user = User(name: "Pablo")
var anotherUser = user
anotherUser.name = "Juan"
print(user.name) // "Pablo"
print(anotherUser.name) // "Juan"
Si bien no esta presente en el código que estamos analizando, Swift posee class
, que podriamos decir es lo mismo que un struct
pero cuyos valores son pasados por referencia (sí, de la misma forma que ocurre en Javascript). La sintaxis es prácticamente la misma y el ejemplo anterior podemos verlo simplemente reemplazando struct
por class
.
class User {
public var name: String
init(name: String) {
self.name = name
}
}
var user = User(name: "Pablo")
var anotherUser = user
anotherUser.name = "Juan"
print(user.name) // "Juan"
print(anotherUser.name) // "Juan"
Como puede verse, también tuve que hacer dos cosas: especificar que el atributo name
es publico (en las clases por defecto son privados) y definir un constructor (sí, el metodo init
es similar al construct
de las class
de Javascript).
Pero volvamos al código inicial de SwiftUI. Como unica propiedad de este struct
, tenemos a body
. En este caso, los ":" (de var body: some view
) nos indica el tipo de body
... some View
.
Esto podríamos leerlo literalmente: body es "alguna" View, no importa cual.
Nuevamente, en Javascript no tenemos nada parecido, porque no tenemos tipos. Pero pensando en Typescript o Java, podriamos preguntarnos: ¿Cual es la diferencia entre some View
o directamente View
siendo que View
es un protocol
?
La respuesta, es que some View
es más similar a un tipo genérico (o generics). Al especificar some View
estamos diciendo que esa variable es de un tipo especifico de View, no cualquier View.
Por ejemplo, el siguiente ejemplo es invalido:
protocol Greeter {
func greet() -> String
}
class Person: Greeter {
func greet() -> String {
return "Hello"
}
}
class User: Greeter {
func greet() -> String {
return "Howdy!"
}
}
func test(a: Int) -> some Greeter {
if a > 5 {
return User()
}
return Person()
}
En este caso, test
intenta retornar un User
o un Person
, ambos implementan Greeter
, pero al especificar que test
retorna some Greeter
, estamos diciendo que retorna un tipo especifico (en el ejemplo, podría ser un User
o un Person
, pero no ambos.
Si borramos la palabra some
, el ejemplo compila correctamente.
Computed properties
Pero body
sigue sorprendiendo, ya que directamente abre una llave que encierra un bloque de código.
Esto es lo que Swift llama "Computed properties", equivalentes a los getter y setters de Javascript. En este caso, al no especificar como asignar un nuevo valor a body, es simplemente un getter.
struct Person {
var name: String
var yearOfBirth: Int
var age: Int {
2020 - yearOfBirth
}
}
var p = Person(name: "Pablo", yearOfBirth: 1987)
print(p.age) // 33
Lo que esta dentro de las llaves es simplemente una función. A Swift le encanta eliminar código redundante, por lo que en funciones de una sola linea, el resultado de dicha expresión es retornado (si hubiese más lineas, debería poner return 2020 - yearOfBirth
).
Por último (¡al fin!), body
retorna Text("Hello world")
. Si hacemos "option + click" en Text, veremos que es una struct
que implementa View (como era de esperarse dado que body
es de tipo some View
).
Views y Components
Podríamos decir que Text("Hello world")
es un componente, como los componentes de React. SwiftUI sabe exactamente como mostrarlo, con que estilo y en que posición. De igual forma, existen varios componentes con distintos fines. Por ejemplo, encerremos nuestro Hello World en un List
struct ContentView: View {
var body: some View {
List {
Text("Hello, World!")
}
}
}
y veremos como cambió nuestra aplicación.
También podríamos hacer nuestro Texto "clickeable" usando un Button
.
struct ContentView: View {
var body: some View {
List {
Button(action: {
print("Hi!")
}) {
Text("Hello, World!")
}
}
}
}
Ahora, cada vez que clickeemos (o tapeemos) nuestro texto, veremos "Hi!" en la consola de debug.
Casi todas las vistas tienen métodos para cambiar sus estilos. Por ejemplo, podemos hacer Text(...).fontWeight(.bold)
para mostrar el texto en negrita.
Curiosidades varias
Etiquetas de parametros
Como habrás visto, al llamar una función en Swift, los parámetros indican el nombre. Swift permite definir etiquetas
a los parámetros e incluso definir nombres distintos para la llamada y la implementación:
getAvatar(for: "Pablo")
func getAvatar(for user: String) {
// ...
// user -> "Pablo"
}
Si en la definición omito el for
, tendria que llamar getAvatar(user: "Pablo")
.
Funciones como último parametro
No se exactamente como se llama esto, pero algo curioso de Swift y que hizo que inicialmente me cueste leer el código, es el caso de Button
más arriba:
Button(action: {
print("Hi!")
}) {
Text("Hello, World!")
}
¿Que es exactamente lo que rodea a Text
?
Al igual que en javascript, Swift permite pasar funciones como valores y por tanto nuestras funciones pueden aceptar funciones como parámetros. Lo curioso, es que en aquellos casos que el último parámetro de la función sea otra función, podemos usar las llaves por fuera del paréntesis.
Como siempre, mejor un ejemplo:
func doSomething(value: Int, method: (Int) -> ()) {
method(value)
}
doSomething(value: 5) { val in
print(val) // 5
}
Tambien podriamos llamar a doSomething siendo explicitos:
doSomething(value: 5, method: { val in
print(val) // 5
})
Conclusión
SwiftUI parte de una premisa muy similar a la de React: nuestra vista es una función de nuestro estado y las vistas se componen de otras vistas (De igual manera que los componentes se componen de otros componentes). Hacer una transición podría decirse que es relativamente sencilla y podemos re-usar mucho de dicho mundo.
Ademas, Xcode nos dara la suficiente ayuda para entender fácilmente que Views y que modificadores tenemos disponibles para armar nuestra aplicación. (cuando no falla, hay que decirlo).
Si llegaste hasta acá, te recomiendo que mires la introducción oficial de Apple a SwiftUI que seguramente es mucho más clara y extensa que este articulo: https://developer.apple.com/videos/play/wwdc2019/216/
Más adelante veremos como manejar estado dentro de SwiftUI usando el framework "Combine" de Apple (que, siguiendo con analogías, podríamos decir es similar a Mobx)
Si te interesa el desarrollo web y/o apps, ¡hablemos! Podes seguirme en twitter como @tehsis
Top comments (0)