1. Code
  2. Mobile Development
  3. iOS Development

SDK de iOS: técnicas avanzadas de UIImage

En este tutorial, veremos algunas de las características avanzadas y patrones de uso de la humilde clase UIImage en iOS. Al final de este tutorial, habrás aprendido lo siguiente: cómo crear imágenes en código, cómo crear imágenes redimensionables para elementos de la interfaz de usuario como llamadas, y cómo crear imágenes animadas.
Scroll to top

Spanish (Español) translation by Andrea Jiménez (you can also view the original English article)

En este tutorial, veremos algunas de las características avanzadas y patrones de uso de la humilde clase UIImage en iOS. Al final de este tutorial, habrás aprendido lo siguiente: cómo crear imágenes en código, cómo crear imágenes redimensionables para elementos de la interfaz de usuario como llamadas, y cómo crear imágenes animadas.


Descripción teórica

Si alguna vez has tenido que mostrar una imagen en tu aplicación iOS, probablemente estés familiarizado con UIImage. Es la clase que te permite representar imágenes en iOS. Esta es, por mucho, la forma más común de usar UIImage y es bastante sencilla: tienes un archivo de imagen en uno de los varios formatos de imagen estándar (PNG, JPEG, BMP, entre otros) y quieres mostrarlo en la interfaz de tu aplicación. Creas una instancia de UIImage nueva enviando el mensaje de clase imageNamed:. Si tienes una instancia de UIImageView, puedes configurar tu propiedad image en la instancia de UIImage, y luego puedes pegar la vista de la imagen en tu interfaz configurándola como una subvista de tu vista en pantalla:

1
 UIImage *img = [UIImage imageNamed:filename];
2
 UIImageView *imgView = [[UIImageView alloc] initWithImage:img];
3
 // Set frame, etc. ...

4
 [self.view addSubview:imgView];

También puedes realizar el procedimiento equivalente al anterior directamente en Interface Builder.

Hay otras formas de crear instancias de una imagen, como desde una dirección URL, o desde una imagen archivada que se almacenó como un tipo NSData, pero no nos centraremos en esos aspectos en este tutorial.

Antes de hablar sobre la creación de imágenes en código, recuerda que en el nivel más primitivo, sabemos qué es realmente una imagen 2D: una matriz bidimensional de valores de píxeles. La región de la memoria que representa la matriz de píxeles de una imagen a menudo se denomina almacenamiento de mapa de bits. A veces es útil tener esto en cuenta al hacer consideraciones sobre la memoria. Sin embargo, es importante darse cuenta de que un UIImage es realmente una abstracción de una imagen de mayor nivel que la de una matriz de píxeles, y que se ha optimizado de acuerdo con las demandas y los escenarios de uso de una plataforma móvil. Aunque teóricamente es posible crear una imagen rellenando una matriz de píxeles completa con valores, o llegar al mapa de bits de una imagen existente y leer o modificar el valor de un píxel individual, es bastante incómodo hacerlo en iOS y la API no lo facilita realmente. Sin embargo, dado que la mayoría de los desarrolladores de aplicaciones rara vez encuentran la necesidad real de meterse con las imágenes a nivel de píxeles, por lo general no es un problema.

Lo que UIImage (o más generalmente, UIKit y Core Graphics) le facilita al desarrollador es crear una nueva imagen mediante la composición de imágenes existentes de maneras interesantes, o generar una imagen mediante la rasterización de un dibujo vectorial construido con la clase UIBezierPath de UIKit o con las funciones CGPath... de Core graphic. Si quieres escribir una aplicación que le permita al usuario crear un collage de sus imágenes, es fácil de hacer con UIKit y UIImage. Digamos, si desarrollaste una aplicación de dibujo a mano alzada y quieres permitir que el usuario guarde su creación, entonces el enfoque más simple implicaría extraer un UIImage del contexto del dibujo. En la primera sección de este tutorial, aprenderás exactamente ¡cómo se pueden lograr ambas ideas!

Es importante tener en cuenta que un UIImage construido de esta manera no es diferente de una imagen obtenida abriendo una imagen del álbum de fotos o descargándola de Internet: se puede guardar en el archivo o cargar en el álbum de fotos o mostrar en un UIImageView.

El cambio de tamaño de imagen es un tipo importante de manipulación de imágenes. Obviamente, te gustaría evitar agrandar una imagen, porque eso hace que la calidad y la nitidez de la imagen se vean afectadas. Sin embargo, hay ciertos escenarios en los que se necesitan imágenes redimensionables y, de hecho, existen formas sensatas de hacerlo que no degraden la calidad de la imagen. UIImage se adapta a esta situación al permitir imágenes que tienen un área de tamaño interior e "inserciones de borde" en los bordes de la imagen que cambian de tamaño en una dirección determinada o no cambian de tamaño en absoluto. Además, el cambio de tamaño se puede realizar ya sea mediante mosaicos o estirando las partes redimensionables para dos efectos algo diferentes que pueden ser útiles en diferentes situaciones.

La segunda parte de la implementación mostrará una implementación concreta de esta idea. ¡Escribiremos una pequeña clase ingeniosa que puede mostrar cualquier cantidad de texto dentro de una imagen redimensionable!

Por último, hablaremos un poco sobre la animación de imágenes con UIImage. Como probablemente puedas adivinar, esto significa "reproducir" una serie de imágenes sucesivas, como una animación muy parecida a los GIF animados que ves en Internet. Si bien esto puede parecer un poco limitado, en situaciones simples, el soporte de imágenes animadas de UIImage podría ser justo lo que necesitas, y todo lo que necesita es un par de líneas de código para comenzar a funcionar. ¡Eso es lo que veremos en la tercera y última sección de este tutorial! ¡Es hora de concentrarnos y ponernos a trabajar!


1. Inicio de un nuevo proyecto

Crea un nuevo proyecto de iOS en Xcode, con la plantilla "Aplicación vacía". Llámalo "UIImageFun". Marca la opción de Recuento automático de referencias, pero desactiva las opciones de Datos principales y Pruebas unitarias.

Creating a new Xcode projectCreating a new Xcode projectCreating a new Xcode project
Creación de un nuevo proyecto Xcode

Una pequeña nota, antes de continuar: este tutorial utiliza varios conjuntos de imágenes, y para obtenerlas, deberás hacer clic donde dice "Descargar archivos de origen" en la parte superior de esta página. Después de descargar y descomprimir el archivo, arrastra la carpeta llamada "Imágenes" al Navegador de proyectos, la pestaña situada más a la izquierda en el panel de la izquierda en Xcode. Si el panel izquierdo no está visible, presiona la combinación de teclas ⌘ + 0 para hacerlo visible y asegúrate de que la pestaña del extremo izquierdo, cuyo ícono parece una carpeta, esté seleccionada.

Drag and drop the Images folder into your projectDrag and drop the Images folder into your projectDrag and drop the Images folder into your project
Arrastra y suelta la carpeta Imágenes en tu proyecto
Asegúrate de que la opción "Copiar elementos en la carpeta del grupo de destino (si es necesario)" del cuadro de diálogo esté marcada".
Ensure that the files get copied to your project folderEnsure that the files get copied to your project folderEnsure that the files get copied to your project folder
Asegúrate de que los archivos se copien en la carpeta de tu proyecto

El archivo descargado también contiene el proyecto Xcode completo con las imágenes ya añadidas al proyecto, en caso de que te quedes atascado en algún lugar.


2. Creación de una imagen en el código

Crea un nuevo archivo para una clase Objective-C, llámalo ViewController y conviértelo en una subclase de UIViewController. Asegúrate de que las opciones relacionadas con iPad y XIB no estén marcadas.

Make a new view controllerMake a new view controllerMake a new view controller
Crea un nuevo controlador de vista

Reemplaza todo el código de ViewController.m con lo siguiente:

1
#import "ViewController.h"

2
3
@interface ViewController ()
4
{
5
    UIImage *img;
6
    UIImageView *iv;
7
    NSMutableArray *ivs;
8
}
9
@end
10
11
@implementation ViewController
12
13
14
- (void)viewDidLoad
15
{
16
    [super viewDidLoad];
17
    
18
    // (1) Creating a bitmap context, filling it with yellow as "background" color:

19
    CGSize size = CGSizeMake(self.view.bounds.size.width, self.view.bounds.size.height);
20
    UIGraphicsBeginImageContextWithOptions(CGSizeMake(size.width, size.height), YES, 0.0); 
21
    [[UIColor yellowColor] setFill];
22
    UIRectFill(CGRectMake(0, 0, size.width, size.height));
23
24
    // (2) Create a circle via a bezier path and stroking+filling it in the bitmap context:

25
    UIBezierPath *bezierPath = [UIBezierPath bezierPathWithArcCenter:CGPointMake(size.width/2, size.height/2) radius:140 startAngle:0 endAngle:2 * M_PI clockwise:YES];
26
    [[UIColor blackColor] setStroke];
27
    bezierPath.lineWidth = 5.0;
28
    [bezierPath stroke];
29
    [[UIColor redColor] setFill];
30
    [bezierPath fill];
31
    
32
    // (3) Creating an array of images:

33
    NSArray *rocks = @[[UIImage imageNamed:@"rock1"],
34
                       [UIImage imageNamed:@"rock2"],
35
                       [UIImage imageNamed:@"rock3"],
36
                       [UIImage imageNamed:@"rock4"],
37
                       [UIImage imageNamed:@"rock5"],
38
                       [UIImage imageNamed:@"rock6"],
39
                       [UIImage imageNamed:@"rock7"],
40
                       [UIImage imageNamed:@"rock8"],
41
                       [UIImage imageNamed:@"rock9"]];
42
                       
43
    // (4) Drawing rocks in a loop, each chosen randomly from the image set and drawn at a random position in a circular pattern:

44
    for ( int i = 0; i < 100; i++)
45
    {
46
        int idx = arc4random() % rocks.count;
47
        NSLog(@"idx = %d", idx);
48
        int radius = 100;
49
        int revolution = 360;
50
        float r = (float)(arc4random() % radius);
51
        float angle = (float)(arc4random() % revolution);
52
        float x = size.width/2 + r * cosf(angle * M_PI/180.0);
53
        float y = size.height/2 + r * sinf(angle * M_PI/180.0);
54
        CGSize rockSize = ((UIImage *)rocks[idx]).size;
55
        [rocks[idx] drawAtPoint:CGPointMake(x-rockSize.width/2, y-rockSize.height/2)];
56
    }
57
    
58
    // (5) Deriving a new UIImage instance from the bitmap context:

59
    UIImage *fImg = UIGraphicsGetImageFromCurrentImageContext();
60
    // (6) Closing the context:

61
    UIGraphicsEndImageContext();
62
    // (7) Setting the image view's image property to the created image, and displaying

63
    iv = [[UIImageView alloc] initWithImage:fImg];
64
    [self.view addSubview:iv];
65
}
66
67
@end

Configura App Delegate para usar una instancia de ViewController como el controlador de vista raíz reemplazando el código en AppDelegate.m con lo siguiente:

1
#import "AppDelegate.h"

2
#import "ViewController.h"

3
4
@implementation AppDelegate
5
6
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
7
{
8
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
9
    self.window.rootViewController = [[ViewController alloc] init];
10
    self.window.backgroundColor = [UIColor whiteColor];
11
    [self.window makeKeyAndVisible];
12
    return YES;
13
}
14
15
@end

Examinemos el código de viewDidLoad: donde ocurre toda la acción. Nos referiremos a los números en el contexto del código.

    1. Queremos empezar dibujando una imagen, lo que significa que necesitamos un "lienzo". En la terminología adecuada, esto se denomina contexto de imagen (o contexto de mapa de bits). Creamos uno llamando a la función UIGraphicsBeginImageContextWithOptions(). Esta función toma como argumentos un CGSize, que establecimos en el tamaño de la vista de nuestro controlador de vista, lo que significa toda la pantalla. El BOOL nos dice si el contexto es opaco o no. Un contexto opaco es más eficiente, pero puedes "ver a través" de él. Como no hay nada de interés debajo de nuestro contexto, lo configuramos en YES. El factor de escala, que es un valor float que establecemos en 0.0 (un valor que garantiza la escala específica del dispositivo). Dependiendo de si el dispositivo tiene una pantalla Retina, el factor de escala se establecerá en 2.0 o 1.0 respectivamente. En un momento hablaré un poco más sobre el factor de escala, pero para una discusión más completa, te referiré a la documentación oficial (específicamente, la sección "Puntos vs píxeles" en la Guía de dibujo e impresión para iOS).

      Una vez que creamos un contexto de imagen de esta manera, se convierte en el contexto actual. Esto es importante porque para dibujar con UIKit, debemos tener un contexto de dibujo actual donde ocurre todo el dibujo implícito. Ahora establecemos un color de relleno para el contexto actual y rellenamos un rectángulo del tamaño de todo el contexto.

    2. Ahora creamos una instancia de UIBezierPath en forma de círculo, que trazamos con un contorno grueso y rellenamos con un color diferente. Con esto concluye la parte de dibujo de nuestra creación de imágenes.

    3. Creamos una matriz de imágenes, con cada imagen instanciada a través del inicializador imageNamed: de UIImage. Es importante observar aquí que tenemos dos conjuntos de imágenes de rocas: rock1.png, rock2.png,... y rock1@2x.png, rock2@2x.png, siendo el segundo el doble de la resolución del primero. Una de las grandes características de UIImage es que, en tiempo de ejecución, el método imageNamed: busca automáticamente una imagen con el sufijo @2x que se presume que tiene doble resolución en un dispositivo Retina. Si hay uno disponible, ¡se usa! Si la imagen con sufijo está ausente o si el dispositivo no es Retina, se utiliza la imagen estándar. Ten en cuenta que no especificamos el sufijo de la imagen en el inicializador. El uso de imágenes de resolución simple y doble junto con la escala dependiente del dispositivo (como resultado de configurar scale en 0.0) garantiza que el tamaño real de los objetos en pantalla será el mismo. Naturalmente, las imágenes Retina serán más nítidas debido a la mayor densidad de píxeles.
Si ves las imágenes de rocas, notarás que las imágenes de doble resolución están volteadas con respecto a las de resolución única. Hice eso a propósito para poder confirmar que en tiempo de ejecución se estaban usando las imágenes con la resolución correcta, eso es todo. Normalmente los dos conjuntos de imágenes serían iguales (aparte de la resolución).
  1. Componemos nuestra imagen en un bucle colocando una roca elegida al azar de nuestro conjunto de imágenes en un punto aleatorio (restringido a estar en un círculo) en cada iteración. El método UIImage drawAtPoint: dibuja la imagen de la roca elegida en el punto especificado en el contexto de la imagen actual.
  2. Ahora extraemos un nuevo objeto UIImage del contenido del contexto de la imagen actual, llamando a UIGraphicsGetImageFromCurrentImageContext().
  3. La llamada a UIGraphicsEndImageContext() finaliza el contexto de la imagen actual y limpia la memoria.
  4. Por último, configuramos la imagen que creamos como la propiedad image de nuestro UIImageView y la mostramos en la pantalla.

Construye y ejecuta. La salida debería verse como la siguiente, solo que aleatoriamente diferente:

UIImage created by drawing and compositionUIImage created by drawing and compositionUIImage created by drawing and composition
UIImage creado por dibujo y composición

Al realizar pruebas en dispositivos Retina y que no son Retina o al cambiar el tipo de dispositivo en el Simulador en el menú Hardware, puedes asegurarte de que las rocas se muevan entre sí. Una vez más, solo hice esto para que pudiéramos confirmar fácilmente que se seleccionaría el conjunto correcto de imágenes en tiempo de ejecución. ¡Normalmente, no hay razón para que hagas esto!

Para resumir, a riesgo de demorar el punto, creamos una nueva imagen (un objeto UIImage) componiendo imágenes juntas que ya tenemos encima de un dibujo que dibujamos.

¡A la siguiente parte de la implementación!


3. Imágenes de tamaño redimensionable

Ten en cuenta la siguiente figura.

A callout (left), deconstructed to show how the image might be resized sensibly (right)A callout (left), deconstructed to show how the image might be resized sensibly (right)A callout (left), deconstructed to show how the image might be resized sensibly (right)
Una llamada (izquierda), deconstruida para mostrar cómo se puede cambiar el tamaño de la imagen con sensatez (derecha)

La imagen izquierda muestra una llamada o "burbuja de voz" similar a la que se ve en muchas aplicaciones de mensajería. Obviamente, nos gustaría que la llamada se expandiera o redujera según la cantidad de texto que contenga. Además, nos gustaría utilizar una sola imagen a partir de la cual podamos generar llamadas de cualquier tamaño. Si ampliamos toda la llamada por igual en todas las direcciones, toda la imagen se pixela o se vuelve borrosa según el algoritmo de cambio de tamaño que se esté usando. Sin embargo, ten en cuenta la forma en que se diseñó la imagen de llamada. Se puede expandir en ciertas direcciones sin pérdida de calidad simplemente replicando (en mosaico) píxeles a medida que avanzamos. Las formas de las esquinas no se pueden cambiar de tamaño sin cambiar la calidad de la imagen, pero, por otro lado, el medio es solo un bloque de píxeles de color uniforme que se puede hacer del tamaño que queramos. Los lados superior e inferior se pueden estirar horizontalmente sin perder calidad, y los lados izquierdo y derecho verticalmente. Todo esto se muestra en la imagen del lado derecho.

Por suerte para nosotros, UIImage tiene un par de métodos para crear imágenes redimensionables de este tipo. El que vamos a usar es resizableImageWithCapInsets:. Aquí los "límites de inserciones" representan las dimensiones de las esquinas no estirables de la imagen (comenzando desde el margen superior y moviéndose en sentido contrario a las agujas del reloj) y se encapsulan en un struct de tipo UIEdgeInsets compuesta de cuatro float:

1
typedef struct {
2
 float top, left, bottom, right;
3
 } UIEdgeInsets;

La siguiente figura debería aclarar lo que representan estos números:

¡Explotemos UIImages redimensionables para crear una clase simple que nos permita incluir cualquier cantidad de texto en una imagen redimensionable!

Crea una subclase NSObject denominada Note e introduce el siguiente código en Note.h y Note.m respectivamente.

1
 #import <Foundation/Foundation.h>

2
3
@interface Note : NSObject
4
5
@property (nonatomic, readonly) NSString *text;
6
@property (nonatomic, readonly) UIImageView *noteView;
7
8
9
- (id)initWithText:(NSString *)text fontSize:(float)fontSize noteChrome:(UIImage *)img edgeInsets:(UIEdgeInsets)insets maximumWidth:(CGFloat)width topLeftCorner:(CGPoint)corner;
10
11
@end
1
#import "Note.h"

2
3
@implementation Note
4
5
- (id)initWithText:(NSString *)text fontSize:(float)fontSize noteChrome:(UIImage *)img edgeInsets:(UIEdgeInsets)insets  maximumWidth:(CGFloat)width topLeftCorner:(CGPoint)corner
6
{
7
    if (self = [super init])
8
    {
9
#define LARGE_NUMBER 10000 // just a large (but arbitrary) number because we don't want to impose any vertical constraint on our note size

10
        
11
        _text = [NSString stringWithString:text];
12
        CGSize computedSize = [text sizeWithFont:[UIFont systemFontOfSize:fontSize] constrainedToSize:CGSizeMake(width, LARGE_NUMBER) lineBreakMode:NSLineBreakByWordWrapping];
13
 
14
        UILabel *textLabel = [[UILabel alloc] init];
15
        textLabel.font = [UIFont systemFontOfSize:fontSize];
16
        textLabel.text = self.text;
17
        textLabel.numberOfLines = 0; // unlimited number of lines

18
        textLabel.lineBreakMode = NSLineBreakByWordWrapping;
19
20
        textLabel.frame = CGRectMake(insets.left, insets.top, computedSize.width , computedSize.height);
21
        _noteView = [[UIImageView alloc] initWithFrame:CGRectMake(corner.x, corner.y, textLabel.bounds.size.width+insets.left+insets.right, textLabel.bounds.size.height+insets.top+insets.bottom)];
22
        _noteView.image = [img resizableImageWithCapInsets:insets];
23
        
24
        [_noteView addSubview:textLabel];
25
        
26
    }
27
    return self;
28
    
29
}
30
31
@end

El método de inicialización para Note, -initWithText:fontSize:noteChrome:edgeInsets:maximumWidth:topLeftCorner: toma varios parámetros, incluida la cadena de texto que se mostrará, el tamaño de fuente, la nota "chrome" (que es el UIImage redimensionable que rodeará el texto), los límites de las inserciones, el ancho máximo que puede tener la vista de la imagen de la nota y la esquina superior izquierda del marco de la nota.

Una vez inicializado, la propiedad noteView de la clase Note (de tipo UIImageView) es el elemento de interfaz de usuario que mostraremos en la pantalla.

La implementación es bastante simple. Explotamos un método muy útil de la categoría NSString en UIKit, sizeWithFont:constrainedToSize:lineBreakMode:, el cual calcula el tamaño que ocupará un bloque de texto en la pantalla, dados ciertos parámetros. Una vez hecho esto, construimos una etiqueta de texto (UILabel) y la rellenamos con el texto proporcionado. Teniendo en cuenta los tamaños de inserción y el tamaño de texto calculado, asignamos a la etiqueta un marco adecuado, así como hacer que el image de nuestro noteView sea lo suficientemente grande (utilizando el método resizableImageWithCapInsets) para que la etiqueta se ajuste cómodamente en la parte superior del área interior de la imagen.

En la siguiente figura, la imagen de la izquierda representa cómo se vería una nota típica que contiene algunas líneas de texto.

Using the smallest possible image for constructing a resizable imageUsing the smallest possible image for constructing a resizable imageUsing the smallest possible image for constructing a resizable image
Utilizando la imagen más pequeña posible para construir una imagen redimensionable

Ten en cuenta que el interior no tiene nada de interés. De hecho, podemos "recortar" la imagen al mínimo (como se muestra a la derecha) eliminando todos los píxeles del interior con un software de edición de imágenes. De hecho, en la documentación, Apple recomienda que para un mejor rendimiento, el área interior debe estar en mosaico de 1 x 1 píxeles. Eso es lo que representa la pequeña imagen divertida de la derecha, y esa es la que vamos a pasar a nuestro inicializador de Note. Asegúrate de que se agregó a tu proyecto como squeezednote.png cuando arrastraste la carpeta Imágenes a tu proyecto.

En ViewController.m, ingresa la instrucción #import "Note.h" en la parte superior. Comenta el formulario anterior viewDidLoad: e introduce lo siguiente:

1
- (void)viewDidLoad
2
{
3
    [super viewDidLoad];
4
    NSString *text1 = @"Hi!";
5
    NSString *text2 = @"I size myself according to my content!";
6
    NSString *text3 = @"Standard boring random text: Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.";
7
    
8
    UIImage *noteChrome = [UIImage imageNamed:@"squeezednote"];
9
    UIEdgeInsets edgeInsets = UIEdgeInsetsMake(37, 12, 12, 12);
10
    Note *note1 = [[Note alloc] initWithText:text1 fontSize:25.0 noteChrome:noteChrome edgeInsets:edgeInsets maximumWidth:300 topLeftCorner:CGPointMake(10, 10)];
11
    Note *note2 = [[Note alloc] initWithText:text2 fontSize:30.0 noteChrome:noteChrome edgeInsets:edgeInsets maximumWidth:300 topLeftCorner:CGPointMake(200, 10)];
12
    Note *note3 = [[Note alloc] initWithText:text3 fontSize:16.0 noteChrome:noteChrome edgeInsets:edgeInsets maximumWidth:300 topLeftCorner:CGPointMake(10, 200)];
13
    
14
    [self.view addSubview:note1.noteView];
15
    [self.view addSubview:note2.noteView];
16
    [self.view addSubview:note3.noteView];
17
18
}

Simplemente estamos creando objetos Note con una cantidad diferente de texto. Construye, ejecuta y observa cómo el "chrome" alrededor de la nota cambia de tamaño para acomodar el texto dentro de sus límites.

Nicely resizing noteNicely resizing noteNicely resizing note
Nota bien redimensionada

En aras de la comparación, así es como se vería la salida si "squeezednote.png" se configurara como un UIImage "normal" instanciado con imageNamed: y se cambiara de tamaño por igual en todas las direcciones.

Nota mal redimensionada

Es cierto que en realidad no usaríamos una imagen "mínima" como "squeezednote" a menos que estuviéramos usando imágenes redimensionables en primer lugar, por lo que el efecto mostrado en la captura de pantalla anterior es muy exagerado. Sin embargo, el problema de desenfoque definitivamente estaría allí.

¡A la parte final del tutorial!


4. Imágenes animadas

Por imagen animada, en realidad me refiero a una secuencia de imágenes 2D individuales que se muestran sucesivamente. Básicamente, esta es solo la animación de sprites que se usa en la mayoría de los juegos 2D. UIImage tiene un método inicializador animatedImageNamed:duration: al que le pasas una cadena que representa el prefijo de la secuencia de imágenes a animar, por lo que si tus imágenes se llaman "robot1.png", "robot2.png", ..., "robot60.png", simplemente pasarías la cadena "robot" a este método. La duración de la animación también se pasa. ¡Eso es todo! Cuando la imagen se agrega a un UIImageView, se anima continuamente en la pantalla. Implementemos un ejemplo.

Comenta la versión anterior de viewDidLoad: e ingresa la siguiente versión.

1
 - (void)viewDidLoad
2
{
3
    [super viewDidLoad];
4
    ivs = [NSMutableArray array];
5
    img = [UIImage animatedImageNamed:@"explosion" duration:2.0];
6
    UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(explosion:)];
7
    [self.view addGestureRecognizer:tap];
8
}
9
10
11
- (void)explosion:(UITapGestureRecognizer *)t
12
{
13
    CGPoint loc = [t locationInView:self.view];
14
    
15
    for (UIImageView *v in ivs)
16
    {
17
        if ([v pointInside:[v convertPoint:loc fromView:self.view] withEvent:nil])
18
        {
19
            [ivs removeObject:v];
20
            [v removeFromSuperview];
21
            return;
22
        }
23
    }
24
    UIImageView *v = [[UIImageView alloc] initWithImage:img];
25
    v.center = loc;
26
    [ivs addObject:v];
27
    [self.view addSubview:v];
28
}

Agregamos un conjunto de imágenes PNG a nuestro proyecto, explosion1.png a explosion81.png, que representan una secuencia animada de una ardiente explosión. Nuestro código es bastante simple. Detectamos un toque en la pantalla y colocamos una nueva animación de explosión en el punto de toque, o si ya había una explosión en ese punto, la eliminamos. Ten en cuenta que el código esencial consiste en simplemente crear una imagen animada a través de animatedImageNamed: a la que le pasamos la cadena @"explosion" y un valor flotante para la duración.

Tendrás que ejecutar la aplicación en el simulador o en un dispositivo tú mismo para disfrutar de la exhibición de fuegos artificiales, pero esta es una imagen que captura un solo fotograma de la acción, con varias explosiones al mismo tiempo.

Animated explosionsAnimated explosionsAnimated explosions
Explosiones animadas

Es cierto que si estuvieras desarrollando un juego de acción de alto ritmo de trabajo, como un juego de disparos o un juego de plataformas de desplazamiento lateral, entonces el soporte de UIImage para imágenes animadas parecería bastante antiguo. No serían tu enfoque preferido para aplicar la animación. Eso no es realmente para lo que está diseñado el UIImage, pero en otros escenarios menos exigentes, ¡podría ser lo mejor! Dado que la animación se ejecuta continuamente hasta que eliminas la imagen animada o la vista de imagen de la interfaz, puedes hacer que las animaciones se detengan después de un intervalo de tiempo prescrito enviando un mensaje retrasado con -performSelector:withObject:afterDelay: o usa un NSTimer.


Conclusión

En este tutorial, analizamos algunas características útiles pero menos conocidas de la clase UIImage que pueden resultar útiles. Te sugiero que revises la Referencia de clase UIImage porque algunas de las características que discutimos en este tutorial tienen más opciones. Por ejemplo, las imágenes se pueden componer utilizando una de varias opciones de fusión. Las imágenes redimensionables se pueden configurar en uno de dos modos de redimensionamiento, mosaico (que es el que usamos implícitamente) o estiramiento. Incluso las imágenes animadas pueden tener inserciones. No hablamos del tipo opaco CGImage subyacente que UIImage ajusta. Debes lidiar con CGImage si tu programa en el nivel Core Graphics, que es la API basada en C que se encuentra un nivel por debajo de UIKit en el framework de iOS. Core Graphics es más poderoso para programar que UIKit, pero no tan fácil. Tampoco hablamos de imágenes creadas con datos de un objeto Core Image, ya que eso tendría más sentido en un tutorial de Core Image.

Espero que este tutorial te haya resultado útil. ¡Sigue codificando!