UIView
のサブクラスであるクラスがあります。 drawRect
メソッドを実装するか、CALayer
のデリゲートメソッドであるdrawLayer:inContext:
を実装することにより、ビュー内にデータを描画できます。
2つの質問があります。
drawLayer:inContext:
を実装すると、ビューを_(VARIABLE)として割り当てなくても、drawRect
が呼び出されます(少なくともブレークポイントを設定できる限り、CALayer
は呼び出されません) _次を使用して委任する:
[[self layer] setDelegate:self];
インスタンスがレイヤーのデリゲートとして定義されていない場合、デリゲートメソッドが呼び出されるのはなぜですか? drawLayer:inContext:
が呼び出された場合にdrawRect
が呼び出されるのを防ぐメカニズムは何ですか?
使用するアプローチを決定する方法は?それぞれにユースケースはありますか?
常に_drawRect:
_を使用し、UIView
の描画デリゲートとしてCALayer
を使用しないでください。
インスタンスがレイヤーのデリゲートとして定義されていない場合、なぜデリゲートメソッドが呼び出されますか? _
drawLayer:inContext:
_が呼び出された場合にdrawRectが呼び出されるのを防ぐメカニズムは何ですか?
すべてのUIView
インスタンスは、そのバッキングCALayer
の描画デリゲートです。 _[[self layer] setDelegate:self];
_が何もしないように見えたのはそのためです。冗長です。 _drawRect:
_メソッドは、事実上、ビューのレイヤーの描画デリゲートメソッドです。内部的に、UIView
は_drawLayer:inContext:
_を実装し、そこで独自の処理を行ってから_drawRect:
_を呼び出します。デバッガで確認できます:
_drawRect:
_を実装したときに_drawLayer:inContext:
_が呼び出されなかったのはこのためです。また、カスタムCALayer
サブクラスにUIView
描画デリゲートメソッドを実装しないでください。また、ビューを別のレイヤーの描画デリゲートにしないでください。それはあらゆる種類の奇抜を引き起こします。
CGContextRef
にアクセスする必要があるために_drawLayer:inContext:
_を実装している場合は、UIGraphicsGetCurrentContext()
を呼び出すことで_drawRect:
_の内部から取得できます。
drawRect
は、どうしても必要な場合にのみ実装してください。 drawRect
のデフォルトの実装には、ビューのレンダリングをインテリジェントにキャッシュするなど、いくつかのスマートな最適化が含まれています。オーバーライドすると、これらの最適化がすべて回避されます。良くないね。レイヤー描画メソッドを効果的に使用すると、ほとんどの場合、カスタムdrawRect
よりも優れたパフォーマンスを発揮します。 AppleはUIView
のデリゲートとしてCALayer
を頻繁に使用します-実際、すべての IViewはそのレイヤーのデリゲートです 。 UIView内のレイヤー描画をカスタマイズする方法は、ZoomingPDFViewerを含むいくつかのAppleサンプル(現時点では)で確認できます。
drawRect
の使用は一般的ですが、少なくとも2002/2003年、IIRCから推奨されていません。その道を進む理由は多くありません。
iPhone OSの高度なパフォーマンス最適化 (スライド15)
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
{
...
}
カスタム描画コードに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値を取得するには、backgroundColor
のlayer
にアクセスする必要がありますparameterdrawLayer(_:inContext:)
。また、context
parameterにも描画する必要があります。
ビューの_self.layer
_とdrawLayer(_:inContext:)
に渡されるlayer
パラメーターが常に同じレイヤーではないことを知ることは非常に重要です!後者は前者のコピーで、一部のアニメーションが既にプロパティに適用されている場合があります。この方法で、飛行中のアニメーションの正しいプロパティ値にアクセスできます。
これで、図面は期待どおりに機能します。
_override func drawLayer(layer: CALayer, inContext context: CGContext) {
let colorForCustomDrawing = layer.backgroundColor
// your custom drawing code
}
_
しかし、2つの新しい問題があります:setNeedsDisplay()
と、backgroundColor
やopaque
などのいくつかのプロパティは、ビューに対して機能しなくなりました。 UIView
は、呼び出しと変更を独自のレイヤーに転送しなくなりました。
setNeedsDisplay()
は、ビューがdrawRect(_:)
を実装している場合にのみ何かを行います。関数が実際に何かを行うかどうかは関係ありませんが、UIKitはそれを使用して、カスタム描画を行うかどうかを決定します。
UIView
のdrawLayer(_: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.
}
_
IOSでは、ビューとそのレイヤーのオーバーラップは非常に大きくなります。デフォルトでは、ビューはそのレイヤーのデリゲートであり、レイヤーのdrawLayer:inContext:
メソッドを実装します。私が理解しているように、drawRect:
とdrawLayer:inContext:
はこの場合、ほぼ同等です。おそらく、drawLayer:inContext:
のデフォルト実装はdrawRect:
を呼び出すか、drawRect:
がサブクラスによって実装されていない場合にのみdrawLayer:inContext:
が呼び出されます。
使用するアプローチを決定する方法は?それぞれにユースケースはありますか?
本当に問題ではありません。慣習に従うために、私は通常drawRect:
を使用し、ビューの一部ではないカスタムサブレイヤーを実際に描画する必要がある場合はdrawLayer:inContext:
の使用を予約します。
Apple Documentation にはこれがあります:「下層のコンテンツを直接設定するなど、ビューのコンテンツを提供する他の方法もありますが、drawRect:メソッドをオーバーライドするのが最も一般的な手法です。 」
しかし、それは詳細には入らないので、それは手がかりになるはずです。あなたが本当に手を汚したくない限り、それをしないでください。
UIViewのレイヤーのデリゲートは、UIViewを指しています。ただし、UIViewの動作は、drawRect:が実装されているかどうかによって異なります。たとえば、レイヤーのプロパティ(背景色やコーナー半径など)を直接設定した場合、drawRect:メソッドがあれば、これらの値は上書きされます-完全に空の場合(つまり、superを呼び出さない場合でも).
カスタムコンテンツを含むレイヤー付きビューの場合は、引き続きビューのメソッドをオーバーライドして描画を行う必要があります。レイヤーに裏付けされたビューは自動的にレイヤーのデリゲートになり、必要なデリゲートメソッドを実装します。その構成を変更しないでください。代わりに、ビューのdrawRect:メソッドを実装してコンテンツを描画する必要があります。 Core Animation Programming Guide