• 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

T#094 – Creare un contatore meccanico animato per iPhone o iPad

By IgnazioC | on 3 Giugno 2011 | 14 Comments
Senza categoria

Buongiorno a tutti! Il corso di C mi ha tenuto parecchio impegnato ed è da un pò che non scrivo tutorial per iOS, ma oggi ho una piccola chicca da proporvi.
L’obbiettivo di oggi è quello di realizzare un contatore meccanico, simile a quello che si vede in molti giochi, con tanto di animazione per passare da un numero ad un altro.
In questa immagine si vede l’effetto finale da “fermo” e durante un’animazione (a fondo articolo trovate anche un video con il contatore all’opera):


Creare-un-contatore-animato-per-iPhone-iPad-01 Creare-un-contatore-animato-per-iPhone-iPad-02

Come sempre cercheremo di creare del codice che sia riutilizzabile in diverse occasioni quindi una volta realizzato questo tutorial non vi resterà altro da fare che copiare i file nel vostro progetto ed interagire con il contatore come si fa con qualsiasi altro oggetto dell’UIKit.
Per iniziare abbiamo bisogno delle immagini per il nostro contatore, potete realizzarle con photoshop, potete comprarle online oppure ancora potete anche usare le mie se vi piacciono. Nel progetto allegato le troverete già separate, non impazzite a tagliare queste:


Creare-un-contatore-animato-per-iPhone-iPad-03

Adesso qui si svela il primo trucco, le immagini che serviranno saranno 20 e non 10 come si potrebbe pensare all’inizio, perché ci serviranno delle imagini per l’animazione.


Creare-un-contatore-animato-per-iPhone-iPad-04

Io ho cercato con photoshop di dare l’effetto “movimento” inserendo due numeri per volta con una sfocatura verticale. Ad essere sincero questa idea non è tutta farina del mio sacco, ma l’idea mi è venuta giocando a Flight Control, infatti come potete vedere in questo screen sul loro gioco utilizzano proprio questa tecnica:


Creare-un-contatore-animato-per-iPhone-iPad-06

Un’aspetto da non sottovalutare è la naming convention, ossia i nomi da dare ai file, sarà importante chiamare le immagini con dei nomi come “immagine_0.png”, “”immagine_1.png” etc.
io ho usato da “mc_0.png” fino a “mc_9.png” per le immagini delle singole cifre, e da “mc_10.png” fino a “mc_20” per le altre 10 immagini.

Realizzate quindi le immagini non ci resta che passare ad xcode, create quindi un nuovo progetto ed importate le immagini che avete creato.
Io per semplicità ho creato un progetto di tipo “ViewBased application” e nel metodo “viewDidLoad” del viewcontroller principale ho inserito questo codice per generare lo sfondo ed il pulsante:

- (void)viewDidLoad
{
   
[super viewDidLoad];
        /* Creazione dello sfondo
         */
UIImageView *bg = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"sfondo_devapp.png"]];
        [bg setFrame:[[UIScreen mainScreen] bounds]];
        [self.view addSubview:bg];
        [bg release];
        
        
        /* Creazione del pulsante
         */
        UIButton *btn = [UIButton buttonWithType:UIButtonTypeCustom];
        [btn setFrame:CGRectMake(160 - 150 / 2, 230 - 60 / 2, 150, 60)];
        [btn setBackgroundImage:[UIImage imageNamed:@"button.png"] forState:UIControlStateNormal];
        [btn setTitle:@"TAP ME!" forState:UIControlStateNormal];
        [btn addTarget:self action:@selector(buttonPressed:) forControlEvents:UIControlEventTouchUpInside];
        [self.view addSubview:btn];

Questo codice è stato inserito solo per completezza e non ha direttamente a che vedere con il nostro contatore.

Arriviamo quindi al cuore del progetto, realizziamo la nostra classe “MechanicalCounter” creando una nuova classe che derivi direttamente da uiview.
La dichiarazione della classe è la seguente:

#import 

@interface MechanicalCounter : UIView {
        NSMutableArray *ciphers;
        int currentValue;
        int *values;
}

@property (nonatomic, retain) NSMutableArray *ciphers;
@property (nonatomic, assign) int currentValue;

- (id)initWithFrame:(CGRect)frame andCiphers:(int)nchipers;
- (void)setValue:(int)newValue;
- (void)showNextValue;

@end

Ho dichiarato e variabili di istanza, la prima è un NSMutableArray, conterrà le UIImageView del nostro contatore, la seconda variabile è un intero che contiene il valore attualmente visualizzato sul contatore mentre l’ultima variabile è un puntatore ad intero, verrà usata per costruire un semplice array in puro stile C. Questo array conterrà in ogni posizione il valore della cifra corrispondente visualizzata nel contatore.
Facciamo un piccolo esempio: supponiamo che il contatore visualizzi 001234, l’array conterrà 6 uiimageview, la variabile currentValue conterrà il valore 1234, mentre l’array values sarà così:

values[0] = 0;
values[1] = 0;
values[2] = 1;
values[3] = 2;
values[4] = 3
values[5] = 4;

Perchè uso un array C invece di un NSArray? Perché negli NSArray non si possono inserire variabili di tipo int, quindi dovrei passare da nsnumber e lo ritengo un’inutile spreco di spazio.

Guardiamo quindi il primo metodo:

-(id)initWithFrame:(CGRect)frame andCiphers:(int)nciphers {
        self = [super initWithFrame:frame];
        if (self) {
                self.ciphers = [NSMutableArray arrayWithCapacity:nciphers];
                values = calloc(nciphers, sizeof(int));
                
                /* Calcolo la dimensione in larghezza
                 */
                UIImage *imgtest = [UIImage imageNamed:@"mc_0.png"];
                float width;
                if (imgtest.size.width * nciphers > 320) {
                        width = 320 / nciphers;
                }
                else {
                        width = frame.size.width / nciphers;
                }
                
                
                /* Calcolo l'array delle immagini per l'animazione
                 */
                NSMutableArray *arr = [NSMutableArray arrayWithCapacity:10];
                for (int i = 10; i <= 19; i++) {
                        NSString *str = [NSString stringWithFormat:@"mc_%d.png",i];
                        [arr addObject:[UIImage imageNamed:str]];
                }
                
                
                /* Istanzio le singole view
                 */
                for (int i = 0; i < nciphers; i++) {
                        UIImageView *v = [[UIImageView alloc] initWithFrame:CGRectMake(i * width, 0, width, frame.size.height)];
                        [v setImage:imgtest];
                        v.contentMode = UIViewContentModeScaleAspectFit;
                        [v setAnimationImages:arr];
                        [v setAnimationRepeatCount:1];
                        [v setAnimationDuration:0.5];
                        [ciphers addObject:v];
                        [self addSubview:v];
                        [v release];
                }
        }
        return self;
}

Questo metodo è il costruttore principale della nostra classe, lo invochiamo passando il frame in cui vogliamo visualizzare il contatore ed il numero di cifre che devono essere visualizzate.

self.ciphers = [NSMutableArray arrayWithCapacity:nciphers];
values = calloc(nciphers, sizeof(int));

Iniziamo quindi con il richiamare il costruttore della classe super e se tutto è andato per il verso giusto proseguiamo inizializzando l'array ciphers e l'array values.

UIImage *imgtest = [UIImage imageNamed:@"mc_0.png"];
                float width;
                if (imgtest.size.width * nciphers > 320) {
                        width = 320 / nciphers;
                }
                else {
                width = frame.size.width / nciphers;
}

Segue un piccolo calcolo per ottenere la larghezza di ogni singola cifra, notare che se il numero delle cifre moltiplicato per la larghezza della singola immagine supera la dimensione dello schermo, tutto viene ridimensionato per far rientrare tutto il contatore entro i 320px dell'iphone.

/* Calcolo l'array delle immagini per l'animazione
                 */
                NSMutableArray *arr = [NSMutableArray arrayWithCapacity:10];
                for (int i = 10; i <= 19; i++) {
                        NSString *str = [NSString stringWithFormat:@"mc_%d.png",i];
                        [arr addObject:[UIImage imageNamed:str]];
                }

Creo un array con le uiimage che comporranno la mia animazione, lo creo solo una volta e lo associo a tutte le uiimageview.

/* Istanzio le singole view
                 */
                for (int i = 0; i < nciphers; i++) {
                        UIImageView *v = [[UIImageView alloc] initWithFrame:CGRectMake(i * width, 0, width, frame.size.height)];
                        [v setImage:imgtest];
                        v.contentMode = UIViewContentModeScaleAspectFit;
                        [v setAnimationImages:arr];
                        [v setAnimationRepeatCount:1];
                        [v setAnimationDuration:0.5];
                        [ciphers addObject:v];
                        [self addSubview:v];
                        [v release];
                }
        }

Creo infine tutte le UIView che mi serviranno, e per ciascuna di esse imposto tutti i parametri necessari per l'animazione.

A questo punto l'inizializzazione è terminata, questo oggetto può essere gestito come tutti gli altri oggetti dell'UIKit, infatti tornando al viewcontroller principale, sempre nel metodo viewdidiload possiamo aggiungere:

//mc è una variabile di tipo MechanicalCounter dichiarata nel file .h        
mc = [[MechanicalCounter alloc] initWithFrame:CGRectMake(0, 0, 320, 124) andCiphers:10];
        [self.view addSubview:mc];

per vedere questo risultato:


Creare-un-contatore-animato-per-iPhone-iPad-05

Ma ovviamente non è solo questo quello che ci aspettiamo, ci serve un contatore che "conti" :D, quindi rimbocchiamoci le maniche ed implementiamo i restanti due metodi dichirati nel file .h

- (void)setValue:(int)newValue;
- (void)showNextValue;

Partirei dal secondo, la cui implementazione è banale:

-(void)showNextValue {
        [self setValue:++self.currentValue];        
}

Quello che fa questo metodo è richiamare un ulteriore metodo, che non è dichiarato nel file .h e quindi si potrebbe definire "privato" passando come parametro il valore della variabile currentValue incrementato di 1.
Anche il primo metodo se vogliamo è piuttosto semplice perché la sua implementazione è questa:

- (void)setCurrentValue:(int)acurrentValue {
        currentValue = acurrentValue;
        [self setValue:self.currentValue];
}

Si occupa di impostare correttamente la variabiel currentValue e richiamare lo stesso metodo privato del precedente.

A questo punto allora bisogna capire cosa fa questo metodo setValue: !!

Il codice è questo, c'è un pò di matematica, ma niente paura:

- (void)setValue:(int)newValue {
        if (newValue > (pow(10, [ciphers count]) - 1)) {
                if (newValue == (pow(10, [ciphers count]))) {
                        newValue = 0;
                        currentValue = 0;
                }
                else {
                        NSLog(@"Error: value too high");
                        return;
                }
        }
        int cifra;
        for (int i = [ciphers count] - 1; i >= 0; i--) {
                cifra = newValue / pow(10, i);
                if (cifra) {
                        newValue = newValue - cifra * pow(10, i);
                }
                [self setCipher:[ciphers count] - i - 1 value:cifra];
        }        
}

Con

if (newValue > (pow(10, [ciphers count]) - 1)) {

Verifico se il valore da settare è maggiore del più grande numero rappresentabile con quel dato numero di cifre. Ricordo che [chipers count] ritorna il numero di cifre del contatore, supponiamo sia 5, allora il maggiore numero rappresentabile è 10^5 - 1 = 99999.

if (newValue == (pow(10, [ciphers count]))) {

Questo secondo controllo l'ho inserito perché voglio ottenere un contatore che quando arriva al suo massimo valore, se incrementato di 1 si riazzera, un pò come i contachilometri delle auto.
In questo caso se il valore da settare è proprio 100000 allora riporto a zero il contatore ed il currentvalue, viceversa scrivo un messaggio d'errore sulla console.

int cifra;
        for (int i = [ciphers count] - 1; i >= 0; i--) {
                cifra = newValue / pow(10, i);
                if (cifra) {
                        newValue = newValue - cifra * pow(10, i);
                }
                [self setCipher:[ciphers count] - i - 1 value:cifra];
        }

Qui c'è un attimino di matematica, è il primo modo che mi è venuto in mente, magari si può ottimizzare, quello che mi serve è recuperare le singole cifre da un numero intero.
Quello che faccio è questo: con un ciclo for sulla variabile i parto dalla cifra più significativa e calcolo la divisione tra il nuovo valore da settare e 10^i Se il risultato è maggiore di zero allora sottraggo dal valore da settare il prodotto del risultato per 10^i infine imposto il valore della singola cifra con il metodo setCipher: value:

Vediamo come al solito un esempio per chiarire meglio.

Supponiamo di avere un contatore a 4 cifre e di voler visualizzare il valore 234.
La prima iterazione del ciclo for calcolerà 234 / 10^3 = 0 (divisione intera, viene arrotondata) qiundi non entra nell'if e richiama direttamente il metodo per impostare a 0 la cifra in posizione 0 dell'array.
Alla seconda iterazione verrà calcolato 234 / 10^2 = 2 quindi entro nell'if e porto il nuovo valore a 234 - 200 = 34, infine imposto a 2 la cifra in posizione 1 dell'array.
Alla terza iterazione verrà calcolato 34 / 10^1 = 3 quindi entro nell if e porto il nuovo valore a 34 - 30 = 4, ed imposto a 3 la cifra in posizione 2 dell'array.
All'ultima iterazione verrà calcolato 4 / 10^0 = 4 e quindi entro nell'if e porto il nuovo valore a 4 - 4 = 0, ed imposto a 4 la cifra in posizione 4 dell'array.
Ecco che quindi nell'array ho impostato 0234, proprio il numero che volevo. Difficile? no, dai!!
Non ci resta che vedere il metodo che modifica il valore della singola cifra:

- (void)setCipher:(int)acipher value:(int)value {
        if (value != values[acipher]) {
                NSString *str2 = [NSString stringWithFormat:@"mc_%d.png",value];
                [[ciphers objectAtIndex:acipher] setImage:[UIImage imageNamed:str2]];        
                [[ciphers objectAtIndex:acipher] startAnimating];
                values[acipher] = value;
        }
}

Anche questo metodo è piuttosto auto-esplicativo, l'unica particolarità è che faccio il controllo per verificare se effettivamente il nuovo valore è diverso dal precedente, in questo modo, quando viene incrementato il contatore non si animano tutte le cifre, ma solo quelle che effettivamente devono cambiare.

Qui potete vedere anche il video del contatore in azione:



Non mi resta che lasciarvi il progetto in allegato ed attendere i vostri commenti.
Ciao!

IgnazioC

Se avete problemi con il tutorial, questo è il nostro file di progetto.

Share this story:
  • tweet

Tags: animazioni xcodecontatore animato ipadcontatore animato iphonecreare un contatore XCodeEsempi Objective-Cesempio animazione UIViewIgnazio CalòTutorial Praticitutorial Xcode

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

  • T#112 – Animazioni ed effetti per le nostre applicazioni iPhone e iPad

    31 Gennaio 2013 - 2 Comments
  • Xcode 4: Distribuzione Ad Hoc delle applicazioni iPhone e iPad

    10 Gennaio 2012 - 20 Comments
  • Tutorial pratici per iOS SDK: disponibile la versione 2.0 aggiornata a iOS 5 e XCode 4.2

    16 Novembre 2011 - 6 Comments

Author Description

14 Responses to “T#094 – Creare un contatore meccanico animato per iPhone o iPad”

  1. 3 Giugno 2011

    Fra

    Tutorial carino 🙂 Dov’è il progetto?

  2. 3 Giugno 2011

    Francesco Bianchetti

    Dov’ è il progetto! 😉

  3. 3 Giugno 2011

    Rino Picardi

    OPS.. scusate.. l’ho perso per strada.. 😛 lo recupero e lo metto su..

  4. 3 Giugno 2011

    Ragazzetto

    come sempre ……..
    ……. tutorial spettacolare !!!!

    Bravo Ignazio !!

    e naturalmente grazie !

  5. 4 Giugno 2011

    Cesare

    Come potrei impostarlo per far si che “conti” fino ad una determinata data con la sola unità di misura dei secondi?

    Grazie

  6. 4 Giugno 2011

    C.I.R.

    stupendo!

  7. 4 Giugno 2011

    Ignazioc


    Cesare:

    Come potrei impostarlo per far si che “conti” fino ad una determinata data con la sola unità di misura dei secondi?
    Grazie

    Quel contatore “conta” e basta 🙂 se poi vuoi aggiungervi della logica lo devi fare tu in qualche modo.

  8. 4 Giugno 2011

    Francesco Bianchetti


    Ignazioc:


    Cesare:

    Come potrei impostarlo per far si che “conti” fino ad una determinata data con la sola unità di misura dei secondi?
    Grazie

    Quel contatore “conta” e basta se poi vuoi aggiungervi della logica lo devi fare tu in qualche modo.

    ma il progetto!?

  9. 5 Giugno 2011

    Rino Picardi

    Progetto online 😉

  10. 7 Giugno 2011

    Gabriele

    puoi rispondere ad una mia domanda nel tutorial 059?? è importante

  11. 7 Giugno 2011

    Ignazioc

    “è importante”? c’è il forum per le richieste e poi bisogna sempre tenere in mente questo link: http://www.catb.org/~esr/faqs/smart-questions.html
    cmq vai a leggere, ti ho dato la risposta.

  12. 10 Giugno 2011

    cursao

    Davvero bello, anche se per completezza manca il metodo buttonPressed, anche se la presenza del progetto colma tutti i gap! Grandi continuate così

  13. 16 Settembre 2011

    milonet

    Una domanda.. se io volessi fare in modo che ogni secondo si tappa da solo? 😀 tipo per fare in timer.. dove lo richiamo il meoto?
    grandissimo cmq ;D

  14. 19 Dicembre 2012

    Francesco

    bravo ignazio… complimenti!!

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