[iOs] Accesso alla fotocamera, alla Libreria e modifica di una foto con Aviary

Condivido qui un progettino xCode che esemplifica come accedere ad una foto salvata  nella Libreria Foto oppure scattata direttamente con la fotocamera del vostro device. Una volta prelevata la foto, è possibile modificarla, applicando su di essa vari effetti e filtri, grazie alla libreria Aviary (disponibile non solo per iOs, ma anche per Windows Phone, Android e HTML5).

Aviary-iOS-SDK_4

Ecco il link al mio progetto su GitHubhttps://github.com/fficetola/AviaryExample/

Quindi, ricapitolando, ecco le funzionalità che troverete nell’esempio:

  • visualizzazione di una foto, prelevata dalla Libreria Foto oppure dalla fotocamera del device, in una UIImageView
  • possibilità di modificarla grazie all’editor di Aviary
  • visualizzazione della foto in modalità fullscreen con gesture per lo zoom (pinch-on/pinch-off/double tap)
  • salvataggio dell’immagine modificata nella Libreria Foto

Procediamo per step, descrivendo le funzionalità su elencate (vi invito a visionare i file ViewController.m e BannerViewController.m  in cui vi è tutta la logica di questo tutorial).

 

1. Selezione di una immagine dalla Libreria Foto o dalla fotocamera

La selezione di una foto già salvata in Libreria o scattata dalla fotocamera avviene grazie al controller UIImagePickerController, il quale appare come una modale che a seconda del “source type” richiesto (camera o gallery), vi permetterà di importare appunto la foto da libreria o fotocamera.

- (void) selectPhoto:(UIImagePickerControllerSourceType) pickerType {

    picker = [[UIImagePickerController alloc] init];
    picker.delegate = self;

    picker.sourceType = pickerType;
    //picker.sourceType = UIImagePickerControllerSourceTypeCamera;
    //picker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;

    [self presentModalViewController:picker animated:YES];

}

Il parametro in input al metodo selectPhoto è di tipo UIImagePickerControllerSourceType e può essere valorizzato con i seguenti valori:

  • UIImagePickerControllerSourceTypeCamera
  • UIImagePickerControllerSourceTypePhotoLibrary

I metodi di callback, che permettono di intercettare se l’utente ha selezionato o meno una foto, sono quelli dell’UIImagePickerControllerDelegate (metodi implementati nel ViewController):

#pragma mark - UIImagePicker Delegate

-(void)imagePickerControllerDidCancel:(UIImagePickerController *) photopicker {

    [photopicker dismissModalViewControllerAnimated:YES];

    [photopicker release];

}

- (void)imagePickerController:(UIImagePickerController *) photopicker didFinishPickingMediaWithInfo:(NSDictionary *)info {

    imageView.image = [info objectForKey:UIImagePickerControllerOriginalImage];
    [photopicker dismissModalViewControllerAnimated:YES];
    [photopicker release];

}

A cosa servono questi metodi del delegate?

  • imagePickerControllerDidCancel: permette di intercettare il click sul tasto Annulla del picker (o della libreria o della fotocamera), quindi senza che sia avvenuta la selezione della foto. In questo metodo viene chiuso il picker (con il dismissModalViewControllerAnimated)
  • didFinishPickingMediaWithInfo: permette di intercettare il click sul tasto Done con conseguente scelta della foto. Il recupero dell’immagine selezionata viene fatto accedendo alla proprietà UIImagePickerControllerOriginalImage del dizionario. Nell’esempio, tale foto viene inserita in una UIImageView per la visualizzazione.

 

2. Modifica dell’immagine con Aviary 

ios_aviary

Per poter modificare la foto, applicandovi  filtri ed effetti vari (cropping, resizing, rotating, ecc.), ho utilizzato la libreria Aviaryla cui versione “free” vi permette di eseguire tutte le features disponibili, ma non di trattare foto ad altissima risoluzione (vedi i dettagli nella sezione Pricing del sito www.aviary.com). Per maggiori dettagli sulle funzionalità previste, ecco la pagina della documentazione per iOshttp://www.aviary.com/ios/documentation

L’installazione della libreria Aviary nel vostro progetto è molto semplice:

  1. Innanzitutto scaricate il progetto di esempio direttamente dal sito ufficiale di Aviary: lo troverete nella sezione Documentazione, cliccando sul bottone GET CODE. Potete eseguirlo per vedere quali sono le features messe a disposizione dalla libreria o come esempio per implementare funzionalità più avanzate (visto che in questo tutorial vi spiego solo l’uso di base)
  2. Registratevi al sito (gratuitamente) per ottenere un’API key, che dovrete inserire all’interno del vostro progetto. Basta cliccare sul bottone GET API KEY della sezione Documentazione, registrarvi e poi aggiungere una nuova app. Vi verranno generate una API Key e una Secret Key ( a voi servirà solo la prima).
  3. Dal codice di esempio scaricato dal sito ufficiale, dovrete copiare la cartella AviarySDK e importarla nel vostro progetto. Questa contiene la libreria libAviarySDK.a, i file di Header e di Resources. Copiatela integralmente nel vostro progetto.
  4. inserite nel vostro progetto i seguenti framework, da cui Aviary dipende:
    1. Accelerate.framework
    2. AdSupport.framework (selezionare `Optional` invece di `Required`)
    3. CoreData.framework
    4. CoreText.framework
    5. libsqlite3.0.dylib
    6. libz.1.2.5.dylib
    7. Foundation.framework
    8. MessageUI.framework
    9. OpenGLES.framework
    10. QuartzCore.framework
    11. StoreKit.framework
    12. SystemConfiguration.framework
    13. UIKit.framework
  5. Infine, inserite nel vostro file MyProject-Info.plist la chiave Aviary-API-Key valorizzandola con l’API Key generata al punto 2.

Dopo la fase di configurazione della libreria, si passa all’utilizzo vero e proprio dell’editor. Innanzitutto il ViewController deve avere come delegate la classe AFPhotoEditorControllerDelegate, che ci permetterà di interagire con l’editor di Aviary, ossia con la classe AFPhotoEditorController.

Ecco come far apparire l’editor di Aviary in una modale del ViewController:

- (void)displayEditorForImage:(UIImage *)imageToEdit
{
    AFPhotoEditorController *editorController = [[AFPhotoEditorController alloc] initWithImage:imageToEdit];
    [editorController setDelegate:self];
    [self presentViewController:editorController animated:YES completion:nil];
}

Basta passare all’AFPhotoEditorController l’immagine da modificare (imageToEdit), in modo da farla apparire nell’editor. Qui Aviary vi permetterà di applicare gli effetti e i filtri disponibili. Per poter intercettare le modifiche o l’annullamento delle modifiche (chiusura con tasto Annulla dell’editor), basta implementare i seguenti metodi dell’ AFPhotoEditorControllerDelegate:

#pragma mark - Aviary Delegate

- (void)photoEditor:(AFPhotoEditorController *)editor finishedWithImage:(UIImage *)image
{

    //read modified image
    [imageView setImage:image];

    [self dismissViewControllerAnimated:YES completion:NULL];

}

- (void)photoEditorCanceled:(AFPhotoEditorController *)editor
{
    [self dismissViewControllerAnimated:YES completion:NULL];
}

Penso che le righe di codice precedenti siano abbastanza esplicative: photoEditorCanceled intercetta la chiusura di Aviary (tap su tasto Annulla) e permette di chiudere l’editor (dismissViewControllerAnimated), invece finishedWithImage permette di recuperare l’immagine modificata per riproporla nella UIImageView.

 

3. Zoom in/out dell’immagine (pinch-off/pinch-on o doppio tap per ingrandire/ridurre)

Per poter ingrandire/ridurre la foto visualizzata nell‘UIImageView, nel mio progettino di esempio trovate la classe BannerViewController.m. In essa vi è la logica per poter eseguire lo zoom-in e lo zoom-out della foto eseguendo le gesture pinch-on/pinch-off o il double tap sulla view. Inoltre, nel file BannerViewController.xib noterete una ScrollView contenente l’UIImageView, in cui visualizzare e zoomare l’immagine.

Ecco il codice:

-(void)handleDoubleTap:(UIGestureRecognizer *)gestureRecognizer {

    if(self.scrollView.zoomScale > scrollView.minimumZoomScale)
        [self.scrollView setZoomScale:scrollView.minimumZoomScale animated:YES];
    else
        [self.scrollView setZoomScale:scrollView.maximumZoomScale animated:YES];
}

- (void)viewDidLoad
{
    [super viewDidLoad];

    self.title = NSLocalizedString(@"BANNER_TITLE", nil);

    self.navigationItem.rightBarButtonItem = [[[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone target:self action:@selector(closeModal:)]autorelease];

    UITapGestureRecognizer *tapTwice = [[[UITapGestureRecognizer alloc] initWithTarget:self  action:@selector(handleDoubleTap:)]autorelease];

    tapTwice.numberOfTapsRequired = 2;

    [self.view addGestureRecognizer:tapTwice];

    UIImage *imageFullScreen = image;
    imageView.contentMode = UIViewContentModeScaleAspectFit;
    [self.imageView setImage:imageFullScreen];

    //self.scrollView.contentSize = image.size;
    self.scrollView.delegate = self;
    //self.scrollView.minimumZoomScale = 10.0;
    self.scrollView.maximumZoomScale = 50.0;   

}

Nelle precedenti righe notiamo l’allocazione di un oggetto di tipo UITapGestureRecognizer per la registrazione del doppio “tap” sulla vista: la action associata a tale gesture è handleDoubleTap, che permette di zoomare alla scala massima o minima impostata per la ScrollView. Infatti, il minimumZoomScale e il maximumZoomScale si impostano nel viewDidLoad.

Invece, per gestire le gesture di pinch-on e pinch-off sull’immagine, sono utili le seguenti righe di codice:

- (void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];
    CGPoint centerPoint = CGPointMake(CGRectGetMidX(self.scrollView.bounds),
                                      CGRectGetMidY(self.scrollView.bounds));
    [self view:self.imageView setCenter:centerPoint];
}

- (void)view:(UIView*)view setCenter:(CGPoint)centerPoint
{
    CGRect vf = view.frame;
    CGPoint co = self.scrollView.contentOffset;

    CGFloat x = centerPoint.x - vf.size.width / 2.0;
    CGFloat y = centerPoint.y - vf.size.height / 2.0;

    if(x < 0)
    {
        co.x = -x;
        vf.origin.x = 0.0;
    }
    else 
    {
        vf.origin.x = x;
    }
    if(y < 0)
    {
        co.y = -y;
        vf.origin.y = 0.0;
    }
    else 
    {
        vf.origin.y = y;
    }

    view.frame = vf;
    self.scrollView.contentOffset = co;
}

// MARK: - UIScrollViewDelegate
- (UIView*)viewForZoomingInScrollView:(UIScrollView *)scrollView
{
    return  self.imageView;
}

- (void)scrollViewDidZoom:(UIScrollView *)sv
{
    UIView* zoomView = [sv.delegate viewForZoomingInScrollView:sv];
    CGRect zvf = zoomView.frame;
    if(zvf.size.width < sv.bounds.size.width)
    {
        zvf.origin.x = (sv.bounds.size.width - zvf.size.width) / 2.0;
    }
    else 
    {
        zvf.origin.x = 0.0;
    }
    if(zvf.size.height < sv.bounds.size.height)
    {
        zvf.origin.y = (sv.bounds.size.height - zvf.size.height) / 2.0;
    }
    else 
    {
        zvf.origin.y = 0.0;
    }
    zoomView.frame = zvf;
}

 

4. Salvataggio dell’immagine nella Libreria Foto

Infine, per salvare la foto nella Libreria Foto del device, basta utilizzare la classe nativa UIImageWriteToSavedPhotosAlbum:

//Per salvare la foto modificata nella Gallery

-(void)image:(UIImage *)image finishedSavingWithError:(NSError *)error contextInfo:(void *)contextInfo {

    if (error) {

        UIAlertView *message = [[UIAlertView alloc] initWithTitle:@"Salvataggio Fallito"
                                                          message:@" *ATTENZIONE* non è stato possibile salvare l'immagine"
                                                         delegate:nil
                                                cancelButtonTitle:@"OK"
                                                otherButtonTitles:nil];
        [message show];
    }
    else{
        UIAlertView *message = [[UIAlertView alloc] initWithTitle:@"Salvataggio effettuato con successo"
                                                          message:@"La tua foto è stata salvata nella Gallery"
                                                         delegate:nil
                                                cancelButtonTitle:@"OK"
                                                otherButtonTitles:nil];
        [message show];
    }
}

-(IBAction)savePhotoInGallery:(id)sender{

    //salvo la foto nella Gallery
    UIImageWriteToSavedPhotosAlbum(imageView.image, self, @selector(image:finishedSavingWithError:contextInfo:), nil);
}

[iOs] Come configurare, inviare e ricevere le notifiche push in una app iOs

Eccovi un tutorial su come configurare, inviare e ricevere le push notification di Apple in una app iOs. Tali notifiche sono molto utili nel caso in cui vogliate segnalare aggiornamento ed eventi agli utenti che hanno installato la vostra app e che hanno, ovviamente, dato il consenso per la loro ricezione. La configurazione non è molto immediata, specie la parte di creazione dei certificati, per cui vi descriverò i singoli passi nel dettaglio.

Prima di essere operativi, vediamo il modello di programmazione per capire quali sono le componenti in gioco e il flusso funzionale di registrazione, di accreditamento e di invio/ricezione delle notifiche push:

  1. Push-NotificationL’app, registrata sul Provisioning Portal con un AppID, deve essere configurata in modo da avere la funzionalità di push notification abilitata. Una volta abilitata, occorre generare un Provisioning Profile (sia per l’ambiente di sandbox – o development –   che per quello di production – o distribution) da associare all’app stessa. Lato app, quando l’utente dà il consenso alla ricezione delle notifiche push, si fa richiesta ad iOs per la registrazione e l’autorizzazione alle notifiche.
  2. iOs contatterà il server APNS (Apple Push Notification Service), il quale ricevuta la richiesta, genererà un device token da inoltrare direttamente all’app che ha chiesto la registrazione. Il device token è un “indirizzo” univoco che serve all’APNS per rintracciare il device e per inoltrare le notifiche push tramite l’app.
  3. Una volta ricevuto il device token, dall’app occorrerà richiamare un nostro servizio remoto (registerDevice) in cui registrare tale token, oltre ad una serie di altre info utili del device. L’idea è quella di registrare su un nostro database online, la lista di tutti i device, e relativi device token generati, in modo da inoltrare tutte le nostre notifiche a chi ne ha fatto espressamente richiesta.
  4. Un altro servizio remoto presente sul nostro server (sendPush) sarà responsabile dell’invio delle notifiche push (un semplice messaggio di testo). Tale servizio dovrà essere configurato in modo da avere l’autorizzazione dall’APNS server: occorrerà, dunque, generare e configurare un certificato SSL (e una relativa chiave privata) per poter inoltrare una richiesta di notifica push a ciascun device accreditato.
  5. Quando l’APNS server riceve una richiesta di invio di una notifica push per un determinato device token (dopo aver attestato la veridicità del certificato SSL del servizio remoto sendPush), inoltra la notifica. Dall’altra parte, sul device a cui è associato il device token, verrà visualizzato un alert dell’app (o una notifica nel Centro Notifiche) con il messaggio inviato. Oltre l’alert, si può decidere se riprodorre anche un suono e/o visualizzare un badge (il numeretto con il totale delle notifiche sull’icona dell’app). Cliccando sulla notifica o facendo lo “swipe” sull’alert, verrà aperta la vostra app.

 

NOTA. Non è possibile testare le push notification sul simulatore, ma soltanto direttamente su un iPhone/iPad.

 

Push Notification Payload.  Una richiesta di invio di una push notification (dal nostro server all’APNS server) è fatta da un breve messaggio costituito da un device token a cui indirizzare la notifica, un payload ed altre parti. Il payload contiene il messaggio vero e proprio (in formato JSON), compresi alcuni parametri. In totale non deve superare i 256 bytes. Per maggiori informazioni sui parametri, vedi Local and Push Notification Programming Guide.

Un esempio di messaggio è il seguente:

{"aps":{"alert":"Hello, world!","sound":"default"}}

Vi riporto i tutorial da cui sono partito per poter scrivere questa mini-guida:

 

Partiamo ora con la parte operativa:

 

[iOs] Paginare i dati di una UITableView

Vi condivido il codice che ho messo su una repo di GitHub, utile per la paginazione dei dati caricati in una UITableView. Questa feature è molto comoda, quando dovete caricare troppi dati da un vostro servizio online e, dunque, la fase costituita dalla chiamata remota stessa, il fetching dei dati e il popolamento delle celle della vostra UITableView potrebbe risultare alquanto onerosa, se non addirittura far crashare la vostra app.

Ecco il link al progettino: https://github.com/fficetola/PaginationScroller

Prima di entrare nel vivo del codice, vi scrivo qui cosa viene fatto nell’app di esempio:

  • viene inizializzata una UITableView e nel metodo loadData, viene fatta una chiamata remota ad un servizio PHP di test (che vi ho anche condiviso nella cartella php-example-paging);
  • l’output in JSON del servizio di test lo potere vedere qui: http://francescoficetola.it/ios-test/test-paging.php?idx_last_element=0&block_size_paging=2. Nel servizio PHP di test, vengono passati due parametri: idx_last_element, ossia l’indice del primo elemento della lista di dati da cui si vuole partire, e block_size_paging, ossia il numero massimo di elementi che si vuol far restituire a partire dall’indice richiesto (nell’esempio precedente, si richiedono i primi 20 risultati dal primo elemento della lista, con indice 0). Il servizio PHP genera 100 elementi e, dunque, se si richiedono 20 elementi per volta, il numero massimo di pagine restituite sarà 5 (total_pages, è il numero massimo di pagine calcolate e restituite per la paginazione, current_page è la pagina corrente del blocco di dati richiesti)
  • Lato app, si intercetta il JSON di risposta e si “fetchano” i dizionari in singoli oggetti di tipo Object. 
  • Quando si scrolla la UITableView fino all’ultima cella, viene visualizzata una UIActivityIndicatorView, che scompare soltanto quando sono stati caricati altri blocchi di dati (si richiama, infatti, il metodo loadData).

Ecco il metodo della UITableView in cui vi è la vera e propria logica di paginazione:

- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {

    //check last cell in uitable
    if(indexPath.row==self.objects.count-1){

        //Create and add the Activity Indicator to splashView
        UIActivityIndicatorView *activityIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
        activityIndicator.alpha = 1.0;
        //activityIndicator.center = CGPointMake(160, 360);
        activityIndicator.hidesWhenStopped = NO;
        [activityIndicator startAnimating];

        self.tableView.tableFooterView = activityIndicator;

        //arrest condition
         if(_currentPage<_totalPages){

             _lastIndex = indexPath.row+1;
             //timer...
             [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:@selector(loadData) userInfo:nil repeats:NO];

         }
         else{
             [activityIndicator stopAnimating];
             self.tableView.tableFooterView = nil;
         }

         [activityIndicator release];

    }
}

Il metodo willDisplayCell scatta per tutte le celle della UITableView, ogniqualvolta ognuna di queste viene visualizzata. Poiché a noi interessa rilevare soltanto quando è stata visualizzata l’ultima cella della tabella, vi è il seguente controllo:

if(indexPath.row==self.objects.count-1)

In tal caso, viene aggiunto nel footer della UITableView un UIActivityIndicatorView e, se non è verificata la condizione di arresto della paginazione, viene fatta una chiamata al metodo loadData in cui si richiama di nuovo il servizio remoto per la restituizione di un nuovo blocco di dati a partire dal lastIndex aggiornato.

Per determinare la condizione di arresto della paginazione, si va a controllare se la pagina corrente caricata (currentPage) sia l’ultima (totalPage):

if(_currentPage<_totalPages)

Se non si verifica tale condizione, viene eliminato lo spinner dal footer e viene terminata la paginazione (non viene più richiamato il metodo loadata).

UITableView pagination

Creative Commons License
This work by Francesco Ficetola is licensed under a Creative Commons Attribution 4.0 International License.
Based on a work at www.francescoficetola.it.
Permissions beyond the scope of this license may be available at http://www.francescoficetola.it/2013/03/31/ios-paginare-i-dati-di-una-uitableview/.

[iOs] Far votare o recensire un’app su Apple Store con Appirater

Volete invitare l’utente a lasciare un voto o una recensione sulla vostra app su Apple Store? Sono diverse le librerie che permettono di fare il rating, ma tutte di terze parti.

La libreria che vi consiglio, che è anche quella più utilizzata per tale scopo, è Appirater, di cui trovate una presentazione sul sito ufficiale del creatore:

http://arashpayan.com/blog/2009/09/07/presenting-appirater/

Il codice è scaricabile direttamente da GitHubhttp://github.com/arashpayan/appirater/

AppiraterScreenshot

Cosa vi permette di fare Appirater:

  • chiedere all’utente se vuole recensire e votare la vostra app su Apple Store, visualizzando una popup con opzioni
  • permettere di impostare quando visualizzare la popup, se dopo N giorni dall’installazione dell’app (o sua nuova release) sul device oppure dopo N utilizzi (uses) dell’app stessa
  • permettere di posticipare la recensione da parte dell’utente, ricordandogli di farlo dopo N giorni
  • oppure, se l’utente non vuole recensire l’app, “ricordare” tale opzione e non visualizzare più il messaggio di popup
  • i messaggi della popup sono localizzabili in varie lingue (compreso italiano)

Le opzioni sono configurabili nel file Appirater.h e le vedremo in dettaglio dopo aver definito come installare la libreria.

Installazione

  1. Scaricare da GitHub la libreria ed importare tutti i suoi file (comprese le cartelle delle lingue .lproj che vi interessano) nel vostro progetto
  2. Se utilizzate l’ARC (Automatic Reference Counting), poiché la libreria è un pò vecchiotta e non utilizza l’ARC, dovete marcare il file Appirater.m con il flag -fobjc-arc (basta andare sulla root del progetto, selezionare il target e nella scheda Build Phases » Compile Sources, in corrispondenza della classe Appirater.m, inserire il flag su citato cliccando due volte su tale classe)
  3. Aggiungete al vostro progetto i seguenti framework (sempre nella scheda Build Phases, sezione Link Binary with Libraries): CFNetworkSystemConfiguration e StoreKit. Assicuratevi di cambiare da Required ad Optional il flag del framework StoreKit, sempre nella sezione Build Phases » Link Binary with Libraries del target.
  4. Infine, potete utilizzare Appirater direttamente nel vostro AppDelegate.m, in corrispondenza del metodo application:didFinishLaunchingWithOptions:

Utilizzo. Un esempio di utilizzo di Appirater è il seguente: 

    //call the Appirater class
    [Appirater setAppId:YOUR_APPID];
    [Appirater setDaysUntilPrompt:1];
    [Appirater setUsesUntilPrompt:10];
    [Appirater setSignificantEventsUntilPrompt:-1];
    [Appirater setTimeBeforeReminding:2];
    //[Appirater setDebug:YES];
    [Appirater appLaunched:YES];

Tale codice è stato inserito nel metodo dell’AppDelegate application:didFinishLaunchingWithOptions:

Potete settare una serie di opzioni o come fatto nell’esempio precedente o modificando i valori di default dell’interfaccia Appirater.h.

Importante è inserire l’AppID della vostra app, così come visualizzata nelle informazioni relative all’app stessa sull’account di iTunesConnect. Per farvi rilasciare un AppID dovete aver pubblicata una vostra app sullo store o almeno registrato tutte le sue informazioni lì prima dell’invio ad Apple per l’approvazione.

Tra le opzioni che possiamo settare abbiamo:

  • + (void) setAppId:(NSString*)appId; permette di settare l’AppID dell’app (da prelevare su iTunesConnect)
  • + (void) setDaysUntilPrompt:(double)value;  setta il numero di giorni da quando è installata l’app (o una sua nuova release) dopo i quali visualizzare la popup di rating
  • + (void) setUsesUntilPrompt:(NSInteger)value; setta il numero degli “usi” (uses) dopo i quali si chiede all’utente se vuole votare. Un uso potrebbe essere il fatto che l’app si apra in primo piano nel device.
  • + (void) setSignificantEventsUntilPrompt:(NSInteger)value; setta il numero di eventi significativi prima di chiedere all’utente se vuol fare il rating
  • + (void) setTimeBeforeReminding:(double)value; setta il numero di giorni prima di rivisualizzare la popup di rating (una sorta di promemoria)
  • + (void) setDebug:(BOOL)debug; questa opzione è comoda per gli sviluppatori, perché consente di visualizzare immediatamente la popup per poter fare test o sviluppo. Tale opzione è da disabilitare quando si rilascia l’app sullo store.

Eccovi un altro riferimento utile (stavolta in italiano): http://www.htmedia.it/2012/03/tip-ios-31-chiediamo-una-recensione-con-appirater/

Buon rating!

[iOs] Il Logging nelle nostre app con le Preprocessor Macros

Prima di un rilasciare una app iOS su Apple Store, è buona prassi fare un pò di pulizia dei log sparsi nel codice. Molti sviluppatori, infatti, sono restii all’utilizzo del debug e preferiscono utilizzare, man mano che sviluppano, la classe NSLog e farsi stampare gli output sulla console. Dunque, i nostri progetti rimangono pieni di NSLog che, tuttavia, è meglio non rilasciare sulle app in esercizio. Il motivo è dovuto al fatto che la scrittura dei log è onerosa e potrebbe ridurre le performance della nostra app. Inoltre, anche se la console del device è nascosta all’utente, potremmo dimenticare di loggare informazioni sensibili … e ciò è “no buono”.

Ho letto un pò di articoli a riguardo, tra cui i seguenti:

Ecco la procedura che ho seguito, utilizzando le Preprocessor Macros. L’idea è quella di inserire un flag (-DDEBUG) nella configurazione di DEBUG del nostro progetto e importare nelle nostre classi un log “custom” (DLog oppure ALog).

  • Per prima cosa, inserite un file Log.h nel vostro progetto con il seguente contenuto:
#ifdef DDEBUG

#   define DLog(fmt, ...) NSLog((@"%s [Line %d] " fmt), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__);

#else

#   define DLog(...)

#endif

// ALog always displays output regardless of the DEBUG setting

#define ALog(fmt, ...) NSLog((@"%s [Line %d] " fmt), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__);
  •  Poi spostatevi nella configurazione del vostro progetto (click sulla root di progetto, selezionate Targets e poi spostatevi nella scheda Builds Settings). Qui, cercate la voce “Other C Flags” e inserite il flag -DDEBUG in corrispondenza della configurazione di DEBUG.

  • NOTA. Il punto precedente non è necessario se in Log.h si va a controllare il valore del flag nativo DEBUG (che è true se siamo in ambiente di debug, false quando si è in modalità Release/Ad Hoc), anzichè DDEBUG (che è una nostra variabile “custom”)
    #ifdef DEBUG
  • Importate l’interfaccia Log.h nel file di progetto <YourApp>_Prefix.pch, in modo da rendere visibile la macro in tutte le classi (senza importare esplicitamente la macro ogni volta). Nel mio caso, ecco il contenuto del .pch:
#import <Availability.h>
#import "Log.h"

#ifndef __IPHONE_5_0
#endif

#ifdef __OBJC__
	#import <UIKit/UIKit.h>
	#import <Foundation/Foundation.h>
#endif
  • Infine, potete utilizzare i log “custom” normalmente come fareste con NSLog, come segue:
 DLog(@"Logging %d con %@",1, @"DLog");
 ALog(@"Logging %d con %@",2, @"ALog");

Eccovi l’output di esempio delle precedenti righe di log:

2012-12-25 15:03:16.276 MyApp [4451:907] -[AppDelegate application:didFinishLaunchingWithOptions:] [Line 33] Logging 1 con DLog
2012-12-25 15:03:16.281 MyApp [4451:907] -[AppDelegate application:didFinishLaunchingWithOptions:] [Line 34] Logging 2 con ALog

DLog e ALog, per come sono stati configurati nel Log.h, stampano anche il nome della classe, il metodo e la riga di codice in cui si trovano. ALog stampa anche se non siamo in DEBUG (quindi, come NSLog, loggherebbe anche quando abbiamo rilasciato l’app su Apple Store o con una distribuzione “ad hoc”).

Quindi, lanciando la vostra app in DEBUG (sia sul Simulatore che sul device) vi troverete i log stampati sulla console. In Release (sia su App Store che su una distribuzione “ad hoc” dell’IPA) non verrà loggato nulla sul device dell’utente, se avete utilizzato DLog (e non ALog oppure NSLog).

[iOS] Apps that are not very useful – Guidelines reject (2.12 – App Store Review)

Il più frustrante punto di violazione delle Apple Store Review Guidelines (tradotte anche su questo blog al Luglio 2012) è il seguente:

2.12. Apps that are not very useful, unique, are simply web sites bundled as Apps, or do not provide any lasting entertainment value may be rejected

Solitamente la motivazione che viene riportata, dal team di review di Apple nella mail di risposta, è la seguente:

2.12

We found that your app only provides a very limited set of features. While we value simplicity, we consider simplicity to be uncomplicated – not limited in features and functionality

We understand that there are no hard and fast rules to define useful or entertaining, but Apple and Apple customers expect apps to provide a really great user experience. Apps should provide valuable utility or entertainment, draw people in by offering compelling capabilities or content, or enable people to do something they couldn’t do before or in a way they couldn’t do it before.

We encourage you to review your app concept and evaluate whether you can incorporate additional content and features to be in compliance with the Guidelines. For information on the basics of creating great apps, watch the video The Ingredients of Great Apps. If you feel we didn’t understand the features of your app, or that we missed key functionality, and your app was incorrectly rejected, you may appeal to the  App Review Board.

 

Definire questo punto “frustrante” è dir poco, visto che non è una “bocciatura” a cui si può mettere una “pezza” (tecnicamente parlando). Apple ci fa semplicemente capire che la nostra app non serve a nulla o è troppo semplice, dove la linea di “confine” per definire il concetto di semplicità – spiega Apple – non è definibile con regole rigide.

Come si risolve?

Potrei scrivere qui dei suggerimenti, ma dipende dalla vostra app e dall’utilità che offre, visto che ad Apple “sta a cuore” che sia utile e trasmetta agli utenti una “grandiosa esperienza”. La vostra app deve dimostrare che ci sia un lavoro “complesso” dietro: in poche parole, una app con poche funzionalità e con una grafica poco curata, ha bassa probabilità di superare la review.

Arricchitela con maggiori funzionalità: magari una galleria fotografica e video (vedi Three20), più tabelle con contenuti magari scaricati da un server (vedi chiamate asincrone su iOS). Per la parte grafica, rispettate le risoluzioni delle immagini per ciascun device (vedi Custom Icon & Image Creation Guidelines) e le iOS Human Interface Guidelines.

Ecco altro materiale, suggerito da Apple, e che vi consiglio di vedere:

 

Creative Commons License
This work by Francesco Ficetola is licensed under a Creative Commons Attribution 4.0 International License.
Based on a work at www.francescoficetola.it.
Permissions beyond the scope of this license may be available at http://www.francescoficetola.it/2012/12/03/apple-review/.

[iOS] Come effettuare chiamate asincrone

La modalità sicuramente più performante per richiamare dei servizi online di back-end da una vostra app è quella delle chiamate asincrone.

1° METODO (con dispatch_async). Il classico metodo in iOS, che definirei “nativo”, è quello che utilizza il dispatch_async:

// Add to top of file
#import <;dispatch/dispatch.h>;

// Use it
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
     NSData* responseData = [NSData dataWithContentsOfURL:[NSURL URLWithString:@"http://tuo-sito/servizio"]];

/* ... your code here ...*/

});
}

Non occorre importare nessuna libreria. Nella responseData vi è la risposta HTTP, da cui potete recuperare i vostri dati. Tale metodo richiama il Grand Central Dispatcher per eseguire la chiamata e ricevere una risposta (asincrona) dal server in background. Intanto, la vostra app continua ad eseguire sul thread principale il lavoro, senza bloccarsi in attesa dei dati del servizio richiamato.

2° METODO (con ASIHTTPRequest ). Il metodo che, invece, preferisco è quello che utilizza l’utilissima libreria ASIHTTPRequest, che ci permette di effettuare in modo semplice chiamate HTTP (sincrone e asincrone) e interagire con servizi REST (vedi l’articolo “La filosofia REST“, su questo blog). E’ una libreria particolarmente utile per effettuare anche upload e download di file, chiamate con autenticazione sicura HTTPS, e molto altro … e vi consiglio vivamente di provarla.

La potete scaricare direttamente dal sito ufficiale: ASIHTTP-request downloads

Come si spiega qui: Setup-Instructions, per installarla nel vostro progetto, occorre importare le seguenti librerie/framework (che trovate direttamente in xCode – click sulla root di progetto >> Build Phases >> Link Binary With Libraries):

  • CFNetwork,
  • SystemConfiguration
  • MobileCoreServices
  • libz.dylib (attualmente c’è libz.1.2.5.dylib)
Ecco un esempio di chiamata in POST ad un servizio REST che restituisce un JSON:
//import libs
#import "ASIFormDataRequest.h"

/* ... your code in controller ...*/

- (void)fetchedData:(NSData *)responseData {

    NSArray* json = [NSJSONSerialization
                     JSONObjectWithData:responseData
                     options:kNilOptions error:nil];

    NSMutableArray *postTMP = [[NSMutableArray alloc] initWithCapacity:[json count]];

    for (NSDictionary *dict in json) {
        Post *post = [[Post alloc] init];
        post.titolo = [dict objectForKey:@"titolo"];
        post.articolo = [dict objectForKey:@"articolo"];
        post.snippet = [dict objectForKey:@"snippet"];
        post.urlImage = [dict objectForKey:@"urlImage"];
        post.urlThumb = [dict objectForKey:@"urlThumb"];
        [postTMP addObject:post];
    }

    //mi assicuro che l'array non possa essere modificato.
    self.posts = [postTMP copy];
    NSLog(@"posts count: %d",[self.posts count]);

    [self.tableView performSelectorOnMainThread:@selector(reloadData) withObject:nil waitUntilDone:YES];
}

- (void)requestFinished:(ASIHTTPRequest *)request {

    NSLog(@"Response %d ==>; %@", request.responseStatusCode, [request responseString]);
    NSData *responseData = request.responseData;

    [self performSelectorOnMainThread:@selector(fetchedData:) withObject:responseData waitUntilDone:YES];

}

- (void)requestFailed:(ASIHTTPRequest *)request
{
    //[progressAlert release];
    NSError *error = [request error];
    NSLog(@"ERROR %@",error);

    if ([[request error] code] == ASIConnectionFailureErrorType  ||
        [[request error] code] == ASIRequestTimedOutErrorType){
        UIAlertView *alert = [[UIAlertView alloc] initWithTitle:NSLocalizedString(@"ERROR", nil) message:NSLocalizedString(@"CONNECTION_FAILED",nil)
            delegate:self cancelButtonTitle:NSLocalizedString(@"OK",nil) otherButtonTitles:nil];
        [alert show];
        [alert release];
        //return;

    }
    else{
        UIAlertView *alert = [[UIAlertView alloc] initWithTitle:NSLocalizedString(@"ERROR",nil) message:
        NSLocalizedString(@"GENERIC_ERROR",nil)
            delegate:self cancelButtonTitle:NSLocalizedString(@"OK",nil) otherButtonTitles:nil];
        [alert show];
        [alert release];
        //return; 
    }  
}

- (void)viewDidLoad
{
    [super viewDidLoad];

    NSURL *url = [NSURL URLWithString:@"http://francescoficetola.it/services/test.php"];

    ASIFormDataRequest *myRequest = [ASIFormDataRequest requestWithURL:url];

    [myRequest setDefaultResponseEncoding:NSUTF8StringEncoding];
    [myRequest setResponseEncoding:NSUTF8StringEncoding];
    [myRequest setRequestMethod:@"POST"];
    [myRequest setPostValue:@"valueMyParameter" forKey:@"myParameter"];

    [myRequest addRequestHeader:@"Accept" value:@"application/json"];
    [myRequest addRequestHeader:@"Content-Type" value:@"application/json; charset=UTF-8;"];
    [myRequest setDelegate:self];

    [myRequest startAsynchronous];

}
Nel metodo viewDidLoad, si prepara la richiesta asincrona in POST, settando i valori da inserire nell’header HTTP, come il Content-Type, lo User-Agent e la codifica da utilizzare. Con il comando startAsynchronous, la richiesta viene inviata al servizio specificato nella URL. Per effettuare una chiamata in GET, occorre modificare in setRequestMethod:@”GET”

 

Occorre definire poi i due metodi di success ed error callback:
  • requestFailed: è il metodo che viene richiamato se la risposta non è andata a buon fine. E’ possibile intercettare anche il relativo status code, per visualizzare i relativi messaggi di errore all’utente;
  • requestFinished: è il metodo che viene richiamato in caso di ricezione con successo della risposta, raccolta dalla variabile responseData. L’elaborazione di quest’ultima viene effettuata con un metodo (che non fa parte della libreria ASIHTTPRequest) che, in un thread di background, estrae dal JSON una lista di oggetti (in questo esempio, i post di un forum), ricaricandoli poi in una UITableView (con il metodo nativo reloadData).
In alternativa, è possibile dichiarare le funzioni di callback di successo ed errore direttamente prima della chiamata asincrona, definendo dei blocks:
/*  ...  */   

   [request setDelegate:self];
   [request setCompletionBlock:^{         
        NSString *responseString = [request responseString];
        NSLog(@"Response: %@", responseString);

        /* your code here in success case */

    }];
    [request setFailedBlock:^{
        NSError *error = [request error];
        NSLog(@"Error: %@", error.localizedDescription);

       /* your code here in error case */

    }];

   [request startAsynchronous];

/*  ...  */
Direi che entrambi i metodi sono abbastanza semplici, ma quello con la ASIHTTPRequest offre sicuramente maggiore controllo e un ricco set di funzionalità per la connettività ai nostri servizi. Eccole tutte: http://allseeing-i.com/ASIHTTPRequest/How-to-use
Creative Commons License
This work by Francesco Ficetola is licensed under a Creative Commons Attribution 4.0 International License.
Based on a work at www.francescoficetola.it.
Permissions beyond the scope of this license may be available at http://www.francescoficetola.it/2012/11/03/ios-come-effettuare-chiamate-asincrone/.

[iOS] La risoluzione delle icone nelle app iOS, con link utili da dove scaricarle

Eccovi un pò di riferimenti e di link utili per scaricare delle iconcine da inserire nelle vostre app iOS (e non solo). Innanzitutto, il riferimento per la risoluzione delle “custom icon“, da inserire nelle tabbar, toolbar, come badges o sulla springboard…e quant’altro, è quello ufficiale di Apple: Custom Icon and Image Creation Guidelines

Leggete anche questo articolo su DevApp.it (anche se non aggiornato): Linee guida per la creazione di icone e splashscreen per applicazioni iPhone e iPad

Alcuni siti utili da dove scaricare (sia gratuitamente che a pagamento) i set di icone per le vostre app sono i seguenti:

Se ne conoscete altri, segnalateli nei commenti. Grazie! 😉

[iOS] Inserire le mappe e la localizzazione con MapKit e CoreLocation Framework

Sicuramente sapete che Apple è arrivata ai ferri corti, da un bel pezzo, con Google e ha comunicato di non includere neanche YouTube come app preinstallata sul melafonino:

Addirittura Apple ha lanciato la sfida a Google Maps con un’app che dà anche informazioni sul traffico e fornisce un sistema di navigazione in 3D, che dovrebbe uscire su iOS 6.

Tuttavia, per chi volesse ad inserire una mappa o indicazioni di geolocalizzazione nelle proprie app, in attesa delle API ufficiali del nuovo sistema di navigazione Apple succitato, vi riporto un esempio su come utilizzare MapKit e CoreLocation Framework.

Nel progetto di esempio (che vi allego alla fine di questo articolo), trovate una view con una mappa centrata su un punto GPS (latitudine, longitudine) definito (e segnalato da un marker). Vi è possibilità di definire diverse modalità di visualizzazioni della mappa (normale, satellitare, ibrida) e la possibilità di segnalare il percorso (a piedi o in macchina) dal punto GPS in cui si trova il vostro device e il luogo di arrivo preimpostato. Inoltre, viene calcolato anche il tempo stimato e i metri da percorrere (a piedi o in macchina) dall’origine alla destinazione.

Ecco la “lista della spesa” delle librerie/framework che ci servono:

  • RegexKitLite: è una libreria di supporto che permette di utilizzare le espressioni regolari (regex) – Lightweight Objective-C Regular Expressions for Mac OS X using the ICU Library
  • MapKit Framework: fornisce le API per il download delle mappe, lo zoom e l’inserimento delle annotazioni (marker)
  • CoreLocation Framework: permette di determinare la posizione GPS corrente e la direzione spaziale del device