Подтвердить что ты не робот

Использование делегата CALayer

У меня есть UIView, у слоев которого будут подслои. Я хотел бы назначить делегаты для каждого из этих подслоев, поэтому метод делегата может рассказать о том, что рисовать. Мой вопрос:

Что я должен предоставить в качестве делегата CALayer? В документации говорится, что не использовать UIView, в котором находятся слои, поскольку это зарезервировано для основного CALayer представления. Но, создавая еще один класс, чтобы быть делегатом CALayers, я создаю поражения, чтобы не подклассифицировать CALayer. Что люди обычно используют в качестве делегата для CALayer? Или я должен просто подкласс?

Кроме того, почему класс, реализующий методы делегата, не должен соответствовать какому-либо протоколу CALayer? Это более широкий всеобъемлющий вопрос, который я не совсем понимаю. Я думал, что для всех классов, требующих реализации методов делегатов, требуется спецификация протокола для исполнителей, которые соответствуют.

4b9b3361

Ответ 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 цента.