Использование делегата CALayer
У меня есть UIView, у слоев которого будут подслои. Я хотел бы назначить делегаты для каждого из этих подслоев, поэтому метод делегата может рассказать о том, что рисовать. Мой вопрос:
Что я должен предоставить в качестве делегата CALayer? В документации говорится, что не использовать UIView, в котором находятся слои, поскольку это зарезервировано для основного CALayer представления. Но, создавая еще один класс, чтобы быть делегатом CALayers, я создаю поражения, чтобы не подклассифицировать CALayer. Что люди обычно используют в качестве делегата для CALayer? Или я должен просто подкласс?
Кроме того, почему класс, реализующий методы делегата, не должен соответствовать какому-либо протоколу CALayer? Это более широкий всеобъемлющий вопрос, который я не совсем понимаю. Я думал, что для всех классов, требующих реализации методов делегатов, требуется спецификация протокола для исполнителей, которые соответствуют.
Ответы
Ответ 1
Решение Lightest-Wight должно было бы создать небольшой вспомогательный класс в файле как UIView, который использует CALayer:
В MyView.h
@interface MyLayerDelegate : NSObject
. . .
@end
В MyView.m
@implementation MyLayerDelegate
- (void)drawLayer:(CALayer*)layer inContext:(CGContextRef)ctx
{
. . .
}
@end
Просто поместите те, которые находятся в верхней части файла, сразу же под директивами #import. Таким образом, это больше похоже на использование "частного класса" для обработки чертежа (хотя это не так - класс делегата может быть создан каким-либо кодом, который импортирует заголовок).
Ответ 2
Предпочитая сохранить методы делегирования слоя в моем подклассе 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;
Ответ 3
Взгляните на docs на формальные vs неофициальные протоколы. CALayer реализует неофициальный протокол, который означает, что вы можете установить любой объект в качестве его делегата, и он определит, может ли он отправлять сообщения этому делегату, проверив делегат для определенного селектора (т.е. -ответчикToSelector).
Обычно я использую свой контроллер представления как делегат для рассматриваемого уровня.
Ответ 4
Примечание относительно классов-помощников для использования в качестве делегата слоя (по крайней мере, с ARC):
Убедитесь, что вы храните "сильную" ссылку на ваш класс-ассистент/init'd-помощник (например, в свойстве). Просто назначение класса-ассистента/init'd для делегата, похоже, вызывает сбой для меня, по-видимому, потому что mylayer.delegate является слабой ссылкой на ваш вспомогательный класс (как и большинство делегатов), поэтому вспомогательный класс освобождается до уровня может использовать его.
Если я присвою класс-помощнику свойству, затем назначьте его делегату, мои странные сбои исчезнут, и все будет вести себя так, как ожидалось.
Ответ 5
Я лично проголосовал за решение Dave Lee выше как наиболее инкапсулирующее, особенно если у вас несколько слоев. Однако; когда я пробовал его на IOS 6 с ARC, я получил ошибки на этой строке и предположил, что мне нужен мостовой литой
// [_view performSelector: selector withObject: layer withObject: (id)context];
Поэтому я внесла поправки в метод drawLayer Дейва Ли из его делегирующего делегата класса для использования NSInvocation, как показано ниже. Все использование и вспомогательные функции идентичны тем, которые Дэйв Ли опубликовал в своем предыдущем превосходном предложении.
-(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];
}
Ответ 6
Я предпочитаю следующее решение. Я хотел бы использовать метод drawLayer:inContext:
UIView для рендеринга subview, который я мог бы добавить без добавления дополнительных классов по всему месту. Мое решение таково:
Добавьте в проект следующие файлы:
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:
. Насколько я знаю, накладные расходы этого решения минимальны.
Ответ 7
Возможно реализовать делегацию, не прибегая к сильному ref.
ПРИМЕЧАНИЕ. Основная идея заключается в том, что вы пересылаете вызов делегата на вызов селектора
- Создайте экземпляр селектора в NSView, который вы хотите получить из
- реализовать drawLayer (layer, ctx) в NSView, который вы хотите, чтобы делегирование вызывало переменную селектора со слоем и ctx vars
- установите view.selector в метод handleSelector, где вы затем извлекаете слой и ctx (это может быть где угодно в вашем коде, слабый или сильно привязанный).
Чтобы увидеть пример реализации селекторной конструкции: (Постоянная ссылка) https://github.com/eonist/Element/wiki/Progress#selectors-in-swift
ПРИМЕЧАНИЕ: почему мы это делаем? потому что создание переменных внешних методов, когда вы хотите использовать класс Graphic, является нечувствительным
ПРИМЕЧАНИЕ.. И вы также получаете выгоду, что получателю делегации не нужно расширять NSView или NSObject
Ответ 8
Можете ли вы использовать переданный в параметре layer для построения оператора switch, чтобы вы могли поместить все в этот метод (по рекомендации документов):
-(void) drawLayer: (CALayer*) layer inContext: (CGContextRef) context {
if layer = xLayer {...}
}
Только мои 2 цента.