35

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

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

另外,为什么实现委托方法的类不必符合某种 CALayer 协议?这是一个更广泛的总体问题,我不太明白。我认为所有需要实现委托方法的类都需要实现者遵守的协议规范。

4

8 回答 8

31

为了在我的 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;
于 2011-01-27T23:16:54.827 回答
28

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

在 MyView.h 中

@interface MyLayerDelegate : NSObject
. . .
@end

在 MyView.m 中

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

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

于 2010-03-22T03:34:16.760 回答
10

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

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

于 2010-01-06T22:04:40.103 回答
3

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

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

如果我将帮助程序类分配给一个属性,然后将其分配给委托,我奇怪的崩溃就会消失,并且事情会按预期运行。

于 2013-02-23T11:51:05.853 回答
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];

}
于 2012-11-25T01:25:48.837 回答
0

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

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

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:. 据我所知,此解决方案的开销很小。

于 2013-08-24T15:57:07.987 回答
0

可以在不求助于强引用的情况下实现委托。

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

  1. 在要从中获取委托的 NSView 中创建一个选择器实例
  2. 在 NSView 中实现 drawLayer(layer,ctx) 以获取委托,使用 layer 和 ctx vars 调用选择器变量
  3. 将 view.selector 设置为 handleSelector 方法,然后在其中检索层和 ctx(这可以在代码中的任何位置,弱引用或强引用)

要查看如何实现选择器构造的示例:(永久链接)https://github.com/eonist/Element/wiki/Progress#selectors-in-swift

注意:我们为什么要这样做?因为每当你想使用 Graphic 类时在方法之外创建一个变量是没有意义的

注意:您还可以获得委托的接收者不需要扩展 NSView 或 NSObject 的好处

于 2015-12-22T10:34:16.950 回答
-2

你可以使用传入的层参数来构造一个 switch 语句,这样你就可以把所有的东西都放在这个方法中(反对文档的建议):

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

只是我的2美分。

于 2011-07-29T15:33:37.353 回答