web-dev-qa-db-ja.com

iOS:UIViewの「drawRect:」とそのレイヤーのデリゲート「drawLayer:inContext:」の使用

UIViewのサブクラスであるクラスがあります。 drawRectメソッドを実装するか、CALayerのデリゲートメソッドであるdrawLayer:inContext:を実装することにより、ビュー内にデータを描画できます。

2つの質問があります。

  1. 使用するアプローチを決定する方法は?それぞれにユースケースはありますか?
  2. drawLayer:inContext:を実装すると、ビューを_(VARIABLE)として割り当てなくても、drawRectが呼び出されます(少なくともブレークポイントを設定できる限り、CALayerは呼び出されません) _次を使用して委任する:

    [[self layer] setDelegate:self];

    インスタンスがレイヤーのデリゲートとして定義されていない場合、デリゲートメソッドが呼び出されるのはなぜですか? drawLayer:inContext:が呼び出された場合にdrawRectが呼び出されるのを防ぐメカニズムは何ですか?

71
Itamar Katz

使用するアプローチを決定する方法は?それぞれにユースケースはありますか?

常に_drawRect:_を使用し、UIViewの描画デリゲートとしてCALayerを使用しないでください。

インスタンスがレイヤーのデリゲートとして定義されていない場合、なぜデリゲートメソッドが呼び出されますか? _drawLayer:inContext:_が呼び出された場合にdrawRectが呼び出されるのを防ぐメカニズムは何ですか?

すべてのUIViewインスタンスは、そのバッキングCALayerの描画デリゲートです。 _[[self layer] setDelegate:self];_が何もしないように見えたのはそのためです。冗長です。 _drawRect:_メソッドは、事実上、ビューのレイヤーの描画デリゲートメソッドです。内部的に、UIViewは_drawLayer:inContext:_を実装し、そこで独自の処理を行ってから_drawRect:_を呼び出します。デバッガで確認できます:

drawRect: stacktrace

_drawRect:_を実装したときに_drawLayer:inContext:_が呼び出されなかったのはこのためです。また、カスタムCALayerサブクラスにUIView描画デリゲートメソッドを実装しないでください。また、ビューを別のレイヤーの描画デリゲートにしないでください。それはあらゆる種類の奇抜を引き起こします。

CGContextRefにアクセスする必要があるために_drawLayer:inContext:_を実装している場合は、UIGraphicsGetCurrentContext()を呼び出すことで_drawRect:_の内部から取得できます。

72
Nathan Eror

drawRectは、どうしても必要な場合にのみ実装してください。 drawRectのデフォルトの実装には、ビューのレンダリングをインテリジェントにキャッシュするなど、いくつかのスマートな最適化が含まれています。オーバーライドすると、これらの最適化がすべて回避されます。良くないね。レイヤー描画メソッドを効果的に使用すると、ほとんどの場合、カスタムdrawRectよりも優れたパフォーマンスを発揮します。 AppleはUIViewのデリゲートとしてCALayerを頻繁に使用します-実際、すべての IViewはそのレイヤーのデリゲートです 。 UIView内のレイヤー描画をカスタマイズする方法は、ZoomingPDFViewerを含むいくつかのAppleサンプル(現時点では)で確認できます。

drawRectの使用は一般的ですが、少なくとも2002/2003年、IIRCから推奨されていません。その道を進む理由は多くありません。

iPhone OSの高度なパフォーマンス最適化 (スライド15)

Core Animation Essentials

IKitレンダリングの理解

テクニカルQ&A QA1708:iOSでの画像描画パフォーマンスの改善

ビュープログラミングガイド:ビュー描画の最適化

44
quellish

AppleのサンプルZoomingPDFViewerのコードは次のとおりです。

-(void)drawRect:(CGRect)r
{

    // UIView uses the existence of -drawRect: to determine if it should allow its CALayer
    // to be invalidated, which would then lead to the layer creating a backing store and
    // -drawLayer:inContext: being called.
    // By implementing an empty -drawRect: method, we allow UIKit to continue to implement
    // this logic, while doing our real drawing work inside of -drawLayer:inContext:

}

-(void)drawLayer:(CALayer*)layer inContext:(CGContextRef)context
{
    ...
}
12
Dennis Fan

カスタム描画コードにdrawLayer(_:inContext:)またはdrawRect(_:)(または両方)を使用するかどうかは、アニメーション化中にレイヤープロパティの現在の値にアクセスする必要があるかどうかによって異なります。

自分のラベルクラス を実装するときに、これら2つの関数に関連するさまざまなレンダリングの問題に苦労していました。ドキュメントを確認し、試行錯誤を繰り返し、UIKitを逆コンパイルし、Appleの カスタムアニメーション化可能プロパティの例 を調べたところ、どのように機能しているかがよくわかりました。

drawRect(_:)

アニメーション中にレイヤー/ビュープロパティの現在の値にアクセスする必要がない場合は、drawRect(_:)を使用してカスタム描画を実行できます。すべてがうまく機能します。

_override func drawRect(rect: CGRect) {
    // your custom drawing code
}
_

drawLayer(_:inContext:)

たとえば、カスタム描画コードでbackgroundColorを使用するとします。

_override func drawRect(rect: CGRect) {
    let colorForCustomDrawing = self.layer.backgroundColor
    // your custom drawing code
}
_

コードをテストすると、アニメーションの実行中にbackgroundColorが正しい(つまり現在の)値を返さないことに気付くでしょう。代わりに、最終値(つまり、アニメーションが完了したときの値)を返します。

アニメーション中にcurrent値を取得するには、backgroundColorlayerにアクセスする必要がありますparameterdrawLayer(_:inContext:)。また、contextparameterにも描画する必要があります。

ビューの_self.layer_とdrawLayer(_:inContext:)に渡されるlayerパラメーターが常に同じレイヤーではないことを知ることは非常に重要です!後者は前者のコピーで、一部のアニメーションが既にプロパティに適用されている場合があります。この方法で、飛行中のアニメーションの正しいプロパティ値にアクセスできます。

これで、図面は期待どおりに機能します。

_override func drawLayer(layer: CALayer, inContext context: CGContext) {
    let colorForCustomDrawing = layer.backgroundColor
    // your custom drawing code
}
_

しかし、2つの新しい問題があります:setNeedsDisplay()と、backgroundColoropaqueなどのいくつかのプロパティは、ビューに対して機能しなくなりました。 UIViewは、呼び出しと変更を独自のレイヤーに転送しなくなりました。

setNeedsDisplay()は、ビューがdrawRect(_:)を実装している場合にのみ何かを行います。関数が実際に何かを行うかどうかは関係ありませんが、UIKitはそれを使用して、カスタム描画を行うかどうかを決定します。

UIViewdrawLayer(_:inContext:)の独自の実装が呼び出されなくなったため、プロパティはおそらく機能しなくなります。

したがって、解決策は非常に簡単です。 drawLayer(_:inContext:)のスーパークラスの実装を呼び出し、空のdrawRect(_:)を実装するだけです。

_override func drawLayer(layer: CALayer, inContext context: CGContext) {
    super.drawLayer(layer, inContext: context)

    let colorForCustomDrawing = layer.backgroundColor
    // your custom drawing code
}


override func drawRect(rect: CGRect) {
    // Although we use drawLayer(_:inContext:) we still need to implement this method.
    // UIKit checks for its presence when it decides whether a call to setNeedsDisplay() is forwarded to its layer.
}
_

概要

アニメーション中にプロパティが間違った値を返すという問題がない限り、drawRect(_:)を使用します。

_override func drawRect(rect: CGRect) {
    // your custom drawing code
}
_

ビュー/レイヤーのプロパティをアニメーション化しながら現在の値にアクセスする必要がある場合は、drawLayer(_:inContext:)anddrawRect(_:)を使用します。

_override func drawLayer(layer: CALayer, inContext context: CGContext) {
    super.drawLayer(layer, inContext: context)

    let colorForCustomDrawing = layer.backgroundColor
    // your custom drawing code
}


override func drawRect(rect: CGRect) {
    // Although we use drawLayer(_:inContext:) we still need to implement this method.
    // UIKit checks for its presence when it decides whether a call to setNeedsDisplay() is forwarded to its layer.
}
_
9
fluidsonic

IOSでは、ビューとそのレイヤーのオーバーラップは非常に大きくなります。デフォルトでは、ビューはそのレイヤーのデリゲートであり、レイヤーのdrawLayer:inContext:メソッドを実装します。私が理解しているように、drawRect:drawLayer:inContext:はこの場合、ほぼ同等です。おそらく、drawLayer:inContext:のデフォルト実装はdrawRect:を呼び出すか、drawRect:がサブクラスによって実装されていない場合にのみdrawLayer:inContext:が呼び出されます。

使用するアプローチを決定する方法は?それぞれにユースケースはありますか?

本当に問題ではありません。慣習に従うために、私は通常drawRect:を使用し、ビューの一部ではないカスタムサブレイヤーを実際に描画する必要がある場合はdrawLayer:inContext:の使用を予約します。

7
Ole Begemann

Apple Documentation にはこれがあります:「下層のコンテンツを直接設定するなど、ビューのコンテンツを提供する他の方法もありますが、drawRect:メソッドをオーバーライドするのが最も一般的な手法です。 」

しかし、それは詳細には入らないので、それは手がかりになるはずです。あなたが本当に手を汚したくない限り、それをしないでください。

UIViewのレイヤーのデリゲートは、UIViewを指しています。ただし、UIViewの動作は、drawRect:が実装されているかどうかによって異なります。たとえば、レイヤーのプロパティ(背景色やコーナー半径など)を直接設定した場合、drawRect:メソッドがあれば、これらの値は上書きされます-完全に空の場合(つまり、superを呼び出さない場合でも).

2
jamie

カスタムコンテンツを含むレイヤー付きビューの場合は、引き続きビューのメソッドをオーバーライドして描画を行う必要があります。レイヤーに裏付けされたビューは自動的にレイヤーのデリゲートになり、必要なデリゲートメソッドを実装します。その構成を変更しないでください。代わりに、ビューのdrawRect:メソッドを実装してコンテンツを描画する必要があります。 Core Animation Programming Guide

0
Singer Quincy