Controller, sei ruhig! Wir nehmen den Code in UIView heraus

Haben Sie einen großen UIViewController? Für viele ja. Einerseits funktioniert es mit Daten, andererseits - mit der Schnittstelle.

Die Aufgaben der Trennung von Logik und Schnittstelle werden in Hunderten von Artikeln zur Architektur beschrieben: MVP, MVVM, VIPER. Sie lösen das Problem des Datenflusses, beantworten jedoch nicht die Frage, wie mit der Benutzeroberfläche gearbeitet werden soll: An einer Stelle verbleiben die Erstellung von Elementen, das Layout, die Konfiguration, die Verarbeitung von Eingaben und die Animation.

Lassen Sie uns die Ansicht vom Controller trennen und sehen, wie loadView () uns hilft.



Die Anwendungsoberfläche für iOS ist die UIView Hierarchie. Die Aufgaben jeder view : Elemente erstellen, anpassen, an Orten anordnen, animieren. Dies lässt sich an den Methoden der UIView: addSubview(), drawRect(), layoutSubviews(). Klasse UIView: addSubview(), drawRect(), layoutSubviews().

Wenn Sie sich die Methoden der UIViewController Klasse UIViewController , sehen Sie, dass sie die view: verwaltet view: geladen, reagiert auf Ladebildschirme und Benutzeraktionen und zeigt neue Bildschirme an. Oft wird der Code, der in der UIView soll, in Unterklassen des UIViewController , dies macht ihn zu groß. Trenne es.

loadView ()


Der Lebenszyklus eines UIViewController beginnt mit loadView() . Eine vereinfachte Implementierung sieht folgendermaßen aus:

 // CustomViewController.swift func loadView() { self.view = UIView() } 

Wir können die Methode überschreiben und unsere Klasse angeben.

super.loadView() muss nicht aufgerufen werden!

 // CustomViewController.swift override func loadView() { self.view = CustomView() } 

Implementierung von CustomView.swift
 // CustomView.swift final class CustomView { let square: UIView = UIView() init() { super.init() square.backgroundColor = .red addSubview(square) } } 


Der Controller lädt CustomView, fügt es der Hierarchie hinzu und macht .frame . Die .view Eigenschaft ist die Klasse, die wir benötigen:

 // CustomViewController.swift print(view) // CustomView 

Der Compiler kennt die Klasse jedoch nicht und glaubt, dass es eine normale UIView . Beheben wir dies mit einer Typumwandlungsfunktion:

 // CustomViewController.swift func view() -> CustomView { return self.view as! CustomView } 

Jetzt können Sie die CustomView Variablen sehen:

 // CustomViewController.swift func viewDidLoad() { super.viewDidLoad() view().square //  } 

Vereinfachen Sie mit dem zugehörigen Typ
Ruslan Kavetsky schlug vor, die Codeduplizierung mithilfe der Protokollerweiterung zu entfernen:

 protocol ViewSpecificController { associatedtype RootView: UIView } extension ViewSpecificController where Self: UIViewController { func view() -> RootView { return self.view as! RootView } } 

Für jeden neuen Controller müssen Sie nur das Protokoll und die Unterklasse für seine UIView über typealias :

 // CustomViewController.swift final class CustomViewController: UIViewController, ViewSpecificController { typealias RootView = CustomView func viewDidLoad() { super.viewDidLoad() view().square //  } } 

Code in einer Unterklasse von UIView


Steuerelemente erstellen und konfigurieren


Schriftarten, Farben, Konstanten und Hierarchien können direkt im CustomView-Konstruktor festgelegt werden:

 // CustomView.swift init() { super.init() backgroundColor = .lightGray addSubview(square) } 

layoutSubviews ()


Der beste Ort für ein manuelles Layout ist die layoutSubviews() -Methode. Es wird jedes Mal aufgerufen, wenn die view geändert wird, sodass Sie sich für die korrekten Berechnungen auf die Grenzgröße verlassen können:

 // CustomView.swift override func layoutSubviews() { super.layoutSubviews() square.frame = CGRect(x: 0, y: 0: width: 200, height: 200) square.center = CGPoint(x: bounds.width / 2, y: bounds.height / 2) } 

Private Kontrollen, öffentliches Eigentum


Wenn noch Zeit ist, mache ich die property privat, verwalte sie aber über öffentliche Variablen oder Funktionen „im Wissensbereich“. Ein einfacheres Beispiel:

 // CustomView.swift private let square = UIView() var squarePositionIsValid: Bool { didSet { square.backgroundColor = squarePositionIsValid? .green : .red } } func moveSquare(to newCenter: CGPoint) { square.center = newCenter } 

Der Vorteil der Kapselung: Die interne Logik ist hinter der Schnittstelle verborgen. Zum Beispiel kann die Gültigkeit eines Objekts durch die Farbe des Bereichs angezeigt werden, nicht durch das Quadrat, aber der Controller weiß nichts darüber.

Was bleibt in viewDidLoad ()?


Wenn Sie den Interface Builder verwenden, ist viewDidLoad() häufig leer. Wenn Sie eine view im Code erstellen, müssen Sie deren Aktionen über das UIGestureRecognizer binden, einen UIGestureRecognizer hinzufügen oder Delegaten binden.

Anpassbar über Interface Builder


Die Unterklasse für die view kann über den Interface Builder (im Folgenden IB) konfiguriert werden.

Sie müssen das view (nicht den Controller) auswählen und seine Klasse festlegen. Es ist nicht erforderlich, eine eigene loadView() zu schreiben, der Controller loadView() dies selbst. UIView noch UIView Typ UIView .



IBOutlet in UIView


Wenn Sie das Steuerelement in der view auswählen, erkennt der Assistant Editor die UIView Klasse und bietet sie als zweite Datei im automatischen Modus an. So können Sie IBOutlet zur view .



Wenn nicht funktioniert
Öffnen Sie die CustomView Klasse manuell und schreiben Sie IBOutlet . Jetzt können Sie an der Markierung ziehen und mit der Maus über ein Element in IB fahren.



Wenn Sie eine Schnittstelle im Code erstellen, sind alle Objekte nach init() zugänglich. Wenn Sie jedoch mit IB arbeiten, wird der Zugriff auf IBOutlet erst IBOutlet , nachdem Sie die Schnittstelle von UIStoryboard in der Methode awakeFromNib() :

 // CustomView.swift func awakeFromNib() { super.awakeFromNib() square.layer.cornerRadius = 8 } 

IBAction in UIViewController


Für meinen Geschmack sollte der Controller alle Benutzeraktionen verlassen. Vom Standard:

  • Zielaktion von Kontrollen
  • Implementierung in UIViewController
  • Blockimplementierung
  • Reaktion auf Notification

In diesem Fall steuert der UIViewController nur die Schnittstelle. Alles, was mit Geschäftslogik zu tun hat, sollte aus dem Controller entfernt werden. Dies ist jedoch eine Auswahl: MVP, VIPER usw.

Ziel-c


In Objective-C können Sie den UIView Typ vollständig ersetzen. Deklarieren Sie dazu die Eigenschaft mit der gewünschten Klasse, überschreiben Sie setter und getter und geben Sie die Klasse an:

 // CustomViewController.m @interface CustomViewController @property (nonatomic) CustomView *customView; @end @implementation - (void)setView:(CustomView *)view{ [super setView:view]; } - (CustomView *)view { return (CustomView *)super.view; } @end 

Das Ende


Im Beispiel auf GitHub können Sie die Trennung von Klassen für eine einfache Aufgabe betrachten: Die Farbe des Quadrats hängt von seiner Position ab (im grünen Bereich ist es grün, außerhalb ist es rot).

Je komplexer der Bildschirm ist, desto besser ist der Effekt: Der Controller wird reduziert, der Code wird an seinen Platz übertragen. Code wird einfach zum view portiert, aber die Kapselung erleichtert das Interagieren und Lesen von Code. Manchmal kann die view mit einem anderen Controller wiederverwendet werden. Beispielsweise reagieren verschiedene Controller für iPhone und iPad auf ihre eigene Weise auf das Erscheinungsbild der Tastatur, dies ändert jedoch nicht den view .

Ich habe diesen Code in verschiedenen Projekten und mit verschiedenen Leuten verwendet, jedes Mal, wenn das Team die Vereinfachung begrüßte und die Praxis aufnahm. Ich hoffe es gefällt euch auch. Alles einfach UIViewController !

Source: https://habr.com/ru/post/de432718/


All Articles