Sous-classe Swift UIView

95

Je veux sous UIView- classer et afficher une connexion comme une vue. J'ai créé ceci dans Objective-C, mais maintenant je veux le porter sur Swift. Je n'utilise pas de storyboards, donc je crée toute mon interface utilisateur en code.

Mais le premier problème est que je dois mettre en œuvre initWithCoder. Je lui ai donné une implémentation par défaut car elle ne sera pas appelée. Maintenant, lorsque je lance le programme, il plante, car je dois également l'implémenter initWithFrame. Maintenant j'ai ceci:

override init() {
    super.init()
    println("Default init")
}

override init(frame: CGRect) {
    super.init(frame: frame)
    println("Frame init")
}

required init(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
    println("Coder init")
}

Ma question est: où dois-je créer mon champ de texte, etc.

Haagenti
la source

Réponses:

174

Je fais généralement quelque chose comme ça, c'est un peu verbeux.

class MyView: UIView {
    override init(frame: CGRect) {
        super.init(frame: frame)
        addBehavior()
    }

    convenience init() {
        self.init(frame: CGRect.zero)
    }

    required init(coder aDecoder: NSCoder) {
        fatalError("This class does not support NSCoding")
    }

    func addBehavior() {
        print("Add all the behavior here")
    }
}



let u = MyView(frame: CGRect.zero)
let v = MyView()

(Edit: j'ai édité ma réponse pour que la relation entre les initialiseurs soit plus claire)

Raymond
la source
4
Mais addBehavior est appelé deux fois puisque initFrame est appelé et init. Si vous exécutez mon code, la première image init est imprimée, alors l'initialisation par défaut
Haagenti
6
Du bon matos, merci. Au lieu d'utiliser, CGRectZeroje pense qu'il est recommandé d'utiliser CGRect.zeroRect.
Mr Rogers le
57
Ce truc d'initialiseur est complètement compliqué.
Ian Warburton
3
Existe-t-il un moyen de le faire pour la disposition automatique? Le cadre est tellement dépassé.
devios1
8
Ce n'est pas une réponse complète. UIView prend en charge initWithCoding. Toute vue chargée à partir d'une pointe ou d'un storyboard appellera la méthode initWithCoding et plantera.
Iain Delaney
17

C'est plus simple.

override init (frame : CGRect) {
    super.init(frame : frame)
    // Do what you want.
}

required init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
}
Jeff Gu Kang
la source
10

Exemple de sous-classe UIView personnalisée

Je crée généralement des applications iOS sans utiliser de storyboards ou de pointes. Je vais partager quelques techniques que j'ai apprises pour répondre à vos questions.

 

Masquer les initméthodes indésirables

Ma première suggestion est de déclarer une base UIViewpour masquer les initialiseurs indésirables. J'ai discuté de cette approche en détail dans ma réponse à "Comment masquer les initialiseurs spécifiques au storyboard et à la pointe dans les sous-classes de l'interface utilisateur" . Remarque: cette approche suppose que vous n'utiliserez pas BaseViewou ses descendants dans les storyboards ou les nibs, car cela provoquera intentionnellement le blocage de l'application.

class BaseView: UIView {

    // This initializer hides init(frame:) from subclasses
    init() {
        super.init(frame: CGRect.zero)
    }

    // This attribute hides `init(coder:)` from subclasses
    @available(*, unavailable)
    required init?(coder aDecoder: NSCoder) {
        fatalError("NSCoding not supported")
    }
}

Votre sous-classe UIView personnalisée doit hériter de BaseView. Il doit appeler super.init () dans son initialiseur. Il n'a pas besoin d'être mis en œuvre init(coder:). Ceci est démontré dans l'exemple ci-dessous.

 

Ajout d'un UITextField

Je crée des propriétés stockées pour les sous-vues référencées en dehors de la initméthode. Je le ferais généralement pour un UITextField. Je préfère subviews de instancier au sein de la déclaration de la propriété sous - vue comme ceci: let textField = UITextField().

UITextField ne sera visible que si vous l'ajoutez à la liste de sous-vues de la vue personnalisée en appelant addSubview(_:). Ceci est démontré dans l'exemple ci-dessous.

 

Disposition programmatique sans disposition automatique

UITextField ne sera visible que si vous définissez sa taille et sa position. Je fais souvent la mise en page dans le code (sans utiliser la mise en page automatique) dans la méthode layoutSubviews . layoutSubviews()est appelé initialement et chaque fois qu'un événement de redimensionnement se produit. Cela permet d'ajuster la disposition en fonction de la taille de CustomView. Par exemple, si CustomView apparaît sur toute la largeur sur différentes tailles d'iPhones et d'iPad et s'ajuste pour la rotation, il doit s'adapter à de nombreuses tailles initiales et se redimensionner dynamiquement.

Vous pouvez vous référer à frame.heightet à l' frame.widthintérieur layoutSubviews()pour obtenir les dimensions de CustomView à titre de référence. Ceci est démontré dans l'exemple ci-dessous.

 

Exemple de sous-classe UIView

Une sous-classe UIView personnalisée contenant un UITextField qui n'a pas besoin d'être implémenté init?(coder:).

class CustomView: BaseView {

    let textField = UITextField()

    override init() {
        super.init()

        // configure and add textField as subview
        textField.placeholder = "placeholder text"
        textField.font = UIFont.systemFont(ofSize: 12)
        addSubview(textField)
    }

    override func layoutSubviews() {
        super.layoutSubviews()

        // Set textField size and position
        textField.frame.size = CGSize(width: frame.width - 20, height: 30)
        textField.frame.origin = CGPoint(x: 10, y: 10)
    }
}

 

Disposition programmatique avec disposition automatique

Vous pouvez également implémenter la disposition à l'aide de la disposition automatique dans le code. Comme je ne le fais pas souvent, je ne montrerai pas d'exemple. Vous pouvez trouver des exemples de mise en œuvre de la disposition automatique dans le code sur Stack Overflow et ailleurs sur Internet.

 

Cadres de mise en page programmatiques

Il existe des frameworks open source qui implémentent la mise en page dans le code. Un qui m'intéresse mais que je n'ai pas essayé est LayoutKit . Il a été rédigé par l'équipe de développement sur LinkedIn. Depuis le référentiel Github: "LinkedIn a créé LayoutKit car nous avons constaté que la mise en page automatique n'est pas assez performante pour les hiérarchies de vues complexes dans les vues déroulantes."

 

Pourquoi mettre fatalErroreninit(coder:)

Lors de la création de sous-classes UIView qui ne seront jamais utilisées dans un storyboard ou une nib, vous pouvez introduire des initialiseurs avec des paramètres et des exigences d'initialisation différents qui n'ont pas pu être appelés par la init(coder:)méthode. Si vous n'avez pas échoué à init (coder :) avec a fatalError, cela pourrait entraîner des problèmes très déroutants sur toute la ligne en cas d'utilisation accidentelle dans un storyboard / nib. FatalError affirme ces intentions.

required init?(coder aDecoder: NSCoder) {
    fatalError("NSCoding not supported")
}

Si vous souhaitez exécuter du code lorsque la sous-classe est créée, qu'elle soit créée dans le code ou dans un storyboard / nib, vous pouvez faire quelque chose comme ce qui suit (basé sur la réponse de Jeff Gu Kang )

class CustomView: UIView {
    override init (frame: CGRect) {
        super.init(frame: frame)
        initCommon()
    }

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

    func initCommon() {
        // Your custom initialization code
    }
}
Mobile Dan
la source
et? en ajoutant que fatalErrorvous interdisez d'initier cette vue avec des fichiers xib
Vyachaslav Gerchicov
@VyachaslavGerchicov, Ma réponse énonce l'hypothèse que vous n'utilisez pas de xibs ou de storyboards comme le font la réponse acceptée et la question. La question note "Je n'utilise pas de storyboards, donc je crée toute mon interface utilisateur en code".
Mobile Dan du
la prochaine fois que vous écrivez à l' fatalErrorintérieur de la méthode dealloc et dites-nous que cela ne fonctionne pas parce que cette classe devrait être un singleton. Si vous préférez créer des éléments d'interface utilisateur dans le code, vous ne devez pas interdire manuellement toutes les autres méthodes. Enfin la question est de savoir comment créer "par programmation sans storyboards" mais les xibs / nibs ne sont pas mentionnés. Dans mon cas, je dois créer un tableau de cellules avec + xib par programme et les transmettre DropDownMenuKitet cette façon ne fonctionne pas car l'auteur de cette bibliothèque interdit aussi les xib.
Vyachaslav Gerchicov
@VyachaslavGerchicov On dirait que la réponse de Jeff Gu Kang est ce que vous recherchez car elle convient aux Storyboards / Xibs
Mobile Dan
1
@VyachaslavGerchicov La question énonçait également "et si je n'implémente jamais de cadre et de codeur, comment puis-je" cacher "cela?" Lors de la création de sous-classes UIView qui ne seront jamais utilisées dans un Xib / Storyboard, vous pouvez introduire des initialiseurs avec différents paramètres qui ne peuvent pas être appelés par la méthode init (coder :). Si vous n'avez pas échoué à init (coder :) avec une fatalError, cela pourrait conduire à des problèmes très déroutants sur toute la ligne en cas d'utilisation accidentelle dans un Xib / Storyboard. FatalError énonce ces intentions. C'est une pratique intentionnelle et courante comme on le voit dans la réponse acceptée.
Mobile Dan
4

Il est important que votre UIView puisse être créé par le constructeur d'interface / les storyboards ou à partir du code. Je trouve utile d'avoir une setupméthode pour réduire la duplication de tout code de configuration. par exemple

class RedView: UIView {
    override init (frame: CGRect) {
        super.init(frame: frame)
        setup()
    }

    required init(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)!
        setup()
    }

    func setup () {
        backgroundColor = .red
    }
}
Jason Moore
la source
4

Swift 4.0, si vous souhaitez utiliser la vue à partir d'un fichier xib, c'est pour vous. J'ai créé la classe CustomCalloutView Sous-classe de UIView. J'ai créé un fichier xib et dans IB, sélectionnez simplement le propriétaire du fichier, puis sélectionnez Inspecteur d'attributs, définissez le nom de la classe sur CustomCalloutView, puis créez une sortie dans votre classe.

    import UIKit
    class CustomCalloutView: UIView {

        @IBOutlet var viewCallout: UIView! // This is main view

        @IBOutlet weak var btnCall: UIButton! // subview of viewCallout
        @IBOutlet weak var btnDirection: UIButton! // subview of viewCallout
        @IBOutlet weak var btnFavourite: UIButton! // subview of viewCallout 

       // let nibName = "CustomCalloutView" this is name of xib file

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

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

        func nibSetup() {
            Bundle.main.loadNibNamed(String(describing: CustomCalloutView.self), owner: self, options: nil)
            guard let contentView = viewCallout else { return } // adding main view 
            contentView.frame = self.bounds //Comment this line it take default frame of nib view
           // custom your view properties here
            self.addSubview(contentView)
        }
    }

// L'ajout maintenant

    let viewCustom = CustomCalloutView.init(frame: CGRect.init(x: 120, y: 120, 50, height: 50))
    self.view.addSubview(viewCustom)
Gurjinder Singh
la source
-1

Voici un exemple de la façon dont je construis habituellement mes sous-classes (UIView). J'ai le contenu sous forme de variables afin qu'ils puissent être consultés et modifiés peut-être plus tard dans une autre classe. J'ai également montré comment j'utilise la mise en page automatique et l'ajout de contenu.

Par exemple, dans un ViewController, j'ai cette vue initialisée dans ViewDidLoad () car elle n'est appelée qu'une seule fois lorsque la vue est visible. Ensuite, j'utilise ces fonctions que je crée ici addContentToView()et là activateConstraints()pour créer le contenu et définir des contraintes. Si plus tard, dans un ViewController, je veux que la couleur, disons, d'un bouton soit rouge, je le fais simplement dans cette fonction spécifique de ce ViewController. Quelque chose comme:func tweaksome(){ self.customView.someButton.color = UIColor.red}

class SomeView: UIView {


var leading: NSLayoutConstraint!
var trailing: NSLayoutConstraint!
var bottom: NSLayoutConstraint!
var height: NSLayoutConstraint!


var someButton: UIButton = {
    var btn: UIButton = UIButton(type: UIButtonType.system)
    btn.setImage(UIImage(named: "someImage"), for: .normal)
    btn.translatesAutoresizingMaskIntoConstraints = false
    return btn
}()

var btnLeading: NSLayoutConstraint!
var btnBottom: NSLayoutConstraint!
var btnTop: NSLayoutConstraint!
var btnWidth: NSLayoutConstraint!

var textfield: UITextField = {
    var tf: UITextField = UITextField()
    tf.adjustsFontSizeToFitWidth = true
    tf.placeholder = "Cool placeholder"
    tf.translatesAutoresizingMaskIntoConstraints = false
    tf.backgroundColor = UIColor.white
    tf.textColor = UIColor.black
    return tf
}()
var txtfieldLeading: NSLayoutConstraint!
var txtfieldTrailing: NSLayoutConstraint!
var txtfieldCenterY: NSLayoutConstraint!

override init(frame: CGRect){
    super.init(frame: frame)
    self.translatesAutoresizingMaskIntoConstraints = false
}

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



/*
// Only override draw() if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
override func draw(_ rect: CGRect) {
    // Drawing code

}
*/
func activateConstraints(){
    NSLayoutConstraint.activate([self.btnLeading, self.btnBottom, self.btnTop, self.btnWidth])
    NSLayoutConstraint.activate([self.txtfieldCenterY, self.txtfieldLeading, self.txtfieldTrailing])
}

func addContentToView(){
    //setting the sizes
    self.addSubview(self.userLocationBtn)

    self.btnLeading = NSLayoutConstraint(
        item: someButton,
        attribute: .leading,
        relatedBy: .equal,
        toItem: self,
        attribute: .leading,
        multiplier: 1.0,
        constant: 5.0)
    self.btnBottom = NSLayoutConstraint(
        item: someButton,
        attribute: .bottom,
        relatedBy: .equal,
        toItem: self,
        attribute: .bottom,
        multiplier: 1.0,
        constant: 0.0)
    self.btnTop = NSLayoutConstraint(
        item: someButton,
        attribute: .top,
        relatedBy: .equal,
        toItem: self,
        attribute: .top,
        multiplier: 1.0,
        constant: 0.0)
    self.btnWidth = NSLayoutConstraint(
        item: someButton,
        attribute: .width,
        relatedBy: .equal,
        toItem: self,
        attribute: .height,
        multiplier: 1.0,
        constant: 0.0)        


    self.addSubview(self.textfield)
    self.txtfieldLeading = NSLayoutConstraint(
        item: self.textfield,
        attribute: .leading,
        relatedBy: .equal,
        toItem: someButton,
        attribute: .trailing,
        multiplier: 1.0,
        constant: 5)
    self.txtfieldTrailing = NSLayoutConstraint(
        item: self.textfield,
        attribute: .trailing,
        relatedBy: .equal,
        toItem: self.doneButton,
        attribute: .leading,
        multiplier: 1.0,
        constant: -5)
    self.txtfieldCenterY = NSLayoutConstraint(
        item: self.textfield,
        attribute: .centerY,
        relatedBy: .equal,
        toItem: self,
        attribute: .centerY,
        multiplier: 1.0,
        constant: 0.0)
}
}
Starlord
la source