35

レイヤーにサブレイヤーがあるUIViewがあります。これらのサブレイヤーごとにデリゲートを割り当てたいので、デリゲートメソッドはレイヤーに何を描画するかを指示できます。私の質問は:

CALayerの代理人として何を提供する必要がありますか?ドキュメントには、レイヤーが存在するUIViewを使用しないように記載されています。これは、ビューのメインCALayer用に予約されているためです。しかし、私が作成したCALayersのデリゲートになるためだけに別のクラスを作成すると、CALayerをサブクラス化しないという目的が無効になります。CALayerのデリゲートとして通常使用しているのは何ですか?それともサブクラスにする必要がありますか?

また、デリゲートメソッドを実装するクラスが何らかのCALayerプロトコルに準拠する必要がないのはなぜですか?それは私がよく理解していないより広い包括的な質問です。デリゲートメソッドの実装を必要とするすべてのクラスには、実装者が準拠するためのプロトコル仕様が必要だと思いました。

4

8 に答える 8

31

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;
于 2011-01-27T23:16:54.827 に答える
28

最も軽量な解決策は、CALayerを使用するUIViewとしてファイルに小さなヘルパークラスを作成することです。

MyView.hで

@interface MyLayerDelegate : NSObject
. . .
@end

MyView.mで

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

これらをファイルの先頭、#importディレクティブのすぐ下に配置するだけです。そうすれば、「プライベートクラス」を使用して描画を処理するように感じられます(ただし、そうではありません。デリゲートクラスは、ヘッダーをインポートする任意のコードでインスタンス化できます)。

于 2010-03-22T03:34:16.760 に答える
10

公式プロトコルと非公式プロトコルに関するドキュメントをご覧ください。CALayerは非公式のプロトコルを実装しています。つまり、任意のオブジェクトをそのデリゲートに設定でき、特定のセレクター(つまり、-respondsToSelector)のデリゲートをチェックすることにより、そのデリゲートにメッセージを送信できるかどうかを判断します。

私は通常、問題のレイヤーのデリゲートとしてビューコントローラーを使用します。

于 2010-01-06T22:04:40.103 に答える
3

レイヤーのデリゲートとして使用するための「ヘルパー」クラスに関する注意(少なくともARCを使用):

alloc / init'dヘルパークラスへの「強力な」参照(プロパティなど)を必ず保存してください。alloc / init'dヘルパークラスをデリゲートに割り当てるだけでクラッシュが発生するようです。おそらくmylayer.delegateがヘルパークラスへの弱い参照であるため(ほとんどのデリゲートがそうであるように)、ヘルパークラスはレイヤーの前に解放されます。使用できます。

ヘルパークラスをプロパティに割り当ててからデリゲートに割り当てると、奇妙なクラッシュがなくなり、期待どおりに動作します。

于 2013-02-23T11:51:05.853 に答える
2

私は個人的に、特に複数のレイヤーがある場合に、最もカプセル化されているものとして、上記のDaveLeeのソリューションに投票しました。でも; ARCを搭載したIOS6で試してみると、この行でエラーが発生し、ブリッジキャストが必要であることが示唆されました

// [_view performSelector: selector withObject: layer withObject: (id)context];

したがって、私はDave LeeのdrawLayerメソッドを、彼の再委任されたデリゲートクラスから、以下のようにNSInvocationを使用するように修正しました。すべての使用法と補助機能は、DaveLeeが以前の優れた提案に投稿したものと同じです。

-(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];

}
于 2012-11-25T01:25:48.837 に答える
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:。私の知る限り、このソリューションのオーバーヘッドは最小限です。

于 2013-08-24T15:57:07.987 に答える
0

強力な参照に頼ることなく、委任を実装することは可能です。

注:基本的な概念は、デリゲート呼び出しをセレクター呼び出しに転送することです。

  1. 委任を取得するNSViewにセレクターインスタンスを作成します
  2. 委任を取得するNSViewにdrawLayer(layer、ctx)を実装し、layerとctx変数を使用してセレクター変数を呼び出します。
  3. view.selectorをhandleSelectorメソッドに設定し、そこでレイヤーとctxを取得します(これはコード内のどこにでもあり、弱いまたは強く参照されます)

セレクター構造を実装する方法の例を確認するには:(パーマリンク)https://github.com/eonist/Element/wiki/Progress#selectors-in-swift

注:なぜこれを行うのですか?グラフィッククラスを使用するときはいつでもメソッドの外部で変数を作成することは無意味だからです

注:また、委任の受信者がNSViewまたはNSObjectを拡張する必要がないという利点もあります。

于 2015-12-22T10:34:16.950 に答える
-2

渡されたlayerパラメーターを使用してswitchステートメントを作成し、すべてをこのメソッドに入れることができますか(ドキュメントのアドバイスに反して):

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

ちょうど私の2セント。

于 2011-07-29T15:33:37.353 に答える