使用CALayer委托
我有一个UIView的图层将有子层。 我想为每个子层分配委托,所以委托方法可以告诉图层要绘制的内容。 我的问题是:
CALayer的代表应该提供什么? 该文件说,不要使用图层所在的UIView,因为这是为视图的主要CALayer保留。 但是,创build另一个类只是作为我创build的CALayers的代表,这就违背了不inheritanceCALayer的目的。 人们通常使用什么作为CALayer的代表? 或者我应该只是子类?
另外,为什么实现委托方法的类不需要遵循某种CALayer协议? 这是一个更为广泛的问题,我不太明白。 我认为所有需要实现委托方法的类都需要一个协议规范,以便实现者遵守。
最轻的解决scheme是在文件中创build一个小的帮助类,作为使用CALayer的UIView:
在MyView.h中
@interface MyLayerDelegate : NSObject . . . @end
在MyView.m中
@implementation MyLayerDelegate - (void)drawLayer:(CALayer*)layer inContext:(CGContextRef)ctx { . . . } @end
只要将它们放在文件的顶部,紧靠#import指令的下面。 这样的感觉更像是使用“私人类”来处理绘图(尽pipe它不是 – 委托类可以通过导入标题的任何代码实例化)。
首选保留我的UIView子类中的图层委托方法,我使用一个基本的重新委托的委托类。 这个类可以不用定制就可以重复使用,避免了为CALayer创build子类或创build一个独立的委托类,仅仅用于图层绘制。
@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;
看看关于正式和非正式协议的文档 。 CALayer正在实现一个非正式的协议,这意味着你可以设置任何对象作为它的委托,它将通过检查特定select器的委托(即-respondsToSelector)来确定它是否可以向该委托发送消息。
我通常使用我的视图控制器作为所讨论的图层的代表。
关于“helper”类用作图层委托(至less使用ARC)的说明:
确保你存储一个“强”的引用到你的alloc / init的辅助类(比如在一个属性中)。 简单地将alloc / init'd帮助程序类分配给委托似乎会导致崩溃,大概是因为mylayer.delegate是对您的帮助程序类的弱引用(因为大多数委托人),所以帮助程序类在层之前被释放可以使用它。
如果我将helper类分配给一个属性,然后将其分配给委托,我奇怪的崩溃就消失了,事情就像预期的那样。
我个人投票Dave Lee的解决scheme是最封装的,尤其是在你有多个图层的地方。 然而; 当我用ARC在IOS 6上尝试它时,我在这一行上得到了错误,并提示我需要一个桥接模型
// [_view performSelector: selector withObject: layer withObject: (id)context];
因此,我修改了戴维·李的drawLayer方法,从他重新委托的委托类来使用NSInvocation,如下所示。 所有的使用和辅助function都与李先生在他以前的优秀build议中发表的相同。
-(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]; }
我更喜欢以下解决scheme。 我想使用UIView的drawLayer:inContext:
方法来呈现一个子视图,我可以添加,而无需在整个地方添加额外的类。 我的解决scheme如下:
将以下文件添加到您的项目中:
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:
没有错误的分配。 据我所知这个解决scheme的开销是最小的。
可以在不借助强大的参考的情况下实施代表团。
注:基本概念是您将委托调用转发给select器调用
- 在您希望从中获取委托的NSView中创build一个select器实例
- 在NSView中实现drawLayer(layer,ctx)你想获得委托,调用带有图层和ctxvariables的select器variables
- 将view.selector设置为handleSelector方法,然后在该方法中检索图层和ctx(这可以在代码中的任何位置,弱或强引用)
要查看如何实现select器构造的示例:(Permalink) https://github.com/eonist/Element/wiki/Progress#selectors-in-swift
注意:我们为什么要这样做? 因为只要你想使用Graphic类,在方法之外创build一个variables是不合理的
注意:你也得到了代表的接收者不需要扩展NSView或NSObject的好处
你可以使用传入的图层参数来构造一个switch语句,这样你就可以把所有的东西放在这个方法里面(根据文档的build议):
-(void) drawLayer: (CALayer*) layer inContext: (CGContextRef) context { if layer = xLayer {...} }
只是我2美分。