Comment écrire un init personnalisé pour une sous-classe UIView dans Swift?

125

Disons que je veux initune UIViewsous - classe avec un Stringet un Int.

Comment ferais-je cela dans Swift si je ne fais que sous-classer UIView? Si je fais juste une init()fonction personnalisée mais que les paramètres sont une chaîne et un int, cela me dit que "super.init () n'est pas appelé avant de revenir de l'initialiseur".

Et si j'appelle, on super.init()me dit que je dois utiliser un initialiseur désigné. Que dois-je utiliser là-bas? La version cadre? La version du codeur? Tous les deux? Pourquoi?

Doug Smith
la source

Réponses:

207

La init(frame:)version est l'initialiseur par défaut. Vous ne devez l'appeler qu'après avoir initialisé vos variables d'instance. Si cette vue est en cours de reconstitution à partir d'un Nib, votre initialiseur personnalisé ne sera pas appelé et la init?(coder:)version sera appelée à la place. Étant donné que Swift nécessite maintenant une implémentation du requis init?(coder:), j'ai mis à jour l'exemple ci-dessous et changé les letdéclarations de variables en varet facultatives. Dans ce cas, vous les initialiserez dans awakeFromNib()ou ultérieurement.

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
la source
5
Alors faites-les par tous les moyens var. Mais la meilleure pratique par défaut dans Swift est de déclarer des variables à letmoins qu'il n'y ait une raison de les déclarer var. Il n'y avait aucune raison de le faire dans mon exemple de code ci-dessus, par conséquent let.
Wolf McNally
2
Ce code ne se compile pas. Vous devez implémenter l'initialiseur requis init(coder:).
Decade Moon
3
intéressant comment cela compilé il y a des années. De nos jours, il se plaint sous init (coder :) que "la propriété self.s n'est pas initialisée à l'appel super.init"
mafiOSo
Exemple fixe pour Swift 3.1. Compile sous une aire de jeux en important UIKit.
Wolf McNally
1
@LightNight j'ai créé set ifacultatif pour garder les choses simples ici. S'ils n'étaient pas facultatifs, ils devraient également être initialisés dans l'initialiseur requis. En les rendant facultatifs, cela signifie qu'ils sont nilquand ils super.init()sont appelés. S'ils n'étaient pas optionnels, ils auraient en effet besoin d'être affectés avant d'appeler super.init ().
Wolf McNally
32

Je crée un init commun pour le désigné et requis. Pour plus de commodité, je délègue à init(frame:)avec un cadre de zéro.

Avoir zéro frame n'est pas un problème car la vue se trouve généralement dans la vue d'un ViewController; votre vue personnalisée aura une bonne chance de mettre en page ses sous-vues lorsque sa supervision appelle layoutSubviews()ou updateConstraints(). Ces deux fonctions sont appelées par le système de manière récursive dans toute la hiérarchie des vues. Vous pouvez utiliser soit updateContstraints()ou layoutSubviews(). updateContstraints()est appelé en premier, alors layoutSubviews(). En updateConstraints()assurez - vous d'appeler super - dernier . Dans layoutSubviews(), appelez super d' abord .

Voici ce que je fais:

@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
la source
1
Cela ne devrait pas fonctionner: utilisation de 'self' dans l'appel de méthode 'commonInit' avant que super.init ne s'initialise
surfrider
1
Initialisez les arguments personnalisés après l'appel à self.init. J'ai mis à jour ma réponse.
MH175 du
1
Mais que se passe-t-il si vous souhaitez initialiser certaines propriétés dans la commonInitméthode, mais que vous ne pouvez pas le placer après superdans ce cas, car vous devez initialiser toutes les propriétés AVANT l' superappel. Lol, ça ressemble à une boucle morte.
surfrider
1
C'est ainsi que fonctionne souvent l'initialisation Swift: recherchez "initialisation en deux phases". Vous pouvez utiliser des options implicitement non emballées, mais je le déconseille. Votre architecture, en particulier lorsqu'il s'agit de vues, doit initialiser toutes les propriétés locales. J'ai utilisé cette méthode commonInit () pour des centaines de vues maintenant. Ça marche
MH175
17

Voici comment je le fais sur iOS 9 dans 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"); }
}

Voici un projet complet avec exemple:

J-Dizzle
la source
2
cela utiliserait tout l'affichage pour la vue
RaptoX
1
oui! Si vous êtes intéressé par une sous-vue partielle, faites-le moi savoir et je
posterai également ceci
1
J'aime le mieux cette réponse, car avoir le fatalError signifie que je n'ai pas à mettre de code dans l'initialisation requise.
Carter Medlin
1
@ J-Dizzle, j'aimerais voir la solution pour les vues partielles.
Ari Lacenski
votre réponse ne dit-elle pas le contraire de la réponse acceptée? Je veux dire que vous faites la personnalisation après super.init, mais il a dit que cela devrait être fait avant super.init...
Chérie
11

Voici comment faire une sous-vue sur iOS dans 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
la source
4
Bonne utilisation de l'appel fatalError (). Je devais utiliser des options juste pour faire taire les avertissements d'un initialiseur qui n'était même pas utilisé. Cela la ferme! Merci.
Mike Critchley