[JSON] La specifica JSON-RPC 2.0: alcuni client iOs


masthead-jsonrpc2base
Esiste una specifica alquanto diffusa per la distribuzione di servizi RESTful, con l’ovvio formato di interscambio JSON, nota come JSON-RPC 2.0.

Pe le caratteristiche di dettaglio di questa specifica vi rimando al sito ufficiale: http://www.jsonrpc.org/specification 

In breve, JSON-RPC non è altro che un protocollo per la chiamata di procedure remote, dove è possibile passare al servizio anche oggetto complessi ed avere in output dati multipli. La descrizione del servizio (una sorta di WSDL) è ben definita, come anche i codici di errore e di successo restituiti dalla chiamata. In sintesi, non è altro che un “modo” per la scrittura di servizi RESTful secondo un certo standard.

La cosa interessante è che il servizio remoto viene interrogato tramite i comuni protocolli HTTP o TCP/IP e la serializzazione degli oggetti di output avviene in formato JSON. Una richiesta di servizio è una chiamata a un metodo specifico e che contiene le seguenti proprietà:

  • method – una stringa con il nome del metodo da invocare.
  • params – un array di oggetti da passare come parametri al metodo definito.
  • id un valore di qualsiasi tipo, che viene utilizzato per abbinare la risposta alla richiesta

Esempio:  {“jsonrpc”: “2.0”, “method”: “subtract”, “params”: [42, 23], “id”: 1}

json-rpc2.0 Per poter richiamare questi servizi da un’app mobile iOs, ho provato i seguenti client:

Esiste anche la possibilità di richiamare i servizi JSON-RPC in “modalità nativa”, come descritto da questo tutorial:

[iOs] Upload di immagini da app su server

Come primo post dell’anno, vi scrivo una soluzione per l’upload di foto su un vostro server (valida anche per qualsiasi altro tipo di file). Fino a poco tempo fa era possibile utilizzare il framework ASIHTTPRequest, che permetteva in modo agevole di eseguire questa operazione. Tuttavia, essendo tale framework “no-ARC compatibile”, vi consiglio un metodo nuovo che sfrutta la classe “nativa” NSURLConnection, oppure la libreria di terza parte URLConnection (per inserire una comoda progress bar).

Vi scrivo anche lo script PHP da richiamare lato app per l’upload della foto e che ho utilizzato come test (file image_upload.php):

/* image_upload.php */

<?php

//elenco delle estensioni permesse
$extensions_permitted = array("jpg", "png", "JPG", "PNG");

//path assoluto in cui si trova il file di script upload_image.php
$absolute_current_path = getcwd();

//recupero il nome del file dal parametro di input "filename"
$filename = $_POST['filename'];
$response = array();
$error = false;

//recupero l'estensione del file
$ext = pathinfo($_FILES['userfile']['name'], PATHINFO_EXTENSION);

//controllo il nome file
if(empty($filename)){
  $error = true;
  $response['code'] = '500';	
  $response['message'] = ('Nome file non valido!');	
  $response['newfile'] = $filename.'.'.$ext;
}

//controllo l'estensione
if (!in_array($ext, $extensions_permitted)) {
  $error = true;
  $response['code'] = '500';	
  $response['message'] = ('Estensione del file non permessa!');	
  $response['newfile'] = null;
}

if(!$error){
	//NOTA: la seguente directory deve esistere sul proprio server.
	//Assicurarsi di avere i chmod di scrittura
	$uploaddir = $absolute_current_path.'/img/';

	//percorso assoluto del nuovo file
	$uploadfile = $uploaddir . $filename.'.'.$ext;

	if (move_uploaded_file($_FILES['userfile']['tmp_name'], $uploadfile)) {
    	$response['code'] = '200';	
  		$response['message'] = ('File valido e correttamente salvato!');	
        $response['newfile'] = $filename.'.'.$ext;
	} else {
    	echo "Errore nell'upload del file!\n";
        $response['code'] = '500';	
  		$response['message'] = ('Errore nell\'upload del file!');	
        $response['newfile'] = null;
	}

//echo 'Alcune informazioni di debug:';
//print_r($_FILES);

}

echo json_encode($response);

?>

Di seguito, una pagina di test PHP per controllare che il vostro script di upload sia stato configurato correttamente sul vostro server. NOTA. Vi troverete le immagini nella directory IMG relativa al file image_upload.php.

<html>
<head>
	<title>Upload a file</title>
</head>

<body>
<!-- Tipo di codifica dei dati, DEVE essere specificato come segue -->
<form enctype="multipart/form-data" action="upload_image.php" method="POST">

    <!-- MAX_FILE_SIZE deve precedere campo di input del nome file -->
    <input type="hidden" name="MAX_FILE_SIZE" value="30000" />

    <b>Nome file:</b>
    <input type="text" name="filename" value="" />

    <br/>

    <!-- Il nome dell'elemento di input determina il nome nell'array $_FILES -->
    <b>File:</b> <input name="userfile" type="file" />
    <br/>
    <input type="submit" value="Invia File" />
</form>

</body>

</html>

Ecco i codici sorgenti degli script PHP su riportati:

PHP upload script

upload
upload.rar (2 kB)

Template doesn't exists. Use default

Passiamo ora al codice Objective-C per la vostra app client. Ho creato la classe UploaderDelegate, che potete comodamente importare nei vostri progetti e che gestirà l’upload della foto. L’interfaccia è quella che segue:

//
//  UploaderDelegate.h
//
//  Created by Fr@nk on 07/01/14.
//  Copyright (c) 2014. All rights reserved.
//

#import <Foundation/Foundation.h>

@protocol UploaderProtocol <NSObject>

@required
-(void)successUpload;

@optional
-(void)unsuccessUpload;

@optional
-(void)updateProgressBar:(float)progress;

@end

@interface UploaderDelegate : NSObject<NSURLConnectionDataDelegate>{

    NSMutableData *receivedData;

}

//singleton
+ (id)sharedInstance;
-(void)sendImageToServerWithURLPath:(NSString*)pathImage withFilename:(NSString*)filename toServiceURL:(NSString*)serviceURL;

@property (nonatomic, weak) id<UploaderProtocol> delegate;

@end

L’implementazione dell’UploadDelegate è questa:

//
//  UploaderDelegate.m
//
//  Created by Fr@nk on 07/01/14.
//  Copyright (c) 2013. All rights reserved.
//

#import "UploaderDelegate.h"
#import "URLConnection.h"
#import "Configuration.h"

@implementation UploaderDelegate

@synthesize delegate;

static UploaderDelegate *sharedInstance = nil;

// Get the shared instance and create it if necessary.
+ (UploaderDelegate *)sharedInstance {
    if (sharedInstance == nil) {
        sharedInstance = [[super allocWithZone:NULL] init];
    }

    return sharedInstance;
}

- (id)init
{
    self = [super init];

    if (self) {

    }

    return self;
}

-(void)sendImageToServerWithURLPath:(NSString*)pathImage withFilename:(NSString*)filename toServiceURL:(NSString*)serviceURL{

    //controllo se il file esiste
    BOOL fileExists = [[NSFileManager defaultManager] fileExistsAtPath:pathImage];

    if(!fileExists){
        NSLog(@"Error: file not exists!");
        //richiamo l'errorUpload sul Delegate in caso di success
        if (self.delegate && [self.delegate respondsToSelector:@selector(unsuccessUpload)]) {
            [self.delegate unsuccessUpload];
        }
        return;
    }

    //controllo la grandezza del file
    NSError *AttributesError = nil;
	NSDictionary *fileAttributes = [[NSFileManager defaultManager] attributesOfItemAtPath:pathImage error:&AttributesError];
	NSNumber *fileSizeNumber = [fileAttributes objectForKey:NSFileSize];
	long fileSize = [fileSizeNumber longValue];
	NSLog(@"File: %@, Size: %ld", pathImage, fileSize);
    if(fileSize<=0){
        NSLog(@"Error: file size incorrect!");
        //richiamo l'errorUpload sul Delegate in caso di success
        if (self.delegate && [self.delegate respondsToSelector:@selector(unsuccessUpload)]) {
            [self.delegate unsuccessUpload];
        }
        return;
    }

    //se la foto è troppo grande, la comprimo
    UIImage *originalImage = [[UIImage alloc]initWithContentsOfFile:pathImage];

    //NSData *dataImage = [NSData dataWithContentsOfURL:[NSURL URLWithString:pathImage]];
    //NSData *dataImage = [[NSFileManager defaultManager] contentsAtPath:pathImage];

    CGFloat compression = 0.9f;
    CGFloat maxCompression = 0.1f;
    int maxFileSize = MAXFILESIZE_IMAGE_UPLOAD;

    //comprimo l'immagine e la salvo in JPEG, se è troppo grande
    NSData *dataImage = UIImageJPEGRepresentation(originalImage, compression);

    while ([dataImage length] > maxFileSize && compression > maxCompression)
    {
        compression -= 0.1;
        dataImage = UIImageJPEGRepresentation(originalImage, compression);
    }

    NSMutableURLRequest *postRequest = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:serviceURL]];

    [postRequest setHTTPMethod:@"POST"];

    NSString *boundary = @"BVillage";
    NSString *contentType = [NSString stringWithFormat:@"multipart/form-data; boundary=%@",boundary];
    [postRequest addValue:contentType forHTTPHeaderField: @"Content-Type"];

    NSMutableData *body = [NSMutableData data];

    [body appendData:[[NSString stringWithFormat:@"\r\n--%@\r\n",boundary] dataUsingEncoding:NSUTF8StringEncoding]];
    [body appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"filename\"\r\n\r\n"] dataUsingEncoding:NSUTF8StringEncoding]];
    [body appendData:[filename dataUsingEncoding:NSUTF8StringEncoding]];

    [body appendData:[[NSString stringWithFormat:@"\r\n--%@\r\n",boundary] dataUsingEncoding:NSUTF8StringEncoding]];
    [body appendData:[@"Content-Disposition: form-data; name=\"userfile\"; filename=\"tmpFile.jpg\"\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
    [body appendData:[@"Content-Type: application/octet-stream\r\n\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
    [body appendData:[NSData dataWithData:dataImage]];
    [body appendData:[[NSString stringWithFormat:@"\r\n--%@\r\n",boundary] dataUsingEncoding:NSUTF8StringEncoding]];

    [postRequest setHTTPMethod:@"POST"];
    [body appendData:[[NSString stringWithFormat:@"--%@--\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];

    // Setting a timeout
    postRequest.timeoutInterval = 60.0;
    [postRequest setHTTPBody:body];

    [UIApplication sharedApplication].networkActivityIndicatorVisible = YES;

    /*[NSURLConnection sendAsynchronousRequest:postRequest
                                       queue:[NSOperationQueue mainQueue]
                           completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
     */
    [URLConnection asyncConnectionWithRequest:postRequest completionBlock:^(NSData *data, NSURLResponse *response) {

        NSLog(@"Succeeded! Received %d bytes of data",[receivedData length]);
        [UIApplication sharedApplication].networkActivityIndicatorVisible = NO;

        NSDictionary *responseJSON = [NSJSONSerialization
                                      JSONObjectWithData:data
                                      options:kNilOptions error:nil];

        NSLog(@"Response JSON: %@",responseJSON);

        //if(!error){
            //richiamo il successUpload sul Delegate in caso di success
            if (self.delegate && [self.delegate respondsToSelector:@selector(successUpload)]) {
                [self.delegate successUpload];
            }
            return;
        /*}
        else{
            //richiamo l'errorUpload sul Delegate in caso di success
            if (self.delegate && [self.delegate respondsToSelector:@selector(unsuccessUpload)]) {
                [self.delegate unsuccessUpload];
            }
            return;
        }*/

    } errorBlock:^(NSError *error) {
        ////richiamo l'errorUpload sul Delegate in caso di success
        if (self.delegate && [self.delegate respondsToSelector:@selector(unsuccessUpload)]) {
            [self.delegate unsuccessUpload];
        }
        return;
    } uploadProgressBlock:^(float progress) {
        if (self.delegate && [self.delegate respondsToSelector:@selector(updateProgressBar:)]) {
            [self.delegate updateProgressBar:progress];
        }

    } downloadProgressBlock:^(float progress) {
        //
    }

    ];

}

@end

Come potete vedere, ho utilizzato la libreria URLConnection, per poter mostrare durante il caricamento del file una “progress bar” (come spiegato in questo post: http://messagesenttodeallocatedinstance.wordpress.com/2012/04/10/nsurlconnection-with-blocks/).

Per poter usare la classe UploaderDelegate e procedere all’upload della foto, basta inserire nel vostro codice la seguente chiamata:

//chiamata al wrapper per l'upload dei file sul server
UploaderDelegate *uploaderDelegate = [UploaderDelegate sharedInstance];
uploaderDelegate.delegate = self;

NSString *pathFile = ABSOLUTE_PATH_OF_YOUR_PHOTO;
NSString *photoName = FILENAME_OF_YOUR_PHOTO;

    [uploaderDelegate sendImageToServerWithURLPath:pathFile withFilename:photoName toServiceURL:UPLOAD_IMAGE_URL];

NOTA. E’ importante settare che il pathFile sia il percorso assoluto alla vostra foto (comprensivo di nome del file e di estensione). La variabile photoName è il nome della foto (senza estensione) così come verrà salvata sul vostro server. Al posto dell’etichetta UPLOAD_IMAGE_URL ricordatevi di sostituire la URL del vostro servizio PHP di upload (per esempio, http://www.francescoficetola/upload/image_upload.php)

Ricordatevi di importare la classe suddetta e di settare la “delega” nel vostro ViewController, inserendo l’UploaderProtocol. Come ad esempio:

//
//  MyViewController
//
//  Created by Fr@nk on 07/01/14.
//  Copyright (c) 2014. All rights reserved.
//

#import <UIKit/UIKit.h>
#import "UploaderDelegate.h"
#import "KKProgressToolbar.h"

@interface MyViewController : UIViewController<UploaderProtocol, KKProgressToolbarDelegate>{

    IBOutlet UIImageView *imageView;
    IBOutlet UIButton *imageUploadButton;
    KKProgressToolbar *progressBar;
}

/* metodi di delegate */
-(void)successUpload;
-(void)unsuccessUpload;
-(void)updateProgressBar:(float)progress;
-(IBAction)startProgressBarLoading;
-(IBAction)stopProgressBarLoading;

-(IBAction)sendPhotoToServer:(id)sender;

@property (nonatomic, strong) IBOutlet UIImageView *imageView;
@property (nonatomic, strong) KKProgressToolbar *progressBar;
@property (nonatomic, strong) IBOutlet UIButton *imageUploadButton;

@end

Per poter gestire gli eventi relativi all’upload, occorre definire nel vostro ViewController i seguenti metodi di delegate:

  • successUpload: richiamato dall’UploaderDelegate in caso di upload con successo
  • unsuccessUpload: richiamato dall’UploadDelegate in caso di errore nell’upload
  • updateProgressBar: richiamato dall’UploadDelegate durante l’upload per aggiornare la percentuale di caricamento sulla progress bar
  • startProgressBarLoading: da richiamare, lato app, per inizializzare la progress bar
  • stopProgressBarLoading: da richiamare, lato app, per stoppare la progress bar

NOTA. Per gestire la progress bar ho utilizzato la seguente libreria: KKProgressToolbar

#pragma mark Uploader methods

-(void)updateProgressBar:(float)progress{
    self.progressBar.progressBar.progress = progress;
}

-(void)successUpload{
    NSLog(@"successUpload");
    //in caso di successo aggiorno rimuovo la progress bar
    [self stopProgressBarLoading];

}

-(void)unsuccessUpload{

    NSLog(@"unsuccessUpload");
}

#pragma StatusBarLoading

-(void)addProgressBar{

    //aggiungo la progress view:
    CGRect progressBarFrame = CGRectMake(0, self.view.frame.size.height-100, self.view.frame.size.width, 44);
	self.progressBar = [[KKProgressToolbar alloc] initWithFrame:progressBarFrame];
	self.progressBar.actionDelegate = self;
    self.progressBar.progressBar.progress = 0;
    self.progressBar.statusLabel.text = @"Loading from server...";
	[self.view addSubview:self.progressBar];

    [self startProgressBarLoading];

}

- (void)didCancelProgressBarButtonPressed:(KKProgressToolbar *)toolbar {
    [self stopProgressBarLoading];
    //TODO: eliminare la richiesta di upload...
}

- (IBAction)startProgressBarLoading  {

    [self.progressBar show:YES completion:^(BOOL finished) {
        //Code
    }];

}

- (IBAction)stopProgressBarLoading {
    [self.progressBar hide:YES completion:^(BOOL finished) {
        //code
    }];

}

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/2014/01/06/ios-upload-di-immagini-da-app-su-server/.

[iOs] Idee e codici per menù ed altri componenti grafiche con effetti e transizioni su Code4App

Condivido qui un sito interessante, Code4App, dove vengono proposti menù ad effetto con transizioni:

In realtà, sul sito di Code4App troverete anche tante altre idee per la vostra app, come tab bar, animazioni, calendar, tabelle, ecc. ecc. Ovviamente troverete anche i codici sorgenti da scaricare e provare in xCode.

Ecco le categorie attualmente disponibili:

CodeApp - Category

Devo dire che è davvero una bella idea quella di raccogliere su un sito tutti i progettini utili ed interessanti condivisi da utenti di GitHub, BitBucket ed altri. Spesso, infatti, è difficile cercare quello che serve sui social network per programmatori, perché non mettono a disposizione una “vetrina” immediata che consente di individuare il componente desiderato.

Ecco i menù che ho trovato interessanti su Code4App e che ho usato per una mia app:

ios_menu popupscroll_menu ios

[iOs] Link Maker: generazione dinamica dei link di Apple Store

Per la generazione dei link di tutti gli articoli (ebook, musica, app, podcast, audiolibri, ecc.) disponibili su iTunes, iBook ed App Store, Apple mette a disposizione la pagina di iTunes Link Maker:  http://linkmaker.itunes.apple.com

Link Maker Apple

Esistono, in realtà, vari strumenti utili per la generazione dei link (creare link ad iTunes), utili però solo per la creazione di widget e link statici. Ad esempio, in questo Q&A si spiega come generare link a propri item (come, ad esempio, alla pagina delle app sviluppate da uno sviluppatore o da una società: Creating easy-to-read short links to the App Store for your apps and company).

Tuttavia, non esiste un vero e proprio framework che consenta di recuperare dinamicamente tali link. Occorre richiamare una URL di servizio, fornita proprio da Apple, così come si legge dalla Search API Documentation.

Ho così realizzato uno “scriptino” PHP in grado di restituirvi i risultati della pagina di iTunes Link Maker in formato JSON. Per fare ciò, mi sono aiutato con uno sniffer e ho recuperato tutte le URL richiamate dopo aver lanciato una ricerca sulla pagina di Link Maker. Le URL generate “dietro le quinte” dalla ricerca sono un bel po’ e cambiano in base alla combinazione tra “media” selezionato (categoria di item: audio, video, app, ebook, ecc.) e relative entity (sottocategorie di item non selezionabili, ma impostate da Apple come ulteriori keyword di ricerca, in base al media selezionato).

Potete provare la ricerca dalla form di esempio disponibile qui:

Nello script PHP che ho realizzato, non faccio altro che richiamare la stessa URL di Apple, passando una combinazioni di parametri (essenzialmente cambiano “media” ed “entity“):

http://itunes.apple.com/WebObjects/MZStoreServices.woa/ws/wsSearch?term=%s&country=%s&media=%s&entity=%s&limit=%s&genreId=%s&version=2&output=json

Al posto di %s occorre inserire dei valori ammissibili (la lista completa la trovate sulla documentazione ufficiale – Search API Documentation):

  • term: contiene la keyword di ricerca (ad esempio, “Oasis what’s the story morning glory”)
  • country: identificativo del Paese (IT, US, UK, ecc.). La ricerca restituirà gli item disponibili sugli Apple Store del paese selezionato. La lista completa dei Paesi è disponibile qui: http://www.francescoficetola.it/link-maker/countries.json
  • media: deve contenere la categoria di item. I valori ammissibili sono: audiobook (per gli AudioLibri), shortFilm (per i Cortometraggi), movie (per i Film), software (per le iOs app), ebook (per i Libri), macSoftware (per le Mac Apps), music (per i brani audio), podcast (per i PodCast), musicVideo (per i Video).
  • limit: numero massimo di risultati di ricerca da restituire;
  • entity: contiene le sottocategorie dei prodotti. Questo valore dipende dal parametro media impostato (le varie combinazioni sono elencate di seguito);
  • genreId: (opzionale) contiene il codice relativo al genere di media desiderato. Esistono varie combinazioni, disponibili al seguente link:

Ecco le combinazioni di media e relative entity, così come mappate nel mio script PHP:

//AUDIOLIBRI
if($media=='audiobook'){
   $entity = array('audiobookAuthor','audiobook');
}

//CORTOMETRAGGI
if($media=='shortFilm'){
   $entity = array('shortFilmArtist','shortFilm');
}

//FILM
if($media=='movie'){
   $entity = array('movieArtist','movie');
}

//iOS Apps
if($media=='software'){
   $entity = array('softwareDeveloper','software','iPadSoftware');
}

//LIBRI
if($media=='ebook'){
   $entity = array('ebookAuthor','metaEbook,textbook,ebook');
}

//Mac Apps
if($media=='macSoftware'){
   $entity = array('softwareDeveloper','macSoftware');
}

//MUSICA
if($media=='music'){
   $entity = array('musicArtist','song','album','musicVideo');
}

//PODCAST
if($media=='podcast'){
   $entity = array('musicArtist','podcastAuthor','podcast');
}

//VIDEO
if($media=='musicVideo'){
   $entity = array('musicVideoArtist','musicVideo');
}

Se volete richiamare direttamente il mio script PHP, senza passare dalla form di esempio su linkata (magari per farvi restituire un JSON da utilizzare nelle vostre app), eccovi la URL diretta:

  • http://francescoficetola.it/link-maker/linkmaker.php?term=%s&country=%s&media=%s&limit=%s&genreId=%s

I valori ammissibili da passare sono quelli espressi precedentemente e di seguito riassunti:

  • media: audiobook, shortFilm, movie, software, ebook, macSoftware, music, podcast, musicVideo
  • country: IT, US, UK, ecc.
  • term: la vostra query di ricerca (ad esempio, Oasis what’s the story morning glory”)
  • limit: (opzionale, di default è impostato a 50) determina il numero massimo di risultati che occorre restituire per ognuna delle combinazioni di URL di ricerca richiamate da Apple, in base al media impostato.
  • genreId: (opzionale) codice del genere degli item, il cui valore ammissibile è disponibile nel dizionario lmMediaTypeAndGenreDictionaries fornito da Apple.

Le combinazioni di entity sono definite direttamente nello script e non occorre specificare il parametro in input.

Ecco un esempio di ricerca:

http://francescoficetola.it/link-maker/linkmaker.php?term=jovanotti+bella&country=IT&media=music&limit=50&genreId=14

[iOs] Aggiornamento ad xCode 5: novità e how-to ai problemi post-upgrade

xCode 5Dopo gli upgrade di xCode, si hanno gli ormai inevitabili problemi di compatibilità o di change configuration di progetti scritti con una versione precedente. Sono passato al nuovo xCode 5 da quando è stato rilasciato per gli sviluppatori iOs, imbattendomi in alcune problematiche, e ho pensato di scrivere una sorta di “how-to” per la loro risoluzione, riportandolo in fondo a questo articolo.

Novità di xCode 5. Vediamo prima quali sono le novità introdotte in xCode 5:

  • Configurazione automatica: i progetti vengono automaticamente configurati per supportare i servizi Apple come iCloud, Passbook Game Center. Inserendo il proprio Apple ID, è possibile gestire anche il proprio team, i certificati e gli entitlements, il tutto direttamente da IDE (cosa che prima si effettuava connettendosi al Provisioning Portal). In base ai servizi richiesti, xCode importa anche i framework necessari e crea gli appropriati provisioning profiles, “firmando” l’app ogni volta che viene “buildata”.
  • Test Navigator: il test-driven development diventa più semplice, utilizzando il nuovo Test Navigator, editor in cui creare, modificare e lanciare gli unit tests. Si possono lanciare singoli test o gruppi di test, grazie ad Assistant Editor.
  • Bots per il Continuous Integration: xCode si integra (integrerà) con l’OS X Server del Sistema Operativo OS X Mavericks, permettendo la creazione di “bots” che “buildano” continuamente l’app ed eseguono i test e lo static analyzer per scovare potenziali bug. I bots possono essere lanciati su qualsiasi Mac presente in rete, monitorando l’integration work su macchina locale.
  • Auto Layout: l’auto-layouting permette di “aggiustare” automaticamente la dimensione delle view grafiche, il loro orientamento e la localizzazione (internazionalizzazione), direttamente da Interface Builder. Introdotti a tale scopo il free-form canvas e i constraints (in realtà già presenti nelle versioni precedenti di xCode).
  • Asset Management: grazie agli Asset Catalogs (salvati su file compressi con estensione .xcassets) si possono gestire le immagini inserite nelle app. Le diverse versioni della stessa immagine (per le differenti risoluzioni dei device) vengono collezionate tutte in un singolo file. L’Asset Catalog sceglie quale immagine renderizzare in base al device su cui si testa e permette anche di scalarla dinamicamente.
  • Debug deeper analysis: con il nuovo strumento di debug è possibile misurare anche il consumo dei dati, la memoria CPU occupata, l’energia in uso, e altri valori prestazionali (deeper analysis), allertando lo sviluppatore in caso di overhead.
  • Visual Debugger: il debugger utilizzato è l’LLDB engine (nelle precedenti versioni c’era anche GDB, adesso completamente rimosso). Resa più leggibile e chiara la visualizzazione dei messaggi di debug ed inseriti “data tips” più esplicativi. Finalmente, si può “ispezionare” con un singolo click la variabile/oggetto e stampare le sue proprietà direttamente sulla debug console.
  • Source Control: migliorato il sistema di versioning che visualizza anche i branch attivi di progetto e permette agevolmente di switchare, creare e mergiare i branch stessi. E’ possibile gestire in un singolo punto tutte le repository (Accounts preferences) e l’OS X Server su OS X Mavericks permette (permetterà) di “hostare” anche repository GIT per il team di sviluppo.

 

Problemi post-upgrade ad xCode 5. Riporto di seguito le anomalie riscontrate (e corrette) dopo l’aggiornamento ad xCode 5, su progetti scritti su versioni precedenti.

Nota. Questo articolo sarà costantemente aggiornato, man mano che mi si presenteranno anomalie su “vecchi” progetti nel nuovo ambiente di xCode 5.

  • Impossibile debuggare, visualizzazione del messaggio “Attaching appname” con schermata nera sul vostro Simulatore iOs. Se non riuscite più a debuggare le vostre app, vi viene mostrato continuamente il messaggio “Attaching <appname>” e lo screen del Simulatore vi rimane nero, senza dar minimo segno di visualizzazione della vostra app, nelle precedenti versioni di xCode bastava switchare dal debugger LLDB a GDB (Scheda Product>>Edit Scheme). Adesso, avendo eliminato il debugger GDB, tale procedura non funziona più e vi conviene seguire il seguente “how-to”: http://stackoverflow.com/questions/15174282/why-does-the-lldb-debugger-constantly-fail-to-attach. Occorre cioè assicurarsi che sul vostro file di hosts (presente al patch /private/etc/hosts del vostro Mac) si faccia lookup” di localhost su 127.0.0.1. Nel mio file di hosts, invece, vi erano mappati altri valori (colpa di Adobe Professional!).

(continua…)

 

[iOs] AudioStreamer e ShoutCast: come riprodurre la nostra web radio in un’app

internet-radioAvete una vostra web radio o semplicemente volete condividere la vostra playlist preferita? Sicuramente conoscete SHOUTcast, storico programma per la condivisione di streaming audio e la realizzazione di web-radio amatoriali e, direi, professionali. 

Potete scaricare il server di ShoutCast sul sito ufficiale gratuitamente per varie piattaforme:

http://www.shoutcast.com/broadcast-tools

Seguite le istruzioni di installazione e riuscirete in pochi passi a configurare un servizio online (su una pagina web pubblica) per la trasmissione in streaming della vostra radio, grazie a SHOUTcast D.N.A.S. Avrete una pagina web tipo questa, da dove andremo a recuperare lo streaming audio e le info sulla traccia corrente:

 

SHOUTCAST SERVER DNAS

 

Per ulteriori info su come creare la vostra webradio con ShoutCast e Winamp vi consiglio di leggere questo tutorial: http://www.gozzinet.net/2009/06/01/come-creare-una-webradio-con-winamp-e-shoutcast/

In questo articolo ci concentriamo più che altro sulla realizzazione di un’app che vi permetterà di riprodurre lo streaming di ShoutCast, accedendo alla pagina web creata dal server suddetto. Tutto è molto semplice, perché basta scaricare e customizzare l’ottimo progetto di Matt Gallagher su GitHub:

https://github.com/mattgallagher/AudioStreamer

Tale progetto è consigliato addirittura da Google (AudioStreamer Metadata Google Code). La classe AudioStreamer vi permette di:

  • a partire dalla URL della pagina web generata da SHOUTcast D.N.A.S., di riprodurre lo streaming audio
  • vi mette a disposizione i controlli PLAY e STOP per la riproduzione
  • permette il controllo del volume del vostro iPhone

Tuttavia, non vi permette di recuperare le informazioni sulla traccia correntemente in riproduzione. Teoricamente queste informazioni sono contenute nei metadati del pacchetto HTTP dello streaming (più precisamente nel pacchetto ICY), così come si legge in questo articolo: Shoutcast Metadata Protocol.

Onestamente non sono riuscito a recuperare in modo agevole le informazioni su titolo, artista e cover della copertura dell’album della traccia in riproduzione, dai metadati del pacchetto ICY. Occorrerebbe individuare il punto preciso in cui andare a “parsare” tali informazioni.

ShoutCast, però, genera delle pagine accessorie da cui recuperare le info che ci servono. Per esempio, per la web radio http://www.kissradio.info si riescono a recuperare le info su artista e brano in riproduzione, nonché la cover, dalle seguenti URL:

  • http://www.kissradio.info/OnAir.txt
  • http://www.kissradio.info/OnAir.jpg

Ma facendo varie ricerche ho scoperto che ShoutCast inserisce le info anche in una pagina “nascosta” del server di streaming, la 7.html. Per esempio, per il servizio di streaming http://kemoniastreaming2.com:8018, le info della traccia in riproduzione le troviamo qui: http://kemoniastreaming2.com:8018/7.html

Basterà, dunque, “parsare” queste info nella nostra app e visualizzarle mentre il brano viene riprodotto. Sarà ovviamente necessario un refresh delle info secondo un tempo prefissato, magari utilizzando un timer (NSTimer).

 

[iOs] Librerie per la scansione di codici a barre ed il riconoscimento OCR

Come acquisire un codice a barre in un’app iOs? Ce lo dice Marco Giraudo, giovane sviluppatore iOs e PHP, che mi ha mandato questo ottimo articolo, in cui si è trovato a valutare delle librerie per la scansione di codici a barre (BarCode Reader), ma anche per il riconoscimento OCR.

iPhone_barcode_scanner

Eccovi l’esperienza di Marco, a cui va il mio ringraziamento:

“Mi sono fatto questa domanda nelle ultime settimane per motivi di lavoro e vi voglio riportare 2 esempi che sono riuscito a trovare e testare. In particolare, mi serviva riconoscere un codice a barre e cosi ho utilizzato due librerie: ZBarSDK e Tesseract .

1° Esempio: ZBarSDK

ZBarSDKhttp://zbar.sourceforge.net/iphone/sdkdoc/

C’è stato subito un problema da superare, dopo l’installazione sul mio progetto xCode:

Warning: Only the iPhone 3GS and iPhone 4 are supported, as they have a camera with auto-focus. The ZBar library does not support the iPhone 3G and is unlikely to ever support it.

Su iPhone 5 con iOs 6 e successivi mi dava problemi, ma ho trovato la soluzione su http://stackoverflow.com:

http://stackoverflow.com/questions/12506671/zbar-sdk-is-not-working-in-ios6

Oltre a settare come “Valid Architecture” il processore armv7s nelle impostazioni del vostro progetto xCode, potete provare a scaricare ed installare la versione beta compatibile con iOs 6 (ZBarSDK-1.3.1.dmg):

http://sourceforge.net/projects/zbar/files/iPhoneSDK/beta/

Una  volta installato il .DMG, l’unica cosa da fare è leggere il file README riportato e seguire le istruzioni per inserire la libreria nel proprio progetto:

    • trascinare la cartella ZBarSDK nel proprio progetto
    • inserire nel proprio progetto i Frameworks: AVFoundation (weak), CoreMedia (weak), CoreVideo (weak), QuartzCore, libiconv.dylib

Per usare ZBar, basta importare nel vostro controller la libreria:

#import “ZBarSDK.h”

Nello stesso .dmg troverete una cartella con molti esempi da cui prendere spunto. L’ho anche provata con iPhone5 con su installato ios7 beta4 e funziona benissimo.

2° Esempio: Tesseract-OCR (Google)

Secondo esempio che vi riporto è tesseract-ocr, un OCR Engine che vi permette di riconoscere i testi riportati in un’immagine. Ecco il sito ufficiale su Google Code:

http://code.google.com/p/tesseract-ocr/

Per far funzionare l’OCR Engine in iOs, esiste un Wrapper: Tesseract for iOs (https://github.com/ldiqual/tesseract-ios)

Eccovi le istruzioni di installazione:

http://lois.di-qual.net/blog/install-and-use-tesseract-on-ios-with-tesseract-ios/

Spero che questi due esempi possano esservi utili. Alla prossima!”

[iOs] Quick Objective-C Tutorial

Consiglio un bel tutorial per chi volesse iniziare la programmazione su Objective-C:

This tutorial is the place to learn the Objective-C programming language. It’s designed to serve as both a concise quick-reference and a comprehensive introduction for newcomers to the language. A familiarity with basic programming concepts like variables, functions, and objects is recommended, but not strictly required.

Published  – Tested on Xcode 4.5 with Apple LLVM compiler 4.1ry's tutorial

Molto fighe sono le immagini esemplificative e la semplicità di scrittura. Grande Ry!

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/07/31/ios-quick-objective-c-tutorial/.

[iOs] Your app contains non-public API usage…UIDevice methods deprecati

Da 1 Maggio scorso, sono cambiate un bel pò di cose per quanto riguarda il processo di approvazione su Apple Store. Ci si sta preparando all’uscita del iOs 7? Sembra proprio di si, perché alcuni metodi deprecati nelle attuali versioni di iOs, vengono definitivamente vietati.

A me è capitato con gli UIDevice methods, ossia con il metodo uniqueIdentifier, che consente di recuperare la stringa alfanumerica identificativa di ciascun device Apple (UDID). All’atto della sottomissione su Apple Store, ecco l’errore di validazione che mi è stato restituito:

 Your app contains non-public API usage. Please review the errors, correct them and resubmit your application.

Apps are not permitted to access the UDID and must not use the uniqueIdentifier method of UIDevice. Please update your apps and servers to associate users with the Vendor or Advertising identifiers introduced in iOS 6.

If you think this message was sent in error and that you have only used Apple-published APIs in accordance with the guidelines, send the app’s nine-digit Apple ID, along with detailed information about why you believe the above API’s were incorrectly flagged, to appreview@apple.com. For further information, visit the Technical Support page at http://developer.apple.com/support/technical/.

 

La correzione è alquanto semplice, visto che basta sostituire il metodo uniqueIdentifier con il nuovo identifierForVendor. In particolare, ecco una funzione utile che permette di recuperare l’UDID, mantenendo la compatibilità anche per le versioni di iOs precedenti la versione 6:

- (NSString *) idForDevice;
{
  NSString *result = @"";

  UIDevice *thisDevice = [UIDevice currentDevice];
  if ([thisDevice respondsToSelector: @selector(identifierForVendor)])
  {
    NSUUID *myID = [[UIDevice currentDevice] identifierForVendor];
    result = [myID UUIDString];
  }
  else
  {
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    result = [defaults objectForKey: @"appID"];
    if (!result)
    {
      CFUUIDRef myCFUUID = CFUUIDCreate(kCFAllocatorDefault);
      result = (__bridge_transfer NSString *) CFUUIDCreateString(kCFAllocatorDefault, myCFUUID);
      [defaults setObject: result forKey: @"appID"];
      [defaults synchronize];
      CFRelease(myCFUUID);
    }
  }
  return result;
}

Se vi si ripresenta di nuovo lo stesso errore di validazione, nonostante la correzione con la funzione precedente, allora vuol dire che nel vostro progetto state utilizzando qualche libreria che internamente fa uso di metodi deprecati. A me è, infatti, capitato con il framework BugSense, che al momento non ha ancora rilasciato un aggiornamento per correggere tale problema. In tal caso, occorrerà rimuovere la libreria “incriminata” (che tuttavia non vi segnala direttamente xCode) per poter sottoporre l’app ad approvazione.

 

[iOs] Apple dice addio al “compatibility mode” su iPhone 5

Apple dice addio al “compatibility mode” su iPhone 5 delle app che non hanno il layout ottimizzato su tali device.

Per chi non lo sapesse, gli sviluppatori potevano evitare di adattare la grafica delle app su iPhone 5, che è 176 px più lungo dei suoi precursori, permettendo alle app stesse di essere visualizzate in quella che viene definita “letterbox mode“, o appunto “compatibility mode“: iOs visualizza automaticamente delle bande nere sopra e sotto il layout delle app non ottimizzate, solo su iPhone 5.

iOs Letter Box Mode

Per predisporre l’app al “letterbox mode” bastava evitare di inserire la splash screen per i display da 4 pollici (Default-568h@2x.png), di 640×1136 px.

Ecco cosa si legge sulle iOs Human Interface Guidelines:

Note: If you don’t make any changes to your app, it runs in a compatibility mode on iPhone 5. When an app runs in compatibility mode, iOS automatically centers the app’s UI by adding slim black bars above and below it. For some developers, it might be reasonable to simply create a new launch image of the correct size and allow the unchanged app UI to be centered on the iPhone 5 display.

 

Tutte le app rilasciate sullo store prima dell’uscita sul mercato dell’iPhone 5 (seconda metà 2012) erano automaticamente adattate in “letterbox mode“. Tutte quelle, invece, rilasciate ed aggiornate dopo tale evento, dovevano esplicitamente “attestare” l’utilizzo della modalità di compatibilità, evitando di inserire nell’app la splash screen per display da 4″.

Oggi la triste notizia: Apple dal 1 Maggio 2013 impone agli sviluppatori iOs di rendere il layout delle app compatibile per il display dell’iPhone 5. Questo vuol dire che tutti i nuovi rilasci ed aggiornamenti, da tale data in poi, dovranno prevedere che la grafica sia ottimizzata.

Ecco la mail inviata da Apple agli sviluppatori che hanno sottomesso una app alla review, in cui dice “addio” al “letterbox mode” per le nuove app.

Dear developer,

We have discovered one or more issues with your recent delivery for “XXX_MyApp_XXX”. Your delivery was successful, but you may wish to correct the following issues in your next delivery:

iPhone 5 Optimization Requirement – Your binary is not optimized for iPhone 5. As of May 1, all new iPhone apps and app updates submitted must support the 4-inch display on iPhone 5. All apps must include a launch image of the appropriate size. Learn more about iPhone 5 support by reviewing the iOS Human Interface Guidelines.

If you would like to update your binary for this app, you can reject this binary from the Binary Details page in iTunes Connect. Note that rejecting your binary will remove your app from the review queue and the review process will start over from the beginning when you resubmit your binary.

Regards,

The App Store team

 

Pena la bocciatura dell’app all’atto della Apple Review. Ovviamente, le app già sullo store e non aggiornate dopo il 1 maggio continueranno a girare con la modalità di compatibilità.

A me la precedente mail è stata recapitata qualche giorno prima del 1 Maggio scorso, sottoponendo ad approvazione un aggiornamento dell’app LuBannApp. Questa non ha il layout ottimizzato per iPhone 5, ma me l’hanno approvata comunque e viene visualizzata ancora in “letterbox” mode, visto che la data di sottomissione ad Apple Review è antecedente al 1 Maggio.

Se a qualcuno di voi capita di scontrarsi, durante il processo di review, con questo famoso iPhone 5 Optimization Requirement (che oltretutto non vedo ancora sulle linea guida Apple), scrivetelo nei commenti. Grazie.