Disegno in modo incrementale in un UIView (iPhone)

Per quanto ho capito finora, ogni volta che disegno qualcosa nel drawRect: di un UIView, l’intero contesto viene cancellato e quindi ridisegnato.

Quindi devo fare qualcosa del genere per disegnare una serie di punti:

Metodo A: disegno di tutto su ogni chiamata

- (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); } 

Il che significa che devo conservare tutti i miei punti (va bene) e ri-disegnarli tutti, uno per uno, ogni volta che voglio aggiungerne uno nuovo. Purtroppo questo dà la mia prestazione terribile e sono sicuro che ci sia un altro modo per farlo, in modo più efficiente.

EDIT: Usando il codice di MrMage ho fatto quanto segue, che sfortunatamente è altrettanto lento e la fusione dei colors non funziona. Qualche altro metodo che potrei provare?

Metodo B: salvare i disegni precedenti in una UIImage e solo disegnare la nuova roba e questa immagine

 - (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: Dopo tutto ho combinato uno dei metodi menzionati di seguito con il ridisegno solo nel nuovo rect. Il risultato è: METODO VELOCE:

 - (void)addDotAt:(CGPoint)point { 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); } 

Grazie a tutti!

Se stai cambiando solo una piccola parte dei contenuti di UIView ogni volta che disegni (e il resto del contenuto rimane generalmente lo stesso), puoi usare questo. Invece di ridisegnare tutto il contenuto di UIView ogni volta, puoi contrassegnare solo le aree della vista che necessitano di essere ridisegnate utilizzando -[UIView setNeedsDisplayInRect:] invece di -[UIView setNeedsDisplay] . È inoltre necessario assicurarsi che il contenuto grafico non venga cancellato prima del disegno impostando view.clearsContextBeforeDrawing = YES;

Naturalmente, tutto ciò significa anche che drawRect: implementazione deve rispettare il parametro rect, che dovrebbe quindi essere una piccola sottosezione del rect della vista completa (a meno che qualcos’altro abbia sporcato l’intero rect), e solo disegnare in quella porzione.

Puoi salvare il tuo CGPath come membro della tua class. E utilizzalo nel metodo di disegno, dovrai solo creare il percorso quando i punti cambiano ma non tutte le volte che la vista viene ridisegnata, se i punti sono incrementali, continua semplicemente ad aggiungere i puntini di sospensione al percorso. Nel metodo drawRect dovrai solo aggiungere il percorso

 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; } 

Se ho capito bene il tuo problema, proverei a disegnare direttamente su CGBitmapContext anziché sullo schermo. Quindi, in drawRect , disegna solo la parte della bitmap pre-renderizzata che è necessaria dal parametro rect .

Quante ellissi hai intenzione di disegnare? In generale, Core Graphics dovrebbe essere in grado di disegnare un sacco di ellissi rapidamente.

Tuttavia, è ansible memorizzare in cache i vecchi disegni su un’immagine (non so se questa soluzione sia più performante, comunque):

 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]; 

Se si è in grado di memorizzare nella cache il disegno come immagine, è ansible usufruire del supporto CoreAnimation di UIView. Questo sarà molto più veloce rispetto all’utilizzo di Quartz, come Quartz fa il suo disegno nel software.

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