iphone calayer

iphone - Usando el Delegado CALayer



(8)

Tengo una UIView cuyas capas tendrán subcapas. Me gustaría asignar delegados para cada una de esas subcapas, por lo que el método delegado puede decirle a la capa qué dibujar. Mi pregunta es:

¿Qué debería proporcionar como delegado de CALayer? La documentación dice que no se debe usar el UIView en el que residen las capas, ya que está reservado para el CALayer principal de la vista. Pero crear otra clase solo para ser el delegado de los CALayers que creo derrota el propósito de no crear una subclase de CALayer. ¿Qué personas suelen usar como delegados para CALayer? ¿O debería simplemente subclase?

Además, ¿por qué la clase que implementa los métodos delegados no tiene que ajustarse a algún tipo de protocolo CALayer? Esa es una pregunta general más amplia que no entiendo del todo. Pensé que todas las clases que requerían la implementación de métodos de delegado necesitaban una especificación de protocolo para que los ejecutores se ajustaran.


Es posible implementar una delegación sin recurrir a una fuerte referencia.

NOTA: El concepto básico es que reenvía la llamada de delegado a una llamada de selector

  1. Cree una instancia de selector en el NSView del que desee obtener la delegación
  2. implementar el drawLayer (layer, ctx) en el NSView que desea que la delegación invoque la variable selector con la capa y ctx vars
  3. establece view.selector en un método handleSelector donde luego recuperas la capa y ctx (esto puede estar en cualquier parte de tu código, débil o fuertemente referenciado)

Para ver un ejemplo de cómo implementar la construcción del selector: (Permalink) https://github.com/eonist/Element/wiki/Progress#selectors-in-swift

NOTA: ¿por qué estamos haciendo esto? porque la creación de una variable fuera de los métodos siempre que quiera usar la clase Graphic no es sensible

NOTA: Y también obtiene el beneficio de que el receptor de la delegación no necesita extender NSView o NSObject


¿Puede utilizar el parámetro de capa pasada en para construir una declaración de interruptor para que pueda poner todo en este método (contra el consejo de los documentos):

-(void) drawLayer: (CALayer*) layer inContext: (CGContextRef) context { if layer = xLayer {...} }

Solo mis 2 centavos.


Eche un vistazo a los documentos sobre protocolos formales vs informales . El CALayer está implementando un protocolo informal que significa que puede configurar cualquier objeto para que sea su delegado y determinará si puede enviar mensajes a ese delegado marcando el delegado para un selector en particular (es decir, -responde aSelector).

Normalmente utilizo mi controlador de vista como el delegado para la capa en cuestión.


La solución más liviana sería crear una pequeña clase de ayuda en el archivo como UIView que está usando el CALayer:

En MyView.h

@interface MyLayerDelegate : NSObject . . . @end

En MyView.m

@implementation MyLayerDelegate - (void)drawLayer:(CALayer*)layer inContext:(CGContextRef)ctx { . . . } @end

Simplemente coloque los que se encuentran en la parte superior de su archivo, inmediatamente debajo de las directivas #import. De esta forma, se siente más como usar una "clase privada" para manejar el dibujo (aunque no lo es - la clase de delegado puede ser instanciada por cualquier código que importe el encabezado).


Prefiriendo mantener los métodos de delegado de capa en mi subclase UIView, utilizo una clase de delegado de re-delegación básica. Esta clase se puede reutilizar sin personalización, evitando la necesidad de subclase de CALayer o creando una clase de delegado separada solo para el dibujo de capas.

@interface LayerDelegate : NSObject - (id)initWithView:(UIView *)view; @end

con esta implementación:

@interface LayerDelegate () @property (nonatomic, weak) UIView *view; @end @implementation LayerDelegate - (id)initWithView:(UIView *)view { self = [super init]; if (self != nil) { _view = view; } return self; } - (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)context { NSString *methodName = [NSString stringWithFormat:@"draw%@Layer:inContext:", layer.name]; SEL selector = NSSelectorFromString(methodName); if ([self.view respondsToSelector:selector] == NO) { selector = @selector(drawLayer:inContext:); } void (*drawLayer)(UIView *, SEL, CALayer *, CGContextRef) = (__typeof__(drawLayer))objc_msgSend; drawLayer(self.view, selector, layer, context); } @end

El nombre de la capa se usa para permitir los métodos de dibujo personalizados por capa. Por ejemplo, si ha asignado un nombre a su capa, diga layer.name = @"Background"; , entonces puedes implementar un método como este:

- (void)drawBackgroundLayer:(CALayer *)layer inContext:(CGContextRef)context;

Tenga en cuenta que su vista necesitará una referencia fuerte de la instancia de esta clase, y se puede usar como delegado para cualquier cantidad de capas.

layerDelegate = [[LayerDelegate alloc] initWithView:self]; layer1.delegate = layerDelegate; layer2.delegate = layerDelegate;


Una nota con respecto a las clases de "ayuda" para usar como delegado de una capa (con ARC al menos):

Asegúrese de almacenar una referencia "fuerte" a su clase de ayuda alloc / init''d (como en una propiedad). Simplemente asignar la clase alloc / init''d helper al delegado parece causar bloqueos para mí, presumiblemente porque mylayer.delegate es una referencia débil para tu clase de ayuda (como la mayoría de los delegados), por lo que la clase auxiliar se libera antes de la capa puede usarlo

Si asigno la clase de ayuda a una propiedad, luego la asigno al delegado, mis extraños accidentes desaparecen y las cosas se comportan como se esperaba.


Yo personalmente voté por la solución anterior de Dave Lee como la más encapsulante, particularmente cuando tienes varias capas. Sin embargo; cuando lo probé en IOS 6 con ARC obtuve errores en esta línea y sugiriendo que necesito un reparto en puente

// [_view performSelector: selector withObject: layer withObject: (id)context];

Por lo tanto, modifiqué el método drawLayer de Dave Lee de su clase delegado de re-delegación para emplear NSInvocation como a continuación. Todas las funciones auxiliares y de uso son idénticas a las que Dave Lee publicó en su excelente sugerencia anterior.

-(void) drawLayer: (CALayer*) layer inContext: (CGContextRef) context { NSString* methodName = [NSString stringWithFormat: @"draw%@Layer:inContext:", layer.name]; SEL selector = NSSelectorFromString(methodName); if ( ![ _view respondsToSelector: selector]) { selector = @selector(drawLayer:inContext:); } NSMethodSignature * signature = [[_view class] instanceMethodSignatureForSelector:selector]; NSInvocation * invocation = [NSInvocation invocationWithMethodSignature:signature]; [invocation setTarget:_view]; // Actually index 0 [invocation setSelector:selector]; // Actually index 1 [invocation setArgument:&layer atIndex:2]; [invocation setArgument:&context atIndex:3]; [invocation invoke]; }


Yo prefiero la siguiente solución. Me gustaría utilizar el drawLayer:inContext: de UIView para representar una subvista que podría agregar sin agregar clases adicionales por todos lados. Mi solución es la siguiente:

Agregue los siguientes archivos a su proyecto:

UIView + UIView_LayerAdditions.h con contenido:

@interface UIView (UIView_LayerAdditions) - (CALayer *)createSublayer; @end

UIView + UIView_LayerAdditions.m con contenido

#import "UIView+UIView_LayerAdditions.h" static int LayerDelegateDirectorKey; @interface LayerDelegateDirector: NSObject{ @public UIView *view; } @end @implementation LayerDelegateDirector - (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx { [view drawLayer:layer inContext:ctx]; } @end @implementation UIView (UIView_LayerAdditions) - (LayerDelegateDirector *)director { LayerDelegateDirector *director = objc_getAssociatedObject(self, &LayerDelegateDirectorKey); if (director == nil) { director = [LayerDelegateDirector new]; director->view = self; objc_setAssociatedObject(self, &LayerDelegateDirectorKey, director, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } return director; } - (CALayer *)createSublayer { CALayer *layer = [CALayer new]; layer.contentsScale = [UIScreen mainScreen].scale; layer.delegate = [self director]; [self.layer addSublayer:layer]; [layer setNeedsDisplay]; return layer; } @end

Ahora agregue el encabezado a su archivo .pch . Si agrega una capa con el método createSublayer , createSublayer aparecerá sin allocs erróneos en el override para drawLayer:inContext: Hasta donde sé, la sobrecarga de esta solución es mínima.