Ce didacticiel vous expliquera comment mettre en œuvre un algorithme de dessin avancé pour un dessin fluide à main levée sur des appareils iOS. Continuer à lire!
Le toucher est le moyen principal utilisé par un utilisateur pour interagir avec les appareils iOS. L’une des fonctionnalités les plus naturelles et les plus évidentes que ces appareils sont censés fournir est de permettre à l’utilisateur de dessiner sur l’écran avec le doigt. De nombreuses applications de dessin à main levée et de prise de notes sont actuellement disponibles sur l'App Store, et de nombreuses entreprises demandent même aux clients de signer un iDevice lors de leurs achats. Comment fonctionnent ces applications? Arrêtons-nous et réfléchissons un instant à ce qui se passe "sous le capot".
Lorsqu'un utilisateur fait défiler une vue tabulaire, pince pour agrandir une image ou trace une courbe dans une application de peinture, l'affichage de l'appareil est mis à jour rapidement (par exemple, 60 fois par seconde) et la boucle d'exécution de l'application est constamment affichée. échantillonnage l'emplacement du ou des doigts de l'utilisateur. Au cours de ce processus, l'entrée "analogique" d'un doigt glissant sur l'écran doit être convertie en un ensemble de points numériques sur l'écran, et ce processus de conversion peut poser des problèmes considérables. Dans le contexte de notre application de peinture, nous avons un problème "d'adaptation des données". Alors que l'utilisateur gribouille joyeusement sur l'appareil, le programmeur doit essentiellement interpoler les informations analogiques manquantes ("connect-the-dots") qui ont été perdues parmi les points de contact échantillonnés qu'iOS nous a signalés. En outre, cette interpolation doit se produire de manière à obtenir un trait continu, naturel et lisse pour l'utilisateur final, comme s'il dessinait avec un stylo sur un bloc-notes en papier..
Ce didacticiel a pour objectif de montrer comment le dessin à main levée peut être implémenté sur iOS, en partant d'un algorithme de base effectuant une interpolation en ligne droite et en passant à un algorithme plus sophistiqué se rapprochant de la qualité fournie par des applications connues telles que Penultimate. Comme si créer un algorithme qui fonctionne ne soit pas assez difficile, nous devons également nous assurer que cet algorithme fonctionne bien. Comme nous le verrons, une implémentation de dessin naïve peut conduire à une application présentant des problèmes de performances importants qui rendrait le dessin fastidieux et inutilisable..
Je suppose que le développement sur iOS n’est pas totalement nouveau pour moi, j’ai donc survolé les étapes de création d’un nouveau projet, d’ajout de fichiers au projet, etc. Espérons qu’il n’ya rien de trop difficile ici de toute façon, mais juste au cas où, Le code de projet complet est disponible pour que vous puissiez télécharger et jouer avec.
Démarrer un nouveau projet Xcode iPad basé sur le "Application à vue unique"Modèle et nommez-le"FreehandDrawingTut". Assurez-vous d'activer le comptage automatique des références (ARC), mais désélectionnez Storyboards et tests unitaires. Vous pouvez faire de ce projet un iPhone ou une application universelle, en fonction des périphériques disponibles pour les tests..
Continuez ensuite et sélectionnez le projet "FreeHandDrawingTut" dans le navigateur Xcode et assurez-vous que seule l'orientation portrait est prise en charge:
Si vous souhaitez déployer sur iOS 5.x ou une version antérieure, vous pouvez modifier le support d'orientation de cette manière:
- (BOOL) shouldAutorotateToInterfaceOrientation: (UIInterfaceOrientation) interfaceOrientation return (interfaceOrientation == UIInterfaceOrientationPortrait);
Je fais cela pour que les choses restent simples afin que nous puissions nous concentrer sur le principal problème à résoudre.
Je souhaite développer notre code de manière itérative, en l'améliorant progressivement - comme vous le feriez de manière réaliste si vous partiez de zéro - au lieu de vous laisser tomber la version finale d'un seul coup. J'espère que cette approche vous permettra de mieux maîtriser les différentes questions en jeu. Gardant cela à l'esprit, et pour éviter d'avoir à supprimer, modifier et ajouter à plusieurs reprises du code dans le même fichier, ce qui pourrait devenir salissant et sujet à erreurs, je vais adopter l'approche suivante:
Dans Xcode, choisissez Fichier> Nouveau> Fichier… , choisissez la classe Objective-C comme modèle et, dans l'écran suivant, nommez le fichier LinearInterpView et en faire une sous-classe de UIView. Sauvegarde le. Le nom "LinearInterp" est l'abréviation de "interpolation linéaire" ici. Par souci du didacticiel, je vais nommer chaque sous-classe UIView que nous créons pour souligner un concept ou une approche introduite dans le code de classe..
Comme je l'ai déjà mentionné, vous pouvez laisser le fichier d'en-tête tel quel. Effacer tout le code présent dans le fichier LinearInterpView.m et remplacez-le par le texte suivant:
#import "LinearInterpView.h" @implementation LinearInterpView UIBezierPath * path; // (3) - (id) initWithCoder: (NSCoder *) aDecoder // (1) if (self = [super initWithCoder: aDecoder]) [self setMultipleTouchEnabled: NO]; // (2) [self setBackgroundColor: [UIColor whiteColor]]; chemin = [UIBezierPath bezierPath]; [chemin setLineWidth: 2.0]; retourner soi-même; - (void) drawRect: (CGRect) rect // (5) [[UIColor blackColor] setStroke]; [accident vasculaire cérébral]; - (void) toucheBegan: (NSSet *) touche withEvent: (UIEvent *) événement UITouch * touch = [touche anyObject]; CGPoint p = [touch locationInView: self]; [path moveToPoint: p]; - (void) toucheMoved: (NSSet *) touche withEvent: (UIEvent *) événement UITouch * touch = [touche anyObject]; CGPoint p = [touch locationInView: self]; [chemin addLineToPoint: p]; // (4) [self setNeedsDisplay]; - (void) touchesEnded: (NSSet *) touche withEvent: (UIEvent *) event [self touchesMoved: touches withEvent: event]; - (void) touchesCancelled: (NSSet *) touche withEvent: (UIEvent *) event [auto touchesEnded: touches withEvent: event]; @fin
Dans ce code, nous travaillons directement avec les événements tactiles que l'application nous signale chaque fois que nous avons une séquence tactile. en d’autres termes, l’utilisateur place un doigt sur la vue à l’écran, le déplace et le lève enfin de l’écran. Pour chaque événement de cette séquence, l'application nous envoie un message correspondant (en terminologie iOS, les messages sont envoyés au "premier répondant"; vous pouvez vous reporter à la documentation pour plus de détails)..
Pour traiter ces messages, nous implémentons les méthodes -touchesBegan: WithEvent:
et company, qui sont déclarés dans la classe UIResponder dont UIView hérite. Nous pouvons écrire du code pour gérer les événements tactiles comme bon nous semble. Dans notre application, nous souhaitons interroger l'emplacement des touches sur l'écran, effectuer un traitement, puis tracer des lignes à l'écran..
Les points font référence aux nombres commentés correspondants du code ci-dessus:
-initWithCoder:
parce que la vue est née d'un XIB, comme nous allons le mettre en place sous peu. UIBezierPath
est une classe UIKit qui nous permet de dessiner des formes sur l'écran composées de lignes droites ou de certains types de courbes. -drawRect:
méthode. Nous faisons cela en dessinant le chemin chaque fois qu'un nouveau segment de ligne est ajouté.. -drawRect:
méthode, et le résultat de ce que vous voyez est la vue à l'écran. Nous rencontrerons bientôt un autre type de contexte de dessin.Avant de pouvoir créer l'application, nous devons définir la sous-classe de vue que nous venons de créer sur la vue à l'écran..
Maintenant, construisez l'application. Vous devriez obtenir une vue blanche brillante que vous pouvez dessiner avec votre doigt. Compte tenu des quelques lignes de code que nous avons écrites, les résultats ne sont pas trop médiocres! Bien sûr, ils ne sont pas spectaculaires non plus. L’aspect relier les points est plutôt perceptible (et oui, mon écriture craint aussi).
Assurez-vous que vous exécutez l'application non seulement sur le simulateur, mais également sur un appareil réel..
Si vous jouez avec l'application pendant un certain temps sur votre appareil, vous remarquerez sûrement quelque chose: éventuellement, la réponse de l'interface utilisateur commence à ralentir et au lieu des ~ 60 points de contact acquis par seconde, pour une raison quelconque, le nombre de points l'interface utilisateur est capable d'échantillonner des gouttes de plus en plus loin. Puisque les points sont de plus en plus éloignés, l'interpolation en ligne droite rend le dessin encore plus "bloc" qu'auparavant. Ceci est certainement indésirable. Alors que se passe-t-il?
Passons en revue ce que nous avons fait: au fur et à mesure que nous dessinons, nous acquérons des points, nous les ajoutons à un chemin toujours croissant, puis nous rendons le chemin * complet * à chaque cycle de la boucle principale. Ainsi, au fur et à mesure que le chemin s'allonge, le système de dessin a plus à dessiner à chaque itération et devient finalement trop, rendant l'application difficile à suivre. Puisque tout se passe sur le thread principal, notre code de dessin est en concurrence avec le code de l'interface utilisateur qui doit, entre autres, échantillonner les retouches à l'écran..
Vous seriez pardonné de penser qu'il y avait un moyen de dessiner "au-dessus" de ce qui était déjà à l'écran; malheureusement, c’est là que nous devons nous libérer de l’analogie stylo sur papier car le système graphique ne fonctionne pas vraiment de cette façon par défaut. Bien que, en vertu du code que nous allons écrire prochainement, nous allons indirectement mettre en œuvre l’approche «tirage au sort».
Bien que nous puissions essayer de corriger les performances de notre code, nous n'allons mettre en œuvre qu'une idée, car elle s'avère suffisante pour nos besoins actuels..
Créez une nouvelle sous-classe UIView comme vous le faisiez auparavant, en la nommant CachedLIView (le LI est pour nous rappeler que nous faisons encore Ldans l'oreille jenterpolation). Supprimer tout le contenu de CachedLIView.m et le remplacer par ce qui suit:
#import "CachedLIView.h" @implementation CachedLIView UIBezierPath * path; UIImage * incrementalImage; // (1) - (id) initWithCoder: (NSCoder *) aDecoder if (self = [super initWithCoder: aDecoder]) self setMultipleTouchEnabled: NO]; [self setBackgroundColor: [UIColor whiteColor]]; chemin = [UIBezierPath bezierPath]; [chemin setLineWidth: 2.0]; retourner soi-même; - (void) drawRect: (CGRect) rect [incrementalImage drawInRect: rect]; // (3) [tracé de chemin]; - (void) toucheBegan: (NSSet *) touche withEvent: (UIEvent *) événement UITouch * touch = [touche anyObject]; CGPoint p = [touch locationInView: self]; [path moveToPoint: p]; - (void) toucheMoved: (NSSet *) touche withEvent: (UIEvent *) événement UITouch * touch = [touche anyObject]; CGPoint p = [touch locationInView: self]; [chemin addLineToPoint: p]; [self setNeedsDisplay]; - (void) toucheEnded: (NSSet *) touche withEvent: (UIEvent *) event // (2) UITouch * touch = [touche anyObject]; CGPoint p = [touch locationInView: self]; [chemin addLineToPoint: p]; [self drawBitmap]; // (3) [self setNeedsDisplay]; [chemin removeAllPoints]; // (4) - (void) touchesCancelled: (NSSet *) touches withEvent: (UIEvent *) event [self touchesEnded: touches withEvent: event]; - (void) drawBitmap // (3) UIGraphicsBeginImageContextWithOptions (self.bounds.size, YES, 0.0); [[UIColor blackColor] setStroke]; if (! incrementalImage) // premier tirage au sort; peindre en fond blanc avec… UIBezierPath * rectpath = [UIBezierPath bezierPathWithRect: self.bounds]; // encadrant un bitmap par un rectangle défini par un autre objet UIBezierPath [[UIColor whiteColor] setFill]; [remplissage rectpath]; // le remplissant de blanc [incrementalImage drawAtPoint: CGPointZero]; [accident vasculaire cérébral]; incrementalImage = UIGraphicsGetImageFromCurrentImageContext (); UIGraphicsEndImageContext (); @fin
Après la sauvegarde, n'oubliez pas de changer la classe de l'objet vue dans votre / vos XIB en CachedLIView!
Lorsque l'utilisateur place son doigt sur l'écran pour dessiner, nous commençons avec un nouveau chemin, sans points ni lignes, et nous y ajoutons des segments de ligne, comme nous le faisions auparavant..
Encore une fois, en référence aux chiffres dans les commentaires:
-drawRect:
ce contexte est automatiquement mis à notre disposition et reflète ce que nous attirons dans notre vue à l'écran. En revanche, le contexte bitmap doit être créé et détruit explicitement, et le contenu dessiné réside en mémoire..drawRect:
s’appelle, nous dessinons d’abord le contenu de la mémoire tampon dans notre vue qui (par conception) a exactement la même taille, et nous maintenons donc pour l’utilisateur l’illusion de dessiner en continu, mais d’une manière différente de celle d’avant.Bien que ce ne soit pas parfait (et si notre utilisateur continue à dessiner sans lever le doigt, jamais?), Ce sera suffisant pour la portée de ce tutoriel. Nous vous encourageons à expérimenter vous-même pour trouver une meilleure méthode. Par exemple, vous pouvez essayer de mettre le dessin en cache périodiquement plutôt que lorsque l'utilisateur lève le doigt. En l'occurrence, cette procédure de mise en cache hors écran nous offre l'opportunité d'un traitement en arrière-plan, si nous choisissions de l'implémenter. Mais nous ne ferons pas cela dans ce tutoriel. Vous êtes invités à essayer vous-même, bien que!
Nous allons maintenant concentrer notre attention sur l'amélioration du dessin. Jusqu'à présent, nous avons joint des points de contact adjacents avec des segments de droite. Mais normalement, lorsque nous dessinons à main levée, notre trait naturel a une apparence fluide et courbée (plutôt que bloc et rigide). Il est logique d'essayer d'interpoler nos points avec des courbes plutôt que des segments. Heureusement, la classe UIBezierPath nous permet de dessiner son nom: courbes de Bézier.
Quelles sont les courbes de Bézier? Sans invoquer la définition mathématique, une courbe de Bézier est définie par quatre points: deux extrémités parcourues par une courbe et deux "points de contrôle" qui aident à définir les tangentes que la courbe doit toucher à ses extrémités (techniquement une courbe de Bézier cubique, mais pour plus de simplicité, je parlerai simplement de "courbe de Bézier").
Les courbes de Bézier nous permettent de dessiner toutes sortes de formes intéressantes.
Nous allons maintenant essayer de regrouper des séquences de quatre points de contact adjacents et d’interpoler la séquence de points dans un segment de courbe de Bézier. Chaque paire adjacente de segments de Bézier partagera un point final en commun afin de maintenir la continuité du trait.
Vous connaissez la foret maintenant. Créez une nouvelle sous-classe UIView et nommez-la BezierInterpView. Collez le code suivant dans le fichier .m:
#import "BezierInterpView.h" @implementation BezierInterpView UIBezierPath * path; UIImage * incrementalImage; Points CGPoint [4]; // suivre les quatre points de notre segment de Bézier uint ctr; // une variable de compteur permettant de suivre l'indice de point - (id) initWithCoder: (NSCoder *) aDecoder if (self = [super initWithCoder: aDecoder]) self setMultipleTouchEnabled: NO]; [self setBackgroundColor: [UIColor whiteColor]]; chemin = [UIBezierPath bezierPath]; [chemin setLineWidth: 2.0]; retourner soi-même; - (void) drawRect: (CGRect) rect [incrementalImage drawInRect: rect]; [accident vasculaire cérébral]; - (void) touchBegan: (NSSet *) touche withEvent: (UIEvent *) événement ctr = 0; UITouch * touch = [touche anyObject]; pts [0] = [touch locationInView: self]; - (void) toucheMoved: (NSSet *) touche withEvent: (UIEvent *) événement UITouch * touch = [touche anyObject]; CGPoint p = [touch locationInView: self]; ctr ++; pts [ctr] = p; if (ctr == 3) // 4ème point [path moveToPoint: pts [0]]; [chemin addCurveToPoint: pts [3] controlPoint1: pts [1] controlPoint2: pts [2]]; // voici comment une courbe de Bézier est ajoutée à un chemin. Nous ajoutons un Bézier cubique de pt [0] à pt [3], avec les points de contrôle pt [1] et pt [2] [self setNeedsDisplay]; pts [0] = [chemin currentPoint]; ctr = 0; - (void) touchesEnded: (NSSet *) touche withEvent: (UIEvent *) event [self drawBitmap]; [self setNeedsDisplay]; pts [0] = [chemin currentPoint]; // que le deuxième point d'extrémité du segment de Bézier actuel soit le premier du prochain segment de Bézier [path removeAllPoints]; ctr = 0; - (void) touchesCancelled: (NSSet *) touche withEvent: (UIEvent *) event [auto touchesEnded: touches withEvent: event]; - (void) drawBitmap UIGraphicsBeginImageContextWithOptions (self.bounds.size, YES, 0.0); [[UIColor blackColor] setStroke]; if (! incrementalImage) // première fois; peinture de fond blanc UIBezierPath * rectpath = [UIBezierPath bezierPathWithRect: self.bounds]; [[UIColor whiteColor] setFill]; [remplissage rectpath]; [incrementalImage drawAtPoint: CGPointZero]; [accident vasculaire cérébral]; incrementalImage = UIGraphicsGetImageFromCurrentImageContext (); UIGraphicsEndImageContext (); @fin
Comme l'indiquent les commentaires en ligne, le principal changement est l'introduction de deux nouvelles variables permettant de garder une trace des points de nos segments de Bézier, ainsi qu'une modification de la -(void) touchesMoved: withEvent:
méthode permettant de dessiner un segment de Bézier pour quatre points (en fait, tous les trois points, en termes de touches que l'application nous signale, car nous partageons un point de terminaison pour chaque paire de segments de Bézier adjacents).
Vous remarquerez peut-être ici que nous avons négligé le cas où l'utilisateur a levé son doigt et mis fin à la séquence tactile avant de disposer de suffisamment de points pour terminer notre dernier segment de Bézier. Si oui, vous auriez raison! Bien que visuellement, cela ne fasse pas beaucoup de différence, dans certains cas importants, cependant. Par exemple, essayez de dessiner un petit cercle. Il se peut qu’elle ne ferme pas complètement et, dans une application réelle, vous souhaitez gérer cela de manière appropriée dans le -touchesEnded: WithEvent
méthode. Pendant que nous y sommes, nous n’avons également accordé aucune attention particulière au cas de l’annulation tactile. le touchesCancelled: WithEvent
méthode d'instance gère cela. Consultez la documentation officielle pour voir s’il ya des cas particuliers que vous devrez peut-être gérer ici..
Alors, à quoi ressemblent les résultats? Une fois de plus, je vous rappelle de définir la classe correcte dans le XIB avant de construire.
Huh. Cela ne semble pas être beaucoup d'amélioration, n'est-ce pas? Je crois que ça pourrait l'être légèrement mieux que l'interpolation en ligne droite, ou peut-être que c'est juste un vœu pieux. En tout cas, rien ne vaut la peine de se vanter.
Voici ce que je pense qui se passe: pendant que nous prenons la peine d'interpoler chaque séquence de quatre points avec un segment de courbe lisse, nous ne faisons aucun effort pour faire un segment de courbe pour une transition en douceur dans le prochain, si bien que nous avons toujours un problème avec le résultat final.
Alors, que pouvons-nous faire à ce sujet? Si nous allons nous en tenir à l’approche que nous avons commencée dans la dernière version (c’est-à-dire en utilisant les courbes de Bézier), nous devons veiller à la continuité et au lissage au "point de jonction" de deux segments de Bézier adjacents. Les deux tangentes au point final avec les points de contrôle correspondants (deuxième point de contrôle du premier segment et premier point de contrôle du deuxième segment) semblent être la clé; si ces deux tangentes avaient la même direction, la courbe serait plus lisse à la jonction.
Et si nous déplacions le point de terminaison commun quelque part sur la ligne joignant les deux points de contrôle? Sans utiliser des données supplémentaires sur les points de contact, le meilleur point semblerait être le point médian de la ligne joignant les deux points de contrôle considérés et notre exigence imposée concernant la direction des deux tangentes serait satisfaite. Essayons ça!
Créez (encore une fois encore) une sous-classe UIView et nommez-la SmoothedBIView. Remplacez le code complet du fichier .m par le texte suivant:
#import "SmoothedBIView.h" @implementation SmoothedBIView UIBezierPath * path; UIImage * incrementalImage; Points CGPoint [5]; // nous devons maintenant suivre les quatre points d'un segment de Bézier et le premier point de contrôle du segment suivant uint ctr; - (id) initWithCoder: (NSCoder *) aDecoder if (self = [super initWithCoder: aDecoder]) self setMultipleTouchEnabled: NO]; [self setBackgroundColor: [UIColor whiteColor]]; chemin = [UIBezierPath bezierPath]; [chemin setLineWidth: 2.0]; retourner soi-même; - (id) initWithFrame: (CGRect) frame self = [super initWithFrame: frame]; if (self) [self setMultipleTouchEnabled: NO]; chemin = [UIBezierPath bezierPath]; [chemin setLineWidth: 2.0]; retourner soi-même; // substitue uniquement drawRect: si vous effectuez un dessin personnalisé. // Une implémentation vide affecte négativement les performances lors de l'animation. - (void) drawRect: (CGRect) rect [incrementalImage drawInRect: rect]; [accident vasculaire cérébral]; - (void) touchBegan: (NSSet *) touche withEvent: (UIEvent *) événement ctr = 0; UITouch * touch = [touche anyObject]; pts [0] = [touch locationInView: self]; - (void) toucheMoved: (NSSet *) touche withEvent: (UIEvent *) événement UITouch * touch = [touche anyObject]; CGPoint p = [touch locationInView: self]; ctr ++; pts [ctr] = p; si (ctr == 4) pts [3] = CGPointMake ((pts [2] .x + pts [4] .x) /2.0, (pts [2] .y + pts [4] .y) /2.0 ) // déplace le point final au milieu de la ligne joignant le deuxième point de contrôle du premier segment de Bézier et le premier point de contrôle du deuxième segment de Bézier [path moveToPoint: pts [0]]; [chemin addCurveToPoint: pts [3] controlPoint1: pts [1] controlPoint2: pts [2]]; // ajoute un Bezier cubique de pt [0] à pt [3], avec les points de contrôle pt [1] et pt [2] [self setNeedsDisplay]; // remplace les points et prépare le prochain segment pts [0] = pts [3]; pts [1] = pts [4]; ctr = 1; - (void) touchesEnded: (NSSet *) touche withEvent: (UIEvent *) event [self drawBitmap]; [self setNeedsDisplay]; [chemin removeAllPoints]; ctr = 0; - (void) touchesCancelled: (NSSet *) touche withEvent: (UIEvent *) event [auto touchesEnded: touches withEvent: event]; - (void) drawBitmap UIGraphicsBeginImageContextWithOptions (self.bounds.size, YES, 0.0); if (! incrementalImage) // première fois; peinture de fond blanc UIBezierPath * rectpath = [UIBezierPath bezierPathWithRect: self.bounds]; [[UIColor whiteColor] setFill]; [remplissage rectpath]; [incrementalImage drawAtPoint: CGPointZero]; [[UIColor blackColor] setStroke]; [accident vasculaire cérébral]; incrementalImage = UIGraphicsGetImageFromCurrentImageContext (); UIGraphicsEndImageContext (); @fin
Le noeud de l’algorithme que nous avons discuté ci-dessus est implémenté dans le -touchesMoved: WithEvent: méthode. Les commentaires en ligne devraient vous aider à relier la discussion au code..
Alors, comment sont les résultats, visuellement? N'oubliez pas de faire la chose avec le XIB.
Heureusement, il y a eu une amélioration substantielle cette fois-ci. Compte tenu de la simplicité de notre modification, cela semble plutôt bon (si je le dis moi-même!). Notre analyse du problème avec l'itération précédente et la solution proposée ont également été validées..
J'espère que vous avez trouvé ce tutoriel utile. J'espère que vous développerez vos propres idées sur la façon d'améliorer le code. L'une des améliorations les plus importantes (mais faciles) que vous pouvez intégrer est la gestion plus harmonieuse de la fin des séquences tactiles, comme indiqué précédemment..
Un autre cas que j'ai négligé concerne le traitement d'une séquence tactile consistant pour l'utilisateur à toucher la vue avec son doigt, puis à la soulever sans l'avoir déplacée, ce qui se traduit par un tapotement sur l'écran. L'utilisateur s'attendrait probablement à dessiner un point ou un petit gribouillis sur la vue de cette façon, mais avec notre implémentation actuelle, rien ne se produit car notre code de dessin ne se déclenche que si notre vue reçoit le message. -touchesMoved: WithEvent: message. Vous voudrez peut-être jeter un oeil à la UIBezierPath
documentation de classe pour voir quels autres types de chemins vous pouvez construire.
Si votre application fait plus de travail que ce que nous avons fait ici (et dans une application de dessin méritant d'être livrée, ce serait le cas!), Concevez-la de sorte que le code non UI (en particulier la mise en cache hors écran) s'exécute dans un fil d'arrière-plan. faire une différence significative sur un périphérique multicœur (iPad 2 et ultérieur). Même sur un périphérique à processeur unique, tel que l'iPhone 4, les performances devraient être améliorées, car je m'attends à ce que le processeur divise le travail de mise en cache qui, après tout, ne se produit qu'une fois tous les quelques cycles de la boucle principale..
Je vous encourage à faire preuve de souplesse et à jouer avec l'API UIKit afin de développer et d'améliorer certaines des idées mises en œuvre dans ce didacticiel. Amusez-vous et merci d'avoir lu!