Questo tutorial ti insegnerà come implementare un algoritmo di disegno avanzato per un disegno uniforme a mano libera su dispositivi iOS. Continuare a leggere!
Il tocco è il modo principale con cui un utente interagisce con i dispositivi iOS. Una delle funzionalità più naturali e ovvie che questi dispositivi dovrebbero fornire è quella di consentire all'utente di disegnare sullo schermo con il dito. Al momento sono disponibili molte app di disegno a mano libera e app di annotazione nell'App Store e molte aziende chiedono persino ai clienti di firmare un iDevice quando effettuano acquisti. Come funzionano effettivamente queste applicazioni? Fermiamoci e pensiamo per un minuto a cosa sta succedendo "sotto il cofano".
Quando un utente scorre una vista tabella, pizzica per ingrandire un'immagine o disegna una curva in un'app di pittura, la visualizzazione del dispositivo si aggiorna rapidamente (ad esempio, 60 volte al secondo) e il ciclo di esecuzione dell'applicazione è costante campionatura la posizione del / i dito / i dell'utente / i. Durante questo processo, l'input "analogico" di un dito che trascina sullo schermo deve essere convertito in un insieme digitale di punti sul display e questo processo di conversione può porre sfide significative. Nel contesto della nostra app di pittura, abbiamo un problema di "integrazione dei dati" nelle nostre mani. Mentre l'utente scrive scarabocchiamente sul dispositivo, il programmatore deve essenzialmente interpolare le informazioni analogiche mancanti ("connect-the-dots") che sono state perse tra i punti di contatto campionati che iOS ci ha riferito. Inoltre, questa interpolazione deve avvenire in modo tale che il risultato sia un tratto che appare continuo, naturale e liscio all'utente finale, come se fosse stato abbozzato con una penna su un blocco note di carta.
Lo scopo di questo tutorial è mostrare come il disegno a mano libera può essere implementato su iOS, a partire da un algoritmo di base che esegue l'interpolazione lineare e avanzando verso un algoritmo più sofisticato che si avvicina alla qualità fornita da applicazioni ben note come Penultimate. Come se creare un algoritmo che funzioni non fosse abbastanza difficile, dobbiamo anche assicurarci che l'algoritmo funzioni bene. Come vedremo, un'implementazione ingenua del disegno può portare a un'app con problemi di prestazioni significativi che renderanno il disegno ingombrante e infine inutilizzabile.
Suppongo che tu non sia totalmente nuovo allo sviluppo di iOS, quindi ho sfogliato i passaggi della creazione di un nuovo progetto, aggiungendo file al progetto, ecc. Speriamo che non ci sia nulla di troppo difficile qui in ogni caso, ma nel caso, il il codice completo del progetto è disponibile per il download e il gioco.
Avvia un nuovo progetto Xcode per iPad basato sul "Applicazione vista singola"Modello e nome"FreehandDrawingTut"Assicurati di abilitare il conteggio automatico dei riferimenti (ARC), ma deseleziona gli storyboard e i test delle unità.Puoi fare questo progetto su un iPhone o un'app universale, a seconda di quali dispositivi hai a disposizione per i test.
Successivamente, vai avanti e seleziona il progetto "FreeHandDrawingTut" in Xcode Navigator e assicurati che sia supportato solo l'orientamento verticale:
Se hai intenzione di distribuire su iOS 5.xo versioni precedenti, puoi cambiare il supporto dell'orientamento in questo modo:
- (BOOL) shouldAutorotateToInterfaceOrientation: (UIInterfaceOrientation) interfaceOrientation return (interfaceOrientation == UIInterfaceOrientationPortrait);
Lo sto facendo per semplificare le cose in modo che possiamo concentrarci sul problema principale.
Voglio sviluppare il nostro codice in modo iterativo, migliorandolo in maniera incrementale - come faresti realisticamente se avessi iniziato da zero - invece di lasciar cadere la versione finale su di te in un colpo solo. Spero che questo approccio ti consenta di gestire meglio le diverse problematiche. Tenendo presente questo aspetto e per evitare di dover eliminare ripetutamente, modificare e aggiungere codice nello stesso file, che potrebbe risultare disordinato e soggetto a errori, seguirò il seguente approccio:
In Xcode, scegli File> Nuovo> File ... , scegli la classe Objective-C come modello e sul prossimo schermo il nome del file LinearInterpView e renderlo una sottoclasse di UIView. Salvarla. Il nome "LinearInterp" è l'abbreviazione di "interpolazione lineare" qui. Per il tutorial, chiamerò ogni sottoclasse di UIView che creiamo per enfatizzare qualche concetto o approccio introdotto nel codice di classe.
Come ho detto in precedenza, puoi lasciare il file di intestazione così com'è. Elimina tutti il codice presente nel file LinearInterpView.m e sostituirlo con quanto segue:
#import "LinearInterpView.h" @implementation LinearInterpView percorso UIBezierPath *; // (3) - (id) initWithCoder: (NSCoder *) aDecoder // (1) if (self = [super initWithCoder: aDecoder]) [self setMultipleTouchEnabled: NO]; // (2) [self setBackgroundColor: [UIColor whiteColor]]; percorso = [UIBezierPath bezierPath]; [percorso setLineWidth: 2.0]; return self; - (void) drawRect: (CGRect) rect // (5) [[UIColor blackColor] setStroke]; [tratto del percorso]; - (void) touchesBegan: (NSSet *) tocca conEvent: (UIEvent *) event UITouch * touch = [tocca anyObject]; CGPoint p = [touch locationInView: self]; [percorso moveToPoint: p]; - (void) touchesMoved: (NSSet *) tocca conEvent: (UIEvent *) event UITouch * touch = [tocca anyObject]; CGPoint p = [touch locationInView: self]; [percorso addLineToPoint: p]; // (4) [self setNeedsDisplay]; - (void) touchesEnded: (NSSet *) tocca conEvent: (UIEvent *) event [self touchesMoved: touch withEvent: event]; - (void) touchesCancelled: (NSSet *) tocca conEvent: (UIEvent *) event [self touchesEnded: touch withEvent: event]; @fine
In questo codice, stiamo lavorando direttamente con gli eventi touch che l'applicazione ci segnala ogni volta che abbiamo una sequenza di tocco. cioè, l'utente posiziona un dito sulla visualizzazione su schermo, sposta il dito su di esso e infine alza il dito dallo schermo. Per ogni evento in questa sequenza, l'applicazione ci invia un messaggio corrispondente (nella terminologia iOS, i messaggi vengono inviati al "primo risponditore", puoi fare riferimento alla documentazione per i dettagli).
Per gestire questi messaggi, implementiamo i metodi -touchesBegan: withEvent:
e compagnia, che sono dichiarati nella classe UIResponder da cui UIView eredita. Possiamo scrivere codice per gestire gli eventi touch in qualsiasi modo ci piaccia. Nella nostra app vogliamo interrogare la posizione sullo schermo dei tocchi, eseguire alcune elaborazioni e quindi tracciare linee sullo schermo.
I punti si riferiscono ai numeri commentati corrispondenti dal codice qui sopra:
-initWithCoder:
perché la vista nasce da un XIB, come ci apprestiamo a breve. UIBezierPath
è una classe UIKit che ci consente di disegnare forme sullo schermo composte da linee rette o da alcuni tipi di curve. -drawRect:
metodo. Lo facciamo accarezzando il percorso ogni volta che viene aggiunto un nuovo segmento di linea. -drawRect:
metodo, e il risultato di ciò che vedi è la vista sullo schermo. Tra poco incontreremo un'altra sorta di contesto di disegno.Prima di poter creare l'applicazione, è necessario impostare la sottoclasse di visualizzazione appena creata per la visualizzazione su schermo.
Ora crea l'applicazione. Dovresti ottenere una vista bianca lucida che puoi disegnare con il dito. Considerando le poche righe di codice che abbiamo scritto, i risultati non sono troppo trascurati! Certo, non sono neanche spettacolari. L'apparenza di collegare i puntini è piuttosto evidente (e sì, anche la mia calligrafia fa schifo).
Assicurati di eseguire l'app non solo sul simulatore ma anche su un dispositivo reale.
Se giochi con l'applicazione per un po 'sul tuo dispositivo, sei obbligato a notare qualcosa: alla fine la risposta dell'interfaccia utente inizia a rallentare, e invece dei ~ 60 punti di contatto che sono stati acquisiti al secondo, per qualche motivo il numero di punti l'interfaccia utente è in grado di campionare ulteriormente le gocce. Poiché i punti si allontanano ulteriormente, l'interpolazione lineare rende il disegno ancora più "bloccato" rispetto a prima. Questo è certamente indesiderabile. Quindi, cosa sta succedendo?
Esaminiamo ciò che stiamo facendo: mentre disegniamo, stiamo acquisendo punti, aggiungendoli a un percorso in continua crescita e quindi rendendo il percorso * completo * in ogni ciclo del ciclo principale. Quindi, man mano che il percorso si allunga, in ogni iterazione il sistema di disegno ha ancora molto da disegnare e alla fine diventa troppo, rendendo difficile mantenere l'app. Poiché tutto sta accadendo sul thread principale, il nostro codice di disegno è in competizione con il codice dell'interfaccia utente che, tra le altre cose, deve campionare i tocchi sullo schermo.
Saresti perdonato per aver pensato che c'era un modo per disegnare "sopra" quello che era già sullo schermo; sfortunatamente è qui che dobbiamo liberarci dell'analogia con la penna su carta poiché il sistema grafico non funziona in questo modo per impostazione predefinita. Anche se, in virtù del codice che scriverò prossimamente, stiamo indirettamente implementando l'approccio "draw-on-top".
Mentre ci sono alcune cose che potremmo provare a correggere le prestazioni del nostro codice, implementeremo solo un'idea, perché risulterebbe sufficiente per le nostre attuali esigenze.
Crea una nuova sottoclasse UIView come hai fatto prima, nominandola CachedLIView (la LI è per ricordarci che stiamo ancora facendo Lnell'orecchio ionterpolation). Elimina tutti i contenuti di CachedLIView.m e sostituirlo con il seguente:
#import "CachedLIView.h" @implementation CachedLIView UIBezierPath * percorso; UIImage * incrementalImage; // (1) - (id) initWithCoder: (NSCoder *) aDecoder if (self = [super initWithCoder: aDecoder]) [self setMultipleTouchEnabled: NO]; [self setBackgroundColor: [UIColor whiteColor]]; percorso = [UIBezierPath bezierPath]; [percorso setLineWidth: 2.0]; return self; - (void) drawRect: (CGRect) rect [incrementalImage drawInRect: rect]; // (3) [tratto del percorso]; - (void) touchesBegan: (NSSet *) tocca conEvent: (UIEvent *) event UITouch * touch = [tocca anyObject]; CGPoint p = [touch locationInView: self]; [percorso moveToPoint: p]; - (void) touchesMoved: (NSSet *) tocca conEvent: (UIEvent *) event UITouch * touch = [tocca anyObject]; CGPoint p = [touch locationInView: self]; [percorso addLineToPoint: p]; [self setNeedsDisplay]; - (void) touchesEnded: (NSSet *) tocca conEvent: (UIEvent *) event // (2) UITouch * touch = [tocca anyObject]; CGPoint p = [touch locationInView: self]; [percorso addLineToPoint: p]; [self drawBitmap]; // (3) [self setNeedsDisplay]; [percorso removeAllPoints]; // (4) - (void) touchesCancelled: (NSSet *) tocca conEvent: (UIEvent *) event [self touchesEnded: touch withEvent: event]; - (void) drawBitmap // (3) UIGraphicsBeginImageContextWithOptions (self.bounds.size, YES, 0.0); [[UIColor blackColor] setStroke]; if (! incrementalImage) // primo disegno; paint background white by ... UIBezierPath * rectpath = [UIBezierPath bezierPathWithRect: self.bounds]; // che racchiude la bitmap da un rettangolo definito da un altro oggetto UIBezierPath [[UIColor whiteColor] setFill]; [riempimento rettale]; // riempiendolo con bianco [incrementalImage drawAtPoint: CGPointZero]; [tratto del percorso]; incrementalImage = UIGraphicsGetImageFromCurrentImageContext (); UIGraphicsEndImageContext (); @fine
Dopo aver salvato, ricorda di cambiare la classe dell'oggetto vista nel tuo XIB (s) in CachedLIView!
Quando l'utente posiziona il dito sullo schermo per disegnare, iniziamo con un nuovo percorso senza punti o linee, e aggiungiamo segmenti di linea ad esso come abbiamo fatto prima.
Ancora una volta, facendo riferimento ai numeri nei commenti:
-drawRect:
questo contesto è automaticamente messo a nostra disposizione e riflette ciò che trasciniamo nella nostra visione a schermo. Al contrario, il contesto bitmap deve essere creato e distrutto in modo esplicito e i contenuti disegnati risiedono nella memoria.drawRect:
viene chiamato, prima estraiamo il contenuto del buffer di memoria nella nostra vista che (di progettazione) ha la stessa esatta dimensione, e quindi per l'utente manteniamo l'illusione del disegno continuo, solo in modo diverso rispetto a prima.Mentre questo non è perfetto (e se il nostro utente continua a disegnare senza alzare il dito, mai?), Sarà abbastanza buono per lo scopo di questo tutorial. Sei incoraggiato a sperimentare da solo per trovare un metodo migliore. Ad esempio, è possibile provare a memorizzare periodicamente il disegno anziché solo quando l'utente alza il dito. Come succede, questa procedura di caching fuori dallo schermo ci offre l'opportunità dell'elaborazione in background, se dovessimo scegliere di implementarla. Ma non lo faremo in questo tutorial. Sei invitato a provare da solo, però!
Ora rivolgiamo la nostra attenzione a rendere il disegno "un aspetto migliore". Finora, abbiamo aderito ai punti di contatto adiacenti con segmenti di linea retta. Ma normalmente quando disegniamo a mano libera, il nostro tratto naturale ha un aspetto libero e curvo (piuttosto che rigido e rigido). Ha senso che cerchiamo di interpolare i nostri punti con le curve piuttosto che con i segmenti di linea. Fortunatamente, la classe UIBezierPath ci permette di disegnare il suo omonimo: curve di Bezier.
Cosa sono le curve di Bezier? Senza invocare la definizione matematica, una curva di Bezier è definita da quattro punti: due punti finali attraverso i quali passa una curva e due "punti di controllo" che aiutano a definire le tangenti che la curva deve toccare ai suoi estremi (questa è tecnicamente una curva Bezier cubica, ma per semplicità mi riferirò ad esso semplicemente come una "curva di Bezier").
Le curve di Bezier ci permettono di disegnare tutti i tipi di forme interessanti.
Quello che stiamo per provare ora è raggruppare sequenze di quattro punti di contatto adiacenti e interpolare la sequenza di punti all'interno di un segmento di curva di Bezier. Ogni coppia adiacente di segmenti Bezier condividerà un punto finale in comune per mantenere la continuità del tratto.
Adesso conosci il trapano. Creare una nuova sottoclasse UIView e nominarla BezierInterpView. Incolla il seguente codice nel file .m:
#import "BezierInterpView.h" @implementation BezierInterpView percorso UIBezierPath *; UIImage * incrementalImage; Punti CGPoint [4]; // per tenere traccia dei quattro punti del nostro segmento di Bezier uint ctr; // una variabile contatore per tenere traccia dell'indice del punto - (id) initWithCoder: (NSCoder *) aDecoder if (self = [super initWithCoder: aDecoder]) [self setMultipleTouchEnabled: NO]; [self setBackgroundColor: [UIColor whiteColor]]; percorso = [UIBezierPath bezierPath]; [percorso setLineWidth: 2.0]; return self; - (void) drawRect: (CGRect) rect [incrementalImage drawInRect: rect]; [tratto del percorso]; - (void) touchesBegan: (NSSet *) tocca conEvent: (UIEvent *) event ctr = 0; UITouch * touch = [tocca anyObject]; pts [0] = [touch locationInView: self]; - (void) touchesMoved: (NSSet *) tocca conEvent: (UIEvent *) event UITouch * touch = [tocca anyObject]; CGPoint p = [touch locationInView: self]; ctr ++; pts [ctr] = p; if (ctr == 3) // 4 ° punto [percorso moveToPoint: pts [0]]; [percorso addCurveToPoint: pts [3] controlPoint1: pts [1] controlPoint2: pts [2]]; // questo è il modo in cui una curva di Bezier viene aggiunta ad un percorso. Stiamo aggiungendo un Bezier cubico da pt [0] a pt [3], con i punti di controllo pt [1] e pt [2] [self setNeedsDisplay]; pts [0] = [percorso currentPoint]; ctr = 0; - (void) touchesEnded: (NSSet *) tocca conEvent: (UIEvent *) event [self drawBitmap]; [self setNeedsDisplay]; pts [0] = [percorso currentPoint]; // lascia che il secondo endpoint del segmento Bezier corrente sia il primo per il segmento successivo di Bezier [percorso removeAllPoints]; ctr = 0; - (void) touchesCancelled: (NSSet *) tocca conEvent: (UIEvent *) event [self touchesEnded: touch withEvent: event]; - (void) drawBitmap UIGraphicsBeginImageContextWithOptions (self.bounds.size, YES, 0.0); [[UIColor blackColor] setStroke]; if (! incrementalImage) // prima volta; paint background white UIBezierPath * rectpath = [UIBezierPath bezierPathWithRect: self.bounds]; [[UIColor whiteColor] setFill]; [riempimento rettale]; [incrementalImage drawAtPoint: CGPointZero]; [tratto del percorso]; incrementalImage = UIGraphicsGetImageFromCurrentImageContext (); UIGraphicsEndImageContext (); @fine
Come indicano i commenti in linea, il cambiamento principale è l'introduzione di un paio di nuove variabili per tenere traccia dei punti nei nostri segmenti Bezier e una modifica del -(Void) touchesMoved: withEvent:
metodo per disegnare un segmento Bezier per ogni quattro punti (in realtà, ogni tre punti, in termini di tocchi segnalati dall'app, perché condividiamo un endpoint per ogni coppia di segmenti Bezier adiacenti).
Potresti sottolineare che abbiamo trascurato il caso in cui l'utente alzava il dito e terminava la sequenza tattile prima di avere abbastanza punti per completare il nostro ultimo segmento di Bezier. Se è così, avresti ragione! Mentre visivamente questo non fa molta differenza, in alcuni casi importanti lo fa. Ad esempio, prova a disegnare un piccolo cerchio. Potrebbe non chiudersi completamente e in un'app reale dovresti gestirlo in modo appropriato -touchesEnded: withEvent
metodo. Mentre ci siamo, anche noi non abbiamo prestato particolare attenzione al caso della cancellazione del tocco. Il touchesCancelled: withEvent
il metodo di istanza lo gestisce. Dai un'occhiata alla documentazione ufficiale e vedi se ci sono casi speciali che potresti dover gestire qui.
Quindi, come sono i risultati? Ancora una volta, ti ricordo di impostare la classe corretta in XIB prima di costruire.
Huh. Non sembra un grande miglioramento, vero? Penso che potrebbe essere leggermente meglio dell'interpolazione lineare, o forse è solo un pio desiderio. In ogni caso, non vale niente di cui vantarsi.
Ecco cosa penso stia accadendo: mentre ci stiamo prendendo la briga di interpolare ogni sequenza di quattro punti con un segmento di curva regolare, non stiamo facendo alcuno sforzo per trasformare un segmento di curva in una transizione fluida nel prossimo, così efficacemente abbiamo ancora un problema con il risultato finale.
Quindi, cosa possiamo fare a riguardo? Se seguiremo l'approccio iniziato nell'ultima versione (cioè utilizzando le curve di Bezier), dobbiamo occuparci della continuità e della scorrevolezza nel "punto di giunzione" di due segmenti Bezier adiacenti. Le due tangenti nel punto finale con i corrispondenti punti di controllo (il secondo punto di controllo del primo segmento e il primo punto di controllo del secondo segmento) sembrano essere la chiave; se entrambe queste tangenti avessero la stessa direzione, allora la curva risulterebbe più liscia all'incrocio.
Cosa succede se spostiamo l'endpoint comune da qualche parte sulla linea che unisce i due punti di controllo? Senza utilizzare dati aggiuntivi sui punti di contatto, il punto migliore sembrerebbe essere il punto medio della linea che unisce i due punti di controllo in considerazione, e il nostro requisito imposto sulla direzione delle due tangenti sarebbe soddisfatto. Proviamo questo!
Crea una sottoclasse UIView (ancora una volta) e chiamala SmoothedBIView. Sostituisci l'intero codice nel file .m con quanto segue:
#import "SmoothedBIView.h" @implementation SmoothedBIView UIBezierPath * percorso; UIImage * incrementalImage; Punti CGPoint [5]; // ora dobbiamo tenere traccia dei quattro punti di un segmento Bezier e del primo punto di controllo del prossimo segmento uint ctr; - (id) initWithCoder: (NSCoder *) aDecoder if (self = [super initWithCoder: aDecoder]) [self setMultipleTouchEnabled: NO]; [self setBackgroundColor: [UIColor whiteColor]]; percorso = [UIBezierPath bezierPath]; [percorso setLineWidth: 2.0]; return self; - (id) initWithFrame: (CGRect) frame self = [super initWithFrame: frame]; if (self) [self setMultipleTouchEnabled: NO]; percorso = [UIBezierPath bezierPath]; [percorso setLineWidth: 2.0]; return self; // Sostituisce solo drawRect: se si esegue un disegno personalizzato. // Un'implementazione vuota influisce negativamente sulle prestazioni durante l'animazione. - (void) drawRect: (CGRect) rect [incrementalImage drawInRect: rect]; [tratto del percorso]; - (void) touchesBegan: (NSSet *) tocca conEvent: (UIEvent *) event ctr = 0; UITouch * touch = [tocca anyObject]; pts [0] = [touch locationInView: self]; - (void) touchesMoved: (NSSet *) tocca conEvent: (UIEvent *) event UITouch * touch = [tocca anyObject]; CGPoint p = [touch locationInView: self]; ctr ++; pts [ctr] = p; if (ctr == 4) pts [3] = CGPointMake ((pts [2] .x + pts [4] .x) /2.0, (pts [2] .y + pts [4] .y) /2.0 ); // sposta l'endpoint al centro della linea che unisce il secondo punto di controllo del primo segmento di Bezier e il primo punto di controllo del secondo segmento di Bezier [percorso moveToPoint: pts [0]]; [percorso addCurveToPoint: pts [3] controlPoint1: pts [1] controlPoint2: pts [2]]; // aggiungi un Bezier cubico da pt [0] a pt [3], con i punti di controllo pt [1] e pt [2] [self setNeedsDisplay]; // sostituisce i punti e si prepara a gestire il segmento successivo pts [0] = pts [3]; pts [1] = pts [4]; ctr = 1; - (void) touchesEnded: (NSSet *) tocca conEvent: (UIEvent *) event [self drawBitmap]; [self setNeedsDisplay]; [percorso removeAllPoints]; ctr = 0; - (void) touchesCancelled: (NSSet *) tocca conEvent: (UIEvent *) event [self touchesEnded: touch withEvent: event]; - (void) drawBitmap UIGraphicsBeginImageContextWithOptions (self.bounds.size, YES, 0.0); if (! incrementalImage) // prima volta; paint background white UIBezierPath * rectpath = [UIBezierPath bezierPathWithRect: self.bounds]; [[UIColor whiteColor] setFill]; [riempimento rettale]; [incrementalImage drawAtPoint: CGPointZero]; [[UIColor blackColor] setStroke]; [tratto del percorso]; incrementalImage = UIGraphicsGetImageFromCurrentImageContext (); UIGraphicsEndImageContext (); @fine
Il nodo dell'algoritmo sopra descritto è implementato nel -touchesMoved: withEvent: metodo. I commenti in linea dovrebbero aiutarti a collegare la discussione al codice.
Quindi, come sono i risultati, visivamente parlando? Ricordati di fare la cosa con XIB.
Fortunatamente, ci sono miglioramenti sostanziali questa volta. Considerando la semplicità della nostra modifica, sembra piuttosto buono (se lo dico io stesso!). Anche la nostra analisi del problema con l'iterazione precedente e la nostra soluzione proposta sono state validate.
Spero che questo tutorial sia stato utile. Spero che svilupperai le tue idee su come migliorare il codice. Uno dei miglioramenti più importanti (ma facili) che puoi incorporare è gestire la fine delle sequenze di tocco in modo più agevole, come discusso in precedenza.
Un altro caso che ho trascurato è la gestione di una sequenza tattile che consiste nel toccare la vista con il dito dell'utente e quindi sollevarla senza averla spostata, in pratica un tocco sullo schermo. L'utente probabilmente si aspetterebbe di disegnare un punto o un piccolo scarabocchio sulla vista in questo modo, ma con la nostra implementazione attuale non accade nulla perché il nostro codice di disegno non esegue il kick in a meno che la nostra vista non riceva il -touchesMoved: withEvent: Messaggio. Potresti dare un'occhiata al UIBezierPath
documentazione di classe per vedere quali altri tipi di percorsi puoi costruire.
Se la tua app ha più lavoro di quello che abbiamo fatto qui (e in un'app di disegno che vale la spedizione, lo farebbe!), Progettandolo in modo tale che il codice non UI (in particolare, la cache fuori dallo schermo) venga eseguito in un thread in background potrebbe fare una differenza significativa su un dispositivo multicore (da iPad 2 in poi). Anche su un dispositivo a processore singolo, come l'iPhone 4, le prestazioni dovrebbero migliorare, poiché mi aspetto che il processore divida il lavoro di caching che, dopotutto, accade solo una volta ogni pochi cicli del ciclo principale.
Ti incoraggio a flettere i tuoi muscoli codificanti e a giocare con l'API UIKit per sviluppare e migliorare alcune delle idee implementate in questo tutorial. Buon divertimento e grazie per la lettura!