2009-11-25 7 views
11

Pour autant que je l'aie compris jusqu'à présent, chaque fois que je dessine quelque chose dans le dessin: d'un UIView, tout le contexte est effacé puis redessiné.Dessin incrémentiel dans un UIView (iPhone)

donc je dois faire quelque chose comme ça pour dessiner une série de points:

Méthode A: dessin tout sur chaque appel

- (void)drawRect:(CGRect)rect { 

    CGContextRef context = UIGraphicsGetCurrentContext(); 

    CGContextDrawImage(context, self.bounds, maskRef);  //draw the mask 
    CGContextClipToMask(context, self.bounds, maskRef);  //respect alpha mask 
    CGContextSetBlendMode(context, kCGBlendModeColorBurn); //set blending mode 

    for (Drop *drop in myPoints) { 
     CGContextAddEllipseInRect(context, CGRectMake(drop.point.x - drop.size/2, drop.point.y - drop.size/2, drop.size, drop.size)); 
    } 

    CGContextSetRGBFillColor(context, 0.5, 0.0, 0.0, 0.8); 
    CGContextFillPath(context); 
} 

Ce qui signifie, je dois stocker tous mes points (c'est bon) et re-dessinez-les tous, un par un, chaque fois que je veux en ajouter un nouveau. Malheureusement, cela donne ma performance terrible et je suis sûr qu'il y a une autre façon de le faire, plus efficacement.

EDIT: En utilisant le code de MrMage j'ai fait ce qui suit, qui malheureusement est tout aussi lent et le mélange des couleurs ne fonctionne pas. Toute autre méthode que je pourrais essayer?

Méthode B: sauver la précédente puise dans un UIImage et le dessin que les nouveautés et cette image

- (void)drawRect:(CGRect)rect 
{ 
    //draw on top of the previous stuff 
    UIGraphicsBeginImageContext(self.frame.size); 
    CGContextRef ctx = UIGraphicsGetCurrentContext(); // ctx is now the image's context 
    [cachedImage drawAtPoint:CGPointZero]; 
    if ([myPoints count] > 0) 
    { 
     Drop *drop = [myPoints objectAtIndex:[myPoints count]-1]; 
     CGContextClipToMask(ctx, self.bounds, maskRef);   //respect alpha mask 
     CGContextAddEllipseInRect(ctx, CGRectMake(drop.point.x - drop.dropSize/2, drop.point.y - drop.dropSize/2, drop.dropSize, drop.dropSize)); 
     CGContextSetRGBFillColor(ctx, 0.5, 0.0, 0.0, 1.0); 
     CGContextFillPath(ctx); 
    } 
    [cachedImage release]; 
    cachedImage = [UIGraphicsGetImageFromCurrentImageContext() retain]; 
    UIGraphicsEndImageContext(); 

    //draw on the current context 
    CGContextRef context = UIGraphicsGetCurrentContext(); 
    CGContextDrawImage(context, self.bounds, maskRef);   //draw the mask 
    CGContextSetBlendMode(context, kCGBlendModeColorBurn);  //set blending mode 
    [cachedImage drawAtPoint:CGPointZero];      //draw the cached image 
} 

EDIT: Après tout ce que je combine l'une des méthodes mentionner ci-dessous avec redessiner seulement dans le nouveau rect. Le résultat est le suivant: Méthode rapide:

- (void)addDotAt:(CGPoint)point 
{ 
    if ([myPoints count] < kMaxPoints) { 
     Drop *drop = [[[Drop alloc] init] autorelease]; 
     drop.point = point; 
     [myPoints addObject:drop]; 
     [self setNeedsDisplayInRect:CGRectMake(drop.point.x - drop.dropSize/2, drop.point.y - drop.dropSize/2, drop.dropSize, drop.dropSize)];  //redraw 
    } 
} 

- (void)drawRect:(CGRect)rect 
{ 
    CGContextRef context = UIGraphicsGetCurrentContext(); 

    CGContextDrawImage(context, self.bounds, maskRef);            //draw the mask 
    CGContextClipToMask(context, self.bounds, maskRef);            //respect alpha mask 
    CGContextSetBlendMode(context, kCGBlendModeColorBurn);           //set blending mode 

    if ([myPoints count] > 0) 
    { 
     Drop *drop = [myPoints objectAtIndex:[myPoints count]-1]; 
     CGPathAddEllipseInRect (dotsPath, NULL, CGRectMake(drop.point.x - drop.dropSize/2, drop.point.y - drop.dropSize/2, drop.dropSize, drop.dropSize)); 
    } 
    CGContextAddPath(context, dotsPath); 

    CGContextSetRGBFillColor(context, 0.5, 0.0, 0.0, 1.0); 
    CGContextFillPath(context); 
} 

Merci tout le monde!

Répondre

6

Si vous ne changez en fait une petite partie de la Le contenu de UIView chaque fois que vous dessinez (et le reste du contenu reste généralement le même), vous pouvez l'utiliser. Plutôt que de redessiner tout le contenu de UIView à chaque fois, vous pouvez marquer uniquement les zones de la vue qui ont besoin d'être redessinées en utilisant -[UIView setNeedsDisplayInRect:] au lieu de -[UIView setNeedsDisplay]. Vous devez également vous assurer que le contenu graphique n'est pas effacé avant de dessiner en réglant view.clearsContextBeforeDrawing = YES;

Bien sûr, tout cela signifie également que votre implémentation drawRect: doit respecter le paramètre rect, qui devrait alors être une petite partie de votre entier. Voir rect (à moins que quelque chose d'autre sale le rect entier), et seulement dessiner dans cette partie.

+0

Si "* view.clearsContextBeforeDrawing = NO; *" a fait ce qu'il dit dans la documentation qu'il devrait faire, tout le dessin devrait être incrémental, non? Mais ce n'est pas ... – Dimitris

+1

Votre dessin n'est pas incrémentiel sauf si vous vous assurez que votre implémentation drawRect: ne dessine que les sections mises à jour. La manière standard de marquer les "sections mises à jour" (sections sales) utilise setNeedsDisplayInRect :. Si vous définissez clearsContentBeforeDrawing = NO et remplissez tout le contenu avec du contenu, votre avantage en termes de performances est faible. –

0

Combien d'ellipses allez-vous dessiner? En général, Core Graphics devrait être capable de dessiner beaucoup d'ellipses rapidement.

Vous pouvez cependant mettre en cache vos anciens dessins à une image (je ne sais pas si cette solution est plus performante, cependant):

UIGraphicsBeginImageContext(self.frame.size); 
CGContextRef ctx = UIGraphicsGetCurrentContext(); // ctx is now the image's context 

[cachedImage drawAtPoint:CGPointZero]; 
// only plot new ellipses here... 

[cachedImage release]; 
cachedImage = [UIGraphicsGetImageFromCurrentImageContext() retain]; 
UIGraphicsEndImageContext(); 

CGContextRef context = UIGraphicsGetCurrentContext(); 

CGContextDrawImage(context, self.bounds, maskRef);   //draw the mask 
CGContextClipToMask(context, self.bounds, maskRef);   //respect alpha mask 
CGContextSetBlendMode(context, kCGBlendModeColorBurn);  //set blending mode 

[cachedImage drawAtPoint:CGPointZero]; 
+0

Je ne suis pas beaucoup de dessin, disons environ 100. Mais avec le travail supplémentaire (dessiner une image sur chaque drawRect: et le masquage des ellipses sur elle) il devient lent veeery même sur un 3GS. – Dimitris

+0

Vous pouvez également essayer de masquer APRÈS avoir peint les ellipses (peut-être en dessinant d'abord dans un tampon, puis en dessinant le tampon masqué à votre vue). – MrMage

+0

Dans le code ci-dessus, vous dites "ctx" mais signifie "contexte" non? J'essaie votre méthode maintenant pour voir si cela fait une différence. – Dimitris

2

Vous pouvez enregistrer votre compte CGPath en tant que membre de votre classe. Et utilisez cela dans la méthode dessiner, vous aurez seulement besoin de créer le chemin quand les points changent mais pas chaque fois que la vue est redessinée, si les points sont incrémentaux, continuez simplement à ajouter les ellipses au chemin. Dans la méthode drawRect vous ne devez ajouter le chemin

CGContextAddPath(context,dotsPath); 

-(CGMutablePathRef)createPath 
{ 
    CGMutablePathRef dotsPath = CGPathCreateMutable(); 

    for (Drop *drop in myPoints) { 
     CGPathAddEllipseInRect (dotsPath,NULL, 
      CGRectMake(drop.point.x - drop.size/2, drop.point.y - drop.size/2, drop.size, drop.size)); 
    } 

return dotsPath; 
} 
+0

Juste essayé aussi. C'est peut-être un peu plus rapide que les autres méthodes mais c'est quand même assez lent. Je vais essayer de le combiner avec la méthode drawInRect: et voir si cela le fait. Merci. – Dimitris

+0

En fait, le setNeedsDisplayInRect: a fait l'affaire. – Dimitris

+0

Les classes Objective-C n'ont pas de "membres" avec des variables d'instance, ou "ivars". – NSResponder

1

Si je comprends bien votre problème, je voudrais essayer de tirer un CGBitmapContext au lieu de l'écran directement. Ensuite, dans le drawRect, dessinez uniquement la partie du bitmap pré-rendu nécessaire à partir du paramètre rect.

+0

Je n'ai encore rien essayé de pareil. Je vais probablement essayer. Si vous avez un code qui montre comment c'est fait, j'apprécierais. – Dimitris

+0

++ C'est ma méthode de choix. Je viens de "blt" le tout. Il élimine le clignotement et donne l'illusion de dessiner instantanément. –

0

Si vous pouvez mettre en cache le dessin en tant qu'image, vous pouvez tirer parti du support CoreAnimation de UIView. Ce sera beaucoup plus rapide que d'utiliser Quartz, car Quartz fait son dessin dans le logiciel.

- (CGImageRef)cachedImage { 
    /// Draw to an image, return that 
} 
- (void)refreshCache { 
    myView.layer.contents = [self cachedImage]; 
} 
- (void)actionThatChangesWhatNeedsToBeDrawn { 
    [self refreshCache]; 
}