Ich habe eine Klasse, die eine Unterklasse von ist UIView
. Ich bin in der Lage, Dinge in die Ansicht zu zeichnen, indem ich entweder die drawRect
Methode implementiere oder drawLayer:inContext:
eine Delegate-Methode von implementiere CALayer
.
Ich habe zwei Fragen:
- Wie entscheide ich mich für einen Ansatz? Gibt es für jeden einen Anwendungsfall?
Wenn ich implementiere
drawLayer:inContext:
, wird es aufgerufen (unddrawRect
ist es nicht, zumindest soweit das Setzen eines Haltepunkts dies erkennen lässt), auch wenn ich meine Ansicht nicht alsCALayer
Delegat zuweise, indem ich Folgendes verwende:[[self layer] setDelegate:self];
Wie kommt es, dass die Delegatmethode aufgerufen wird, wenn meine Instanz nicht als Delegat der Ebene definiert ist? und welcher Mechanismus verhindert, dass
drawRect
er aufgerufen wird, wenn er aufgerufendrawLayer:inContext:
wird?
quelle
AccelerometerGraph
. Ich vermute, dass sie in diesem Codebeispiel implementiert wurden,drawLayer:inContext:
da der Ebenenhierarchie neben der Stammschicht der Ansicht einige Ebenen hinzugefügt wurden und der Delegat tatsächlich nicht dieUIView
Instanz ist.drawRect
sollte nur implementiert werden, wenn dies unbedingt erforderlich ist. Die Standardimplementierung vondrawRect
enthält eine Reihe intelligenter Optimierungen, z. B. das intelligente Zwischenspeichern des Renderings der Ansicht. Durch das Überschreiben werden all diese Optimierungen umgangen. Das ist schlecht. Durch die effektive Verwendung der Ebenenzeichnungsmethoden wird eine benutzerdefinierte Methode fast immer übertroffendrawRect
. Apple verwendet a häufigUIView
als DelegatCALayer
- tatsächlich ist jedes UIView der Delegat seiner Ebene . Sie können sehen, wie Sie die Ebenenzeichnung in einer UIView in mehreren Apple-Beispielen anpassen, einschließlich (zu diesem Zeitpunkt) ZoomingPDFViewer.Während die Verwendung von
drawRect
üblich ist, ist es eine Praxis, von der seit mindestens 2002/2003, IIRC, abgeraten wird. Es gibt nicht mehr viele gute Gründe, diesen Weg zu gehen.Erweiterte Leistungsoptimierung unter iPhone OS (Folie 15)
Grundlegende Grundlagen der Animation
Grundlegendes zum UIKit-Rendering
Technische Fragen und Antworten QA1708: Verbessern der Leistung beim Zeichnen von Bildern unter iOS
View Programming Guide: Optimieren der Ansichtszeichnung
quelle
drawRect
, der vermieden werden sollte, darin besteht, dass es jedes Mal aufgerufen wird, wenn die Ansicht aktualisiert wird, auch wenn sich nichts wirklich geändert hat. Wenn dies nicht derdrawRect
Fall ist, verwendet Core Animation den vorhandenen Hintergrundspeicher der Ebene, der vermutlich früher gezeichnet wurde. Aber ich bin verzweifelt verwirrt und brauche ein offizielles Apple-Sequenzdiagramm ... irgendwelche Hinweise?Hier sind die Codes von Sample ZoomingPDFViewer von Apple:
-(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 { ... }
quelle
Ob Sie
drawLayer(_:inContext:)
oderdrawRect(_:)
(oder beide) für benutzerdefinierten Zeichnungscode verwenden, hängt davon ab, ob Sie während der Animation Zugriff auf den aktuellen Wert einer Ebeneneigenschaft benötigen.Ich hatte heute Probleme mit verschiedenen Rendering-Problemen im Zusammenhang mit diesen beiden Funktionen, als ich meine eigene Label-Klasse implementierte . Nachdem ich die Dokumentation durchgesehen, einige Versuche und Irrtümer durchgeführt, UIKit dekompiliert und das Beispiel für benutzerdefinierte animierbare Eigenschaften von Apple überprüft hatte, bekam ich ein gutes Gefühl dafür, wie es funktioniert.
drawRect(_:)
Wenn Sie während der Animation nicht auf den aktuellen Wert einer Ebenen- / Ansichtseigenschaft zugreifen müssen, können Sie einfach
drawRect(_:)
Ihre benutzerdefinierte Zeichnung ausführen. Alles wird gut funktionieren.override func drawRect(rect: CGRect) { // your custom drawing code }
drawLayer(_:inContext:)
Angenommen, Sie möchten
backgroundColor
in Ihrem benutzerdefinierten Zeichnungscode Folgendes verwenden:override func drawRect(rect: CGRect) { let colorForCustomDrawing = self.layer.backgroundColor // your custom drawing code }
Wenn Sie Ihren Code testen, werden Sie feststellen, dass
backgroundColor
während des Flugs einer Animation nicht der richtige (dh der aktuelle) Wert zurückgegeben wird. Stattdessen wird der endgültige Wert zurückgegeben (dh der Wert für den Abschluss der Animation).Um den aktuellen Wert während der Animation zu erhalten, müssen Sie auf
backgroundColor
denlayer
Parameter zugreifen, an den übergeben wurdedrawLayer(_:inContext:)
. Und Sie müssen auch auf dencontext
Parameter zeichnen .Es ist sehr wichtig zu wissen, dass die Ansicht
self.layer
und der übergebenelayer
ParameterdrawLayer(_:inContext:)
nicht immer dieselbe Ebene sind! Letzteres könnte eine Kopie des ersteren sein, wobei teilweise Animationen bereits auf seine Eigenschaften angewendet wurden. Auf diese Weise können Sie auf korrekte Eigenschaftswerte von Animationen während des Flugs zugreifen.Jetzt funktioniert die Zeichnung wie erwartet:
override func drawLayer(layer: CALayer, inContext context: CGContext) { let colorForCustomDrawing = layer.backgroundColor // your custom drawing code }
Es gibt jedoch zwei neue Probleme:
setNeedsDisplay()
und einige Eigenschaften wiebackgroundColor
undopaque
funktionieren für Ihre Ansicht nicht mehr.UIView
leitet Anrufe und Änderungen nicht mehr an die eigene Ebene weiter.setNeedsDisplay()
tut nur etwas, wenn Ihre Ansicht implementiert wirddrawRect(_:)
. Es spielt keine Rolle, ob die Funktion tatsächlich etwas tut, aber UIKit verwendet sie, um zu bestimmen, ob Sie benutzerdefinierte Zeichnungen erstellen oder nicht.Die Eigenschaften funktionieren wahrscheinlich nicht mehr, da
UIView
die eigene Implementierung vondrawLayer(_:inContext:)
nicht mehr aufgerufen wird.Die Lösung ist also ganz einfach. Rufen Sie einfach die Implementierung der Superklasse auf
drawLayer(_:inContext:)
und implementieren Sie eine leeredrawRect(_:)
: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. }
Zusammenfassung
Verwenden
drawRect(_:)
Sie diese Option , solange Sie nicht das Problem haben, dass Eigenschaften während einer Animation falsche Werte zurückgeben:override func drawRect(rect: CGRect) { // your custom drawing code }
Verwenden Sie
drawLayer(_:inContext:)
und,drawRect(_:)
wenn Sie auf den aktuellen Wert der Ansichts- / Ebeneneigenschaften zugreifen müssen, während diese animiert werden: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. }
quelle
Unter iOS ist die Überlappung zwischen einer Ansicht und ihrer Ebene sehr groß. Standardmäßig ist die Ansicht der Delegat ihrer Ebene und implementiert die
drawLayer:inContext:
Methode der Ebene . So wie ich es verstehedrawRect:
unddrawLayer:inContext:
in diesem Fall mehr oder weniger gleichwertig bin . Möglicherweise wird die Standardimplementierung vondrawLayer:inContext:
AufrufendrawRect:
oderdrawRect:
nur aufgerufen, wenn siedrawLayer:inContext:
nicht von Ihrer Unterklasse implementiert wird.Es ist nicht wirklich wichtig. Um der Konvention zu folgen, würde ich normalerweise die Verwendung verwenden
drawRect:
und reservieren,drawLayer:inContext:
wenn ich tatsächlich benutzerdefinierte Unterschichten zeichnen muss, die nicht Teil einer Ansicht sind.quelle
In der Apple-Dokumentation heißt es: "Es gibt auch andere Möglichkeiten, den Inhalt einer Ansicht bereitzustellen, z. B. das direkte Festlegen des Inhalts der zugrunde liegenden Ebene, aber das Überschreiben der drawRect: -Methode ist die häufigste Technik."
Aber es geht nicht auf Details ein, also sollte das ein Hinweis sein: Tu es nicht, es sei denn, du willst dir wirklich die Hände schmutzig machen.
Der Delegat der UIView-Ebene zeigt auf die UIView. Das UIView verhält sich jedoch unterschiedlich, je nachdem, ob drawRect: implementiert ist oder nicht. Wenn Sie beispielsweise die Eigenschaften auf der Ebene direkt festlegen (z. B. die Hintergrundfarbe oder den Eckenradius), werden diese Werte überschrieben, wenn Sie eine drawRect: -Methode haben - auch wenn diese vollständig leer ist (dh nicht einmal super aufruft).
quelle