Un PrefixHeader per tutti i Framework

Quante volte è capitato di dover usare vari framework e librerie in un progetto Xcode e dopo averli aggiunti al Target abbiamo dovuto importarli tutti nei vari ViewController che ne facevano uso? Ogni volta con righe del tipo

1
2
#import <Framework/Framework.h>
#import "Library.h"
#import <Framework/Framework.h>
#import "Library.h"

Creando un file PrefixHeader.pch questa operazione diventa semplicissima, dovendo elencare in esso tutti i framework che devono essere a disposizione di tutte le classi:

1
2
3
4
@import UIKit;
@import StoreKit;
@import Foundation;
#import "Library.h"
@import UIKit;
@import StoreKit;
@import Foundation;
#import "Library.h"

Per creare un file PrefixHeader.pch è sufficiente aggiungere un nuovo file e scegliere PrefixHeader dalla sezione Other. Poi bisogna aggiungere nei Build Setting il corrette percorso del file alla chiave Prefix Header (ovvero GCC_PREFIX_HEADER). Un possibile percorso potrebbe essere:

1
$(SRCROOT)/$(PRODUCT_NAME)/PrefixHeader.pch
$(SRCROOT)/$(PRODUCT_NAME)/PrefixHeader.pch

Se poi impostiamo Precompile Prefix Header (GCC_PRECOMPILE_PREFIX_HEADER) a YES, allora guadagneremo in performance durante la compilazione ma dovremo lanciare un Clean ogni qualvolta dovessimo fare un cambiamento a qualche libreria personalizzata (come un oggetto custom).

Questa tecnica è quindi utilizzabile tranquillamente per tutti quei framework che non variano mai fra un build e l’altro. Altrimenti basterà impostare a NO la precedente chiave e potremo tranquillamente includere tutti gli oggetti che vogliamo.

Git: file is missing

Quando si usare git su Xocde può capitare che qualcosa vada storto e dei file, magari semplicemente spostati da una cartella ad un altra, risultino mancanti.

È questo il caso di quando appare nella schermata del Commit un punto interrogativo a fianco ad un file e nell’editor di fianco il messaggio:

1
file is missing
file is missing

Ovviamente questo può accadere anche ad una cartella.

Per ovviare a questo fastidioso problema, che provoca anche dei relativi Warning in fase di compilazione, è sufficiente aprire il Terminale, posizionarsi nella cartella del progetto, e forzare un commit con questo comando:

1
git commit -a -m "Commit Message"
git commit -a -m "Commit Message"

A volte, se avete più Xocde installati, potrebbe essere necessario specificare dove si trova la versione che state usando:

1
sudo xcode-select --switch /Applications/Xcode\ 8/Xcode.app
sudo xcode-select --switch /Applications/Xcode\ 8/Xcode.app

Dichiarare un block type per un completion handerl

In relazione all’articolo Creare un completion handler, si può precisare quanto illustrato qui:

La dichiarazione del block type è un modo per rendere più facilmente modificabile la signature del metodo, poiché nel primo caso basta cambiare la definizione del blocco, senza toccare il metodo.

1
2
3
4
typedef BOOL (^SomeBlockType)(id object, NSUInteger idx, BOOL *stop);
 
- (void)collectionToCheck:(SomeBlockType)checkerBlock;
- (void)singleItemToCheck:(SomeBlockType)checkerBlock;
typedef BOOL (^SomeBlockType)(id object, NSUInteger idx, BOOL *stop);

- (void)collectionToCheck:(SomeBlockType)checkerBlock;
- (void)singleItemToCheck:(SomeBlockType)checkerBlock;

versus:

1
2
- (void)collectionToCheck:(BOOL(^)(id object, NSUInteger idx, BOOL *stop)) checkerBlock;
- (void)singleItemToCheck:(BOOL(^)(id object, NSUInteger idx, BOOL *stop)) checkerBlock;
- (void)collectionToCheck:(BOOL(^)(id object, NSUInteger idx, BOOL *stop)) checkerBlock;
- (void)singleItemToCheck:(BOOL(^)(id object, NSUInteger idx, BOOL *stop)) checkerBlock;

Local notification: da iOS 9 ad iOS 10

Una subdola differenza nel comportamento delle notifiche locali fra iOS 9 e iOS 10 è come viene gestita una notifica la cui proprietà fireDate è impostata ad una data nel passato.

In iOS 9 semplicemente questa notifica veniva ignorata, mentre in iOS 10 viene lanciata immediatamente (probabilmente perché il sistema pensa che adesso sia il momento più vicino possibile alla data ormai passata).

Per cui, prima di impostare una fireDate ad un oggetto UILocalNotification è bene controllare che

1
[fireDate timeIntervalSinceNow] > 0
[fireDate timeIntervalSinceNow] > 0

ovvero che la data sia nel futuro.

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.

Aggiungere una cartella (es. di file HTML) senza perdere la struttura di sottocartelle

Chi ha voluto inserire una cartella di file HTML all’interno di un progetto Xcode si sarà presto accorto che i file vengono trattati come se fossero tutti dentro la cartella principale, e non distribuiti nelle eventuali sottocartelle. Questo crea problemi, ad esempio, nel linkare i file immagine nel file HTML (che andrebbe modificato ad hoc). Ma non solo.

Per ovviare a questo inconveniente è sufficiente ricordarsi di importare la cartella scegliendo l’opzione “Create folder references for any added folders ” (pur con l’opzione Copy items into destination group’s folder (if needed)” attivata).

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.