Tutorial per gli acquisti in-app

Gli acquisti in app sono diventati ormai fondamentali per monetizzare il lavoro svolto nello sviluppare un’app. L’app gratuita attira nuovi utenti e fornire loro nuove funzionalità tramite gli in-app purchases li trasforma in clienti.

Per implementare questa importante funzionalità ecco alcuni link utilissimi:

 

 

Eliminare un App ID con il nuovo portale di provisioning

Fino a pochi giorni fa era impossibile creare un App ID solo per studiare e sperimentare le varie funzioni di Push Notification, iCloud, Game Center ed altro, per poi cancellare tutto e non intasare il Provisioning Portal.

Ma nel nuovo portale del Member Center dedicato al Provisioning, Apple ha finalmente introdotto l’eliminazione dell’App ID.

È sufficiente entrare nei Settings di un App ID e, in fondo, c’è il nuovo pulsante Delete. Dopo una conferma verranno disattivi tutti i provisionig profile collegati.

Warning: localizzare l’immagine di Default (Launch image)

Talvolta può presentarsi un Warning quando tentiamo di archiviare (o lanciando un Build for>Archiving) un’app che presenta un localizzazione dell’immagine di default, ovvero l’immagine che appare prima che l’app sia caricata e lanciata.

L’errore è il seguente:

warning: Icon specified in the Info.plist not found under the top level app wrapper: Default-Landscape@2x~ipad.png

lo specifico file è di un’app per iPad ma il problema è del tutto generale e blocca qualsiasi tentativo di pubblicare l’app in App Store.

Nonostante si intuisca dal testo del warning stesso e venga anche specificato nella documentazione di Apple, non è risolutivo includere nella directory di root dell’app l’immagine di default nella lingua di default (quella da visualizzare quando la localizzazione corretta non è disponibile, spesso quindi in lingua inglese), ovvero l’app non viene più bloccata dalla compilazione per l’archiviazione, ma l’immagine di default rimane sempre quella in lingua di default.

Nel mio caso una pulizia completa del Info.plist, eliminando i nomi immagini provvisorie che erano rimaste memorizzate in tale file come immagini di default alternative ma non più utilizzate lanciando un Clean dal menu Product e ricompilando, la successiva compilazione per l’archiviazione è andata buon fine, pur essendo tecnicamente esattamente tutto come prima.

Se qualcuno dovesse trovare una spiegazione a tutto ciò, non esiti a scriverlo nei commenti così che possa integrare questo articolo.

Produrre una versione Lite senza duplicare un progetto

Spesso le App a pagamento vengono pubblicate anche in versione gratuita (ed in qualche modo limitata) per fare in modo che gli utenti possano rendersi conto dell’utilità dell’App stessa senza il timore di spendere del denaro. Una volta sincerati della qualità della versione gratuita, gli utenti di solito acquistano volentieri l’App completa.

Nonostante per Apple le App e le “App Lite” (o “Free”) siano tecnicamente due app distinte, è possibile produrre la versione Lite senza duplicare il progetto dell’App completa. Questo garantisce una facilità di allineamento fra le funzioni e i bug fix delle due versioni, ma necessita di alcune accortezze.

Se si definisce una variabile globale di tipo BOOL, ad esempio nell’AppDelegate.h con il comando:

1
#define isLite YES
#define isLite YES

allora in qualunque file di implementazione si può lanciare un comando quando l’App è Lite e un’altro quando è completa, con un semplice if.

Altra accortezza è quella di modificare almeno la grafica dell’icona dell’App, di modo che si distingua dall’App completa.

A livello di profili di provisioning è necessario creare un distinto App ID e una coppia di profili di development e distribution.

Nel progetto, invece, va cambiato l’identifier dell’iOS Application Target (lo trovate sul Summary del Target) di modo che sia identico a quello definito nell’App ID. Poi, nei Build Settings del Project selezionate i nuovi profili nella sezione Code Signing (ovviamente dopo aver scaricato e installato i nuovi profili di provisioning).

A questo punto la solita archiviazione genererà una nuova sezione dedicata alla versione Lite e, durante la validazione e la sottomissione verrà in automatico proposto il corretto profilo di provisioning di distribuzione.

Facile, no?

Non dimenticare di includere avvisi di errore di rete nel tuo codice

Nella pagina App Store Submission Tips di cui già ho accennato, c’è un suggerimento particolarmente interessante:

Don’t Forget to Include Network Error Alerts in Your Code

Gli utenti, viene spiegato, gradirebbero sapere perché il contenuto di una certa parte dell’app non è disponibile. E se la ragione è la mancanza di connessione internet allora è bene mostrare un alert per avvisare l’utente.

Reachability è il codice di esempio che Apple propone come punto di partenza. Eliminando il superfluo si può fare come segue.

Nella classe in cui serve sapere se c’è rete (ad esempio in un UIViewController che contiene un UIWebView o un parser RSS), aggiungiamo il framework SystemConfiguration, importiamo nel file .h tre intestazioni:

1
2
3
#import <SystemConfiguration/SystemConfiguration.h>
#import <sys/socket.h>
#import <netinet/in.h>
#import <SystemConfiguration/SystemConfiguration.h>
#import <sys/socket.h>
#import <netinet/in.h>

definiamo una enumerazione

1
2
3
4
5
6
typedef enum
{
    NoConnection = 0,
    WiFiConnected,
    WWANConnected
} NetworkStatus;
typedef enum
{
    NoConnection = 0,
    WiFiConnected,
    WWANConnected
} NetworkStatus;

e dichiariamo un metodo:

1
-(NetworkStatus) getNetworkStatus;
-(NetworkStatus) getNetworkStatus;

Nel file .m implementiamo tale metodo come segue:

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
-(NetworkStatus) getNetworkStatus
{
    struct sockaddr_in nullAddress;
 
    bzero(&nullAddress, sizeof(nullAddress));
    nullAddress.sin_len = sizeof(nullAddress);
    nullAddress.sin_family = AF_INET;
 
    SCNetworkReachabilityRef ref = SCNetworkReachabilityCreateWithAddress(kCFAllocatorDefault, (const struct sockaddr*) &nullAddress);
 
    SCNetworkReachabilityFlags flags;
    SCNetworkReachabilityGetFlags(ref, &flags);
 
    if (!(flags & kSCNetworkReachabilityFlagsReachable))
        return NoConnection;
 
    if (!(flags & kSCNetworkReachabilityFlagsConnectionRequired))
        return WiFiConnected;
 
    if (((flags & kSCNetworkReachabilityFlagsConnectionOnDemand) ||
         (flags & kSCNetworkReachabilityFlagsConnectionOnTraffic)) &&
        !(flags & kSCNetworkReachabilityFlagsInterventionRequired))
        return WiFiConnected;
 
    if ((flags & kSCNetworkReachabilityFlagsIsWWAN) == kSCNetworkReachabilityFlagsIsWWAN)
        return WWANConnected;
 
    return NoConnection;
}
-(NetworkStatus) getNetworkStatus
{
    struct sockaddr_in nullAddress;

    bzero(&nullAddress, sizeof(nullAddress));
    nullAddress.sin_len = sizeof(nullAddress);
    nullAddress.sin_family = AF_INET;

    SCNetworkReachabilityRef ref = SCNetworkReachabilityCreateWithAddress(kCFAllocatorDefault, (const struct sockaddr*) &nullAddress);

    SCNetworkReachabilityFlags flags;
    SCNetworkReachabilityGetFlags(ref, &flags);

    if (!(flags & kSCNetworkReachabilityFlagsReachable))
        return NoConnection;

    if (!(flags & kSCNetworkReachabilityFlagsConnectionRequired))
        return WiFiConnected;

    if (((flags & kSCNetworkReachabilityFlagsConnectionOnDemand) ||
         (flags & kSCNetworkReachabilityFlagsConnectionOnTraffic)) &&
        !(flags & kSCNetworkReachabilityFlagsInterventionRequired))
        return WiFiConnected;

    if ((flags & kSCNetworkReachabilityFlagsIsWWAN) == kSCNetworkReachabilityFlagsIsWWAN)
        return WWANConnected;

    return NoConnection;
}

In questo modo, ogni qualvolta ci serva di conoscere lo stato della connessione potremo chiamare [self getNetworkStatus]. Per seguire il tip di Apple potremo fare così:

1
2
3
4
if ([self getNetworkStatus] == NoConnection) {
        UIAlertView *alert = [[UIAlertView alloc] initWithTitle:NSLocalizedString(@"Errore di caricamento", @"") message:NSLocalizedString(@"Nessuna connessione internet", @"") delegate:self cancelButtonTitle:@"OK" otherButtonTitles: nil];
        [alert show];
}
if ([self getNetworkStatus] == NoConnection) {
        UIAlertView *alert = [[UIAlertView alloc] initWithTitle:NSLocalizedString(@"Errore di caricamento", @"") message:NSLocalizedString(@"Nessuna connessione internet", @"") delegate:self cancelButtonTitle:@"OK" otherButtonTitles: nil];
        [alert show];
}

Suggerimenti prima di inviare un’app

Apple ha predisposto una specifica pagina piena di suggerimenti utili prima che lo sviluppatore sottoponga l’applicazione ad Apple stessa. La pagina si chiama appunto App Store Submission Tips e contiene suggerimenti importanti, molti dei quali determinano una esclusione da App Store se non rispettati (proprio perché sono consequenziali al regolamento stesso di App Store).

Gli argomenti trattati spaziano dal nome da dare alla app, cosa non includere nell’icona, il testare l’app sui dispositivi, assegnare il corretto tipo di prodotto in-app e segnalare la mancanza di connessione.

Application failed code sign verification. The signature was invalid, or it was not signed with an iPhone Distribution Certificate.

Durante la validazione di un App archiviata, quindi poco prima dell’invio ad Apple per l’approvazione, può capitare di ricevere questo errore:

Application failed code sign verification. The signature was invalid, or it was not signed with an iPhone Distribution Certificate.

Questo significa, il più delle volte, che non è stato impostato correttamente il profilo di provisioning in Xcode.

Per risolvere questo problema è sufficiente cliccare sul nome del progetto nella barra di sinistra, poi nella zona centrale di Xcode scegliere il progetto dalla sezione Project e poi selezionare la pagina Build Settings. A questo punto trovate la sezione Code Signing e scegliete in Release il profilo corretto di tipo iPhone Distribution.

Da notare, come curiosità, che Apple abbia mantenuto la dicitura iniziale iPhone Distribution (ma non solo questo) anche quando si sviluppa per iPad o iPod touch. Chissà se in futuro tutto questo verrà rinominato in iOS Distribution.