2009-11-25 13 views
11

Hasta donde he entendido hasta ahora, cada vez que dibujo algo en el drawRect: de un UIView, todo el contexto se borra y luego se vuelve a dibujar.Dibujo incremental en un UIView (iPhone)

Así que tengo que hacer algo como esto para dibujar una serie de puntos:

Método A: dibujo todo lo que en cada llamada

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

Lo que significa, que tengo que almacenar todos mis puntos (está bien) y volver a dibujarlos todos, uno por uno, cada vez que quiera agregar uno nuevo. Desafortunadamente, esto me da un rendimiento terrible y estoy seguro de que hay alguna otra forma de hacerlo, de manera más eficiente.

EDITAR: Usando el código de MrMage hice lo siguiente, que desafortunadamente es igual de lento y la combinación de colores no funciona. ¿Algún otro método que pueda probar?

Método B: salvar el anterior se basa en un UIImage y sólo dibujar las cosas nuevas y esta imagen

- (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: Después de todo lo combinarse uno de los métodos mencionan a continuación con volver a dibujar sólo en el nuevo rect. El resultado es: método rápido:

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

Gracias a todos!

Respuesta

6

Si sólo va a cambiar en realidad una pequeña porción de la El contenido de UIView cada vez que dibujas (y el resto del contenido generalmente permanece igual), puedes usar esto. En lugar de volver a dibujar todo el contenido de UIView cada vez, puede marcar solo las áreas de la vista que necesitan volver a dibujar usando -[UIView setNeedsDisplayInRect:] en lugar de -[UIView setNeedsDisplay]. También es necesario asegurarse de que el contenido de gráficos no se borra antes de dibujar mediante el establecimiento de view.clearsContextBeforeDrawing = YES;

Por supuesto, todo esto también significa que su aplicación drawRect: tiene que respetar el parámetro rect, que debe ser entonces una pequeña subsección de su completa el rect de la vista (a menos que algo más ensucie todo el rect), y solo dibuje en esa parte.

+0

Si "* view.clearsContextBeforeDrawing = NO; *" hizo lo que dice en la documentación que debería hacer, todo el dibujo debería ser incremental, ¿no? Pero no es ... – Dimitris

+1

Su dibujo no es incremental a menos que se asegure de que su implementación drawRect: solo dibuja las secciones actualizadas. La forma estándar de marcar "secciones actualizadas" (secciones sucias) es usar setNeedsDisplayInRect :. Si establece cleararsContentBeforeDrawing = NO y luego completa todo el contexto con contenido, su beneficio de rendimiento es pequeño. –

0

¿Cuántas elipsis vas a dibujar? En general, Core Graphics debería poder dibujar muchas elipses rápidamente.

Se podría, sin embargo, almacenar en caché sus viejos dibujos a una imagen (no sé si esta solución es más performante, sin embargo):

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

No estoy dibujando muchos, digamos unos 100. Pero con el trabajo extra (dibujo de una imagen en cada drawRect: Y enmascarar las elipses en él) se pone muuuy lento incluso en un 3GS. – Dimitris

+0

También podría intentar enmascarar DESPUÉS de haber pintado las elipsis (tal vez al dibujar primero en un búfer, luego dibujar el búfer enmascarado a su vista). – MrMage

+0

En el código anterior, dices "ctx" pero significa "contexto" ¿no? Estoy probando tu método ahora para ver si hace la diferencia. – Dimitris

2

Puede guardar su CGPath como miembro de su clase. Y use eso en el método de dibujar, solo necesitará crear la ruta cuando cambien los puntos, pero no cada vez que se vuelva a dibujar la vista, si los puntos son incrementales, simplemente continúe agregando las elipsis a la ruta. En el método drawRect sólo tendrá que añadir la ruta

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

Acabo de intentar eso también. Puede ser un poco más rápido que los otros métodos, pero aún es bastante lento. Trataré de combinarlo con el método drawInRect: y veré si eso lo hace. Gracias. – Dimitris

+0

En realidad, el setNeedsDisplayInRect: hizo el truco. – Dimitris

+0

Las clases Objective-C no tienen "miembros" que tengan variables de instancia, o "ivars". – NSResponder

1

Si entiendo su problema correctamente, me gustaría tratar llegando a su CGBitmapContext en lugar de la pantalla directamente. Luego, en el drawRect, dibuje solo la parte del mapa de bits prestado que se necesita del parámetro rect.

+0

No he probado nada como esto todavía. Probablemente lo intente. Si tiene algún código que muestre cómo se hace, lo agradecería. – Dimitris

+0

++ Ese es mi método de elección. Yo simplemente 'blt' todo el asunto. Elimina el parpadeo y da la ilusión de dibujar instantáneamente. –

0

Si puede almacenar en caché el dibujo como una imagen, puede aprovechar el respaldo CoreAnimation de UIView. Esto será mucho más rápido que usar Quartz, ya que Quartz hace su dibujo en software.

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