2011-05-01 23 views
16

Antecedentes: tengo una vista de desplazamiento personalizada (subclasificada) que tiene uiimageviews en ella que se pueden arrastrar, en función de los drags necesito dibujar algunas líneas dinámicamente en una subvista de uiscrollview. (Nota: los necesito en una subvista ya que en un momento posterior necesito cambiar la opacidad de la vista.)¿Cuál es el mejor enfoque para dibujar líneas entre las vistas?

Así que antes de pasar años desarrollando el código (soy novato, así que me llevará un tiempo) Miré lo que tenía que hacer y encontré algunas maneras posibles. Me pregunto cuál es la forma correcta de hacer esto.

  1. Crear una subclase de UIView y utilizar el método drawRect trazar la línea que necesito (pero no está seguro cómo hacerlo de forma dinámica leer en los valores)
  2. En los CALayers uso subvista y se basan en que hay
  3. Cree un método de línea de extracción usando CGContext functions
  4. ¿Algo más?

Saludos para la ayuda

Respuesta

40

Conceptualmente, todas sus proposiciones son similares. Todos ellos conducirían a los siguientes pasos (algunos de ellos hechos invisiblemente por UIKit):

  1. Configure un contexto de mapa de bits en la memoria.
  2. Usa los gráficos básicos para dibujar la línea en el mapa de bits.
  3. Copie este mapa de bits en un búfer de GPU (una textura).
  4. Componga la jerarquía de capas (vista) usando la GPU.

La parte costosa de los pasos anteriores son los tres primeros puntos. Conducen a una asignación de memoria repetida, copia de memoria y comunicación CPU/GPU. Por otro lado, lo que realmente quiere hacer es liviano: trace una línea, probablemente animando puntos de inicio/final, ancho, color, alfa, ...

Hay una manera fácil de hacerlo, evitando por completo lo descrito sobrecarga: utilice un CALayer para su línea, pero en lugar de volver a dibujar el contenido en la CPU, rellene completamente con el color de la línea (estableciendo su propiedad backgroundColor en el color de la línea. Luego modifique las propiedades de la capa para posición, límites, transformación, para hacer El CALayer cubre el área exacta de su línea

Por supuesto, este enfoque solo puede dibujar líneas rectas, pero también se puede modificar para dibujar efectos visuales complejos estableciendo la propiedad contents en una imagen. Podría, por ejemplo tener bordes difusos de un efecto de brillo en la línea, usando esta técnica.

Aunque esta técnica tiene sus limitaciones, la utilicé bastante a menudo en diferentes aplicaciones tanto en el iPhone como en la Mac. Siempre tuvo un rendimiento dramáticamente superior al del dibujo basado en gráficos centrales.

Editar: Código para calcular las propiedades de capa:

void setLayerToLineFromAToB(CALayer *layer, CGPoint a, CGPoint b, CGFloat lineWidth) 
{ 
    CGPoint center = { 0.5 * (a.x + b.x), 0.5 * (a.y + b.y) }; 
    CGFloat length = sqrt((a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y)); 
    CGFloat angle = atan2(a.y - b.y, a.x - b.x); 

    layer.position = center; 
    layer.bounds = (CGRect) { {0, 0}, { length + lineWidth, lineWidth } }; 
    layer.transform = CATransform3DMakeRotation(angle, 0, 0, 1); 
} 

segunda edición:Here's a simple test project que muestra la diferencia dramática en el rendimiento entre Graphics Core y Core Animation representación basada.

3rd Edit: Los resultados son bastante impresionantes: la representación de 30 vistas arrastrables, cada una conectada entre sí (resultando en 435 líneas) se procesa suavemente a 60Hz en un iPad 2 usando Core Animation. Al usar el enfoque clásico, la velocidad de fotogramas cae a 5 Hz y eventualmente aparecen advertencias de memoria.

Performance comparison Core Graphics vs. Core Animation

+0

+1 para el enfoque más eficiente, pero calcular la posición correcta y la transformación es más difícil que con la solución de dibujo simple (UIView o CALayer). Dado dos puntos * p1 * y * p2 *, ¿cómo posiciona/gira la capa correctamente? Tengo algunas ideas, pero ninguna me parece "correcta". – DarkDust

+2

@DarkDust Se agregó un código para mostrar cómo establecer las propiedades de las capas. –

+0

@Niklai Ruhe: Gracias, eso es incluso más simple de lo que pensaba. – DarkDust

5

En primer lugar, para dibujar en IOS que necesitan un contexto y al dibujar en la pantalla no se puede conseguir el contexto exterior de drawRect: (UIView) o drawLayer:inContext: (CALayer). Esto significa que la opción 3 está fuera (si tu intención fue hacerlo fuera del método drawRect:).

Podrías conseguir un CALayer, pero iría por una UIView aquí. Por lo que yo he entendido su configuración, usted tiene esto:

UIScrollView 
    |  | | 
ViewA ViewB LineView 

Así LineView es un hermano de viewa y ViewB, habría que ser lo suficientemente grande como para cubrir tanto viewa y ViewB y está dispuesto para estar frente a tanto (y tiene setOpaque:NO conjunto).

La implementación de LineView sería bastante directa: le daría dos propiedades point1 y point2 de tipo CGPoint. Opcionalmente, implemente los métodos setPoint1:/setPoint2:, por lo que siempre llama al [self setNeedsDisplay];, por lo que se redibuja una vez que se ha cambiado un punto.

En LineView drawRect:, todo lo que necesita es trazar la línea ya sea con CoreGraphics o con UIBezierPath. Cuál usar es más o menos una cuestión de gusto. Cuando te gusta usar CoreGraphics, lo haces de esta manera:

- (void)drawRect:(CGRect)rect 
{ 
    CGContextRef context = UIGraphicsGetCurrentContext(); 
    // Set up color, line width, etc. first. 
    CGContextMoveToPoint(context, point1); 
    CGContextAddLineToPoint(context, point2); 
    CGContextStrokePath(context); 
} 

Usando NSBezierPath, se vería bastante similar:

- (void)drawRect:(CGRect)rect 
{ 
    UIBezierPath *path = [UIBezierPath bezierPath]; 
    // Set up color, line width, etc. first. 
    [path moveToPoint:point1]; 
    [path addLineToPoint:point2]; 
    [path stroke]; 
} 

La magia es ahora conseguir las coordenadas correctas para puntos 1 y 2.. Supongo que tienes un controlador que puede ver todas las vistas. UIView tiene dos buenos métodos de utilidad, convertPoint:toView: y convertPoint:fromView: que necesitará aquí. Aquí hay código ficticio para el controlador que causaría la LineView para dibujar una línea entre los centros de viewa y ViewB:

- (void)connectTheViews 
{ 
    CGPoint p1, p2; 
    CGRect frame; 
    frame = [viewA frame]; 
    p1 = CGPointMake(CGRectGetMidX(frame), CGRectGetMidY(frame)); 
    frame = [viewB frame]; 
    p2 = CGPointMake(CGRectGetMidX(frame), CGRectGetMidY(frame)); 
    // Convert them to coordinate system of the scrollview 
    p1 = [scrollView convertPoint:p1 fromView:viewA]; 
    p2 = [scrollView convertPoint:p2 fromView:viewB]; 
    // And now into coordinate system of target view. 
    p1 = [scrollView convertPoint:p1 toView:lineView]; 
    p2 = [scrollView convertPoint:p2 toView:lineView]; 
    // Set the points. 
    [lineView setPoint1:p1]; 
    [lineView setPoint2:p2]; 
    [lineView setNeedsDisplay]; // If the properties don't set it already 
} 

Ya que no sé cómo se ha implementado el arrastre no te puedo decir cómo para disparar llamando a este método en el controlador. Si está completamente encapsulado en sus vistas y el controlador no está involucrado, buscaría una NSNotificación que publique cada vez que la vista se arrastre a una nueva coordenada.El controlador escucharía la notificación y llamaría al método antes mencionado para actualizar LineView.

Una última nota: es posible que desee llamar al setUserInteractionEnabled:NO en su LineView en su método initWithFrame: para que un toque en la línea pase a la vista debajo de la línea.

Happy coding!

+0

+1 para el bien explicado lo fundamental. –

Cuestiones relacionadas