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.

Creare una MKAnnotation

MKAnnotation non è una classe, ma è un protocollo a cui deve aderire la classe che andremo a realizzare per visualizzare l’annotazione sulla mappa.

Per questo dobbiamo aggiungere una nuova coppia di file al progetto creando la classe (ad esempio) MyAnnotation. Il file di intestazione e implementazione saranno qualcosa di simile a quanto segue:

1
2
3
4
5
6
7
@interface MyAnnotation : NSObject<MKAnnotation> {
    CLLocationCoordinate2D coordinate;
}
 
@property (nonatomic, readonly) CLLocationCoordinate2D coordinate;
 
- (id) initWithCoordinate:(CLLocationCoordinate2D)coord;
@interface MyAnnotation : NSObject<MKAnnotation> {
    CLLocationCoordinate2D coordinate;
}

@property (nonatomic, readonly) CLLocationCoordinate2D coordinate;

- (id) initWithCoordinate:(CLLocationCoordinate2D)coord;

e

1
2
3
4
5
- (id) initWithCoordinate:(CLLocationCoordinate2D)coord
{
    coordinate = coord;
    return self;
}
- (id) initWithCoordinate:(CLLocationCoordinate2D)coord
{
    coordinate = coord;
    return self;
}

Poi sarà sufficiente aggiungere l’annotazione alla mappa:

1
2
3
MyAnnotation * annotation = [[MyAnnotation alloc] initWithCoordinate:coordinate];
 
[self.mapView addAnnotation:annotation];
MyAnnotation * annotation = [[MyAnnotation alloc] initWithCoordinate:coordinate];

[self.mapView addAnnotation:annotation];

Se si vogliono anche titolo e sottotitolo nel callout dell’annotazione, sarà sufficiente aggiungere le proprietà title e subtitle.

Resettare lo zoom di una UIScrollView

Resettare lo zoom di una UISCrollView può essere complicato, dal momento che la proprietà scale è relativa e non assoluta. Un metodo per dettare lo zoom quindi non può prescindere dal resettare tutte quelle proprietà che concorrono alla determinazione della scala.

Ecco un metodo interessante (e funzionante):

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
- (void)viewDidLoad {
    NSLog(@"VC view did load");
    [super viewDidLoad];
    // ... other work
    originalImagePos = myImageView.center;
}
 
//...
 
-(void)resetImageZoom {
    NSLog(@"Resetting any image zoom");
    CGAffineTransform transform = CGAffineTransformMakeScale(1.0, 1.0);
    myImageView.transform = transform;
    [myScrollView setContentSize:CGSizeZero];
    myImageView.center = originalImagePos;
}
- (void)viewDidLoad {
    NSLog(@"VC view did load");
    [super viewDidLoad];
    // ... other work
    originalImagePos = myImageView.center;
}

//...

-(void)resetImageZoom {
    NSLog(@"Resetting any image zoom");
    CGAffineTransform transform = CGAffineTransformMakeScale(1.0, 1.0);
    myImageView.transform = transform;
    [myScrollView setContentSize:CGSizeZero];
    myImageView.center = originalImagePos;
}

A cosa serve il pulsante Exit nello Storyboard?

Se il nostro Storyboard lancia dei segue consecutivi, diciamo dal viewController A al B e poi al C, allora potremmo avere la necessità di tornare al viewController A (questo indipendentemente dal tipo di navigazione), ovvero eseguire il cosiddetto unwind. Per fare questo dobbiamo prima creare in A il metodo

1
2
3
4
- (IBAction)done:(UIStoryboardSegue *)segue {
   // eseguire qualsiasi cosa, anche niente 
 
}
- (IBAction)done:(UIStoryboardSegue *)segue {
   // eseguire qualsiasi cosa, anche niente 

}

e poi collegare in C un UIButton al pulsante Exit in fondo al viewController stesso: compariranno tutti i metodi validi come il precedente, permettendo, al tocco del button di passare dal viewController C all’A.

Se invece colleghiamo il viewController stesso all’Exit (partendo dall’icona gialla sotto l’interfaccia fino all’icona verde dell’Exit) possiamo creare un unwind richiamabile via codice (dopo aver assegnato un Identifier nell’Attribute Inspector), usando il metodo performSegueWithIdentifier.

 

 

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);

 

Evitare notification duplicate

Talvolta, per semplificare il codice da utilizzare per un’app, è comodo ricorrere alle notifiche dell’NSNotificationCenter. Una volta aggiunto un observer, però, è buona norma evitare che venga aggiunto una seconda volta, duplicando il codice eseguito una volta innescata la notifica.

Per evitare questo problema è sufficiente ricordarsi di rimuovere l’observer prima di aggiungerlo, poiché rimuovere un observer che non esiste non crea nessun tipo di inconveniente:

1
[[NSNotificationCenter defaultCenter] removeObserver:self name:foo object:bar]
[[NSNotificationCenter defaultCenter] removeObserver:self name:foo object:bar]

Delegato del mailComposer

Un piccolo reminder per sottolineare quanto i piccoli errori possano far perdere molto tempo: quando si implementa il mailComposer di Apple in una apple, ricordarsi di impostare

1
mailComposer.mailComposeDelegate = self;
mailComposer.mailComposeDelegate = self;

e non

1
mailComposer.delegate = self;
mailComposer.delegate = self;

nel secondo caso,infatti, il composer non comunicherà di aver terminato il proprio al viewcontroller che lo ha visualizzato e questo non lo discetterà.

Bloccare lo scroll in una sola direzione

Usando le UIScrollView può capitare di voler disabilitare lo scroll solo orizzontale o solo verticale anche se l’app, tramite qualche funzione, dev’essere in grado di muovere in ogni direzione il contenuto della scrollView. Per fare questo possiamo usare il seguente codice:

1
2
3
4
- (void)scrollViewDidScroll:(UIScrollView *)aScrollView
{
    [aScrollView setContentOffset: CGPointMake(aScrollView.contentOffset.x, 0)];
}
- (void)scrollViewDidScroll:(UIScrollView *)aScrollView
{
    [aScrollView setContentOffset: CGPointMake(aScrollView.contentOffset.x, 0)];
}

In questo modo lo scroll avviene solo in orizzontale. Se lo volessimo, potremmo impostare la coordinata y su un valore diverso da zero. Invertendo x con y, invece, il blocco avverrebbe nella direzione opposta.

Comunicare con un UIContainerViewController

Gli UIContainerViewController sono dei contenitori utilissimi per separare un’interfaccia grafica in parti più piccole e meglio gestibili. Un’enorme semplificazione si può avere nell’uso di una UIScrollView, la cui view contenuta, per essere interamente visualizzabile nello Storyboard, può essere comodo inserirla in una container view.

I problemi nascono quando si vuol fare comunicare il container view con il controller principale che lo contiene. A questo proposito la soluzione più comoda, ma non l’unica, è l’uso dei protocolli. Per uno nemo su come realizzare la struttura del protocollo ecco un utile link.

 

 

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.