Using CALayer Delegate

前端 未结 8 1069
滥情空心
滥情空心 2020-12-02 08:44

I have a UIView whose layers will have sublayers. I\'d like to assign delegates for each of those sublayers, so the delegate method can tell the layer what to draw. My que

相关标签:
8条回答
  • 2020-12-02 09:11

    It's possible to implement a delegation without resorting to a strong ref.

    NOTE: The basic concept is that you forward the delegate call to a selector call

    1. Create a selector instance in the NSView you want to get the delegation from
    2. implement the drawLayer(layer,ctx) in the NSView you want to get the delegation from call the selector variable with the layer and ctx vars
    3. set the view.selector to a handleSelector method where you then retrieve the layer and ctx (this can be anywhere in your code, weak or strongly referenced)

    To see an example of how you implement the selector construction:(Permalink) https://github.com/eonist/Element/wiki/Progress#selectors-in-swift

    NOTE: why are we doing this? because creating a variable outside methods whenever you want to use the Graphic class is non-sensical

    NOTE: And you also get the benefit that the receiver of the delegation doesn't need to extend NSView or NSObject

    0 讨论(0)
  • 2020-12-02 09:12

    Can you use the passed in layer parameter to construct a switch statement so you can put everything in this method(against the advice of the documents):

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

    Just my 2 cents.

    0 讨论(0)
  • 2020-12-02 09:13

    Preferring to keep the layer delegate methods in my UIView subclass, I use a basic re-delegating delegate class. This class can be reused without customization, avoiding the need to subclass CALayer or create a separate delegate class just for layer drawing.

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

    with this implementation:

    @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
    

    The layer name is used to allow for per-layer custom draw methods. For example, if you have assigned a name to your layer, say layer.name = @"Background";, then you can implement a method like this:

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

    Note, your view will need a strong reference the instance of this class, and it can be used as the delegate for any number of layers.

    layerDelegate = [[LayerDelegate alloc] initWithView:self];
    layer1.delegate = layerDelegate;
    layer2.delegate = layerDelegate;
    
    0 讨论(0)
  • 2020-12-02 09:14

    The lightest-wight solution would be to create a small helper class in the the file as the UIView that's using the CALayer:

    In MyView.h

    @interface MyLayerDelegate : NSObject
    . . .
    @end
    

    In MyView.m

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

    Just place those at the top of your file, immediately below the #import directives. That way it feels more like using a "private class" to handle the drawing (although it isn't -- the delegate class can be instantiated by any code that imports the header).

    0 讨论(0)
  • 2020-12-02 09:16

    I personally voted for Dave Lee's solution above as being the most encapsulating, particularly where you have multiple layers. However; when I tried it on IOS 6 with ARC I got errors on this line and suggesting that I need a bridged cast

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

    I therefore amended Dave Lee's drawLayer method from his re-delegating delegate class to employ NSInvocation as below. All usage and ancillary functions are identical to those Dave Lee posted on his earlier excellent suggestion.

    -(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];
    
    }
    
    0 讨论(0)
  • 2020-12-02 09:17

    Take a look at the docs on formal vs informal protocols. The CALayer is implementing an informal protocol which means that you can set any object to be its delegate and it will determine if it can send messages to that delegate by checking the delegate for a particular selector (i.e. -respondsToSelector).

    I typically use my view controller as the delegate for the layer in question.

    0 讨论(0)
提交回复
热议问题