• Programmazione Android
  • CORSI ONLINE
  • Web Agency

Logo

Corsi di programmazione web e mobile online
Navigation
  • Home
  • CORSI ONLINE
  • Tutorial Pratici
  • GUIDE COMPLETE
    • Corso completo di C
    • Corso videogame con Cocos2d
    • Programmazione Cocoa Touch
  • Sezioni
    • Libri e manuali
    • Tips & Tricks
    • Risorse utili
    • Strumenti di Sviluppo
    • Materiale OpenSource
    • Framework
    • Guide Teoriche
    • Guide varie
    • Grafica e Design
    • iPad
    • News
    • Video Tutorial
    • Windows Phone
  • Pubblicità
  • About
    • Chi siamo
    • Pubblicazioni
    • Collabora
    • Sostieni devAPP

MasterDetail: the right way! Impariamo a pensare ad oggetti.

By IgnazioC | on 2 Luglio 2012 | 8 Comments
Senza categoria

Chiedo anticipatamente scusa se il titolo può apparire presuntuoso, la programmazione non è una scienza esatta, quindi non esiste una vita completamente esatta o una completamente errata, ma esistono delle varie sfumature di grigio in cui il programmatore si deve muovere decidendo di volta in volta se prediligere un’aspetto piuttosto che un’altro.
Insomma come sempre si tratta di trovare il giusto compromesso.

Ma allora qual è l’obbiettivo di questo articolo? Beh l’idea è quella di rispondere a tutti gli utenti che sul nostro forum hanno chiesto aiuto nella realizzazione di un’applicazione master/detail fornendo non soltanto una guida su come creare rapidamente questo tipo di applicazioni, ma anche come farlo ragionando in termini di OOP e rispettando il paradigma MVC.

L’applicazione che andremo a realizzare è molto semplice, visualizzerà in una tabella l’elenco dei clienti, cliccando su ciascuno di questi verrà visualizzata la view con i dettagli e da questa view cliccando sull’avatar si potrà visualizzare la foto ingrandita, mentre cliccando sull’indirizzo si accede alla mappa.

Ecco il mockup dell’applicazione:


master-detail-the-right-way-01

Per chi fosse interessato questo mockup è stato realizzato con Balsamiq, un software a pagamento (anche se la demo è perfettamente funzionante) che trovate a questo indirizzo: http://www.balsamiq.com/

Ok, abbiamo tutte le indicazioni che ci servono per realizzare la nostra app, quindi aprite XCode e iniziate a digitare…. FERMI TUTTI! La prima cosa da fare quando si parte con lo sviluppo di un’applicazione è stare fermi e pensare cosa fare, poi quando crediamo di aver pensato abbastanza, ci fermiamo ancora e cerchiamo di capire se quello che abbiamo pensato è giusto.

Iniziamoci a chiedere in quanti viewcontroller dividiamo l’applicazione.

La risposta potrebbe esere uno per la tabella, uno per i dettagli e…. e poi? Siamo sicuri che servano due viewcontroller per la foto e la mappa? non posso semplicemente sostituire una imageview con una mapview a seconda del tasto che ho premuto nella schermata precedente? Certo lo potrei fare… anzi potrei anche caricare tutto sullo stesso viewcontroller se volessi, perché non lo faccio? Ci sono dei pro/contro in termini di prestazioni?

Non ci sono né pro e né contro puramente tecnici, ma le performance di un’applicazione sono soltanto uno degli obbiettivi del programmatore, il cui compito è quello di scrivere codice chiaro e manutenibile.
Per raggiungere l’obbiettivo della chiarezza e semplicità del codice non si può prescindere dal suddividere quanto più possibile compiti e responsabilità.

Se in un’azienda non ci fossero suddivisioni di ruoli e responsabilità a chi vi rivolgereste se aveste qualche problema? Allo stesso modo in un’applicazione, se non ci sono netti confini su chi fa una cosa e chi ne fa un’altra, si crea un caos nel quale è impossibile anche solo manutenere il codice.
Quando si parla di manutenibilità del codice spesso si tende a sottovalutare il problema, perché lo si pensa come qualcosa fissato in un ipotetico futuro lontano. Io dico che il problema della manutenibilità del codice è presente in tutti i progetti che per essere completati richiedono più di due ore, perché in un pomeriggio si può scrivere tanto di quel codice che se non è strutturato bene già l’indomani mattina non riesci più a prenderne le fila.

Tornando quindi al numero dei viewcontroller la mia risposta è cinque.

Cinque? Si cinque:

  • uno per la tabella,
  • uno per i dettagli,
  • uno per la foto,
  • uno per la mappa,
  • e uno per il navigationviewcontroller!

Adesso possiamo iniziare a programmare?

Non ancora, abbiamo visto i viewcontroller, ma non abbiamo parlato del model!

Il modello è forse l’aspetto più sottovalutato tra tutti i nostri utenti più “giovani” nel mondo della OOP.

Alla base del concetto della programmazione OOP c’è la volontà di creare una rappresentazione della realtà all’interno dei nostri programmi. Non parlo di realtà aumentata, non fraintendetemi. Dico che uno degli scopi della programmazione OOP è quello di renderla un’attività più simile a quello che il nostro cervello è abituato a fare tutti i giorni: interagire e far interagire tra di loro gli oggetti.

Chi conosce almeno un linguaggio non ad oggetti, come ad esempio il C, sa bene che non si ha quasi mai a che fare con variabili che rappresentano qualcosa di “tangibile” o comunque appartenenti alla reltà, ma si ha a che fare con variabili char, int e via così. In OOP e nello specifico con iOS, invece, hai per esempio l’oggetto UIButton che cerca di mappare all’interno di un programma il concetto di “pulsante”.

Il nostro compito di programmatori è muoverci in questo ambito, capire l’universo del problema che dobbiamo risolvere e creare i nostri oggetti che mappino le entità di questo universo.

Nel nostro esempio è presto detto, visto che parliamo di clienti sarà opportuno creare una classe Cliente e sarà questa il nostro modello.

Adesso possiamo iniziare a programmare? Non ancora, abbiamo un’idea generale di quali classi aggiungere, ma prima di iniziare veramente a scrivere codice è bene scendere in dettaglio ed esaminare proprietà e metodi di ciascuna classe.

Iniziamo con la classe Cliente.

In questa applicazione il nostro concetto di cliente ha le seguenti caratteristiche:

  • ha un nome
  • ha un cognome
  • ha un indirizzo
  • ha un avatar
  • ha una foto ad alta risoluzione

Per quanto riguarda nome e cognome possiamo tranquillamente considerarle delle stringhe, l’avatar e la foto sono sicuramente due immagini, ma l’indirizzo?

Se abbiamo un pò di lungimiranza sappiamo che per visualizzare il pin sulla mappa non è sufficiente avere un indirizzo, ma questo indirizzo deve essere geolocalizzato, in pratica servono le coordinate geografiche di latitudine e longitudine.

Chi,secondo voi, dovrebbe occuparsi di fare questo lavoro all’interno de nostro programma?

Possono esserci più risposte, più o meno valide ed è bene, quando ci si trova davanti ad un problema come questo, confrontarsi con qualche altro sviluppatore per esaminare attentamente pro e contro di ogni singola soluzione.

A me sono venute in mente un paio di soluzioni, alla fine ne ho scelta una, ma piuttosto che la pappa pronta voglio spiegarvi il ragionamento che mi ha portato a questa soluzione esaminando prima le varie possibilità che sono state scartate:

  1. Il lavoro di geocoding lo effettua la mappa alla quale si passa l’indirizzo.
    pro: sicuramente la soluzione più semplice che si possa pensare, non modifichiamo la classe Cliente e siamo in grado di visualizzare qualsiasi indirizzo.
    contro: il contro più evidente è che ad ogni visualizzazione deve essere fatta una richiesta al server, si potrebbe ovviare con un meccanismo di cache, ma non sarebbe semplice da implementare.
  2. Modifichiamo la classe Cliente e inseriamo all’interno un metodo privato che effettua il geocoding, poi aggiungiamo due variabili di latitudine e longitudine e ogni volta che modifichiamo l’indirizzo del cliente effettuiamo il geocoding memorizzando le informazioni.
    pro: sembra un metodo piuttosto ragionevole, possiamo memorizzare i dati di lat e lng, la mappa riceverebbe direttamente le coordinate.
    contro: dal punto di vista dell’architettura la classe Cliente non ha proprio senso che includa del codice per la geolocalizzazione. Se oltre ai clienti dovessimo aggiungere anche i Fornitori dovremmo forse duplicare il codice? oppure fare una subclass solo perché mantengano la geolocalizzazione come metodo in comune? Se per ciascun cliente dovessi memorizzare più di un indirizzo? Diciamo che domani voglio memorizzare un numero arbitrario di indirizzi per ciascun cliente… devo aggiungere quanti campi latitudine e longitudine per poterne tenere conto?
  3. A questo punto la soluzione che mi è sembrata più corretta:
    Creo una specifica classe per l’indirizzo, che ha al suo interno la logica di geocoding e memorizzazione delle coordinate geografiche.
    pro: posso avere più indirizzi per ogni cliente, o anche altre classi che non siano clienti e tutto funziona, ottengo un sistema di cache
    contro: non me ne vengono in mente 😉


master-detail-the-right-way-02

Il Viewcontroller principale

Servirà per mostrare l’elenco dei clienti, avrà come variabile di istanza un’array di clienti, ma non sarà accessibile pubblicamente perché il viewcontroller provvederà autonomamente a inserire all’interno di questa lista tutti i clienti memorizzati.

Il viewcontroller di dettaglio

Chiediamoci che stato interno può avere? E quali sono i suoi metodi che posso richiamare dall’esterno?
Ragionando un pò ci rendiamo conto che lo stato interno del viewcontroller cambia in funzione del cliente che deve essere visualizzato, quindi il viewcontroller avrà un metodo che verrà utilizzato per settare quale cliente visualizzare.

Mappa e foto

Per i restanti due viewcontroller aggiungerei soltato una variabile di istanza di tipo UIImage e una di tipo NSString per memorizzare l’indirizzo da visualizzare.

Ragionando in questi termini abbiamo ottenuto una notevole seprazione dei ruoli, se volessimo ad esempio sostiture il viewcontroller delle mappe con uno più complesso tutto il resto della nostra app resterebbe illeso!

Adesso finalmente possiamo iniziare a programmare. Apriamo quindi XCode e selezioniamo il template “master detail”, specifichiamo applicazione solo per iPhone e chiamiamola “learnMasterDetail”. Abilitate il supporto ARC e delezionate l’opzione per utilizzare CoreData.

Se non avete mai utilizzato questo template prendetevi qualche minuto per verificarne il funzionamento.

Il progetto si presenta con due file AppDelegate.h e .m e due viewcontroller, il MasterViewController e DetailViewController.

Iniziamo a creare le classi che costituiranno il nostro model, quindi file -> new -> file e scegliamo “Objective-C Class” chiamiamola Location e creiamola come subclass di NSObject.

Aggiungiamo quindi nel file Location.h la dichiarazione delle nostre property, il file completo è questo:

#import 

Una volta dichiarate le property bisogna aggiungere il @syntetize, in questo caso ho deciso di segure la consuetudine che vuole che il nome della variabile di istanza sia precedura da un underscore, mentre il metodo ha il nome della property.

Il file .m completo è questo:

#import "Location.h"
@implementation Location
@synthesize address = _address;
@synthesize lng = _lng;
@synthesize lat = _lat;
@end

Se vi state chiedendo del perché di questa dichiarazione, diciamo brevemente che in questo modo è chiaro che se scrivo _lng = 0 accedo direttamente alla variabile di istanza, mentre se scrivo self.lng accedo al metodo.

Dobbiamo creare adesso la classe Cliente, seguiamo la stessa procedura fatta precedentemente e aggiungiamo i due file Cliante.h e Cliente.m.

Importiamo il file Location.h e dichiariamo le property.
Riporto qui i file completi .h e .m

#import 
#import "Location.h"
@interface Cliente : NSObject
@property (nonatomic, strong) NSString *nome;
@property (nonatomic, strong) NSString *cognome;
@property (nonatomic, strong) Location *defaultLocation;
@property (nonatomic, strong) UIImage *thumb;
@property (nonatomic, strong) UIImage *fullImage;
@end
-----------------------------------------------------------------------------------------------------------------------
#import "Cliente.h"
@implementation Cliente
@synthesize nome = _nome;
@synthesize cognome = _cognome;
@synthesize defaultLocation = _defaultLocation;
@synthesize thumb = _thumb;
@synthesize fullImage = _fullImage;
@end

Ottimo, adesso che abbiamo le classi più importanti del nostro model passiamo al primo viewcontroller, il MasterViewController.

Selezioniamo quindi il file MasterViewController.h e aggiugiamo in alto:

#import "Cliente.h"
#import "Location.h"

Questa classe sembra non avere alcuna variabile di istanza, ma invece non è così. Per una maggiore pulizia del codice gli ingegnieri Apple hanno posto la variabile di istanza all'interno di una category (se non sapete cos`è c'è un articolo su devAPP) troviamo infatti una variabile dichiarata nel file .m ed in particolare in questo blocco:

@interface MasterViewController () {
    NSMutableArray *_objects;
}
@end

Il termine _objects è abbastanza generico e possiamo riciclarlo per la nostra app, infatti questo array conterrà l'elenco dei clienti da visualizzare.

Prima di continuare eliminiamo tutto ciò che non ci serve da questo file, il quale contiene tutti i metodi per aggiungere delle righe a runtime, ma che a noi non servono.

Quindi partendo dal metodo viewDidLoad:

Lo riduciamo all'osso, con la sola chiamata alla superclass. Non l'ho tolto del tutto perché ci servirà tra un attimo.
Togliamo del tutto il metodoo insertNewObject: e anche il metodo tableView:canEditRowAtIndexPath:
Togliamo anche il metodo:
tableView:commitEditingStyle:forRowAtIndexPath:

Provate a compilare, se non avete eliminato nulla di fondamentale l'applicazione dovrebbe continuare a girare, con l'unica differenza che non sono più presenti le funzionalità per modificare la tabella e per aggiungere una nuova riga.

Dobbiamo ora creare i nostri clienti, quindi all'interno del metodo viewDidLoad li creiamo e li memorizziamo all'interno dell'array _objects.
Utilizziamo questo codice:

 _objects = [[NSMutableArray alloc] init];
    for (int i = 0; i < 20; i++) {
        Cliente *c = [[Cliente alloc] init];
        c.nome = [NSString stringWithFormat:@"Nome_%i", i];
        c.cognome = [NSString stringWithFormat:@"Cognome_%i", i];
        c.thumb     = [UIImage imageNamed:@"thumb_%i.png"];
        c.fullImage = [UIImage imageNamed:@"fullsize_%i.png"];
       
        Location *loc = [[Location alloc] init];
        loc.address = @"Milano, piazza duomo";
       
        c.defaultLocation = loc;
       
        [_objects addObject:c];
       
    }

Come funziona questo codice dovrebbe essere abbastanza chiaro, in pratica vengono creati 20 clienti e per ciascuno vengono settate le proprietà con una sequenza come Nome_1, Nome_2.
In questo modo ci risparmiamo di digitare nomi casuali e abbiamo anche un certo controllo sugli errori.

Le immagini sono anche quelle numerate progressivamente e se non avete voglia di trovare 40 immagini e non conoscete imagemagik, potete trovare quelle che ho generato io all'interno del progetto.

Ciascun cliente è cosi aggiunto all'elenco.

Provate a compilare e dovreste ottenere questo risultato:


master-detail-the-right-way-03

Non è proprio il massimo, ma è già un buon inizio.

Modifichiamo quindi il metoto che si occupa di disegnare la cella:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath

Modifichiamolo per viasualizzare il nome e cognome del contatto:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *CellIdentifier = @"Cell";
   
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell == nil) {
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
        cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
   }
    Cliente *object = [_objects objectAtIndex:indexPath.row];
    cell.textLabel.text = [NSString stringWithFormat:@"%@ %@", object.cognome, object.nome];
    return cell;
}

Come è evidente dal metodo abbiamo estratto un oggetto di tipo Cliente dall'array e le sue proprietà di nome e cognome vengono visualizzate sulla cella.

Potete provare a cliccare su un elemento, vi porterà al DetailViewController, ma dobbiamo modificarlo affinché mostri i dettagli del cliente selezionato.

DetailViewController

Modifichiamo il DetailViewController importando le classi del model e cambiando il tipo di variabile.

Il DetailViewController.h finito appare così:

#import 
#import "Cliente.h"
#import "Location.h"
@interface DetailViewController : UIViewController
@property (strong, nonatomic) Cliente *detailItem;
@end

Creiamo in Interface Builder la grafica del viewcontroller e colleghiamo gli IBOutlet. Questo non dovrebbe causare molto difficoltà se avete più o meno capito come funzione XCode, in ogni caso questi sono i due file di classe, nel progetto trovate il file xib.

#import 
#import "Cliente.h"
#import "Location.h"
@interface DetailViewController : UIViewController {
   
}
@property (strong, nonatomic) Cliente *detailItem;
@property (strong, nonatomic) IBOutlet UILabel *nome;
@property (strong, nonatomic) IBOutlet UILabel *cognome;
@property (strong, nonatomic) IBOutlet UIImageView *thumb;
@property (strong, nonatomic) IBOutlet UILabel *indirizzo;
@end
----------------------------------------------------------------------------------------------------------------------------
#import "DetailViewController.h"
@interface DetailViewController ()
- (void)configureView;
@end
@implementation DetailViewController
@synthesize detailItem = _detailItem;
@synthesize nome = _nome;
@synthesize cognome = _cognome;
@synthesize thumb = _thumb;
@synthesize indirizzo = _indirizzo;
#pragma mark - Managing the detail item
- (void)setDetailItem:(id)newDetailItem
{
    if (_detailItem != newDetailItem) {
        _detailItem = newDetailItem;
       
        // Update the view.
        [self configureView];
    }
}
- (void)configureView
{
    // Update the user interface for the detail item.
    if (self.detailItem) {
        self.nome.text = self.detailItem.nome;
        self.cognome.text = self.detailItem.cognome;
        self.indirizzo.text = self.detailItem.defaultLocation.address;
        self.thumb.image = self.detailItem.thumb;
    }
}
- (void)viewDidLoad
{
    [super viewDidLoad];
        // Do any additional setup after loading the view, typically from a nib.
    [self configureView];
}
- (void)viewDidUnload
{
    [self setNome:nil];
    [self setCognome:nil];
    [self setThumb:nil];
    [self setIndirizzo:nil];
    [super viewDidUnload];
    // Release any retained subviews of the main view.
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
    return (interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown);
}
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
    if (self) {
        self.title = NSLocalizedString(@"Detail", @"Detail");
    }
    return self;
}
                                                        
@end

Il punto focale di questa classe è il setter della variabile detailItem.

Quando viene impostato un nuovo detailItem la classe chiama un secondo metodo che si chiama configureView. In questo metodo si recuperano le proprietà dell'oggetto passato e si mostrano sullo schermo.

Come potete vedere non c'è nessun riferimento al precedente viewController, né tantomeno il MasterViewcontroller decide cosa visualizzare sulle label del DetailViewcontroller!
Il tutto avviene in una maniera molto elegante.

Esercitazione a sorpresa 😛

Adesso una piccola sopresa per chi ha letto fino a qui... la restante parte del progetto è una piccola sfida per tutti voi! Provate, con la base e con le specifiche che ho fornito in questo articolo, ad aggiungere le due funzionalità mancanti:

  • la visualizzazione della foto in grande
  • e la visualizzazione sulla mappa

Postate le vostre soluzioni sul forum e verranno commentate. Se preferite mantenere l'anonimato nessun problema, mandatemi il progetto con un messaggio privato, vi risponderò sul forum senza citare il nome dell'autore!

Buona programmazione!

Share this story:
  • tweet

Tags: Guide variemaster detail iosmvc iosooppensare ad oggettiprogettare applicazione iPhone

Recent Posts

  • Parte il percorso programmatori iOS in Swift su devACADEMY.it

    20 Dicembre 2017 - 0 Comment
  • Android, crittografare dati velocemente con Encryption

    24 Settembre 2018 - 0 Comment
  • Sql2o, accesso immediato ai database tramite Java

    3 Settembre 2018 - 0 Comment
  • Okio, libreria per ottimizzare l’input/output in Java

    27 Agosto 2018 - 0 Comment

Related Posts

  • Programmare a oggetti in TypeScript

    7 Aprile 2017 - 0 Comment
  • Introduzione ai Design Patterns

    12 Novembre 2012 - 7 Comments
  • L#009 – Programmazione Orientata agli Oggetti (OOP)

    11 Dicembre 2009 - 2 Comments

Author Description

8 Responses to “MasterDetail: the right way! Impariamo a pensare ad oggetti.”

  1. 2 Luglio 2012

    ittaglia

    E’ un buon articolo, soprattutto per gli utenti che si affacciano ora su linguaggi orientati agli oggetti! E’ utilissimo poi per quelli che “si sono fatti da soli”!

  2. 2 Luglio 2012

    ignazioc

    grazie.
    “farsi da soli” va diventare ciechi..per questo ho scritto quest’articolo 😀 😀

  3. 2 Luglio 2012

    Mimmo

    Ottimo tutorial, unico consiglio che posso dare, i nomi delle variabili e dei metodi, meglio farli tutti in italiano o inglese e non mischiati. Ciao!

  4. 2 Luglio 2012

    ittaglia

    hahahah.. hai ragione… hahahaha

  5. 2 Luglio 2012

    Acunamatata

    Trovo questo articolo fantastico, poche volte si trovano articoli propedeutici alla programmazione così ben fatti… e per esperienza il copia e incolla del codice non è mai una scelta vincente alla lunga, soprattutto se si volle imparare e non realizzare solo un’app. Complimenti.

  6. 2 Luglio 2012

    Ignazioc


    Mimmo:

    Ottimo tutorial, unico consiglio che posso dare, i nomi delle variabili e dei metodi, meglio farli tutti in italiano o inglese e non mischiati. Ciao!

    Hai ragione…io uso sempre l’inglese per metodi, variabili e commenti ma quando scrivo gli articoli mi sforzo di mettere qualcosa in italiano con il risultato che vedi 😉
    Forza ragazzi, imparate l’inglese!!!

  7. 21 Novembre 2012

    MasterDetail: the right way! Impariamo a pensare ad oggetti. | TCNews24.it

    […] il resto dell’articolo cliccando qui Guide varie, master detail ios, mvc ios, oop, pensare ad oggetti, progettare applicazione iPhone […]

  8. 30 Novembre 2012

    Luca

    Salve a tutti,

    io non riesco a caricare il controller del dettaglio…

    viene invocato il metodo…

    – (void)tableViewUITableView *)tableView didSelectRowAtIndexPathNSIndexPath *)indexPath
    {
    NSLog(@”did Select”);
    MyDetailViewController *detailViewController = [[MyDetailViewController alloc] initWithNibName:nil bundle:nil];
    [self.navigationController pushViewController:detailViewController animated:YES];
    [detailViewController release];
    }

    Grazie

Leave a Reply

Your email address will not be published. Required fields are marked *


*
*

Corso online di programmazione android e java

SEZIONI

  • Android
  • Comunicazioni
  • Contest
  • Corsi ed Eventi
  • Corso completo di C
  • Corso programmazione videogiochi
  • Framework
  • Grafica e Design
  • Guida rapida alla programmazione Cocoa Touch
  • Guide Teoriche
  • Guide varie
  • iPad
  • Le nostre applicazioni
  • Libri e manuali
  • Materiale OpenSource
  • News
  • Pillole di C++
  • Progetti completi
  • Risorse utili
  • Strumenti di Sviluppo
  • Swift
  • Tips & Tricks
  • Tutorial Pratici
  • Video Tutorial
  • Windows Phone

Siti Amici

  • Adrirobot
  • Allmobileworld
  • Apple Notizie
  • Apple Tribù
  • Avvocato360
  • Blog informatico 360°
  • bubi devs
  • fotogriPhone
  • GiovaTech
  • iApp-Mac
  • iOS Developer Program
  • iPodMania
  • MelaRumors
  • Meritocracy
  • SoloTablet
  • TecnoUser
  • Privacy & Cookie Policy
©2009-2018 devAPP - All Rights Reserved | Contattaci
devAPP.it è un progetto di DEVAPP S.R.L. - Web & Mobile Agency di Torino
Str. Volpiano, 54 - 10040 Leini (TO) - C.F. e P.IVA 11263180017 - REA TO1199665 - Cap. Soc. € 10.000,00 i.v.

devACADEMY.it

Vuoi imparare a programmare?

Iscriviti e accedi a TUTTI i corsi con un’unica iscrizione.
Oltre 70 corsi e migliaia di videolezioni online e in italiano a tua disposizione.

ISCRIVITI SUBITO