Una Introducción a Quartz 2D
Spanish (Español) translation by Javier Salesi (you can also view the original English article)
¿Qué es Quartz 2D?
Quartz 2D es el motor de dibujo 2D de Apple, un importante componente del framework Core Graphics. Con frecuencia puedes ver que a Quartz 2D se le llama Core Graphics o simplemente CG.
Quartz 2D utiliza el "modelo del pintor". En el modelo del pintor, cada operación de dibujo sucesiva aplica una capa de "pintura" a un "lienzo" resultante, con frecuencia llamado una página. Piensa en ésto como un artista que trabaja en una pintura. Si el artista fuera a pintar el lienzo completo de azul y luego pinta algunas nubes en en lienzo entonces las nubes cubrirían al azul debajo de ellas. Una vez que algo es "pintado" en el lienzo, no puede ser cambiado sino añadir mas pintura sobre eso.
Todo lo que se pinta en Quartz 2D se hace a través de un contexto gráfico de tipo CGContextRef. Con una referencia a un contexto gráfico, puedes usar las funciones de Quartz 2D para dibujar al contexto, ejecutar operaciones, tales como trasladar el contexto, y cambiar los parámetros del estado gráfico, como el grosor de una línea y color de relleno. Quartz 2D es una API basada en C, como tal invocarás las funciones de C pasando el contexto como un parámetro.
Para dibujar en la pantalla en iOS, debes tomar como subclase un UIView y sobreescribir su método drawRect(_:). Es dentro de éste método drawRect(_:) que harás cualquier dibujo personalizado. Nunca deberías llamar al método drawRect(_:) directamente en tu código. Si necesitas actualizar la pantalla con nuevos comandos de dibujo, deberías llamar a los métodos setNeedsDisplay() o setNeedsDisplayInRect(_:).
Cuando se usa Quartz 2D en iOS, la coordenada (0,0) se ubica en la esquina superior izquierda de la pantalla. La coordenada x se incrementa mientras te mueves a la derecha y la coordenada y se incrementa mientras te mueves hacia abajo.
A través de éste tutorial, puedes querer consultar la guía de programación para Quartz 2D. El objetivo de éste tutorial es iniciarte en el uso de Quartz 2D. Hay mucho que no será cubierto y para apreciar completamente todo lo que Quartz 2D tiene para ofrecer te sugiero leer la guía de programación.
Dejando atrás esta breve introducción, comencemos a usar Quartz 2D.
1. Preparando un UIView para Dibujar
Asumiendo que tienes un proyecto abierto y listo para empezar a trabajar con Quartz 2D, los pasos que necesitas dar son muy simples. Necesitarás crear una clase que es una subclase de UIView, agregar un view desde la librería Object a tu proyecto en Interface Builder, y establecer la clase de view en la subclase UIView que creaste. Veamos esto paso por paso.
Paso 1: estableciendo la subclase UIView
Ve a File > New > File... En la sección iOS, selecciona Source y luego elige Cocoa Touch Class como la plantilla.



En la sguiente pantalla, da un nombre a tu clase, asegúrate que es una subclase UIView, y establece el lenguaje en Swift. Pulsa Next y elige donde guardar tu nueva subclase.



Si ves la fuente de tu clase recién creada, verás el méodo drawRect(_:). Está actualmente deshabilitado como comentario, pero cambiaremos eso en unos momentos.
1 |
import UIKit |
2 |
|
3 |
class DrawLineView: UIView { |
4 |
|
5 |
/*
|
6 |
// Only override drawRect: if you perform custom drawing.
|
7 |
// An empty implementation adversely affects performance during animation.
|
8 |
override func drawRect(rect: CGRect) {
|
9 |
// Drawing code
|
10 |
}
|
11 |
*/
|
12 |
|
13 |
}
|
Paso 2: Agregando un View y estableciendo la Clase
Abre la storyboard del proyecto y abre Object Library ubicada a la derecha. En el campo de búsqueda abajo, ingresa "UIView" para filtrar objetos que no nos interesan.



Arrastra una instancia de UIView al view controller. Con el view seleccionado, abre el Indentity Inspector a la derecha y cambia Class a como hayas nombrado la subclase.



Cualquier código que agregues dentro del método drawRect(_:) será dibujado cuando la subclase UIView sea instanciada. La view automáticamente configura el entorno de dibujo para que puedas empezar a dibujar inmediatamente. La view configura el CGContextRef mencionado al inicio de esta lección y está dentro del método drawRect(_:) del que obtendrás referencia.
2. Proyecto de Inicio
Para comenzar rápidamente, he proporcionado un proyecto de inicio que tiene todos los view ya preparados y listos para usar. Las subclases UIView son nombradas como el comando de dibujo que estaremos explorando. Por ejemplo, cuando estamos aprendiendo a dibujar líneas la correspondiente clase será nombrada DrawLinesView.
Puedes descargar el proyecto de inicio desde Github. Comenzaremos a codificar en el próximo paso.
3. Obteniendo una Referencia al Contexto Gráfico
Antes de que puedas dibujar cualquier cosa, necesitas obtener una referencia al contexto gráfico. Esto se logra como sigue.
1 |
let context = UIGraphicsGetCurrentContext() |
Ésto regresa un tipo opaco, CGContextRef, y pasarás éste contexto en las funciones C para hacer el dibujo personalizado. Ahora que sabemos como obtener una referencia al contexto gráfico podemos comenzar a explorar los comandos de dibujo.
4. Dibujar una Línea
Si has descargado el proyecto de inicio, abre DrawLineView.swift y agrega lo siguiente al método drawRect(_:).
1 |
override func drawRect(rect: CGRect) { |
2 |
let context = UIGraphicsGetCurrentContext() |
3 |
CGContextSetStrokeColorWithColor(context, UIColor.redColor().CGColor) |
4 |
CGContextMoveToPoint(context, 0, 0) |
5 |
CGContextAddLineToPoint(context, 200, 200) |
6 |
CGContextStrokePath(context) |
7 |
}
|
Primero obtenemos una referencia al contexto de dibujo como se discutió anteriormente. Debido a que ésto es algo que haremos para cada ejemplo, no mencionaré ésto en los próximos ejemplos.
La función CGContextSetStrokeColorWithColor(_:_:) establece el color con el que la línea será dibujada o trazada. Los parámetros que pasamos son el contexto gráfico y el nuevo color del trazo.
Si piensas en el contexto gráfico como el lienzo de un pintor, entonces la función CGContextMoveToPoint(_:_:_:) mueve el pincel a un punto particular en el lienzo desde el cual comenzar o continuar dibujando. Imagina dibujar en un pedazo de papel, levantando tu mano, y mover a una parte diferente del papel y continuar dibujando. Esencialmente eso es lo que éste método logra. Pasamos el contexto gráfico y una coordenada x y y desde donde comenzamos a dibujar.
La función CGContextAddLineToPoint(_:_:_:) toma como parámetros el contexto gráfico, el valor de x para el final del segmento de línea, y el valor y para el final del segmento de línea. Después de agregar el segmento de línea, el punto actual será establecido al punto final del segmento de línea. Comenzamos la operación de dibujo en (0,0), después de ésta operación de dibujo el cursor o pincel está en (200,200).
Finalmente, para hacer el dibujo necesitas llamar a la función CGContextStrokePath(_:) pasando el contexto gráfico. Ésta función simplemente dibuja una línea a lo largo de la ruta que especificamos.
Compila y ejecuta el proyecto de ejemplo para ver el efecto.
5. Dibujar un Rectángulo
Abre DrawRectangleView.swift y agrega lo siguiente al método drawRect(_:). Deberías estar familiarizado con las dos primeras líneas a estas alturas.
1 |
override func drawRect(rect: CGRect) { |
2 |
let context = UIGraphicsGetCurrentContext() |
3 |
CGContextSetStrokeColorWithColor(context,UIColor.redColor().CGColor) |
4 |
let rectangle = CGRectMake(50,50,frame.size.width-100,frame.size.height-100) |
5 |
CGContextAddRect(context,rectangle) |
6 |
CGContextStrokePath(context) |
7 |
}
|
La función CGRectMake(_:_:_:_:) es parte de CGGeometry y proporciona una forma fácil de crear una estructura CGRect. Como su nombre lo implica, CGRect es una estructura que contiene la ubicación y dimensiones de un rectángulo. Un CGRect tiene dos campos, origin y size, que son un CGPoint y un CGSize respectivamente. Si no estás familiarizado con éstos tipos de datos, entonces lee la Guía de CGGeometry.
Creamos un constante rectangle, usando la funcilón CGRectMake(_:_:_:_:) y llamamos a la función CGContextAddRect(_:_:_:_:), que toma como parámetros el contexto gráfico y un CGRect. Finalmente, llamamos a CGContextStrokePath(context) para dibujar el rectángulo.
Compila y ejecuta el proyecto para ver el rectángulo dibujado en la pantalla.
6. Dibujar un Círculo
Abre DrawCircleView.swift y actualiza el método drawRect(_:) como sigue.
1 |
override func drawRect(rect: CGRect) { |
2 |
let context = UIGraphicsGetCurrentContext() |
3 |
CGContextSetStrokeColorWithColor(context,UIColor.redColor().CGColor) |
4 |
let rectangle = CGRectMake(50,50,frame.size.width-100,frame.size.height-100) |
5 |
CGContextAddEllipseInRect(context,rectangle) |
6 |
CGContextStrokePath(context) |
7 |
}
|
Te puedes preguntar ¿por qué estamos llamando a CGRectMake(_:_:_:_:) cuando estamos dibujando un círculo? El rectángulo es el área en la que el círculo debe caber. En el código de arriba, creamos un círculo al usar un cuadrado. Si quieres dibujar un óvalo o una elipse, entonces necesitas hacer el rectángulo con la forma mas rectangular.
Entonces llamamos a la función CGContextAddEllilpseInRect(_:_:), que toma como parámetros el contexto gráfico y el rectángulo en el cual dibujar la elipse. El círculo es dibujado al llamar a CGContextStrokePath(_:), pasando el contexto gráfico.
7. Dibujando un Arco
Abre DrawArcView.swift y agrega el siguiente código dentro del método drawRect(_:).
1 |
override func drawRect(rect: CGRect) { |
2 |
let context = UIGraphicsGetCurrentContext() |
3 |
CGContextSetStrokeColorWithColor(context,UIColor.redColor().CGColor) |
4 |
CGContextAddArc(context, 100,100,50,3.14,0,1) |
5 |
CGContextStrokePath(context) |
6 |
}
|
La función CGContextAddArc(_:_:_:_:_:_:_:) toma pocos parámetros:
- el contexto gráfico
- el valor x para el centro del arco
- el valor y para el centro del arco
- el radio del arco
- el ángulo para el punto inicial del arco, medido en radianes desde el eje x positivo.
- el ángulo para el punto final del arco, medido en radianes desde el eje x positivo.
- un valor de 1 para crear un arco en sentido de las manecillas del reloj o un valor de 0 para crear un arco en sentido contrario a las manecillas del reloj.
8. Dibujando un Trazado
Para dibujar formas mas complejas, creas una ruta y la trazas. Observa el método drawRect(_:) en DrawPathView.swift.
1 |
override func drawRect(rect: CGRect) { |
2 |
let context = UIGraphicsGetCurrentContext() |
3 |
CGContextSetStrokeColorWithColor(context, UIColor.redColor().CGColor) |
4 |
CGContextMoveToPoint(context, 25, 150) |
5 |
CGContextAddLineToPoint(context, 175, 150) |
6 |
CGContextAddLineToPoint(context, 100, 50) |
7 |
CGContextAddLineToPoint(context, 25, 150) |
8 |
CGContextStrokePath(context) |
9 |
}
|
En el método drawRect(_:), llamamos a CGContextAddLineToPoint(_:_:_:) un número de veces para crear un triángulo. Nota que el triángulo no está lleno, solo trazado. En el próximo paso, veremos como rellenar el triángulo con color.
9. Rellenando una Ruta
Abre FillPathView.swift y actualiza el método drawRect(_:) como se muestra abajo.
1 |
override func drawRect(rect: CGRect) { |
2 |
let context = UIGraphicsGetCurrentContext() |
3 |
CGContextMoveToPoint(context, 25, 150) |
4 |
CGContextAddLineToPoint(context, 175, 150) |
5 |
CGContextAddLineToPoint(context, 100, 50) |
6 |
CGContextAddLineToPoint(context, 25, 150) |
7 |
CGContextSetFillColorWithColor(context,UIColor.redColor().CGColor) |
8 |
CGContextFillPath(context) |
9 |
}
|
En el paso previo, trazamos una ruta, pero también puedes rellenar una ruta con un color particular. En el método drawRect(_:) de arriba, comenzamos creando una ruta para el mismo triángulo como en el ejemplo previo. Ésta vez establecemos un color de relleno usando la función CGContextSetFillColorWithColor(_:_:) y llamamos a CGContextFillPath(_:) en lugar de CGContextStrokePath(_:).
10. Rellenando una Elipse
Aparte de rellenar rutas, también puedes rellenar elipses y rectángulos. En este ejemplo, rellenaremos una elipse. Rellenar un rectángulo, sin embargo, es muy similar. La documentación te dirá como se hace. Actualiza el método drawRect(_:) en FillEllipseView.swift como se muestra abajo.
1 |
override func drawRect(rect: CGRect) { |
2 |
let context = UIGraphicsGetCurrentContext() |
3 |
CGContextSetLineWidth(context, 8.0) |
4 |
CGContextSetStrokeColorWithColor(context,UIColor.redColor().CGColor) |
5 |
let rectangle = CGRectMake(50,50,frame.size.width-100,frame.size.height-100) |
6 |
CGContextAddEllipseInRect(context, rectangle) |
7 |
CGContextStrokePath(context) |
8 |
CGContextSetFillColorWithColor(context,UIColor.greenColor().CGColor) |
9 |
CGContextFillEllipseInRect(context, rectangle) |
10 |
}
|
La mayoría de éste código a estas alturas debería serte familiar. Estamos usando una nueva función, CGContextSetLineWidth(_:_:) para establecer el grosor de la línea y llamamos a CGContextFillEllipseInRect(_:_:) para rellenar la elipse. Ésta función toma como parámetros el contexto de gráficos y el rectangulo en el cual rellenar la elipse.
11. Agregando Líneas
La función CGContextAddLines(_:_:_:) es una función útil cuando tienes un número de segmentos de líneas rectas conectadas que deseas dibujar. Aquí recreamos el triángulo anterior en los ejemplos, usando la función CGContextAddLines(_:_:_:). Agrega el siguiente código a AddLinesView.swift.
1 |
override func drawRect(rect: CGRect) { |
2 |
let context = UIGraphicsGetCurrentContext() |
3 |
CGContextSetStrokeColorWithColor(context, UIColor.redColor().CGColor) |
4 |
let lines = [CGPointMake(25,150),CGPointMake(175,150),CGPointMake(100,50),CGPointMake(25,150)] |
5 |
CGContextAddLines(context,lines,4) |
6 |
CGContextStrokePath(context) |
7 |
}
|
La función CGContextAddLines(_:_:_:) toma como parámetros el contexto de gráficos, un arreglo de valores que especifican los puntos de inicio y fin de los segmentos de línea para dibujar como estructuras CGPoint, y el número de elementos en el arreglo. Nota que el primer punto en el arreglo especifica el punto de inicio.
12. Dibujando un Degradado
Con Quartz 2D, es fácil dibujar degradados. Son soportados tanto degradados lineales como radiales. En este ejemplo, dibujaremos un degradado lineal. La documentación te ayudará si estás interesado en dibujar degradados lineales. Agrega lo siguiente a DrawGradientView.swift.
1 |
override func drawRect(rect: CGRect) { |
2 |
let context = UIGraphicsGetCurrentContext() |
3 |
let colorspace = CGColorSpaceCreateDeviceRGB() |
4 |
let colors = [UIColor.redColor().CGColor,UIColor.blueColor().CGColor] |
5 |
let locations: [CGFloat] = [ 0.0, 0.5] |
6 |
let gradient = CGGradientCreateWithColors(colorspace,colors,locations) |
7 |
let startPoint = CGPointMake(0,0) |
8 |
let endPoint = CGPointMake(200,200) |
9 |
CGContextDrawLinearGradient(context, gradient,startPoint, endPoint, 0) |
10 |
}
|
La función CGContextDrawLinearGradient(_:_:_:_:_:) toma como parámetros:
- el contexto de gráficos
- una estructura
CGGradient - un punto de inicio
- un punto final
- modificadores opcionales que especifican si el relleno se extiende mas allá del punto de inicio o del punto final.
Una estructura CGGradient define una suave transición entre colores a través de un área. Tiene un espacio de color, dos o mas colores, y una ubicación para cada color. Las constantes colorspace, colors, y locations en el ejemplo de arriba representan éstas partes que conforman el CGGradient.
Para dibujar el degradado, llamamos a la función CGContextDrawLinearGradient(_:_:_:_:_:), pasando el contexto gráfico, el CGGradient, valores de inicio y fin, y 0 para indicar que el relleno debería extenderse mas alla de a ubicación inicial.
13. Dibujando una Sombra
Una sombra es una imagen dibujada debajo, y con un desplazamiento de, un objeto gráfico para que la sombra imite el efecto de una fuente de luz proyectada sobre el objeto gráfico. -Guía de Programación de Quartz 2D
Hay dos funciones que puedes usar para dibujar sombras, CGContextSetShadow(_:_:_:) y CGContextSetShadowWithColor(_:_:_:_:). Cuando se usa CGContextSetShadow(_:_:_:), a todos los objetos dibujados se les aplica una sombra usando un color negro con un alfa de 1/3. La función CGContextSetShadowWithColor(_:_:_:_:) te permite especificar un color para la sombra.
Veamos como funciona esto en la práctica. Agrega lo siguiente a SetShadowWithColor.swift.
1 |
override func drawRect(rect: CGRect) {
|
2 |
let context = UIGraphicsGetCurrentContext() |
3 |
CGContextSaveGState(context) |
4 |
let shadowOffset = CGSizeMake(-15,20) |
5 |
CGContextSetShadowWithColor(context,shadowOffset,3,UIColor.greenColor().CGColor) |
6 |
CGContextSetStrokeColorWithColor(context,UIColor.redColor().CGColor) |
7 |
let rectangle = CGRectMake(50,50,frame.size.width-100,frame.size.height-100) |
8 |
CGContextAddRect(context, rectangle) |
9 |
CGContextStrokePath(context) |
10 |
CGContextRestoreGState(context) |
11 |
} |
Cuando se dibujan sombras, deberías guardar el estado del contexto gráfico, elaborar cualquier cambio requerido, y luego restablecer el estado gráfico. Llamamos a CGContextSaveGState(_:) para guardar el estado actual del contexto gráfico, especificar un desplazamiento para la sombra, shadowOffset, y llamar a la función CGContextSetShadowWithColor(_:_:_:_:). Éstas funciones toman como parámetros:
- el contexto gráfico
- el desplazamiento para la sombra
- la cantidad de desenfoque
- el color de la sombra
El resto del código debería serte familiar. Finalmente, restablecemos el contexto gráfico al llamar a CGContextRestoreGState(_:), pasando el contexto gráfico.
14. Dibujando una Cara Feliz
Pensé que sería divertido finalizar este tutorial al dibujar una simple cara feliz usando lo que hemos aprendido a través de éste tutorial. Agrega lo siguiente a DrawHappyFaceView.swift.
1 |
override func drawRect(rect: CGRect) { |
2 |
let context = UIGraphicsGetCurrentContext() |
3 |
let face = CGRectMake(50,50,frame.size.width-100,frame.size.height-100) |
4 |
CGContextAddEllipseInRect(context, face) |
5 |
CGContextSetFillColorWithColor(context,UIColor.yellowColor().CGColor) |
6 |
CGContextFillEllipseInRect(context, face) |
7 |
let leftEye = CGRectMake(75,75,10,10) |
8 |
CGContextSetFillColorWithColor(context,UIColor.blackColor().CGColor) |
9 |
CGContextFillEllipseInRect(context, leftEye) |
10 |
let rightEye = CGRectMake(115,75,10,10) |
11 |
CGContextFillEllipseInRect(context, rightEye) |
12 |
CGContextSetLineWidth(context, 3.0) |
13 |
CGContextAddArc(context, 100,100,30,3.14,0,1) |
14 |
CGContextStrokePath(context) |
15 |
}
|
La implementación del método drawRect(_:) debería tener sentido por ahora y deberías tener una cara feliz dibujada en la pantalla.
Conclusión
Ésto llega al final del tutorial. Ahora deberías tener una comprensión básica de como ejecutar dibujos personalizados usando el motor de dibujo Quartz 2D. Espero que hayas aprendido algo útil al leer éste tutorial.
¡Sé el primero en conocer las nuevas traducciones–sigue @tutsplus_es en Twitter!



