Wie schreibe ich eine benutzerdefinierte Init für eine UIView-Unterklasse in Swift?

125

Angenommen, ich möchte initeine UIViewUnterklasse mit einem Stringund einem Int.

Wie würde ich das in Swift machen, wenn ich nur eine Unterklasse habe UIView? Wenn ich nur eine benutzerdefinierte init()Funktion erstelle, die Parameter jedoch ein String und ein Int sind, wird mir mitgeteilt, dass "super.init () vor der Rückkehr vom Initialisierer nicht aufgerufen wird".

Und wenn ich anrufe, wird super.init()mir gesagt, dass ich einen bestimmten Initialisierer verwenden muss. Was soll ich dort verwenden? Die Rahmenversion? Die Codiererversion? Beide? Warum?

Doug Smith
quelle

Antworten:

207

Die init(frame:)Version ist der Standardinitialisierer. Sie müssen es erst aufrufen, nachdem Sie Ihre Instanzvariablen initialisiert haben. Wenn diese Ansicht von einer Schreibfeder aus wiederhergestellt wird, wird Ihr benutzerdefinierter Initialisierer nicht aufgerufen, sondern die init?(coder:)Version. Da Swift jetzt eine Implementierung des erforderlichen erfordert init?(coder:), habe ich das folgende Beispiel aktualisiert und die letVariablendeklarationen in varund optional geändert . In diesem Fall würden Sie sie in awakeFromNib()oder zu einem späteren Zeitpunkt initialisieren .

class TestView : UIView {
    var s: String?
    var i: Int?
    init(s: String, i: Int) {
        self.s = s
        self.i = i
        super.init(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }
}
Wolf McNally
quelle
5
Dann machen Sie sie auf jeden Fall var. Die Standard-Best Practice in Swift besteht jedoch darin, Variablen zu deklarieren, es letsei denn, es gibt einen Grund, sie zu deklarieren var. In meinem obigen Codebeispiel gab es daher keinen solchen Grund dafür let.
Wolf McNally
2
Dieser Code wird nicht kompiliert. Sie müssen den erforderlichen Initialisierer implementieren init(coder:).
Decade Moon
3
Interessant, wie sich das vor Jahren zusammengestellt hat. Heutzutage beschwert es sich unter init (coder :), dass "Property self.s bei super.init call nicht initialisiert wurde"
mafiOSo
Beispiel für Swift 3.1 behoben. Kompiliert unter einem Spielplatz, der UIKit importiert.
Wolf McNally
1
@ LightNight habe ich gemacht sund ioptional, um die Dinge hier einfach zu halten. Wenn sie nicht optional wären, müssten sie auch im erforderlichen Initialisierer initialisiert werden. Wenn sie optional sind, werden sie angezeigt, nilwenn sie super.init()aufgerufen werden. Wenn sie nicht optional wären, müssten sie tatsächlich zugewiesen werden, bevor super.init () aufgerufen wird.
Wolf McNally
32

Ich erstelle eine gemeinsame Init für die vorgesehenen und erforderlichen. Der Einfachheit halber delegiere ich init(frame:)mit einem Rahmen von Null an.

Ein Null-Frame ist kein Problem, da sich die Ansicht normalerweise in der Ansicht eines ViewControllers befindet. Ihre benutzerdefinierte Ansicht erhält eine gute und sichere Möglichkeit, ihre Unteransichten zu gestalten, wenn die Übersicht layoutSubviews()oder updateConstraints(). Diese beiden Funktionen werden vom System in der gesamten Ansichtshierarchie rekursiv aufgerufen. Sie können entweder updateContstraints()oder verwenden layoutSubviews(). updateContstraints()wird dann zuerst aufgerufen layoutSubviews(). In updateConstraints()stellen Sie sicher, super zuletzt anzurufen . In layoutSubviews()rufe Super zuerst .

Folgendes mache ich:

@IBDesignable
class MyView: UIView {

      convenience init(args: Whatever) {
          self.init(frame: CGRect.zero)
          //assign custom vars
      }

      override init(frame: CGRect) {
           super.init(frame: frame)
           commonInit()
      }

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

      override func prepareForInterfaceBuilder() {
           super.prepareForInterfaceBuilder()
           commonInit()
      }

      private func commonInit() {
           //custom initialization
      }

      override func updateConstraints() {
           //set subview constraints here
           super.updateConstraints()
      }

      override func layoutSubviews() {
           super.layoutSubviews()
           //manually set subview frames here
      }

}
MH175
quelle
1
Es sollte nicht Arbeit: Die Verwendung von ‚Selbst‘ in Methodenaufruf ‚commonInit‘ vor super.init selbst initialisiert
surfrider
1
Initialisieren Sie benutzerdefinierte Argumente nach dem Aufruf von self.init. Meine Antwort wurde aktualisiert.
MH175
1
Was aber, wenn Sie einige Eigenschaften in der commonInitMethode initialisieren möchten , diese aber superin diesem Fall nicht mehr platzieren können, da Sie alle Eigenschaften VOR dem superAufruf initialisieren sollten . Lol es scheint wie eine tote Schleife.
Surfrider
1
So funktioniert die Swift-Initialisierung häufig: Suchen Sie nach "Zwei-Phasen-Initialisierung". Sie können implizit ausgepackte Optionen verwenden, aber ich empfehle dagegen. Ihre Architektur sollte, insbesondere beim Umgang mit Ansichten, alle lokalen Eigenschaften initialisieren. Ich habe diese commonInit () -Methode jetzt für Hunderte von Ansichten verwendet. Es funktioniert
MH175
17

So mache ich es auf iOS 9 in Swift -

import UIKit

class CustomView : UIView {

    init() {
        super.init(frame: UIScreen.mainScreen().bounds);

        //for debug validation
        self.backgroundColor = UIColor.blueColor();
        print("My Custom Init");

        return;
    }

    required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented"); }
}

Hier ist ein vollständiges Projekt mit Beispiel:

J-Dizzle
quelle
2
Dies würde die gesamte Anzeige für die Ansicht
RaptoX
1
ja! Wenn Sie an einer Teilansicht interessiert sind, lassen Sie es mich wissen und ich werde dies auch
posten
1
Diese Antwort gefällt mir am besten, weil der fatalError bedeutet, dass ich keinen Code in die erforderliche Init einfügen muss.
Carter Medlin
1
@ J-Dizzle, ich würde gerne die Lösung für Teilansichten sehen.
Ari Lacenski
Sagt Ihre Antwort nicht das Gegenteil der akzeptierten Antwort? Ich meine, Sie machen die Anpassung danach super.init, aber er sagte, es sollte vorher gemacht werden super.init...
Honey
11

Hier ist, wie ich eine Unteransicht auf iOS in Swift mache -

class CustomSubview : UIView {

    init() {
        super.init(frame: UIScreen.mainScreen().bounds);

        let windowHeight : CGFloat = 150;
        let windowWidth  : CGFloat = 360;

        self.backgroundColor = UIColor.whiteColor();
        self.frame = CGRectMake(0, 0, windowWidth, windowHeight);
        self.center = CGPoint(x: UIScreen.mainScreen().bounds.width/2, y: 375);

        //for debug validation
        self.backgroundColor = UIColor.grayColor();
        print("My Custom Init");

        return;
    }

    required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented"); }
}
J-Dizzle
quelle
4
Gute Verwendung des Aufrufs fatalError (). Ich musste Optionen verwenden, um Warnungen von einem Initialisierer, der nicht einmal verwendet wurde, zum Schweigen zu bringen. Das hat es richtig gemacht! Vielen Dank.
Mike Critchley