Preview dell’Autolayout

Fondamentale per l’uso e l’apprendimento dell’Autolayout è la Preview. Ecco la procedura per attivarla:

  1. Apri uno Storyboard.
  2. Apri l’Assistant Editor
  3. Nell’Assistant Editor, clicca sul quarto pulsante (quello che solitamente dice Manual/Automatic/ecc.).
  4. Scegli “Preview”, in fondo all’elenco.
  5. Ora in basso a destra si può scegliere i dispositivi su cui mostrare la Preview.

Animare il cambiamento di una vista

Questa tecnica è utilizzabile sia per creare dissolvenze in uno slideshow di UIImage che per animare la variazione di UI in una UIView.

1
2
3
4
5
6
7
8
/* qui il codice che modifica la vista */
 
CATransition *transition = [CATransition animation];
transition.duration = 1.0f;
transition.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
transition.type = kCATransitionFade;
 
[aView.layer addAnimation:transition forKey:nil];
/* qui il codice che modifica la vista */

CATransition *transition = [CATransition animation];
transition.duration = 1.0f;
transition.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
transition.type = kCATransitionFade;

[aView.layer addAnimation:transition forKey:nil];

Si può aggiungere un .subtype, ad esempio se si vuole far scorrere la vista e decidere la direzione:

1
2
3
4
5
CATransition *transition = [CATransition animation];
transition.duration = .2f;
transition.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
transition.type = kCATransitionMoveIn;
transition.subtype = kCATransitionFromLeft;
CATransition *transition = [CATransition animation];
transition.duration = .2f;
transition.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
transition.type = kCATransitionMoveIn;
transition.subtype = kCATransitionFromLeft;

(fonte)

Creare una immagine a partire da una mappa

Le funzioni di Quartz sono veramente molto utili perché ci permettono di creare delle immagini e inserirle nell’interfaccia in maniera molto semplice ed efficace. Se però dobbiamo fare il passaggio inverso, ovvero ottenere una immagine a partire dall’interfaccia, come dobbiamo procedere?

In realtà questo è ancora più semplice e, se ad esempio stiamo parlando di un mapView , possiamo usare il seguente codice:

1
2
3
4
UIGraphicsBeginImageContextWithOptions(mapView.bounds.size, NO, 0.0f);
    [mapView.layer renderInContext:UIGraphicsGetCurrentContext()];
    UIImage *mapImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
UIGraphicsBeginImageContextWithOptions(mapView.bounds.size, NO, 0.0f);
    [mapView.layer renderInContext:UIGraphicsGetCurrentContext()];
    UIImage *mapImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();

dove assegnamo al contex il render di un layer, e poi usiamo il contex per generare una UIImage. Tutto molto semplice.

Rotazioni e ancoraggi per animazioni e gesti

Quando si anima un oggetto dell’interfaccia in iOS si possono eseguire una serie molto ampia di movimenti, cambi di colore, dimensioni e quant’altro. Quando parliamo però di rotazioni talvolta può essere utile ridefinire il punto attorno al quale l’oggetto deve eseguire la rotazione.

Infatti, anche se non ci si pensa spesso, una rotazione senza ridefinire altro che l’angolo, avviene attorno al centro dell’oggetto, ma molte rotazioni non sono corrette se avvengono in questa maniera (pensiamo ad una gamba che calcia un pallone, una mazza da golf che colpisce una pallina o la testa di un personaggio che fa cenno di “no”).

Iniziamo con una rotazione eseguita con Quartz:

1
2
3
4
5
6
7
8
9
10
<code>#import <QuartzCore/QuartzCore.h>
 
CABasicAnimation *fullRotation;
fullRotation = [CABasicAnimation animationWithKeyPath:@"transform.rotation"];
fullRotation.fromValue = [NSNumber numberWithFloat:0];
fullRotation.toValue = [NSNumber numberWithFloat:((360*M_PI)/180)];
fullRotation.duration = 3.5f;
fullRotation.repeatCount = MAXFLOAT;
 
[view.layer addAnimation:fullRotation forKey:@"360"];   </code>
<code>#import <QuartzCore/QuartzCore.h>

CABasicAnimation *fullRotation;
fullRotation = [CABasicAnimation animationWithKeyPath:@"transform.rotation"];
fullRotation.fromValue = [NSNumber numberWithFloat:0];
fullRotation.toValue = [NSNumber numberWithFloat:((360*M_PI)/180)];
fullRotation.duration = 3.5f;
fullRotation.repeatCount = MAXFLOAT;

[view.layer addAnimation:fullRotation forKey:@"360"];   </code>

Mentre, se vogliamo cambiare il punto di ancoraggio dobbiamo cambiare l’anchorPoint e il position del layer (proprietà su cui agisce CoreGraphics:)

1
2
3
4
self.imgView.layer.anchorPoint = CGPointMake(0.0,1.0);
self.imgView.layer.position = CGPointMake(100,200.0);
CGAffineTransform cgaRotateHr = CGAffineTransformMakeRotation(-(3.141/4));
[self.imgView setTransform:cgaRotateHr];
self.imgView.layer.anchorPoint = CGPointMake(0.0,1.0);
self.imgView.layer.position = CGPointMake(100,200.0);
CGAffineTransform cgaRotateHr = CGAffineTransformMakeRotation(-(3.141/4));
[self.imgView setTransform:cgaRotateHr];

Per una chiara spiegazione dei riferimenti che determinano il punto di ancoraggio delle rotazioni, vedere questo articolo.

Riguardo i gesti invece, è spesso necessario eseguire manipolazioni multiple di oggetti.

Innanzitutto un buon esempio per le gesture che possono determinare le manipolazioni comunemente conosciuto come scale, move e rotate si può trovare a questa pagina, mentre qui trovate una spiegazione di come effettuare il reset di rotazione e scala.

Infine un piccolo suggerimento su come eseguire contemporaneamente più trasformazioni su un oggetto:

1
2
3
4
5
6
// Rotate 45 degrees
CGAffineTransform rotate = CGAffineTransformMakeRotation(45*(M_PI/180));
// Move to the left
CGAffineTransform translate = CGAffineTransformMakeTranslation(-50,0);
// Apply them to a view
self.view.transform = CGAffineTransformConcat(translate, rotate);
// Rotate 45 degrees
CGAffineTransform rotate = CGAffineTransformMakeRotation(45*(M_PI/180));
// Move to the left
CGAffineTransform translate = CGAffineTransformMakeTranslation(-50,0);
// Apply them to a view
self.view.transform = CGAffineTransformConcat(translate, rotate);

 

AlertViewStyle

Fino a poco tempo fa, prima dell’avvento di iOS 5, qualsiasi elemento di un UIAlertView che non fosse il title, il message e i buttons, doveva essere costruito a mano ed aggiunto alla view. Adesso è possibile utilizzare la proprietà alertViewStyle che permette di creare velocemente un campo di testo (sia normale che per le password) o due campi di testo login (quindi tipicamente nome utente e password). Con l’avvento di iOS 7, inoltre l’uso dell’alertViewStyle diventa necessario per evitare problemi nella costruzione dell’interfaccia.

Il semplice e chiaro articolo “iOS 5 SDK: UIAlertView Text Input and Validation” spiega in dettaglio come usare questa nuova tecnica e a come fare attenzione ad eventuali problemi che possono insorgere.

Dimensione massima delle icone per la TabBar

Tutti gli ottimi cheat sheet dedicati all’interfaccia di iOS citano le dimensioni ufficiali date da Apple per quanto riguarda le icone. Ma forse avrete notato che, ad esempio per le icone del UITabBarViewController, lo spazio realmente a dispisizione è più grande dell’ufficiale 30×30 punti.

Magari potrà essere utile a qualcun altro, oltre che a me, allora ecco le dimensioni massime di una icona nella TabBar: 40×32. Oltre l’icona verrà tagliata in basso e a sinistra.

TIP: Convertire una UIImage in NSData

Un semplice tip nel caso voleste convertire una immagine in un paccheto dati NSData:

1
2
UIImage *img = [UIImage imageNamed:@"some.png"];
NSData *dataObj = UIImageJPEGRepresentation(img, 1.0);
UIImage *img = [UIImage imageNamed:@"some.png"];
NSData *dataObj = UIImageJPEGRepresentation(img, 1.0);

(fonte)

Ritagliare una UIImage

Usando le librerie Core Graphics si possono manipolare in vario modo le immagini. Per ritagliare una immagine è sufficiente definire un rettangono CGRect e usare questo codice:

1
2
3
4
CGImageRef imageRef = CGImageCreateWithImageInRect([largeImage CGImage], cropRect);
 
[UIImageView setImage:[UIImage imageWithCGImage:imageRef]]; 
CGImageRelease(imageRef);
CGImageRef imageRef = CGImageCreateWithImageInRect([largeImage CGImage], cropRect);

[UIImageView setImage:[UIImage imageWithCGImage:imageRef]]; 
CGImageRelease(imageRef);

Sostituendo a UIImageView l’UIImageView corretta. Il comando CGImageRelease(imageRef) serve solo nel caso non si stia usando l’ARC.

Salvare una UIImage contenente altre UIImage

Se capita di dover fondere insieme delle immagini (di solito PNG) sovrapposte ad una immagine di sfondo, allora servirà un metodo apposito che “appiattisca” tutto in una unica immagine. Ecco un metodo fornito da Apple stessa come esempio:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
- (UIImage*)imageFromView:(UIView *)view 
{
    // Create a graphics context with the target size
    // On iOS 4 and later, use UIGraphicsBeginImageContextWithOptions to take the scale into consideration
    // On iOS prior to 4, fall back to use UIGraphicsBeginImageContext
    CGSize imageSize = [view bounds].size;
    if (NULL != UIGraphicsBeginImageContextWithOptions)
        UIGraphicsBeginImageContextWithOptions(imageSize, NO, 0);
    else
        UIGraphicsBeginImageContext(imageSize);
 
    CGContextRef context = UIGraphicsGetCurrentContext();
 
    // -renderInContext: renders in the coordinate space of the layer,
    // so we must first apply the layer's geometry to the graphics context
    CGContextSaveGState(context);
    // Center the context around the view's anchor point
    CGContextTranslateCTM(context, [view center].x, [view center].y);
    // Apply the view's transform about the anchor point
    CGContextConcatCTM(context, [view transform]);
    // Offset by the portion of the bounds left of and above the anchor point
    CGContextTranslateCTM(context,
                          -[view bounds].size.width * [[view layer] anchorPoint].x,
                          -[view bounds].size.height * [[view layer] anchorPoint].y);
 
    // Render the layer hierarchy to the current context
    [[view layer] renderInContext:context];
 
    // Restore the context
    CGContextRestoreGState(context);
 
    // Retrieve the screenshot image
    UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
 
    UIGraphicsEndImageContext();
 
    return image;
}
- (UIImage*)imageFromView:(UIView *)view 
{
    // Create a graphics context with the target size
    // On iOS 4 and later, use UIGraphicsBeginImageContextWithOptions to take the scale into consideration
    // On iOS prior to 4, fall back to use UIGraphicsBeginImageContext
    CGSize imageSize = [view bounds].size;
    if (NULL != UIGraphicsBeginImageContextWithOptions)
        UIGraphicsBeginImageContextWithOptions(imageSize, NO, 0);
    else
        UIGraphicsBeginImageContext(imageSize);

    CGContextRef context = UIGraphicsGetCurrentContext();

    // -renderInContext: renders in the coordinate space of the layer,
    // so we must first apply the layer's geometry to the graphics context
    CGContextSaveGState(context);
    // Center the context around the view's anchor point
    CGContextTranslateCTM(context, [view center].x, [view center].y);
    // Apply the view's transform about the anchor point
    CGContextConcatCTM(context, [view transform]);
    // Offset by the portion of the bounds left of and above the anchor point
    CGContextTranslateCTM(context,
                          -[view bounds].size.width * [[view layer] anchorPoint].x,
                          -[view bounds].size.height * [[view layer] anchorPoint].y);

    // Render the layer hierarchy to the current context
    [[view layer] renderInContext:context];

    // Restore the context
    CGContextRestoreGState(context);

    // Retrieve the screenshot image
    UIImage *image = UIGraphicsGetImageFromCurrentImageContext();

    UIGraphicsEndImageContext();

    return image;
}

Alcune righe di codice relative alla traslazione possono essere superflue, mentre è fondamentale importare Quartz:

1
#import <QuartzCore/QuartzCore.h>
#import <QuartzCore/QuartzCore.h>

Zoomare con il gesto pinch senza interruzioni

Quando si usa UIPinchGestureRecognizer ci si accorge subito che appena viene ingrandito una immagine in una view, questo ritorna alle dimensioni iniziali appena ricominciamo ad ingrandirlo. Fare successivi gesti pinch (pizzicare) per ingrandire una immagine può risultare difficoltoso.

Per risolvere questo problema dobbiamo agire sulla proprietà scale della gesture usando questo codice quando implementiamo l’ingrandimento dell’immagine:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
- (void)pinch:(UIPinchGestureRecognizer *)gesture {
    if (gesture.state == UIGestureRecognizerStateEnded
        || gesture.state == UIGestureRecognizerStateChanged) {
        NSLog(@"gesture.scale = %f", gesture.scale);
 
        CGFloat currentScale = self.frame.size.width / self.bounds.size.width;
        CGFloat newScale = currentScale * gesture.scale;
 
        if (newScale < MINIMUM_SCALE) {
            newScale = MINIMUM_SCALE;
        }
        if (newScale > MAXIMUM_SCALE) {
            newScale = MAXIMUM_SCALE;
        }
 
        CGAffineTransform transform = CGAffineTransformMakeScale(newScale, newScale);
        self.transform = transform;
        gesture.scale = 1;
    }
}
- (void)pinch:(UIPinchGestureRecognizer *)gesture {
    if (gesture.state == UIGestureRecognizerStateEnded
        || gesture.state == UIGestureRecognizerStateChanged) {
        NSLog(@"gesture.scale = %f", gesture.scale);

        CGFloat currentScale = self.frame.size.width / self.bounds.size.width;
        CGFloat newScale = currentScale * gesture.scale;

        if (newScale < MINIMUM_SCALE) {
            newScale = MINIMUM_SCALE;
        }
        if (newScale > MAXIMUM_SCALE) {
            newScale = MAXIMUM_SCALE;
        }

        CGAffineTransform transform = CGAffineTransformMakeScale(newScale, newScale);
        self.transform = transform;
        gesture.scale = 1;
    }
}

I passaggi fondamentali sono il calcolo della newScale e l’impostazione finale della gesture con scale di nuovo settata a 1, pronto per un nuovo pinch che partirà, questa volta dalla nuova dimensione acquisita.