Comment initialiser / instancier une classe UIView personnalisée avec un fichier XIB dans Swift

139

J'ai une classe appelée MyClassqui est une sous-classe de UIView, que je veux initialiser avec un XIBfichier. Je ne sais pas comment initialiser cette classe avec le fichier xib appeléView.xib

class MyClass: UIView {

    // what should I do here? 
    //init(coder aDecoder: NSCoder) {} ?? 
}
Stephen Fox
la source
5
reportez-vous à l'exemple de code source complet pour iOS9 Swift 2.0 github.com/karthikprabhuA/CustomXIBSwift et thread associé stackoverflow.com/questions/24857986/…
karthikPrabhu Alagu

Réponses:

266

J'ai testé ce code et cela fonctionne très bien:

class MyClass: UIView {        
    class func instanceFromNib() -> UIView {
        return UINib(nibName: "nib file name", bundle: nil).instantiateWithOwner(nil, options: nil)[0] as UIView
    }    
}

Initialisez la vue et utilisez-la comme ci-dessous:

var view = MyClass.instanceFromNib()
self.view.addSubview(view)

OU

var view = MyClass.instanceFromNib
self.view.addSubview(view())

METTRE À JOUR Swift> = 3.x & Swift> = 4.x

class func instanceFromNib() -> UIView {
    return UINib(nibName: "nib file name", bundle: nil).instantiate(withOwner: nil, options: nil)[0] as! UIView
}
Ezimet
la source
1
Il devrait être var view = MyClass.instanceFromNib()& self.view.addSubview(view)par opposition à var view = MyClass.instanceFromNib& self.view.addSubview(view()). Juste une petite suggestion pour améliorer la réponse :)
Logan
1
Dans mon cas, vue initialisée plus tard! s'il s'agit de self.view.addSubview (vue), la vue doit être var view = MyClass.instanceFromNib ()
Ezimet
2
@Ezimet qu'en est-il des IBActions dans cette vue. où les manipuler. Comme j'ai un bouton dans ma vue (xib), comment gérer l'événement de clic IBAction de ce bouton.?
Qadir Hussain
6
Doit-il renvoyer "MyClass" au lieu de simplement UIView?
Kesong Xie
2
IBoutlets ne fonctionnent pas dans cette approche ... J'obtiens: "cette classe n'est pas compatible avec le codage de la valeur de clé pour la clé"
Radek Wilczak
82

La solution de Sam est déjà excellente, même si elle ne prend pas en compte différents bundles (NSBundle: forClass vient à la rescousse) et nécessite un chargement manuel, c'est-à-dire la saisie de code.

Si vous voulez une prise en charge complète de vos sorties Xib, différents bundles (à utiliser dans les frameworks!) Et obtenir un bel aperçu dans Storyboard, essayez ceci:

// NibLoadingView.swift
import UIKit

/* Usage: 
- Subclass your UIView from NibLoadView to automatically load an Xib with the same name as your class
- Set the class name to File's Owner in the Xib file
*/

@IBDesignable
class NibLoadingView: UIView {

    @IBOutlet weak var view: UIView!

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

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

    private func nibSetup() {
        backgroundColor = .clearColor()

        view = loadViewFromNib()
        view.frame = bounds
        view.autoresizingMask = [.FlexibleWidth, .FlexibleHeight]
        view.translatesAutoresizingMaskIntoConstraints = true

        addSubview(view)
    }

    private func loadViewFromNib() -> UIView {
        let bundle = NSBundle(forClass: self.dynamicType)
        let nib = UINib(nibName: String(self.dynamicType), bundle: bundle)
        let nibView = nib.instantiateWithOwner(self, options: nil).first as! UIView

        return nibView
    }

}

Utilisez votre xib comme d'habitude, c'est-à-dire connectez Outlets au File Owner et définissez la classe File Owner sur votre propre classe.

Utilisation: Sous-classez simplement votre propre classe View de NibLoadingView et définissez le nom de la classe sur File's Owner dans le fichier Xib

Aucun code supplémentaire requis plus.

Crédits où le crédit est dû: fourché cela avec des changements mineurs de DenHeadless sur GH. Mon essentiel: https://gist.github.com/winkelsdorf/16c481f274134718946328b6e2c9a4d8

Frederik Winkelsdorf
la source
8
Cette solution freine les connexions de sortie (car elle ajoute la vue chargée en tant que sous-vue) et l'appel nibSetupdepuis init?(coder:)provoquera une récursivité infinie en cas d'incorporation NibLoadingViewdans un XIB.
redent84
3
@ redent84 Merci pour votre commentaire et votre vote défavorable. Si vous avez un deuxième regard, il devrait remplacer le SubView précédent (aucune nouvelle variable d'instance n'a été donnée). Vous avez raison sur la récursion infinie, qui devrait être omise si vous avez des difficultés avec IB.
Frederik Winkelsdorf
1
Comme mentionné, «connectez Outlets au propriétaire de fichier et définissez la classe de propriétaire de fichier sur votre propre classe». connectez les prises au propriétaire du fichier
Yusuf X
1
Je suis toujours mal à l'aise d'utiliser cette méthode pour charger la vue depuis xib. Nous ajoutons essentiellement une vue qui est une sous-classe de la classe A, dans une vue qui est également une sous-classe de la classe A. N'y a-t-il pas un moyen d'empêcher cette itération?
Prajeet Shrestha
1
@PrajeetShrestha Cela est probablement dû au fait que nibSetup () remplace la couleur d'arrière-plan .clearColor()- après l'avoir chargée à partir du Storyboard. Mais cela devrait fonctionner si vous le faites par code après son instanciation. Quoi qu'il en soit, comme dit une approche encore plus élégante est celle basée sur le protocole. Alors bien sûr, voici un lien pour vous: github.com/AliSoftware/Reusable . J'utilise maintenant une approche similaire concernant les UITableViewCells (que j'ai implémentées avant de découvrir ce projet vraiment utile). hth!
Frederik Winkelsdorf
77

Depuis Swift 2.0, vous pouvez ajouter une extension de protocole. À mon avis, c'est une meilleure approche car le type de retour est Selfplutôt que UIView, donc l'appelant n'a pas besoin de convertir en classe de vue.

import UIKit

protocol UIViewLoading {}
extension UIView : UIViewLoading {}

extension UIViewLoading where Self : UIView {

  // note that this method returns an instance of type `Self`, rather than UIView
  static func loadFromNib() -> Self {
    let nibName = "\(self)".characters.split{$0 == "."}.map(String.init).last!
    let nib = UINib(nibName: nibName, bundle: nil)
    return nib.instantiateWithOwner(self, options: nil).first as! Self
  }

}
Sam
la source
4
C'est une meilleure solution que la réponse sélectionnée car il n'y a pas besoin de transtypage et elle sera également réutilisable dans toutes les autres sous-classes UIView que vous créez à l'avenir.
user3344977
3
J'ai essayé cela avec Swift 2.1 et Xcode 7.2.1. Cela fonctionnait une partie du temps et se bloquait de manière imprévisible à d'autres avec un verrou mutex. Les deux dernières lignes utilisées directement dans le code ont fonctionné à chaque fois avec la dernière ligne modifiée pour êtrevar myView = nib.instantiate... as! myViewType
Paul Linsay
@ jr-root-cs Votre édition contenait des fautes de frappe / erreurs, j'ai dû l'annuler. Et de toute façon, veuillez ne pas ajouter de code aux réponses existantes. Faites plutôt un commentaire; ou ajoutez votre version dans votre propre réponse . Merci.
Eric Aya
J'ai posté du code que j'avais ouvert et testé dans mon projet en utilisant Swift 3(XCode 8.0 beta 6) sans problème. La faute de frappe était là Swift 2. Pourquoi cela devrait-il être une autre réponse lorsque cette réponse est bonne et que les utilisateurs aimeront probablement rechercher quels sont les changements lorsqu'ils utiliseront XC8
jr.root.cs
1
@ jr.root.cs Oui, cette réponse est bonne, c'est pourquoi personne ne devrait la modifier. C'est la réponse de Sam, pas la vôtre. Si vous souhaitez le commenter, laissez un commentaire; si vous souhaitez publier une nouvelle version / mise à jour, faites-le dans votre propre message . Les modifications visent à corriger les fautes de frappe / l'indentation / les balises pour ne pas ajouter vos versions dans les publications d'autres personnes. Merci.
Eric Aya
37

Et c'est la réponse de Frederik sur Swift 3.0

/*
 Usage:
 - make your CustomeView class and inherit from this one
 - in your Xib file make the file owner is your CustomeView class
 - *Important* the root view in your Xib file must be of type UIView
 - link all outlets to the file owner
 */
@IBDesignable
class NibLoadingView: UIView {

    @IBOutlet weak var view: UIView!

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

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

    private func nibSetup() {
        backgroundColor = .clear

        view = loadViewFromNib()
        view.frame = bounds
        view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        view.translatesAutoresizingMaskIntoConstraints = true

        addSubview(view)
    }

    private func loadViewFromNib() -> UIView {
        let bundle = Bundle(for: type(of: self))
        let nib = UINib(nibName: String(describing: type(of: self)), bundle: bundle)
        let nibView = nib.instantiate(withOwner: self, options: nil).first as! UIView

        return nibView
    }
}
Mahmood S
la source
31

Méthode universelle de chargement de la vue depuis xib:

Exemple:

let myView = Bundle.loadView(fromNib: "MyView", withType: MyView.self)

La mise en oeuvre:

extension Bundle {

    static func loadView<T>(fromNib name: String, withType type: T.Type) -> T {
        if let view = Bundle.main.loadNibNamed(name, owner: nil, options: nil)?.first as? T {
            return view
        }

        fatalError("Could not load view with type " + String(describing: type))
    }
}
Максим Мартынов
la source
Meilleure réponse à moi comme vue de sortie comme type de la sous
UIView
26

Réponse Swift 3: Dans mon cas, je voulais avoir un point de vente dans ma classe personnalisée que je pourrais modifier:

class MyClassView: UIView {
    @IBOutlet weak var myLabel: UILabel!

    class func createMyClassView() -> MyClass {
        let myClassNib = UINib(nibName: "MyClass", bundle: nil)
        return myClassNib.instantiate(withOwner: nil, options: nil)[0] as! MyClassView
    }
}

Lorsque dans le .xib, assurez-vous que le champ Classe personnalisée est MyClassView. Ne vous embêtez pas avec le propriétaire du fichier.

Assurez-vous que la classe personnalisée est MyClassView

Assurez-vous également de connecter la prise dans MyClassView à l'étiquette: Outlet pour myLabel

Pour l'instancier:

let myClassView = MyClassView.createMyClassView()
myClassView.myLabel.text = "Hello World!"
Jérémie
la source
Je récupère "chargé la pointe" MyClas "mais la sortie de vue n'a pas été définie. '" Si le propriétaire n'est pas défini
jcharch
22

Swift 4

Ici, dans mon cas, je dois transmettre des données dans cette vue personnalisée, donc je crée une fonction statique pour instancier la vue.

  1. Créer une extension UIView

    extension UIView {
        class func initFromNib<T: UIView>() -> T {
            return Bundle.main.loadNibNamed(String(describing: self), owner: nil, options: nil)?[0] as! T
        }
    }
  2. Créer MyCustomView

    class MyCustomView: UIView {
    
        @IBOutlet weak var messageLabel: UILabel!
    
        static func instantiate(message: String) -> MyCustomView {
            let view: MyCustomView = initFromNib()
            view.messageLabel.text = message
            return view
        }
    }
  3. Définissez la classe personnalisée sur MyCustomView dans le fichier .xib. Branchez la prise si nécessaire comme d'habitude. entrez la description de l'image ici

  4. Instancier la vue

    let view = MyCustomView.instantiate(message: "Hello World.")
mnémonique23
la source
s'il y a des boutons dans la vue personnalisée, comment pouvons-nous gérer leurs actions dans différents contrôleurs de vue?
jayant rawat le
vous pouvez utiliser le délégué de protocole. jetez un œil ici stackoverflow.com/questions/29602612/… .
mnemonic23
Il n'a pas réussi à charger une image dans xib, obtenant l'erreur suivante. Impossible de charger l' image " IBBrokenImage " référencée à partir d'une pointe dans le bundle avec l'identifiant "test.Testing"
Ravi Raja Jangid
-4
override func draw(_ rect: CGRect) 
{
    AlertView.layer.cornerRadius = 4
    AlertView.clipsToBounds = true

    btnOk.layer.cornerRadius = 4
    btnOk.clipsToBounds = true   
}

class func instanceFromNib() -> LAAlertView {
    return UINib(nibName: "LAAlertView", bundle: nil).instantiate(withOwner: nil, options: nil)[0] as! LAAlertView
}

@IBAction func okBtnDidClicked(_ sender: Any) {

    removeAlertViewFromWindow()

    UIView.animate(withDuration: 0.4, delay: 0.0, options: .allowAnimatedContent, animations: {() -> Void in
        self.AlertView.transform = CGAffineTransform(scaleX: 0.1, y: 0.1)

    }, completion: {(finished: Bool) -> Void in
        self.AlertView.transform = CGAffineTransform.identity
        self.AlertView.transform = CGAffineTransform(scaleX: 0.0, y: 0.0)
        self.AlertView.isHidden = true
        self.AlertView.alpha = 0.0

        self.alpha = 0.5
    })
}


func removeAlertViewFromWindow()
{
    for subview  in (appDel.window?.subviews)! {
        if subview.tag == 500500{
            subview.removeFromSuperview()
        }
    }
}


public func openAlertView(title:String , string : String ){

    lblTital.text  = title
    txtView.text  = string

    self.frame = CGRect(x: 0, y: 0, width: screenWidth, height: screenHeight)
    appDel.window!.addSubview(self)


    AlertView.alpha = 1.0
    AlertView.isHidden = false

    UIView.animate(withDuration: 0.2, animations: {() -> Void in
        self.alpha = 1.0
    })
    AlertView.transform = CGAffineTransform(scaleX: 0.0, y: 0.0)

    UIView.animate(withDuration: 0.3, delay: 0.2, options: .allowAnimatedContent, animations: {() -> Void in
        self.AlertView.transform = CGAffineTransform(scaleX: 1.1, y: 1.1)

    }, completion: {(finished: Bool) -> Void in
        UIView.animate(withDuration: 0.2, animations: {() -> Void in
            self.AlertView.transform = CGAffineTransform(scaleX: 1.0, y: 1.0)

        })
    })


}
Madan Kumawat
la source
code mal formaté, pas d'explication, d'où le vote négatif.
J. Doe
Qu'est-ce que tout cela a à voir avec la question?
Ashley Mills