使用CALayer委托

时间:2010-01-06 18:48:36

标签: iphone calayer

我有一个UIView,其图层将有子图层。我想为每个子图层分配代理,因此委托方法可以告诉图层要绘制什么。我的问题是:

我应该提供什么作为CALayer的代表?文档说不要使用层所在的UIView,因为这是为视图的主CALayer保留的。但是,创建另一个类只是为了成为我创建的CALayers的代表,这就失去了不继承CALayer的目的。人们通常使用什么作为CALayer的代表?或者我应该只是子类?

另外,为什么实现委托方法的类不必遵循某种CALayer协议?这是一个我不太了解的更广泛的首要问题。我认为所有需要实现委托方法的类都需要协议规范,以便实现者遵守。

8 个答案:

答案 0 :(得分:30)

我希望在我的UIView子类中保留图层委托方法,我使用一个基本的重新委派委托类。这个类可以在没有自定义的情况下重复使用,从而无需为CALayer创建子类或为图层绘制创建单独的委托类。

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

有了这个实现:

@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

图层名称用于允许每层自定义绘制方法。例如,如果您为图层指定了名称,例如layer.name = @"Background";,那么您可以实现如下方法:

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

请注意,您的视图需要强大的引用此类的实例,并且它可以用作任意数量的图层的委托。

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

答案 1 :(得分:27)

最轻微的解决方案是在文件中创建一个小助手类作为使用CALayer的UIView:

在MyView.h中

@interface MyLayerDelegate : NSObject
. . .
@end

在MyView.m中

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

将它们放在文件顶部,紧靠#import指令的下方。这样,感觉更像是使用“私有类”来处理绘图(虽然它不是 - 委托类可以由任何导入标题的代码实例化)。

答案 2 :(得分:9)

看看docs on formal vs informal protocols。 CALayer正在实现一个非正式协议,这意味着您可以将任何对象设置为其委托,并通过检查特定选择器的委托(即-respondsToSelector)来确定它是否可以向该委托发送消息。

我通常使用视图控制器作为相关图层的委托。

答案 3 :(得分:3)

关于“帮助”类的注释,用作图层的委托(至少使用ARC):

确保存储对alloc / init'd帮助程序类的“强”引用(例如在属性中)。简单地将alloc / init'd帮助器类分配给委托似乎会导致崩溃,大概是因为mylayer.delegate是对辅助类的弱引用(就像大多数委托一样),因此辅助类在层之前被释放可以使用它。

如果我将辅助类分配给属性,然后将其分配给委托,我的奇怪的崩溃就会消失,事情就像预期的那样。

答案 4 :(得分:2)

我个人投票赞成Dave Lee的解决方案是最具封装性的,特别是在你有多层的情况下。然而;当我在带有ARC的IOS 6上尝试时,我在这一行上遇到错误,并建议我需要一个桥接演员

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

因此我修改了Dave Lee的drawLayer方法,从他的重新授权委托类中采用NSInvocation,如下所示。所有使用和辅助功能都与Dave Lee在之前的优秀建议中发布的相同。

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

}

答案 5 :(得分:0)

我更喜欢以下解决方案。我想使用UIView的drawLayer:inContext:方法来渲染我可能添加的子视图,而无需在整个地方添加额外的类。我的解决方案如下:

将以下文件添加到项目中:

UIView + UIView_LayerAdditions.h ,内容为:

@interface UIView (UIView_LayerAdditions)

- (CALayer *)createSublayer;

@end

UIView + UIView_LayerAdditions.m 内容

#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

现在将标头添加到.pch文件中。如果使用createSublayer方法添加图层,它将自动显示在覆盖drawLayer:inContext:的情况下没有错误的分配。据我所知,这个解决方案的开销很小。

答案 6 :(得分:0)

可以在不诉诸强大参考的情况下实施授权。

注意:基本概念是您将委托调用转发给选择器调用

  1. 在要从
  2. 获取委派的NSView中创建一个选择器实例
  3. 在NSView中实现drawLayer(layer,ctx),你想让委托从图层和ctx vars中调用选择器变量
  4. 将view.selector设置为handleSelector方法,然后检索图层和ctx(这可以是代码中的任何位置,弱或强引用)
  5. 要查看如何实现选择器构造的示例:(永久链接)https://github.com/eonist/Element/wiki/Progress#selectors-in-swift

    注意:为什么我们这样做?因为每当你想使用Graphic类时,在方法外创建一个变量是非感性的

    注意:您还可以获得代表接收方不需要扩展NSView或NSObject的好处

答案 7 :(得分:-2)

您是否可以使用传入的图层参数来构造switch语句,以便您可以将所有内容放在此方法中(根据文档的建议):

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

只需2美分。