我有一个UIView,其图层将有子图层。我想为每个子图层分配代理,因此委托方法可以告诉图层要绘制什么。我的问题是:
我应该提供什么作为CALayer的代表?文档说不要使用层所在的UIView,因为这是为视图的主CALayer保留的。但是,创建另一个类只是为了成为我创建的CALayers的代表,这就失去了不继承CALayer的目的。人们通常使用什么作为CALayer的代表?或者我应该只是子类?
另外,为什么实现委托方法的类不必遵循某种CALayer协议?这是一个我不太了解的更广泛的首要问题。我认为所有需要实现委托方法的类都需要协议规范,以便实现者遵守。
答案 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)
注意:基本概念是您将委托调用转发给选择器调用
要查看如何实现选择器构造的示例:(永久链接)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美分。