Использование CALayer Delegate - PullRequest
34 голосов
/ 06 января 2010

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

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

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

Ответы [ 8 ]

30 голосов
/ 28 января 2011

Предпочитая хранить методы делегата слоя в моем подклассе 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;
27 голосов
/ 22 марта 2010

Самым легким решением было бы создание небольшого вспомогательного класса в файле как UIView, использующего CALayer:

В MyView.h

@interface MyLayerDelegate : NSObject
. . .
@end

In MyView.m

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

Просто поместите их в верхнюю часть вашего файла, сразу под директивами #import. Таким образом, больше похоже на использование «частного класса» для обработки чертежа (хотя это не так - класс делегата может быть создан любым кодом, который импортирует заголовок).

9 голосов
/ 07 января 2010

Взгляните на документы по формальным или неформальным протоколам .CALayer реализует неформальный протокол, который означает, что вы можете установить любой объект в качестве его делегата, и он определит, может ли он отправлять сообщения этому делегату, проверив делегат для определенного селектора (то есть -respondsToSelector).* Обычно я использую свой контроллер представления в качестве делегата для рассматриваемого слоя.

3 голосов
/ 23 февраля 2013

Примечание относительно "вспомогательных" классов для использования в качестве делегата слоя (по крайней мере, с ARC):

Убедитесь, что вы храните "сильную" ссылку на свой вспомогательный класс alloc / init'd (например, в свойстве). Похоже, простое назначение вспомогательного класса alloc / init'd для делегата вызывает у меня сбои, предположительно потому, что mylayer.delegate является слабой ссылкой на ваш вспомогательный класс (как и большинство делегатов), поэтому вспомогательный класс освобождается перед уровнем можно использовать.

Если я назначу вспомогательный класс свойству, а затем назначу его делегату, мои странные сбои исчезнут, и все будет работать так, как ожидалось.

2 голосов
/ 25 ноября 2012

Я лично голосовал за решение Дэйва Ли, описанное выше, как наиболее инкапсулирующее, особенно если у вас несколько слоев. Тем не мение; когда я попробовал это на 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];

}
0 голосов
/ 22 декабря 2015

Реализовать делегирование можно, не прибегая к сильным ссылкам.

ПРИМЕЧАНИЕ: Основная концепция заключается в том, что вы перенаправляете вызов делегата на вызов селектора

  1. Создайте экземпляр селектора в NSView, от которого вы хотите получить делегирование
  2. реализуйте drawLayer (layer, ctx) в NSView, для которого вы хотите получить делегирование от вызова селекторной переменной с layer и ctx vars
  3. установить view.selector для метода handleSelector, где вы затем извлекаете слой и ctx (это может быть где угодно в вашем коде, слабая или с сильной ссылкой)

Чтобы увидеть пример реализации конструкции селектора: (Постоянная ссылка) https://github.com/eonist/Element/wiki/Progress#selectors-in-swift

ПРИМЕЧАНИЕ: почему мы это делаем? потому что создание переменной вне методов всякий раз, когда вы хотите использовать класс Graphic, бессмысленно

ПРИМЕЧАНИЕ: И вы также получаете преимущество, заключающееся в том, что получателю делегации не нужно расширять NSView или NSObject

0 голосов
/ 24 августа 2013

Я предпочитаю следующее решение. Я хотел бы использовать 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:. Насколько я знаю, накладные расходы на это решение минимальны.

0 голосов
/ 29 июля 2011

Можете ли вы использовать переданный параметр layer для создания оператора switch, чтобы вы могли поместить все в этот метод (вопреки советам документов):

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

Только мои 2 цента.

...