SwiftUI en Playgrounds para MacOS

En el artículo anterior se explicó cómo utilizar Playgrounds para MacOS y creamos nuestro primer Playground en el que saludábamos a todo el mundo.

En este artículo seguiremos saludando a todo el mundo pero con SwiftUI.

¿Qué es SwiftUI?

SwiftUI es la nueva librería de Apple para crear interfaces de usuario de forma declarativa. Esto implica que a diferencia de AppKit o UIKit, las librerías anteriores para crear interfaces de usuario para MacOS e iOS, el que sea declarativa facilita muchísimo la creación de estas interfaces.

En SwiftUI todos los elementos visibles se denominan vistas. Ventanas, botones, etiquetas de texto o casillas de verificación son vistas. Estas vistas se codifican como structs en Swift en la que cada struct tiene un cuerpo (body) en el que se declara cómo se verá la vista o qué vistas serán contenidas por esa vista que estamos declarando.

El código

A continuación se muestra el código para nuestro Playground.

import PlaygroundSupport
import SwiftUI

struct ContentView: View {
var body: some View {
Text("¡Hola mundo!")
}
}

PlaygroundPage.current.setLiveView(ContentView())

Lo más importante de este código para Playgrounds es la primera línea, en la que importamos un módulo para realizar operaciones para Playgrounds y la última línea donde indicamos al controlador de páginas de Playground qué vista de SwiftUI queremos mostrar en la pantalla.

El resto del código es SwiftUI estándar.

Conclusión

Con este simple Playground se nos abre la posibilidad de poder empezar a aprender SwiftUI y crear nuestras propias interfaces de usuario.

Aprende y practica Swift en tu Mac con Swift playgrounds

El mundo de la programación es cada vez más accesible y necesario para todas las personas. Por esa razón aparecen aplicaciones para que todo el mundo pueda aprender y practicar. Apple nos ofrece Swift playgrounds tanto para Mac como para iPad para que podamos aprender y practicar el lenguaje de programación Swift.

Swift playgrounds para Mac

En este artículo veremos la versión de Swift playgrounds para MacOS.

Esta aplicación resulta bastante accesible siempre y cuando sigamos unas reglas y entendamos que algunos cursos generados por otros desarrolladores son muy visuales y quedan fuera de la accesibilidad propuesta por Apple.

Su instalación es muy sencilla desde la Mac Appstore.

Abriendo Swift playgrounds

Nada más abrir Swift playgrounds nos encontramos que la aplicación habla de áreas de juego, es la traducción de playgrounds.

En la pantalla principal encontramos un área llamada Mis áreas de juegos, donde se alojarán los cursos y pruebas que tengamos descargados o disponibles para descargar. 

La zona de más áreas de juegos nos permite ver una galería de cursos, lecciones y experimentos que podemos descargar y seguir.

Creando nuestro primer programa en Swift

Una cosa que nunca debemos olvidar es que las aplicaciones para MacOS tienen un menú en la barra de menú que nos dará acceso a muchas de las opciones de la aplicación.

Vamos a crear un playground para poder escribir nuestro primer programa en Swift.

Abrimos el menú Archivo de la barra de menú y bajamos hasta la opción Nueva área de juegos en blanco. Los usuarios de VoiceOver deben pulsar Control+Opción+M y pulsar flecha a la derecha hasta llegar al menú Archivo. Luego pulsar flecha abajo hasta la opción de nueva area de juegos en blanco y pulsar enter.

Estamos en la ventana de creación y ejecución de un Playground. En esta ventana encontramos las siguientes zonas:

Una barra de herramientas con opciones para importar código de otros playgrounds y abrir o cerrar la barra lateral.

La barra lateral donde se nos da acceso a los módulos y páginas de un playground.

El editor de código donde estará el código fuente de nuestro programa.

Un botón de opciones de ejecución que desplegará un menú para elegir distintas opciones mientras se ejecuta nuestro código.

Un botón para ejecutar código.

Un botón para abrir la consola y ver los resultados de ejecutar nuestro código.

Por último un área donde se verán los resultados y que VoiceOver identifica erróneamente como una zona para introducir código.

Comenzamos a codificar

Es hora de ponernos manos a la obra y a programar se aprende programando así que vamos a crear nuestro primer programa en Swift.

Pinchamos en el editor de código, con VoiceOver nos colocamos sobre el editor de código y pulsamos Control+Opción+espacio. Si no hacemos esto con VoiceOver la edición de código no será del todo accesible para personas ciegas.

Al entrar en el editor debemos introducir el siguiente código:

print("¡Hola mundo!")

Tras esto debemos pulsar el botón de ejecutar código. Los usuarios de VoiceOver para salir del editor simplemente deben de dejar de interactuar con el editor, para ello se pulsa Control+Opción+Mayúsculas+flecha arriba.

Al pulsar el botón de ejecutar código no se aprecia que haya sucedido nada. Esto se debe a que la función print imprime un texto por la consola y la consola actualmente está oculta. Debemos pulsar el botón Abrir consola para ver el resultado.

Encontraremos un área llamada salida de la consola que mostrará el texto ¡Hola mundo!.

Volvemos a editar nuestro código y añadimos otra línea a nuestro código anterior dejando nuestro programa con el siguiente código:

print("Hola mundo!")
print("Este es mi primer programa en Swift.")

Volvemos a pulsar el botón ejecutar código y la salida de la consola se actualizará con el nuevo mensaje.

 

Conclusión

Tras crear nuestro primer programa en Swift ya nada nos impide progresar en nuestro camino de aprendizaje y poder crear nuestras propias aplicaciones.

Podemos comenzar nuestro aprendizaje sin necesidad de instalar una aplicación tan compleja como Xcode y además tenemos acceso a otras lecciones.

Cambia la tecla BlockMayus por otra en MacOS de forma rápida

A la hora de utilizar de forma virtual en un equipo Mac un sistema operativo distinto a MacOS nos podemos encontrar el problema de que nuestro teclado no tiene las teclas apropiadas para el sistema operativo virtualizado.

Este problema es aún más preocupante en el caso de usuarios de lectores de pantalla que requieren acceso a teclas como Insert o NumPadInsert que no aparecen el teclado estándar de Mac.

Aunque existen algunas aplicaciones que nos permiten estas modificaciones en muchos casos o no son accesibles o no son compatibles con las últimas versiones de MacOS.

Para dar una solución simple a este problema he creado un script de AppleScript que modifica el comportamiento de la tecla BlockMayus por otra. De esta forma podemos tener acceso a una tecla Insert o NumPadInsert en nuestro Windows o Linux virtualizado.

Podéis encontrar este script en la página del repositorio de ChangeKeyboardForVM

SOLID: Principio de inversión de dependencias o Dependency inversion principle

Este es el quinto y último principio de programación SOLID.

Significado

Este principio establece que las clases de alto nivel no deberían depender de las clases de bajo nivel. En su lugar las clases deberían depender de las abstracciones.

Además las abstracciones no deben depender de los detalles sino que los detalles deben depender de las abstracciones.

Con este principio se busca reducir las dependencias entre módulos buscando un menor nivel de acoplamiento entre clases.

Esto es indispensable en proyectos con muchos módulos en los que es necesario utilizar inyección de dependencias.

Ejemplo

Veamos el siguiente ejemplo en el que en nuestro proyecto tenemos una clase que nos permite el acceso a la base de datos en general que llamaremos CargadorDesdeBaseDeDatos y tenemos una clase PersonaEnBaseDeDatos que nos permite cargar datos de la clase Persona.

Para ello la clase PersonaEnBaseDeDatos tiene una propiedad de tipo CargadorDesdeBaseDeDatos que se utiliza como driver de acceso al sistema de almacenamiento de bases de datos de la aplicación.

 

class CargadorDesdeBaseDeDatos {

    funcion obtenerDatosGenerales()

}

 

class PersonaEnBaseDeDatos {

    propiedad datos = CargadorDesdeBaseDeDatos()

    propiedad listaDePersonas

 

    funcion cargaDatos() {

        listaGeneral = datos.obtenerDatosGenerales()

        listaDePersonas = convertirDatosGeneralesEnDatosDePersonas(listaGeneral)

    }

}

 

En el ejemplo en la clase PersonaEnBaseDeDatos dentro de la función cargaDatos() hay una dependencia de la clase AccesoABaseDeDatos. Esto es un problema porque si un día necesitamos cambiar de sistema de base de datos o de obtener datos de Internet tendríamos que modificar demasiado código ya que ambas clases tienen una fuerte dependencia entre ellas.

Imaginemos por ejemplo que queremos incluir la opción de descargar los datos desde una API rest en Internet. El código se complicaría demasiado en todas las clases.

Solución

La solución a este problema es utilizar una abstracción que permita identificar un acceso a datos mediante una interface o protocolo, dependiendo de las posibilidades del lenguaje de programación.

Veamos el código.

 

interface AccesoADatos {

    funcion cargaDatosDePersonas

}

 

class CargadorDesdeBaseDeDatos Implementa AccesoADatos {

    propiedad listaDeDatos

 

    funcion obtenerDatosGeneralesDesdeMySQL() {

        listaDeDatos = cargaDesdeMySQL()

    }

 

    funcion cargaDatosDePersonas() {

        obtenerDatosGeneralesDesdeMySQL()

        return convertirATipoPersona(listaDeDatos)

    }

}

 

class CargadorDesdeInternet Implementa AccesoADatos {

    propiedad listaDeDatos

 

    funcion obtenerDatosGeneralesDeInternet() {

        listaDeDatos = descargaDeLaNube()

    }

 

    funcion cargaDatosDePersonas() {

        obtenerDatosGeneralesDeInternet()

        return convertirATipoPersona(listaDeDatos)

    }

}

 

class PersonaEnBaseDeDatos {

    propiedad datos = AccesoADatos

    propiedad listaDePersonas

 

    funcion constructor(inyeccion: AccesoADatos) {

        datos = inyeccion

    }

 

    funcion cargaDatos() {

        listaDePersonas = datos.cargaDatosDePersonas()

    }

}

 

Con esta solución podemos inyectar cualquier clase del tipo interface AccesoADatos en el constructor de la clase PersonaEnBaseDeDatos. De esta forma podemos cambiar cómo accedemos a esos datos inyectando cualquier clase que implemente la interfaz AccesoADatos consiguiendo un mayor nivel de desacoplamiento entre clases. De esta forma tanto el módulo de alto nivel como el de bajo nivel dependen de abstracciones, por lo que se cumple el principio de inversión de dependencias y además, esto nos obliga a cumplir el principio de Liskov, ya que las clases que accederán a cualquier base de datos son intercambiables entre ellas ya que todas implementan la interfaz de AccesoADatos.

SOLID: Principio de segregación de interfaz o Interface segregation principle

En este artículo hablamos del cuarto principio de los principios de programación SOLID.

Significado

Este principio establece que las clases clientes de otras no deberían verse forzados a depender de interfaces que no usan. En su lugar se apoya la definición de interfaces más específicas para cada caso.

Cuando hablamos de interfaces estamos hablando del concepto de interfaz de clase del lenguaje de programación Java así como la adaptación a cualquier otro tipo de lenguaje de programación. Por ejemplo en Swift estaríamos hablando de protocolos.

En pocas palabras una interface es un acuerdo entre las clases que implementan la interfaz y la propia interfaz definida con sus propiedades y funciones. El acuerdo consiste en que cada clase que implemente dicha interfaz deberá codificar cada una de las propiedades y funciones definidas en la interfaz.

Ejemplo

Este principio es más sencillo de entender con un ejemplo.

Imaginemos que estamos modelando una interfaz de acciones para distintas clases de aves.

 

interface Ave {

    funcion comer()

    funcion cantar()

    funcion volar()

}

 

class Loro implements Ave {

    propiedad nombre

    propiedad color

    propiedad tamaño

 

    funcion comer() {

        // código para comer

    }

 

    funcion cantar() {

        // código para cantar

    }

 

    funcion volar() {

        // código para volar

    }

}

 

class Aguila implements Ave {

    propiedad nombre

    propiedad color

    propiedad tamaño

 

    funcion comer() {

        // código para comer

    }

 

    funcion cantar() {

        noHacerNada()

    }

 

    funcion volar() {

        // código para volar

    }

}

 

class Gallina implements Ave {

    propiedad nombre

    propiedad color

    propiedad tamaño

 

    funcion comer() {

        // código para comer

    }

 

    funcion cantar() {

        // código para cantar

    }

 

    funcion volar() {

        noHacerNada()

    }

}

 

En este ejemplo tenemos una interfaz llamada Ave con 3 acciones pero no todas las aves realizan esas tres acciones. Por ejemplo las águilas no cantan y las gallinas no vuelan. Pero si una clase implementa una interfaz está obligada a incluir esas funciones aunque no hagan nada.

Solución

La solución consiste en segregar la interfaz Ave en 3 interfaces más específicas.

 

interface Ave {

    funcion comer()

}

 

interface AveCantora {

    funcion cantar()

} 

 

interface AveVoladora {

    funcion volar()

}

 

class Loro implements Ave, AveCantora, AveVoladora {

    propiedad nombre

    propiedad color

    propiedad tamaño

 

    funcion comer() {

        // código para comer

    }

 

    funcion cantar() {

        // código para cantar

    }

 

    funcion volar() {

        // código para volar

    }

}

 

class Aguila implements Ave, AveVoladora {

    propiedad nombre

    propiedad color

    propiedad tamaño

 

    funcion comer() {

        // código para comer

    }

 

    funcion volar() {

        // código para volar

    }

}

 

class Gallina implements Ave, AveCantora {

    propiedad nombre

    propiedad color

    propiedad tamaño

 

    funcion comer() {

        // código para comer

    }

 

    funcion cantar() {

        // código para cantar

    }

}

 

Ahora cada clase sólo implementa las interfaces necesarias a sus capacidades por lo que no hay funciones que no hacen nada.

Con esta solución es sencillo incluir en un futuro por ejemplo aves que puedan nadar y cada clase sólo implementará las funciones necesarias.

Además con esta solución se mejora aún más el principio de responsabilidad única.

SOLID: Principio de substitución de Liskov o Liskov substitution principle

Este es el tercer principio de los principios de programación SOLID y se relaciona con una característica de la programación orientada a objetos: la Herencia.

Significado

El principio Declara que una subclase debe ser sustituible por su superclase.

Si en nuestro programa al hacer esto la aplicación falla, estaremos incumpliendo este principio SOLID.

Cumpliendo con este principio se confirmará que nuestro programa tiene una jerarquía de clases fácil de entender y un código reutilizable.

Ejemplo

Siguiendo con los ejemplos de los artículos de Principio de responsabilidad única y el Principio de abierto/cerrado imaginemos que queremos controlar si una persona puede acceder a una sala restringida sólo a ciertos trabajadores. En nuestro ejemplo tendremos operarios, técnicos, estudiantes, jefes y guardias y sólo los guardias y los jefes podrán acceder a la sala de control ya que estas dos clases incluyen la función entraEnSalaDeControl(). Realizaremos esta gestión en la clase ControlarAcceso que contiene una función para imprimir si tiene acceso o no a la sala.

A esta función se le pasa como parámetro(array) una lista de objetos de la clase Persona.

Veamos el código.

 

class Persona {
    propiedad nombre
    propiedad apellidos
    propiedad nacionalidad

    propiedad fechaDeNacimiento

    funcion calculaEdad()
}

 

class Operario: Persona {
    propiedad numeroDeIdentificación

    funcion utilizaHerramienta()

    funcion tomaUnDescanso()

}

 

class Técnico: Persona {
    propiedad numeroDeIdentificación

    funcion utilizaHerramienta()

    funcion revisaOperario()

    funcion controlaCalidad()

    funcion tomaUnDescanso()

}

 

class Jefe: Persona {
    propiedad departamento

    funcion revisaPersona()

    funcion controlaCalidad()

    funcion entraEnSalaDeControl() {

        imprime(“Jefe.nombre accede a la sala de control»)

 

    }

}

 

class Guardia: Persona {
    propiedad numeroDePlaca

    funcion vigilaSala()

    funcion arrestaPersona()

    funcion entraEnSalaDeControl() {

        imprime(“Guardia.nombre accede a la sala de control»)

    }
}

 

class ControlarAcceso {

    funcion imprimePermisos(listaDePersonas: Array de Persona) {

        Bucle personaDeLaLista en listaDePersonas {

            Si personaDeLaLista esInstanciaDe Operario

            ENTONCES imprime(“personaDeLaLista.nombre no tiene acceso.»)

            Si personaDeLaLista esInstanciaDe Técnico

            ENTONCES imprime(“personaDeLaLista.nombre no tiene acceso.»)

            Si personaDeLaLista esInstanciaDe Guardia

            ENTONCES personaDeLaLista.entraEnSalaDeControl()

            Si personaDeLaLista esInstanciaDe Jefe

            ENTONCES personaDeLaLista.entraEnSalaDeControl()

        }

    }

}

 

Este código además de incumplir el principio de abierto / cerrado incumple el principio de substitución de Liskov.

Solución

Utilizando las propiedades de la Herencia en programación orientada a objetos podemos incluir una función entraEnSalaDeControl() con un valor por defecto de no acceso en la clase Persona. 

Todas las clases hijas heredarán esta función por lo que sólo habría que sobreescribir esta función en las clases que si tengan acceso.

El código quedaría de la siguiente forma.

 

class Persona {
    propiedad nombre
    propiedad apellidos
    propiedad nacionalidad

    propiedad fechaDeNacimiento

    funcion calculaEdad()

    funcion entraEnSalaDeControl() {

        imprime(“Persona.nombre no tiene acceso a la sala de control»)

    }

}

 

class Operario: Persona {
    propiedad numeroDeIdentificación

    funcion utilizaHerramienta()

    funcion tomaUnDescanso()

}

 

class Técnico: Persona {
    propiedad numeroDeIdentificación

    funcion utilizaHerramienta()

    funcion revisaOperario()

    funcion controlaCalidad()

    funcion tomaUnDescanso()

}

 

class Jefe: Persona {
    propiedad departamento

    funcion revisaPersona()

    funcion controlaCalidad()

    SobreEscrito funcion entraEnSalaDeControl() {

        imprime(“Persona.nombre accede a la sala de control»)

    }

}

 

class Guardia: Persona {
    propiedad numeroDePlaca

    funcion vigilaSala()

    funcion arrestaPersona()

    SobreEscribe funcion entraEnSalaDeControl() {

        imprime(“Persona.nombre accede a la sala de control»)

    }
}

 

class ControlarAcceso {

    funcion imprimePermisos(listaDePersonas: Array de Persona) {

        Bucle personaDeLaLista en listaDePersonas {

            personaDeLaLista.entraEnSalaDeControl

        }

    }

}

 

Ahora la función imprimePermisos no tiene que consultar a qué clase pertenece la persona de la lista ya que por contrato de herencia todas las clases hijas de Persona incluyen la función entraEnSalaDeControl(). Esto facilita que si en el futuro es necesario agregar nuevas clases hijas de Persona sólo habrá que sobreescribir la función en aquellas clases que si tengan acceso. De esta forma mejoramos el mantenimiento del proyecto.