Richtige Vorgehensweise für die Unterklasse von UIView?


158

Ich arbeite an einigen benutzerdefinierten UIView-basierten Eingabesteuerelementen und versuche, die richtige Vorgehensweise zum Einrichten der Ansicht zu ermitteln. Wenn Sie mit einem UIViewController arbeiten, ist es ziemlich einfach, den loadViewund verwandten zu verwenden viewWill.viewDid Methoden, aber wenn ein UIView Subklassen, ist die nächste methosds ich habe `awakeFromNib, drawRectund layoutSubviews. (Ich denke an Rückrufe zum Einrichten und Herunterfahren.) In meinem Fall richte ich meinen Frame und meine internen Ansichten ein layoutSubviews, sehe aber nichts auf dem Bildschirm.

Was ist der beste Weg, um sicherzustellen, dass meine Ansicht die richtige Höhe und Breite hat, die ich haben möchte? (Meine Frage gilt unabhängig davon, ob ich Autolayout verwende, obwohl es möglicherweise zwei Antworten gibt.) Was ist die richtige "Best Practice"?

Antworten:


298

Apple hat UIViewim Dokument ziemlich klar definiert, wie Unterklassen erstellt werden sollen .

Schauen Sie sich die Liste unten an, insbesondere werfen Sie einen Blick auf initWithFrame:und layoutSubviews. Ersteres dient zum Einrichten des Rahmens von Ihnen, UIViewwährend letzteres den Rahmen und das Layout seiner Unteransichten einrichten soll.

initWithFrame:Denken Sie auch daran, dass dies nur aufgerufen wird, wenn Sie Ihre UIViewprogrammgesteuert instanziieren . Wenn Sie es aus einer NIB-Datei (oder einem Storyboard) laden, initWithCoder:wird es verwendet. Da initWithCoder:der Frame noch nicht berechnet wurde, können Sie den in Interface Builder eingerichteten Frame nicht ändern. Wie in dieser Antwort vorgeschlagen , können Sie anrufeninitWithFrame: aus initWithCoder:dem Rahmen , um der Einrichtung.

Wenn Sie Ihre UIViewDaten von einer Schreibfeder (oder einem Storyboard) laden , haben Sie außerdem die awakeFromNibMöglichkeit, seit wann benutzerdefinierte Rahmen- und Layoutinitialisierungen durchzuführenawakeFromNib Aufruf garantiert ist, dass jede Ansicht in der Hierarchie nicht archiviert und initialisiert wurde.

Aus dem Dokument von NSNibAwaking(jetzt ersetzt durch das Dokument von awakeFromNib):

Nachrichten an andere Objekte können sicher aus awakeFromNib gesendet werden. Zu diesem Zeitpunkt ist sichergestellt, dass alle Objekte nicht archiviert und initialisiert sind (obwohl dies natürlich nicht unbedingt aktiviert ist).

Es ist auch erwähnenswert, dass Sie mit Autolayout den Rahmen Ihrer Ansicht nicht explizit festlegen sollten. Stattdessen sollten Sie eine Reihe ausreichender Einschränkungen angeben, damit der Frame automatisch von der Layout-Engine berechnet wird.

Direkt aus der Dokumentation :

Methoden zum Überschreiben

Initialisierung

  • initWithFrame:Es wird empfohlen, diese Methode zu implementieren. Sie können zusätzlich zu oder anstelle dieser Methode auch benutzerdefinierte Initialisierungsmethoden implementieren.

  • initWithCoder: Implementieren Sie diese Methode, wenn Sie Ihre Ansicht aus einer Interface Builder-NIB-Datei laden und Ihre Ansicht eine benutzerdefinierte Initialisierung erfordert.

  • layerClassImplementieren Sie diese Methode nur, wenn Ihre Ansicht eine andere Kernanimationsebene für den Hintergrundspeicher verwenden soll. Wenn Sie beispielsweise OpenGL ES zum Zeichnen verwenden, möchten Sie diese Methode überschreiben und die CAEAGLLayer-Klasse zurückgeben.

Zeichnen und Drucken

  • drawRect:Implementieren Sie diese Methode, wenn Ihre Ansicht benutzerdefinierten Inhalt zeichnet. Wenn Ihre Ansicht keine benutzerdefinierten Zeichnungen ausführt, sollten Sie diese Methode nicht überschreiben.

  • drawRect:forViewPrintFormatter: Implementieren Sie diese Methode nur, wenn Sie den Inhalt Ihrer Ansicht während des Druckvorgangs anders zeichnen möchten.

Einschränkungen

  • requiresConstraintBasedLayout Implementieren Sie diese Klassenmethode, wenn für Ihre Ansichtsklasse Einschränkungen erforderlich sind, damit sie ordnungsgemäß funktionieren.

  • updateConstraints Implementieren Sie diese Methode, wenn Ihre Ansicht benutzerdefinierte Einschränkungen zwischen Ihren Unteransichten erstellen muss.

  • alignmentRectForFrame:, frameForAlignmentRect:Implementieren Sie diese Methoden außer Kraft zu setzen , wie Sie Ihre Ansichten zu anderen Ansichten ausgerichtet sind.

Layout

  • sizeThatFits:Implementieren Sie diese Methode, wenn Ihre Ansicht eine andere Standardgröße haben soll als normalerweise beim Ändern der Größe. Sie können diese Methode beispielsweise verwenden, um zu verhindern, dass Ihre Ansicht so weit verkleinert wird, dass Unteransichten nicht mehr korrekt angezeigt werden können.

  • layoutSubviews Implementieren Sie diese Methode, wenn Sie eine genauere Kontrolle über das Layout Ihrer Unteransichten benötigen, als dies durch die Einschränkung oder das Verhalten bei der automatischen Größenänderung möglich ist.

  • didAddSubview:, willRemoveSubview:Implementieren diese Verfahren je nach Bedarf die Ergänzungen und Löschungen von Subviews zu verfolgen.

  • willMoveToSuperview:, didMoveToSuperviewImplementieren diese Verfahren als notwendig , um die Bewegung der aktuellen Ansicht in der Ansichtshierarchie zu verfolgen.

  • willMoveToWindow:, didMoveToWindowImplementieren Sie diese Methoden als notwendig , um die Bewegung Ihrer Ansicht in ein anderes Fenster zu verfolgen.

Handhabung des Events:

  • touchesBegan:withEvent:, touchesMoved:withEvent:, touchesEnded:withEvent:, touchesCancelled:withEvent:Implementieren Sie diese Methoden , wenn Sie direkt Berührungsereignisse zu handhaben müssen. (Verwenden Sie für gestenbasierte Eingaben Gestenerkenner.)

  • gestureRecognizerShouldBegin: Implementieren Sie diese Methode, wenn Ihre Ansicht Berührungsereignisse direkt verarbeitet und möglicherweise verhindern möchte, dass angehängte Gestenerkenner zusätzliche Aktionen auslösen.


Was ist mit - (void) setFrame: (CGRect) frame?
pfrank

Nun, Sie können es definitiv überschreiben, aber zu welchem ​​Zweck?
Gabriele Petronella

Layout / Zeichnung jederzeit zu ändern, wenn sich die Rahmengröße oder Position ändert
pfrank

1
Was ist mit layoutSubviews?
Gabriele Petronella

In stackoverflow.com/questions/4000664/… heißt es: "Das Problem dabei ist, dass Unteransichten nicht nur ihre Größe ändern, sondern auch diese Größenänderung animieren können. Wenn UIView die Animation ausführt, werden nicht jedes Mal layoutSubviews aufgerufen." Habe es aber nicht persönlich getestet
pfrank

38

Dies kommt bei Google immer noch hoch an. Unten finden Sie ein aktualisiertes Beispiel für Swift.

Mit dieser didLoadFunktion können Sie Ihren gesamten benutzerdefinierten Initialisierungscode eingeben. Wie bereits erwähnt, didLoadwird es aufgerufen, wenn eine Ansicht programmgesteuert über erstellt wird init(frame:)oder wenn der XIB- Deserializer eine XIB- Vorlage über in Ihre Ansicht zusammenführtinit(coder:)

Nebenbei : layoutSubviewsund updateConstraintswerden für die meisten Ansichten mehrmals aufgerufen. Dies ist für erweiterte Layouts und Anpassungen mit mehreren Durchgängen vorgesehen, wenn sich die Grenzen einer Ansicht ändern. Persönlich vermeide ich Layouts mit mehreren Durchgängen, wenn dies möglich ist, da sie CPU-Zyklen verbrennen und alles zu Kopfschmerzen machen. Außerdem habe ich den Initialisierern selbst Einschränkungscode hinzugefügt, da ich sie selten ungültig mache.

import UIKit

class MyView: UIView {
  //-----------------------------------------------------------------------------------------------------
  //Constructors, Initializers, and UIView lifecycle
  //-----------------------------------------------------------------------------------------------------
  override init(frame: CGRect) {
      super.init(frame: frame)
      didLoad()
  }

  required init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
    didLoad()
  }

  convenience init() {
    self.init(frame: CGRectZero)
  }

  func didLoad() {
    //Place your initialization code here

    //I actually create & place constraints in here, instead of in
    //updateConstraints
  }

  override func layoutSubviews() {
     super.layoutSubviews()

     //Custom manually positioning layout goes here (auto-layout pass has already run first pass)
  }

  override func updateConstraints() {
    super.updateConstraints()

    //Disable this if you are adding constraints manually
    //or you're going to have a 'bad time'
    //self.translatesAutoresizingMaskIntoConstraints = false

    //Add custom constraint code here
  }
}

Können Sie erklären, wann / warum Sie den Code für Layoutbeschränkungen zwischen einer von Ihrem Init-Prozess aufgerufenen Methode und layoutSubviews und updateConstraints aufteilen würden? Es scheint, dass es sich bei allen drei möglichen Kandidatenpositionen um Platzierungscode handelt. Woher wissen Sie also, wann / was / warum Layout-Code zwischen den drei aufgeteilt werden muss?
Clay Ellis

3
Ich benutze niemals updateConstraints; updateConstraints kann hilfreich sein, da Sie wissen, dass Ihre Ansichtshierarchie vollständig in init eingerichtet wurde. Sie können also keine Ausnahme auslösen, indem Sie eine Einschränkung zwischen zwei Ansichten hinzufügen, die nicht in der Hierarchie enthalten sind :) layoutSubviews sollten niemals Einschränkungen ändern. Dies kann leicht zu einer unendlichen Rekursion führen, da layoutSubviews aufgerufen wird, wenn Einschränkungen während des Layoutdurchlaufs "ungültig" werden. Die manuelle Einrichtung des Layouts (wie beim direkten Einstellen von Frames, die Sie nur aus Leistungsgründen selten mehr durchführen müssen) erfolgt in layoutSubviews. Persönlich platziere ich die Erstellung von Einschränkungen in init
seo

Sollten wir für benutzerdefinierten Rendering-Code die drawMethode überschreiben ?
Petrus Theron

14

Die Apple- Dokumentation enthält eine anständige Zusammenfassung, die im kostenlosen Stanford-Kurs bei iTunes ausführlich behandelt wird. Ich präsentiere meine TL; DR-Version hier:

Wenn Ihre Klasse hauptsächlich aus Unteransichten besteht, sind die initMethoden der richtige Ort, um sie zuzuweisen . Für Ansichten gibt es zwei verschiedene initMethoden, die aufgerufen werden können, je nachdem, ob Ihre Ansicht über Code oder über eine Schreibfeder / ein Storyboard instanziiert wird. Ich schreibe meine eigene setupMethode und rufe sie dann sowohl von der initWithFrame:als auch von der initWithCoder:Methode auf.

Wenn Sie benutzerdefinierte Zeichnungen erstellen, möchten Sie diese tatsächlich überschreiben drawRect: . Wenn Ihre benutzerdefinierte Ansicht jedoch hauptsächlich ein Container für Unteransichten ist, müssen Sie dies wahrscheinlich nicht tun.

Überschreiben layoutSubViewsSie nur, wenn Sie beispielsweise eine Unteransicht hinzufügen oder entfernen möchten, je nachdem, ob Sie sich im Hoch- oder Querformat befinden. Andernfalls sollten Sie es in Ruhe lassen können.


Ich verwende Ihre Antwort, um den Frame der Unteransicht der Ansicht (die awakeFromNib ist) zu ändern. layoutSubViewsEs hat funktioniert.
Flugzeuge

1

layoutSubviews soll den Rahmen für untergeordnete Ansichten festlegen, nicht für die Ansicht selbst.

Für UIView, ist der designierte Konstruktor der Regel , initWithFrame:(CGRect)frameund Sie sollten den Rahmen dort eingestellt (oder in initWithCoder:), möglicherweise nicht beachtet werden Rahmenwert übergeben. Sie können auch einen anderen Konstruktor angeben und den Rahmen dort festlegen.


Könntest du es genauer betrachten? Ich kannte Ihren Mittelwert nicht. Wie kann ich den SubView-Frame einer Ansicht festlegen? Die Ansicht istawakeFromNib
Flugzeug

Beim schnellen Vorlauf auf 2016 sollten Sie wahrscheinlich überhaupt keine Frames festlegen und Autolayout (Einschränkungen) verwenden. Wenn die Ansicht von XIB (oder Storyboard) stammt, sollte die Unteransicht bereits eingerichtet sein.
Proxi
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.