Un vistazo a UIKit, Core Animation y Core Graphics

Una de las cosas que más disfruto al ser programador iOS, es la gran cantidad de frameworks que tenemos disponibles para crear nuestras apps y mejor aún, versiones modernas de iOS y Xcode nos permiten integrar éstos frameworks muy fácilmente (a decir verdad, lejos quedaron los días que teníamos que exportar un framework como CoreGraphics para hacer uso de su API dentro de nuestras clases).

Sin embargo, nuestro trabajo del día a día puede hacer que con el tiempo perdamos de vista la intención de cada framework.

Tal es el caso con UIKit, Core Animation y Core Graphics. Debido a que nos permiten realizar funcionalidades similares (por ejemplo, dibujar y realizar animaciones…), puede que confundamos su objetivo y que lo que intentamos solucionar con UIKit, sea mejor hacerlo con Core Animation.

Es por ello, que en este post analizaremos estos 3 frameworks de tal manera que podamos:

  • Conocer para qué nos sirven.
  • Identificar las similitudes y diferencias entre sí.
  • Señalar cuándo sería conveniente uno con respecto del otro

Nota: Algunos conceptos que repasaremos a continuación pueden resultar básicos para programadores iOS más experimentados. La idea es poder dar una introducción de estos frameworks a nuevos programadores.

UIKit

Es el framework que nos permite construir y gestionar una interfaz de usuario de manera gráfica y en función de eventos.

UIKit nos provee de las vistas y ventanas para que podamos diseñar nuestra interfaz y de los mecanismos para procesar “formas de entrada” que recibe la aplicación como lo son gestos o Multi-Touch. Algunas de las otras funcionalidades que UIKit proporciona o soporta son:

  • Animación
  • Documentos
  • Dibujo e Impresión
  • Información acerca del dispositivo
  • Despliegue de texto
  • Accessibility o funcionalidades para gente con discapacidad.

Así como tenemos UIKit para crear aplicaciones para iOS o tvOS, con AppKit construimos aplicaciones para macOS (pero esa es otra historia, aunque no esta demás recordar esta diferencia).

Ejemplos de clases de UIKit:

  • UIViewController: un objeto encargado de administrar / desplegar el contenido en pantalla, responder ante las interacciones de usuario mediante sus vistas, comunicarse con otros UIViewControllers…etc.
  • UIView: administra el contenido en un area rectangular en pantalla, gracias a él podemos realizar animaciones, dibujo (con UIKit ó Core Graphics), administrar el despliegue (layout) de sus subviews y responder a eventos.
  • UIGestureRecognizer: clase base encargada del reconocimiento de gestos, de ella se derivan subclases que interpretan gestos específicos (por ejemplo UITapGestureRecognizer que interpreta taps o “clicks”)

Para dejar más claro la diferencia entre un UIViewController y un UIView, hablaremos del siguiente ejemplo:

El siguiente Gif muestra una aplicación iOS que consiste en un juego de memoria donde el usuario tiene que escoger un par de cartas, estas se voltean para mostrar las imágenes que contienen…si éstas no son iguales, las cartas vuelven a su estado original y el usuario tiene que continuar jugando. Pero caso de que las imágenes coincidan, el usuario logró un punto y las cartas que eligió quedan descartadas del juego…y así hasta el final.

En pantalla se está desplegando un UIView de un UIViewController, este UIView a su vez, contiene otras UIView (o subviews) que son los 10 áreas rectangulares (cartas) con imágenes de perritos.

Cada vez que el usuario presiona una carta, el UIViewController interpreta este gesto y valida si es la segunda carta a elegir, de ser el caso, realiza la lógica para voltear/mostrar las carta. (Nota: el reconocimiento del tap bien puede hacerlo con un UITapGestureRecognizer).

Core Animation

Es el framework que nos permite renderizar, componer y animar elementos visuales.

Core Animation nos facilita hacer animaciones, a decir verdad, gracias a este framework los programadores nos despreocupamos de hacer lógicas complejas que implicarían hacer una simple animación tal como desplazar un componente (por ejemplo UIView) de un punto de la pantalla a otro. Por ejemplo, no es necesario hacer iteraciones/ciclos/loops para mover pixel por pixel cada UIView (eso sería ineficiente y costo a nivel CPU).

De hecho, Core Animation le deja la “mayor parte del trabajo” al hardware al dibujar cada frame de animación. Por nuestra parte, los desarrolladores solo establecemos ciertos parámetros que tendrá dicha animación.

Eso no es todo, con Core Animation también podemos hacer:

  • Degradados
  • Dibujos de figuras
  • Renderización de texto

Ejemplos de clases de Core Animation:

  • CALayer: los objetos de esta clase se encargan de desplegar contenido, cuenta con propiedades que se pueden establecer como color de fondo, bordes y sombras. Además cuenta con información geométrica tales como posición, frame/rectángulo y transform.
  • CAAnimation : clase abstracta para hacer animaciones en Core Graphics. De ella heredan subclases como CABasicAnimation, CAKeyframeAnimation, CAAnimationGroup, oCATransition que nos permiten animar capas de Core Animation u objetos 3D con SceneKit
  • CAGradientLayer: clase que nos permite realizar degradados como color de fondo.

Con el siguiente ejemplo podemos darnos una idea de las capacidades de Core Animation.

En la siguiente imagen se puede apreciar un rectángulo amarillo con un punto rojo en la esquina superior izquierda.

Notarán que este circulo rojo esta bordeado por una linea blanca y además todo el contenido despliega una sombra negra. El código por implementarlo es fácil:

//1 
let
view = UIView(frame: CGRect(origin: CGPoint.zero, size: CGSize(width: 200, height: 200)))
view.backgroundColor = UIColor.yellow
//2
var
redLayer = CALayer()
//3
redLayer.frame = CGRect(x: 20, y: 20, width: 50, height: 50)
redLayer.backgroundColor = UIColor.red.cgColor
//4
redLayer.cornerRadius = redLayer.bounds.height / 2
redLayer.borderColor = UIColor.white.cgColor
redLayer.borderWidth = 3
//5
view.layer.addSublayer(redLayer)
//6
redLayer.shadowOffset = CGSize(width: 0, height: 0)
redLayer.shadowColor = UIColor.black.cgColor
redLayer.shadowRadius = CGFloat(10)
redLayer.shadowOpacity = 0.8

Aqui va la explicación:

  1. Creamos una vista (objeto UIView) cuyo origen es la coordenada x:0, y:0, de dimensiones de 200pts ancho y 200pts alto. El color de fondo es amarillo…es una clase de UIKit llamada UIColor.
  2. Instanciamos una capa (CALayer) que será nuestro punto rojo (al que vamos a colocar, colorear y bordear 😃)
  3. Esta capa la colocaremos en la esquina izquierda de arriba dentro de la vista (para ser precisos en el origen x:20, y:20) con un ancho y alto de 50pts. También le asignamos un color rojo mediante una clase de Core Graphics llamada CGColor.
  4. Para crear el círculo usamos la propiedad cornerRadius, un radio que da bordes curveados a las capas. Dado que la capa tiene un ancho igual a su alto (50pts), basta con dividir el alto (o el ancho) entre 2 para dar esa forma circular. También aprovechamos para indicar un borde blanco (otra vez gracias a la clase CGColor) de ancho de 3 puntos.
  5. Agregamos la capa a la vista amarilla. Cada UIView cuenta con una propiedad llamada layer de tipo CALayer cuyo método addSublayer nos permite agregar la capa que creamos.
  6. Para crear la sombra sobre el punto rojo, usamos algunas propiedades de CALayer:
  • shadowOffset indica el acomodo de la sombra. Un tamaño de 0 x 0 indica que la sombra se mostrará de manera uniforme sobre el contorno del circulo.
  • shadowColor indica el color que tendrá la sombra, en este caso es de color negro.
  • shadowRadius indica el radio que tendrá la sombra, es decir, ‘qué tanto queremos esparcir la sombra’ (en este caso indicamos 10 pts)
  • shadowOpacity un valor entre 0 y 1 que indica que tan transparente será la sombre (0 indica totalmente transparente y 1 indica totalmente opaco)

Core Graphics

Es el framework que nos permite renderizar de forma ligera contenido en 2D, así como gestión de colores, documentos PDF, gradientes e imágenes.

Core Graphics esta basada en una tecnología llamada Quartz que funciona como motor de dibujo para la renderización en 2D y nos permite realizar otras tareas sofisticadas que UIKit ni Core Animation lo hacen por naturaleza como lo es crear imágenes, enmascarar, crear PDFs…etc.

Ejemplos de clases de Core Graphics:

  • CGPoint: estructura que contiene una coordenada en un sistema de dos dimensiones (‘x’ y ‘y’)
  • CGPath : clase que representa rutas, lineas o figuras que son dibujadas en un contexto gráfico.
  • CGContext : esta clase contiene toda la información para renderizar la pintura de cierta página a un destino (ya sea ventana de aplicación, una imagen bitmap, un documento PDF)

Con el siguiente ejemplo podremos darnos cuenta cómo utilizamos Core Graphics en conjunto con algunas clases de Core Animation (…y UIKit) para hacer un sencillo dibujo: un paisaje con montañas y su río.

//0
let
view = UIView(frame: CGRect(origin: CGPoint.zero, size: CGSize(width: 200, height: 200)))
view.backgroundColor = UIColor(red: 9/255, green: 144/255, blue: 223/255, alpha: 1)
//1
let rect = CGRect(x: 0, y: view.center.y, width: view.bounds.width, height: view.bounds.height / 2)
let grassPath = UIBezierPath(rect: rect)
//2
let
grassLayer = CAShapeLayer()
grassLayer.fillColor = UIColor.green.cgColor
grassLayer.path = grassPath.cgPath
view.layer.addSublayer(grassLayer)

A continuación la explicación del código:

0. Primero creamos nuestro “canvas”, el cual será el cielo azul para ello instanciamos una vista con dimensiones 200 x 200, con color de fondo azul claro mediante una clase UIColor indicando los valores RGB

  1. Para crear el césped, creamos un rectángulo (CGRect) que inicie justo por la mitad de nuestro canvas y que tenga tamaño 200pts de ancho y 100pts de alto. Luego usamos la clase UIBezierPath para hacer el trazo de este rectángulo, para ello le pasamos nuestro CGRect en su inicializador.
  2. Utilizamos una subclase de CALayer: CAShapeLayer para plasmar los trazos UIBezierPath sobre nuestra vista.

Para ello, instanciamos esta clase y le indicamos un color de relleno verde mediante la propiedad fillColor y la ruta de nuestro trazo con la propiedad path. Estas propiedades son CGColor y CGPath respectivamente.

Finalmente agregamos esta capa a nuestra vista.

//3
let
mountainPath = UIBezierPath()
mountainPath.move(to: CGPoint(x: 0, y: view.center.y))
mountainPath.addLine(to: CGPoint(x: view.bounds.width / 4, y: 0))
mountainPath.addLine(to: CGPoint(x: view.bounds.width / 2, y: view.center.y))
mountainPath.addLine(to: CGPoint(x: (3 * view.bounds.width) / 4, y: 0))
mountainPath.addLine(to: CGPoint(x: view.bounds.width, y: view.center.y))
mountainPath.fill()
let mountainLayer = CAShapeLayer()
mountainLayer.fillColor = UIColor.brown.cgColor
mountainLayer.path = mountainPath.cgPath
view.layer.addSublayer(mountainLayer)

3. Llego el turno de hacer las montañas, en este caso usamos otro UIBezierPath y mediante las funciones move y addLine creamos esas formas puntiagudas (los picos se ubican en la cuarta parte y 3/4 del canvas).

Una vez generado el trazo, repetimos el proceso de crear un nuevo CAShapeLayer de modo que le proporcionemos un trazo BezierPath y los colores. Finalmente lo agregamos a nuestra vista.

//4
let
snowFirstMontainPath = UIBezierPath()
snowFirstMontainPath.move(to: CGPoint(x: (view.bounds.width / 4) — 10, y: 20))
snowFirstMontainPath.addLine(to: CGPoint(x: view.bounds.width / 4, y: 0))
snowFirstMontainPath.addLine(to: CGPoint(x: (view.bounds.width / 4) + 10, y: 20))
snowFirstMontainPath.fill()
let snowFirstMountainLayer = CAShapeLayer()
snowFirstMountainLayer.fillColor = UIColor.white.cgColor
snowFirstMountainLayer.path = snowFirstMontainPath.cgPath
view.layer.addSublayer(snowFirstMountainLayer)
let snowSecondMontainPath = UIBezierPath()
snowSecondMontainPath.move(to: CGPoint(x: 3 * (view.bounds.width) / 4, y: 0))
snowSecondMontainPath.addLine(to: CGPoint(x: (3 * (view.bounds.width / 4) — 10), y: 20))
snowSecondMontainPath.addLine(to: CGPoint(x: (3 * (view.bounds.width / 4) + 10), y: 20))
snowSecondMontainPath.fill()
let snowSecondMountainLayer = CAShapeLayer()
snowSecondMountainLayer.fillColor = UIColor.white.cgColor
snowSecondMountainLayer.path = snowSecondMontainPath.cgPath
view.layer.addSublayer(snowSecondMountainLayer)

4. Para hacer la nieve de la montaña creamos un bezier path y dado que su forma es recta, utilizamos las mismas funciones move y addLine para colocarla en la punta de cada montaña.

Creamos una capa, le indicamos el color blanco y el trazo del bezier path. Y lo agregamos a nuestra vista.

//5
let
riverPath = UIBezierPath()
riverPath.move(to: view.center)
riverPath.addCurve(to: CGPoint(x: view.center.x, y: view.bounds.height),
controlPoint1: CGPoint(x: (view.center.x) / 4 ,y: 3 * view.bounds.height / 4),
controlPoint2: CGPoint(x: 3 * (view.center.x) / 4 , y: 3 * view.bounds.height / 4))
riverPath.addLine(to: CGPoint(x: view.center.x + 20, y: view.bounds.height))
riverPath.addCurve(to: view.center,
controlPoint1: CGPoint(x: 3 * (view.center.x) / 4 , y: 3 * view.bounds.height / 4),
controlPoint2: CGPoint(x: 3 * (view.center.x) / 4 , y: 3 * view.bounds.height / 4))
//10
let riverLayer = CAShapeLayer()
riverLayer.fillColor = UIColor.blue.cgColor
riverLayer.path = riverPath.cgPath
view.layer.addSublayer(riverLayer)

5. Para hacer el río utilizamos funciones diferentes como lo es addCurve al cual indicamos un punto destino y 2 puntos de control que nos sirven para hacer la inflexión de la curva.

Creamos nuevamente una capa CAShapeLayer indicando un color de relleno azul y trazo que creamos.

Conclusión

En este artículo revisamos a manera general 3 importantes frameworks (UIKit, Core Animation y Core Graphics). Vimos en qué consisten, algunos ejemplos de clases, un ejercicio práctico para darnos mayor idea de su aplicación y de las diferencias entre ellos.

Por ejemplo, observamos como UIKit nos proporcionó los elementos de interfaz de usuario como vistas (UIViews) que pudimos decorar. Core Animation nos proporcionó las capas (CALayers) las cuales pudimos agregarles efectos como sombreado o bordes de color y gracias Core Graphics, pudimos hacer un dibujo sofisticado con ayuda de puntos, líneas, curvas y trazos.

Sin embargo, apenas dimos un pequeño vistazo de las capacidades de estos frameworks, existen muchos más conceptos por explorar (ej. ciclo de vida de las vistas, controladores, cómo hacen la animación UIKit y cómo lo hace Core Animation…por mencionar algunos). De los cuales espero podamos explorar más adelante.

¿Qué les pareció este artículo? No duden en compartirme sus comentarios y su experiencia de haber manejado estos frameworks. 😃

Si se quedaron con la curiosidad del código del juego de memoria, les comparto la liga del repositorio. Espero les agrade.