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

Error: Could not load the “…” image referenced from a nib in the bundle with identifier “…”

Può capitare di aggiungere una immagine, o una qualsiasi altra risorsa (anche file di codice .h e .m) ma ricevere questo errore in fase di compilazione:

1
Could not load the "..." image referenced from a nib in the bundle with identifier "…"
Could not load the "..." image referenced from a nib in the bundle with identifier "…"

Questo accade perché il progetto contiene la risorsa ma non è correttamente dichiarata nella sezione “Copy Bundle Resources” che si trova nel “Build Phases” del Target. Basterà quindi aggiungerla manualmente tramite il piccolo pulsante “+” e da quel momento la fase di compilazione non restituirà più questo errore.

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

 

Copia&Incolla su iOS

UIPasteboard è la classe che Apple mette a disposizione degli sviluppatori per usufruire della comoda funzione di copia e incolla via codice. I cosiddetti “appunti” possono essere richiamati da qualunque applicazione ed è questa la loro principale fonte di utilità.

Per fare questo si possono usare una delle seguenti 5 proprietà:

1
2
3
4
NSString *string = pasteboard.string;
UIImage *image = pasteboard.image;
NSURL *url = pasteboard.URL;
UIColor *color = pasteboard.color;
NSString *string = pasteboard.string;
UIImage *image = pasteboard.image;
NSURL *url = pasteboard.URL;
UIColor *color = pasteboard.color;

Per una guida esaustiva, vedere qui.

Sommare due NSNumber

Può sembrare una domanda banale, ma eseguire operazioni su un NSNumber richiede un minimo di attenzione dal momento che stiamo parlandoci oggetti e non di variabili scalari.

Per sommare due NSNumber, infatti, bisogna prima trasformarli in scalari (ad esempio float) e poi usare il risultato per creare un nuovo NSNumber object. Ecco l’esempio:

1
NSNumber *sum = [NSNumber numberWithFloat:([one floatValue] + [two floatValue])];
NSNumber *sum = [NSNumber numberWithFloat:([one floatValue] + [two floatValue])];

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]