Jak napisać niestandardowy plik init dla podklasy UIView w języku Swift?

125

Powiedzmy chcę initna UIViewpodklasy z Stringi Int.

Jak mam to zrobić w Swift, skoro tylko podklasuję UIView? Jeśli po prostu init()utworzę funkcję niestandardową, ale parametry to String i Int, to mówi mi, że „super.init () nie jest wywoływana przed powrotem z inicjatora”.

A jeśli zadzwonię super.init(), powiedziano mi, że muszę użyć wyznaczonego inicjatora. Czego mam tam używać? Wersja ramowa? Wersja kodera? Obie? Czemu?

Doug Smith
źródło

Odpowiedzi:

207

init(frame:)Wersja jest domyślnym inicjator. Musisz go wywołać dopiero po zainicjowaniu zmiennych instancji. Jeśli ten widok jest odtwarzany ze stalówki, twój niestandardowy inicjator nie zostanie wywołany, a zamiast tego init?(coder:)zostanie wywołana wersja. Ponieważ Swift wymaga teraz implementacji wymaganego init?(coder:), zaktualizowałem poniższy przykład i zmieniłem letdeklaracje zmiennych na vari opcjonalne. W takim przypadku zainicjowałbyś je awakeFromNib()później lub później.

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
źródło
5
Więc jak najbardziej je zrób var. Ale domyślną najlepszą praktyką w Swift jest deklarowanie zmiennych, letchyba że istnieje powód, aby je zadeklarować var. W związku z tym w powyższym przykładzie kodu nie było takiego powodu let.
Wolf McNally
2
Ten kod nie kompiluje się. Musisz zaimplementować wymagany inicjator init(coder:).
Decade Moon
3
ciekawe, jak to skompilowano lata temu. Obecnie narzeka init (koder :), że "Property self.s nie jest zainicjowany w wywołaniu super.init"
mafiOSo
Naprawiono przykład dla Swift 3.1. Kompiluje się w ramach placu zabaw importującego UIKit.
Wolf McNally
1
@LightNight stworzyłem si iopcjonalnie, aby wszystko było proste. Gdyby nie były opcjonalne, musiałyby również zostać zainicjowane w wymaganym inicjatorze. Uczynienie ich opcjonalnymi oznacza, że ​​mogą być, nilgdy super.init()zostanie wywołany. Gdyby nie były opcjonalne, rzeczywiście należałoby je przypisać przed wywołaniem super.init ().
Wolf McNally
32

Tworzę wspólny init dla wyznaczonych i wymaganych. Dla wygody deleguję inity init(frame:)z ramką zerową.

Brak ramki nie jest problemem, ponieważ zazwyczaj widok znajduje się wewnątrz widoku ViewControllera; Twój widok niestandardowy będzie miał dobrą, bezpieczną szansę na ułożenie swoich podglądów podrzędnych, gdy wywoła nadzór layoutSubviews()lub updateConstraints(). Te dwie funkcje są wywoływane przez system rekurencyjnie w całej hierarchii widoków. Możesz użyć albo updateContstraints()lub layoutSubviews(). updateContstraints()jest wtedy wywoływana jako pierwsza layoutSubviews(). W updateConstraints()upewnij się, aby zadzwonić Super ostatni . W layoutSubviews()zadzwoń Super pierwszy .

Oto co robię:

@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
źródło
1
To nie powinno działać: użycie `` self '' w wywołaniu metody `` commonInit '', zanim super.init zainicjuje self
surfrider
1
Zainicjuj argumenty niestandardowe po wywołaniu self.init. Zaktualizowałem moją odpowiedź.
MH175
1
Ale co, jeśli chcesz zainicjalizować niektóre właściwości w commonInitmetodzie, ale nie możesz ich umieścić później, superponieważ powinieneś zainicjować wszystkie właściwości PRZED superwywołaniem. Lol, wygląda na martwą pętlę.
surfrider
1
Oto jak często działa inicjalizacja Swift: wyszukaj „inicjalizacja dwufazowa”. Możesz używać niejawnie rozpakowanych opcji, ale odradzam to. Twoja architektura, szczególnie w przypadku widoków, powinna inicjalizować wszystkie lokalne właściwości. Użyłem tej metody commonInit () do setek widoków. To działa
MH175
17

Oto jak to robię na iOS 9 w 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"); }
}

Oto pełny projekt z przykładem:

J-Dizzle
źródło
2
wykorzystałoby to cały wyświetlacz dla View
RaptoX
1
tak! Jeśli interesuje Cię częściowy podgląd, daj mi znać, a ja też to opublikuję
J-Dizzle
1
Najbardziej podoba mi się ta odpowiedź, ponieważ posiadanie fatalError oznacza, że ​​nie muszę umieszczać żadnego kodu w wymaganym pliku init.
Carter Medlin
1
@ J-Dizzle, chciałbym zobaczyć rozwiązanie dla widoków częściowych.
Ari Lacenski
czy twoja odpowiedź nie jest przeciwieństwem zaakceptowanej odpowiedzi? Mam na myśli, że robisz dostosowywanie później super.init, ale powiedział, że powinno to być zrobione przed super.init...
Kochanie,
11

Oto, jak mogę zrobić podgląd podrzędny na iOS w Swift -

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
źródło
4
Niezłe użycie wywołania fatalError (). Musiałem używać opcji tylko po to, aby wyciszyć ostrzeżenia z inicjatora, który nawet nie był używany. To zamknęło się! Dzięki.
Mike Critchley