Charger un UIView à partir de la pointe dans Swift

148

Voici mon code Objective-C que j'utilise pour charger une pointe pour mon personnalisé UIView:

-(id)init{

    NSArray *subviewArray = [[NSBundle mainBundle] loadNibNamed:@"myXib" owner:self options:nil];
    return [subviewArray objectAtIndex:0];

}

Quel est le code équivalent dans Swift?

Bagusflyer
la source

Réponses:

172

Solution originale

  1. J'ai créé un XIB et une classe nommée SomeView (utilisé le même nom pour plus de commodité et de lisibilité). J'ai basé les deux sur un UIView.
  2. Dans le XIB, j'ai changé la classe "File's Owner" en SomeView (dans l'inspecteur d'identité).
  3. J'ai créé une prise UIView dans SomeView.swift, en la reliant à la vue de niveau supérieur dans le fichier XIB (nommée "vue" pour plus de commodité). J'ai ensuite ajouté d'autres prises à d'autres contrôles dans le fichier XIB au besoin.
  4. dans SomeView.swift, j'ai chargé le XIB dans l'initialiseur "init with code". Il n'est pas nécessaire d'attribuer quoi que ce soit à «soi». Dès que le XIB est chargé, toutes les prises sont connectées, y compris la vue de niveau supérieur. La seule chose qui manque, c'est d'ajouter la vue de dessus à la hiérarchie des vues:

.

class SomeView: UIView {
   required init(coder aDecoder: NSCoder) {
      super.init(coder: aDecoder)
      NSBundle.mainBundle().loadNibNamed("SomeView", owner: self, options: nil)
      self.addSubview(self.view);    // adding the top level view to the view hierarchy
   }
   ...
}

Notez que de cette façon, j'obtiens une classe qui se charge à partir de nib. Je pourrais alors utiliser SomeView comme classe chaque fois que UIView pourrait être utilisé dans le projet (dans le générateur d'interface ou par programme).

Mise à jour - utilisation de la syntaxe Swift 3

Le chargement d'un xib dans l'extension suivante est écrit comme une méthode d'instance, qui peut ensuite être utilisée par un initialiseur comme celui ci-dessus:

extension UIView {

    @discardableResult   // 1
    func fromNib<T : UIView>() -> T? {   // 2
        guard let contentView = Bundle(for: type(of: self)).loadNibNamed(String(describing: type(of: self)), owner: self, options: nil)?.first as? T else {    // 3
            // xib not loaded, or its top view is of the wrong type
            return nil
        }
        self.addSubview(contentView)     // 4
        contentView.translatesAutoresizingMaskIntoConstraints = false   // 5 
        contentView.layoutAttachAll(to: self)   // 6 
        return contentView   // 7
    }
}
  1. Utiliser une valeur de retour jetable car la vue renvoyée n'a généralement aucun intérêt pour l'appelant lorsque toutes les prises sont déjà connectées.
  2. Il s'agit d'une méthode générique qui renvoie un objet facultatif de type UIView. S'il ne parvient pas à charger la vue, il renvoie nil.
  3. Tentative de chargement d'un fichier XIB portant le même nom que l'instance de classe actuelle. Si cela échoue, nil est renvoyé.
  4. Ajout de la vue de niveau supérieur à la hiérarchie des vues.
  5. Cette ligne suppose que nous utilisons des contraintes pour mettre en page la vue.
  6. Cette méthode ajoute des contraintes de haut, de bas, de début et de fin - en attachant la vue à «soi» de tous les côtés (voir: https://stackoverflow.com/a/46279424/2274829 pour plus de détails)
  7. Retour à la vue de niveau supérieur

Et la méthode de l'appelant pourrait ressembler à ceci:

final class SomeView: UIView {   // 1.
   required init?(coder aDecoder: NSCoder) {   // 2 - storyboard initializer
      super.init(coder: aDecoder)
      fromNib()   // 5.
   }
   init() {   // 3 - programmatic initializer
      super.init(frame: CGRect.zero)  // 4.
      fromNib()  // 6.
   }
   // other methods ...
}
  1. SomeClass est une sous-classe UIView qui charge son contenu à partir d'un fichier SomeClass.xib. Le mot-clé "final" est facultatif.
  2. Un initialiseur lorsque la vue est utilisée dans un storyboard (n'oubliez pas d'utiliser SomeClass comme classe personnalisée de votre vue storyboard).
  3. Un initialiseur pour quand la vue est créée par programme (c'est-à-dire: "let myView = SomeView ()").
  4. Utilisation d'un cadre entièrement zéros puisque cette vue est mise en page à l'aide de la mise en page automatique. Notez qu'une méthode "init (frame: CGRect) {..}" n'est pas créée indépendamment, car la mise en page automatique est utilisée exclusivement dans notre projet.
  5. & 6. Chargement du fichier xib en utilisant l'extension.

Crédit: L'utilisation d'une extension générique dans cette solution a été inspirée par la réponse de Robert ci-dessous.

Modifier Changer "vue" en "contentView" pour éviter toute confusion. Également changé l'indice du tableau en ".first".

GK100
la source
7
Définir le nom de la classe pour qu'il File's Owneratteigne l'endroit ... Merci!
Aviel Gross
13
UIView n'a pas de vue de propriété, donc appeler self.view provoque une erreur
Nastya Gorban
4
@NastyaGorban self.view fait en fait référence dans ce cas à la propriété de sortie (nommée "vue) que GK100 a liée de la vue de niveau supérieur dans le .xib à SomeView.swift. Ne pas ajouter cette sortie vous donnera une erreur car il n'y a pas de" vue " "propriété dans les classes NSView comme vous dites.
Ausiàs
3
Je reçois un crash lors du chargement de nib (loadNibNamed). Utilisation de Xcode 6.3 et Swift
karthikPrabhu Alagu
11
l'appel fromNib()de l'intérieur init(coder aDecoder: NSCoder)crée une boucle infinie lorsque le chargement de la Nib à l'intérieur de la fromNib()méthode appelle:init(coder aDecoder: NSCoder)
Matthew Cawley
334

Ma contribution:

extension UIView {
    class func fromNib<T: UIView>() -> T {
        return Bundle(for: T.self).loadNibNamed(String(describing: T.self), owner: nil, options: nil)![0] as! T
    }
}

Alors appelez-le comme ceci:

let myCustomView: CustomView = UIView.fromNib()

..ou même:

let myCustomView: CustomView = .fromNib()
Robert Gummesson
la source
20
Meilleure réponse de loin.
CodyMace
7
Meilleure réponse ici. Clean and Simple
Marquavious Draggon
3
@YuchenZhong - Je préfère [0] à .first car cela renverrait une option. Si vous forcez le déballage, ce ne serait pas plus sûr. ... et cela soulève la question: pourquoi ne pas retourner une option comme certaines des solutions ci-dessus? Réponse: vous pouvez. Aucun problème avec ça. Mais ... si jamais il retournait nil, le nom de la classe xib / ne correspond pas. Il s'agit d'une erreur du développeur et doit être détectée immédiatement et ne jamais arriver en production. Ici, je préférerais que l'application plante au lieu de la laisser dans un état étrange. Juste mes 2 cents / préférence.
Robert Gummesson
1
@allenlinli - La méthode est une extension statique de UIView comme supposé pour CustomView. Cela fonctionne car le compilateur infère le type à l'aide de l'annotation de type explicite. Étant donné que CustomView est une sous-classe de UIView et que le type a déjà été déduit, nous n'avons pas besoin de l'inférer à nouveau, UIView peut donc être omis comme indiqué dans mon deuxième exemple. Cela dit, vous pouvez évidemment passer l'appel comme vous l'avez écrit.
Robert Gummesson
3
Cette solution n'a pas fonctionné pour moi dans le cas où il y avait une vue personnalisée dans le .xib. Je suggérerais de corriger cette partie à: return Bundle.main.loadNibNamed (String (décrivant: self), owner: nil, options: nil)! [0] as! T
Nadzeya
79

Le fait de pouvoir revenir -> Selfrapidement permet de simplifier un peu cela. Dernière confirmation sur Swift 5.

extension UIView {
    class func fromNib(named: String? = nil) -> Self {
        let name = named ?? "\(Self.self)"
        guard
            let nib = Bundle.main.loadNibNamed(name, owner: nil, options: nil)
            else { fatalError("missing expected nib named: \(name)") }
        guard
            /// we're using `first` here because compact map chokes compiler on
            /// optimized release, so you can't use two views in one nib if you wanted to
            /// and are now looking at this
            let view = nib.first as? Self
            else { fatalError("view of type \(Self.self) not found in \(nib)") }
        return view
    }
}

Si votre .xibfichier et votre sous-classe partagent le même nom, vous pouvez utiliser:

let view = CustomView.fromNib()

Si vous avez un nom personnalisé, utilisez:

let view = CustomView.fromNib(named: "special-case")

REMARQUE:

Si vous obtenez l'erreur "vue de type YourType introuvable dans ..", vous n'avez pas défini la classe de la vue dans le .xibfichier

Sélectionnez votre vue dans le .xibfichier, et appuyez sur cmd + opt + 4et dans l' classentrée, entrez votre classe

Logan
la source
1
Je ne peux pas faire fonctionner cela sous XCode 7.1 beta 3 - je ne sais pas si c'est une chose bêta, mais en gros, j'ai essayé par tous les moyens de créer une vue personnalisée directement à partir d'une pointe dans Swift et j'obtiens toujours le même résultat: la classe qu'il crée n'est pas compatible KVC avec les prises. Je ne sais pas si c'est quelque chose que je fais mal, mais ma classe est assez simple et le propriétaire du fichier est correct. J'avais l'habitude de faire cela tout le temps sous Objective-C.
Echelon
1
@Logan ce n'est pas vraiment lié à votre code, mais les vues personnalisées imo devraient prendre en charge le chargement à partir de Storyboard / XIB. Mon commentaire était juste une notification pour ceux qui veulent créer de telles vues
Nikita a pris
1
Remarque J'ai toujours un problème en utilisant la deuxième forme d'appel de cette fonction, à savoir let myCustomView = UIView.fromNib() as? CustomView. Dans ce cas, T.selfrésout UIViewplutôt que CustomViewet ne parvient pas à trouver la pointe. Je ne sais pas pourquoi c'est - peut-être le type inféré pour les letmoyens que la fonction est appelée en tant que UIView?
Echelon du
2
Il est important de souligner qu'essayer d'utiliser File's Owner pour brancher les prises (comme nous le faisions au bon vieux temps) provoquera un plantage. Dans IB, le propriétaire du fichier doit être nul / vide et les prises doivent être connectées à la vue à la place.
Echelon du
1
@Echelon tu as sauvé ma journée !!! J'ai connecté mes prises en utilisant le propriétaire du fichier et cela n'a pas fonctionné, l'utilisation de la vue a plutôt fonctionné.
Jan Schlorf
19

essayez de suivre le code.

var uiview :UIView?

self.uiview = NSBundle.mainBundle().loadNibNamed("myXib", owner: self, options: nil)[0] as? UIView

Éditer:

import UIKit

class TestObject: NSObject {

     var uiview:UIView?

    init()  {
        super.init()
       self.uiview = NSBundle.mainBundle().loadNibNamed("myXib", owner: self, options: nil)[0] as? UIView
    }


}
Jasmin
la source
J'ai besoin d'appeler cette méthode dans la méthode d'initialisation d'objet qui est init () dans Swift.
Bagusflyer
12

Extensions de protocole Swift 4

public protocol NibInstantiatable {

    static func nibName() -> String

}

extension NibInstantiatable {

    static func nibName() -> String {
        return String(describing: self)
    }

}

extension NibInstantiatable where Self: UIView {

    static func fromNib() -> Self {

        let bundle = Bundle(for: self)
        let nib = bundle.loadNibNamed(nibName(), owner: self, options: nil)

        return nib!.first as! Self

    }

}

Adoption

class MyView: UIView, NibInstantiatable {

}

Cette implémentation suppose que le Nib porte le même nom que la classe UIView. Ex. MyView.xib. Vous pouvez modifier ce comportement en implémentant nibName () dans MyView pour renvoyer un nom différent de l'implémentation d'extension de protocole par défaut.

Dans le xib, le propriétaire des fichiers est MyView et la classe de vue racine est MyView.

Usage

let view = MyView.fromNib()
Brody Robertson
la source
3
C'est de loin la solution la plus élégante et la plus simple et je ne sais pas pourquoi ce n'est pas la réponse acceptée!
horseshoe7
@ Horseshoe7 parce que c'est écrit 4 ans après la question.
Freeubi le
11

J'ai réalisé cela avec Swift par le code suivant:

class Dialog: UIView {
    @IBOutlet var view:UIView!

    override init(frame: CGRect) {
        super.init(frame: frame)
        self.frame = UIScreen.mainScreen().bounds
        NSBundle.mainBundle().loadNibNamed("Dialog", owner: self, options: nil)
        self.view.frame = UIScreen.mainScreen().bounds
        self.addSubview(self.view)
    }

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

N'oubliez pas de connecter votre prise de vue XIB pour voir la prise définie dans swift. Vous pouvez également définir First Responder sur votre nom de classe personnalisé pour commencer à connecter des prises supplémentaires.

J'espère que cela t'aides!

Amro Shafie
la source
10

Testé dans Xcode 7 beta 4, Swift 2.0 et iOS9 SDK. Le code suivant attribuera xib à uiview. Vous pouvez utiliser cette vue xib personnalisée dans le storyboard et accéder également à l'objet IBOutlet.

import UIKit

@IBDesignable class SimpleCustomView:UIView
{
    var view:UIView!;

    @IBOutlet weak var lblTitle: UILabel!

   @IBInspectable var lblTitleText : String?
        {
        get{
            return lblTitle.text;
        }
        set(lblTitleText)
        {
            lblTitle.text = lblTitleText!;
        }
    }

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

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        loadViewFromNib ()
    }
    func loadViewFromNib() {
        let bundle = NSBundle(forClass: self.dynamicType)
        let nib = UINib(nibName: "SimpleCustomView", bundle: bundle)
        let view = nib.instantiateWithOwner(self, options: nil)[0] as! UIView
        view.frame = bounds
        view.autoresizingMask = [.FlexibleWidth, .FlexibleHeight]
        self.addSubview(view);



    }


}

Accéder à la vue personnalisée par programme

self.customView =  SimpleCustomView(frame: CGRectMake(100, 100, 200, 200))
        self.view.addSubview(self.customView!);

Code source - https://github.com/karthikprabhuA/CustomXIBSwift

karthikPrabhu Alagu
la source
9

Si vous avez beaucoup de vues personnalisées dans votre projet, vous pouvez créer une classe comme UIViewFromNib

Swift 2.3

class UIViewFromNib: UIView {

    var contentView: UIView!

    var nibName: String {
        return String(self.dynamicType)
    }

    //MARK:
    override init(frame: CGRect) {
        super.init(frame: frame)

        loadViewFromNib()
    }

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

        loadViewFromNib()
    }

    //MARK:
    private func loadViewFromNib() {
        contentView = NSBundle.mainBundle().loadNibNamed(nibName, owner: self, options: nil)[0] as! UIView
        contentView.autoresizingMask = [.FlexibleWidth, .FlexibleHeight]
        contentView.frame = bounds
        addSubview(contentView)
    }
}

Swift 3

class UIViewFromNib: UIView {

    var contentView: UIView!

    var nibName: String {
        return String(describing: type(of: self))
    }

    //MARK:
    override init(frame: CGRect) {
        super.init(frame: frame)

        loadViewFromNib()
    }

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

        loadViewFromNib()
    }

    //MARK:
    func loadViewFromNib() {
        contentView = Bundle.main.loadNibNamed(nibName, owner: self, options: nil)?[0] as! UIView
        contentView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        contentView.frame = bounds
        addSubview(contentView)
    }
}

Et dans chaque classe juste héritée de UIViewFromNib, vous pouvez également remplacer la nibNamepropriété si le .xibfichier a un nom différent:

class MyCustomClass: UIViewFromNib {

}
ChikabuZ
la source
8

S'appuyant sur les solutions ci-dessus.

Cela fonctionnera sur tous les lots de projets et aucun besoin de génériques lors de l'appel de fromNib ().

Swift 2

extension UIView {

    public class func fromNib() -> Self {
        return fromNib(nil)
    }

    public class func fromNib(nibName: String?) -> Self {

        func fromNibHelper<T where T : UIView>(nibName: String?) -> T {
            let bundle = NSBundle(forClass: T.self)
            let name = nibName ?? String(T.self)
            return bundle.loadNibNamed(name, owner: nil, options: nil)?.first as? T ?? T()
        }
        return fromNibHelper(nibName)
    }
}

Swift 3

extension UIView {

    public class func fromNib() -> Self {
        return fromNib(nibName: nil)
    }

    public class func fromNib(nibName: String?) -> Self {
        func fromNibHelper<T>(nibName: String?) -> T where T : UIView {
            let bundle = Bundle(for: T.self)
            let name = nibName ?? String(describing: T.self)
            return bundle.loadNibNamed(name, owner: nil, options: nil)?.first as? T ?? T()
        }
        return fromNibHelper(nibName: nibName)
    }
}

Peut être utilisé comme ceci:

let someView = SomeView.fromNib()

Ou comme ça:

let someView = SomeView.fromNib("SomeOtherNibFileName")
Genèse
la source
6

Swift 4

N'oubliez pas d'écrire ".first as? CustomView".

if let customView = Bundle.main.loadNibNamed("myXib", owner: self, options: nil)?.first as? CustomView {    
    self.view.addSubview(customView)
    }

Si vous souhaitez utiliser n'importe où

La meilleure solution est la réponse de Robert Gummesson .

extension UIView {
    class func fromNib<T: UIView>() -> T {
        return Bundle.main.loadNibNamed(String(describing: T.self), owner: nil, options: nil)![0] as! T
    }
}

Alors appelez-le comme ceci:

let myCustomView: CustomView = UIView.fromNib()
Ali Ihsan URAL
la source
5
let subviewArray = NSBundle.mainBundle().loadNibNamed("myXib", owner: self, options: nil)
return subviewArray[0]
BaSha
la source
Mais dans init () de Swift, il n'y a pas de valeur renvoyée. J'ai oublié de mentionner que je dois appeler loadNibNamed lors de l'initialisation d'un UIView.
Bagusflyer
Que voulez-vous dire "aucune valeur de retour"? selfest renvoyé implicitement de toutes les initméthodes ...
Grimxn
1
Ce que je veux dire, c'est que j'appelle loadNibNamed dans la méthode init. l'UIView chargé est assigné à self dans ObjC. Mais rapidement, ce n'est pas le cas.
Bagusflyer
5

Une bonne façon de faire cela avec Swift est d'utiliser une énumération.

enum Views: String {
    case view1 = "View1" // Change View1 to be the name of your nib
    case view2 = "View2" // Change View2 to be the name of another nib

    func getView() -> UIView? {
        return NSBundle.mainBundle().loadNibNamed(self.rawValue, owner: nil, options: nil).first as? UIView
    }
}

Ensuite, dans votre code, vous pouvez simplement utiliser:

let view = Views.view1.getView()
totiG
la source
2
Notez que si vous faites cela avec un fichier nib vide ou un fichier nib avec un nœud racine UIView non, vous planterez car vous ne vérifiez pas la taille du tableau ou l'élément en position 0.
Matthew Cawley
4

Je préfère cette solution (basée sur la réponse si @ GK100):

  1. J'ai créé un XIB et une classe nommée SomeView (utilisé le même nom pour plus de commodité et de lisibilité). J'ai basé les deux sur un UIView.
  2. Dans le XIB, j'ai changé la classe "File's Owner" en SomeView (dans l'inspecteur d'identité).
  3. J'ai créé une prise UIView dans SomeView.swift, en la reliant à la vue de niveau supérieur dans le fichier XIB (nommée "vue" pour plus de commodité). J'ai ensuite ajouté d'autres prises à d'autres contrôles dans le fichier XIB au besoin.
  4. Dans SomeView.swift, j'ai chargé le XIB dans l' initialiseur initou init:frame: CGRect. Il n'est pas nécessaire d'attribuer quoi que ce soit à «soi». Dès que le XIB est chargé, toutes les prises sont connectées, y compris la vue de niveau supérieur. La seule chose qui manque, c'est d'ajouter la vue de dessus à la hiérarchie des vues:

    class SomeView: UIView {
      override init(frame: CGRect) {
        super.init(frame: frame)
        NSBundle.mainBundle().loadNibNamed("SomeObject", owner: self, options: nil)
        self.addSubview(self.view);    // adding the top level view to the view hierarchy
      }
    
      required init(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        NSBundle.mainBundle().loadNibNamed("SomeObject", owner: self, options: nil)
        self.addSubview(self.view);    // adding the top level view to the view hierarchy
      }
    
    
      ...
    }
confiler
la source
Je préfère utiliser init avec frame donc j'ai déraciné ça! une chose à noter ... ajoutez self.view.frame = frame si vous souhaitez que la vue corresponde à l'image que vous passez
Mike E
3

Version Swift 3 de la réponse de Logan

extension UIView {
    public class func fromNib(nibName: String? = nil) -> Self {
        return fromNib(nibName: nibName, type: self)
    }

    public class func fromNib<T: UIView>(nibName: String? = nil, type: T.Type) -> T {
        return fromNib(nibName: nibName, type: T.self)!
    }

    public class func fromNib<T: UIView>(nibName: String? = nil, type: T.Type) -> T? {
        var view: T?
        let name: String

        if let nibName = nibName {
            name = nibName
        } else {
            name = self.nibName
        }

        if let nibViews = Bundle.main.loadNibNamed(name, owner: nil, options: nil) {
            for nibView in nibViews {
                if let tog = nibView as? T {
                    view = tog
                }
            }
        }

        return view
    }

    public class var nibName: String {
        return "\(self)".components(separatedBy: ".").first ?? ""
    }

    public class var nib: UINib? {
        if let _ = Bundle.main.path(forResource: nibName, ofType: "nib") {
            return UINib(nibName: nibName, bundle: nil)
        } else {
            return nil
        }
    }
}
Alexey Karetski
la source
3

Voici une manière propre et déclarative de charger par programme une vue à l'aide d'un protocole et d'une extension de protocole (Swift 4.2):

protocol XibLoadable {
    associatedtype CustomViewType
    static func loadFromXib() -> CustomViewType
}

extension XibLoadable where Self: UIView {
    static func loadFromXib() -> Self {
        let nib = UINib(nibName: "\(self)", bundle: Bundle(for: self))
        guard let customView = nib.instantiate(withOwner: self, options: nil).first as? Self else {
            // your app should crash if the xib doesn't exist
            preconditionFailure("Couldn't load xib for view: \(self)")
        }
        return customView
    }
}

Et vous pouvez utiliser ceci comme ceci:

// don't forget you need a xib file too
final class MyView: UIView, XibLoadable { ... }

// and when you want to use it
let viewInstance = MyView.loadFromXib()

Quelques considérations supplémentaires :

  1. Assurez-vous que le fichier xib de votre vue personnalisée a l' Custom Classensemble de la vue (et les sorties / actions définies à partir de là), et non celui du propriétaire du fichier.
  2. Vous pouvez utiliser ce protocole / extension externe à votre vue personnalisée ou interne. Vous voudrez peut-être l'utiliser en interne si vous avez d'autres travaux de configuration lors de l'initialisation de votre vue.
  3. Votre classe de vue personnalisée et votre fichier xib doivent avoir le même nom.
Jason Z
la source
2

Je fais juste de cette façon:

if let myView = UINib.init(nibName: "MyView", bundle: nil).instantiate(withOwner: self)[0] as? MyView {
// Do something with myView
}

Cet exemple utilise la première vue de la pointe "MyView.xib" dans le bundle principal. Mais vous pouvez faire varier l'index, le nom de la pointe ou le bundle (main par défaut).

J'avais l'habitude de réveiller des vues dans la méthode view init ou de créer des méthodes génériques comme dans les solutions ci-dessus (qui sont intelligentes d'ailleurs), mais je ne le fais plus.

De cette façon, je peux utiliser différentes dispositions ou caractéristiques tout en conservant la même logique de vue et le même code.

Je trouve plus facile de laisser un objet d'usine (généralement le viewController qui utilisera la vue) le créer comme il en a besoin. Parfois, vous avez besoin d'un propriétaire (généralement lorsque la vue créée est connectée au créateur), parfois non.

C'est probablement pourquoi Apple n'a pas inclus de initFromNibméthode dans sa classe UIView ...

Pour prendre un exemple au niveau du sol, vous ne savez pas comment vous êtes né. Vous venez de naître. Ainsi sont les vues;)

élan
la source
2

Tout ce que vous avez à faire est d'appeler la méthode init dans votre UIViewclasse.

Faites-le de cette façon:

class className: UIView {

    @IBOutlet var view: UIView!

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

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

    func setup() {
        UINib(nibName: "nib", bundle: nil).instantiateWithOwner(self, options: nil)
        addSubview(view)
        view.frame = self.bounds
    }
}

Maintenant, si vous souhaitez ajouter cette vue en tant que sous-vue dans le contrôleur de vue, faites-le de cette façon dans le fichier view controller.swift:

self.view.addSubview(className())
Alap Anerao
la source
c'est une bonne réponse mais il y a quelque chose qui ne va pas, je vais le modifier.
C0mrade
C'est la façon dont j'ai mis en œuvre. Mais vous pouvez l'improviser. Merci d'avance @ C0mrade
Alap Anerao
1

Similaire à certaines des réponses ci-dessus mais une extension Swift3 UIView plus cohérente:

extension UIView {
    class func fromNib<A: UIView> (nibName name: String, bundle: Bundle? = nil) -> A? {
        let bundle = bundle ?? Bundle.main
        let nibViews = bundle.loadNibNamed(name, owner: self, options: nil)
        return nibViews?.first as? A
    }

    class func fromNib<T: UIView>() -> T? {
        return fromNib(nibName: String(describing: T.self), bundle: nil)
    }
}

Ce qui donne la commodité de pouvoir charger la classe à partir d'une nib auto-nommée mais aussi à partir d'autres nibs / bundles.

Mark Johnson
la source
1

Vous pouvez le faire via le storyboard, ajoutez simplement les contraintes appropriées pour la vue. Vous pouvez le faire facilement en sous-classant n'importe quelle vue de votre choix, disons BaseView:

Objectif c

BaseView.h


/*!
 @class BaseView
 @discussion Base View for getting view from xibFile
 @availability ios7 and later
 */
@interface BaseView : UIView

@end


BaseView.m


#import "BaseView.h"

@implementation BaseView

#pragma mark - Public

- (instancetype)initWithCoder:(NSCoder *)coder
{
    self = [super initWithCoder:coder];
    if (self) {
        [self prepareView];
    }
    return self;
}

#pragma mark - LifeCycle

- (instancetype)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        [self prepareView];
    }
    return self;
}

#pragma mark - Private

- (void)prepareView
{
    NSArray *nibsArray = [[NSBundle mainBundle] loadNibNamed:NSStringFromClass([self class]) owner:self options:nil];
    UIView *view = [nibsArray firstObject];

    view.translatesAutoresizingMaskIntoConstraints = NO;
    [self addSubview:view];
    [self addConstraintsForView:view];
}

#pragma mark - Add constraints

- (void)addConstraintsForView:(UIView *)view
{
    [self addConstraints:@[[NSLayoutConstraint constraintWithItem:view
                                                        attribute:NSLayoutAttributeBottom
                                                        relatedBy:NSLayoutRelationEqual
                                                           toItem:self attribute:NSLayoutAttributeBottom
                                                       multiplier:1.0
                                                         constant:0],
                           [NSLayoutConstraint constraintWithItem:view
                                                        attribute:NSLayoutAttributeTop
                                                        relatedBy:NSLayoutRelationEqual
                                                           toItem:self attribute:NSLayoutAttributeTop
                                                       multiplier:1.0
                                                         constant:0],
                           [NSLayoutConstraint constraintWithItem:view
                                                        attribute:NSLayoutAttributeLeft
                                                        relatedBy:NSLayoutRelationEqual
                                                           toItem:self attribute:NSLayoutAttributeLeft
                                                       multiplier:1.0
                                                         constant:0],
                           [NSLayoutConstraint constraintWithItem:view
                                                        attribute:NSLayoutAttributeRight
                                                        relatedBy:NSLayoutRelationEqual
                                                           toItem:self attribute:NSLayoutAttributeRight
                                                       multiplier:1.0
                                                         constant:0]
                           ]];
}

@end

Swift 4

import UIKit

class BaseView : UIView {

    // MARK: - LifeCycle

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

        prepareView()
    }

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

        prepareView()
    }

    internal class func xibName() -> String {
        return String(describing: self)
    }

    // MARK: - Private
    fileprivate func prepareView() {
        let nameForXib = BaseView.xibName()
        let nibs = Bundle.main.loadNibNamed(nameForXib, owner: self, options: nil)
        if let view = nibs?.first as? UIView {
            view.backgroundColor = UIColor.clear
            view.translatesAutoresizingMaskIntoConstraints = false
            addSubviewWithConstraints(view, offset: false)
        }
    }
}

UIView+Subview


public extension UIView {
    // MARK: - UIView+Extensions

    public func addSubviewWithConstraints(_ subview:UIView, offset:Bool = true) {
        subview.translatesAutoresizingMaskIntoConstraints = false
        let views = [
            "subview" : subview
        ]
        addSubview(subview)

        var constraints = NSLayoutConstraint.constraints(withVisualFormat: offset ? "H:|-[subview]-|" : "H:|[subview]|", options: [.alignAllLeading, .alignAllTrailing], metrics: nil, views: views)
        constraints.append(contentsOf: NSLayoutConstraint.constraints(withVisualFormat: offset ? "V:|-[subview]-|" : "V:|[subview]|", options: [.alignAllTop, .alignAllBottom], metrics: nil, views: views))
        NSLayoutConstraint.activate(constraints)
    }
}

Je propose 2 variantes pour ajouter des contraintes - commune et dans le langage de format visuel - sélectionnez celle que vous voulez :)

De plus, par défaut, on suppose que ce xibnom a le même nom que le nom de la classe d'implémentation. Si non, changez simplement le xibNameparamètre.

Si vous sous-classez votre vue de BaseView- vous pouvez facilement placer n'importe quelle vue et spécifier la classe dans IB.

gbk
la source
1
class func loadFromNib<T: UIView>() -> T {
    let nibName = String(describing: self)
    return Bundle.main.loadNibNamed(nibName, owner: nil, options: nil)![0] as! T
}
Nadzeya
la source
0

Version plus puissante basée sur la réponse de Logan

extension UIView {
    public class func fromNib(nibName: String? = nil) -> Self {
        return fromNib(nibName: nibName, type: self)
    }

    public class func fromNib<T: UIView>(nibName: String? = nil, type: T.Type) -> T {
        return fromNib(nibName: nibName, type: T.self)!
    }

    public class func fromNib<T: UIView>(nibName: String? = nil, type: T.Type) -> T? {
        var view: T?
        let name: String

        if let nibName = nibName {
            name = nibName
        } else {
            name = self.nibName
        }

        if let nibViews = nibBundle.loadNibNamed(name, owner: nil, options: nil) {
            if nibViews.indices.contains(nibIndex), let tog = nibViews[nibIndex] as? T {
                view = tog
            }
        }

        return view
    }

    public class var nibName: String {
        return "\(self)".components(separatedBy: ".").first ?? ""
    }

    public class var nibIndex: Int {
        return 0
    }

    public class var nibBundle: Bundle {
        return Bundle.main
    }
}

Et vous pouvez utiliser comme

class BaseView: UIView {
    override class var nibName: String { return "BaseView" }
    weak var delegate: StandardStateViewDelegate?
}

class ChildView: BaseView {
    override class var nibIndex: Int { return 1 }
}
user2790103
la source
0

La mise en œuvre la plus pratique. Ici, vous avez besoin de deux méthodes, afin de retourner directement à l'objet de votre classe, pas à UIView.

  1. viewId marqué comme une classe , permettant le remplacement
  2. Votre .xib peut contenir plus d'une vue du niveau supérieur, cette situation est également gérée correctement.

extension UIView {

class var viewId: String {
    return String(describing: self)
}

static func instance(from bundle: Bundle? = nil, nibName: String? = nil,
                    owner: Any? = nil, options: [AnyHashable : Any]? = nil) -> Self? {

    return instancePrivate(from: bundle ?? Bundle.main,
                           nibName: nibName ?? viewId,
                           owner: owner,
                           options: options)
}

private static func instancePrivate<T: UIView>(from bundle: Bundle, nibName: String,
                                              owner: Any?, options: [AnyHashable : Any]?) -> T? {

    guard
        let views = bundle.loadNibNamed(nibName, owner: owner, options: options),
        let view = views.first(where: { $0 is T }) as? T else { return nil }

    return view
}
}

Exemple:

guard let customView = CustomView.instance() else { return }

//Here customView has CustomView class type, not UIView.
print(customView is CustomView) // true
SeRG1k
la source
0

Si vous voulez que la sous-classe Swift UIView soit entièrement autonome et puisse être instanciée en utilisant init ou init (frame :) sans exposer les détails d'implémentation de l'utilisation d'un Nib, alors vous pouvez utiliser une extension de protocole pour y parvenir. Cette solution évite la hiérarchie UIView imbriquée comme suggéré par de nombreuses autres solutions.

public class CustomView: UIView {

    @IBOutlet weak var nameLabel: UILabel!
    @IBOutlet weak var valueLabel: UILabel!

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

    public override convenience init(frame: CGRect) {
        self.init(internal: nil)
        self.frame = frame
    }

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

    fileprivate func commonInit() {
    }
}

fileprivate protocol _CustomView {
}

extension CustomView: _CustomView {
}

fileprivate extension _CustomView {

    // Protocol extension initializer - has the ability to assign to self, unlike
    // class initializers. Note that the name of this initializer can be anything
    // you like, here we've called it init(internal:)

    init(internal: Int?) {
        self = Bundle.main.loadNibNamed("CustomView", owner:nil, options:nil)![0] as! Self;
    }
}
appfigurer
la source
0
  let bundle = Bundle(for: type(of: self))
   let views = bundle.loadNibNamed("template", owner: self, options: nil)
    self.view.addSubview(views?[0] as! UIView)
Divesh singh
la source
Les réponses au code uniquement sont déconseillées. Veuillez ajouter quelques explications sur la façon dont cela résout le problème ou en quoi cela diffère des réponses existantes. De l'avis
Nick
0

Je préfère l'extension ci-dessous

extension UIView {
    class var instanceFromNib: Self {
        return Bundle(for: Self.self)
            .loadNibNamed(String(describing: Self.self), owner: nil, options: nil)?.first as! Self
    }
}

La différence entre cette extension et l'extension la plus répondue est que vous n'avez pas besoin de la stocker en tant que constante ou variable.

class TitleView: UIView { }

extension UIView {
    class var instanceFromNib: Self {
        return Bundle(for: Self.self)
            .loadNibNamed(String(describing: Self.self), owner: nil, options: nil)?.first as! Self
    }
}

self.navigationItem.titleView = TitleView.instanceFromNib
iCoder
la source
Quelle version de Xcode utilisez-vous? Veuillez vous assurer que vous utilisez la dernière version de XCode. Fonctionne très bien pour moi avec XCode 11.5 (dernière version en date).
iCoder
0
    let nibs = Bundle.main.loadNibNamed("YourView", owner: nil, options: nil)
    let shareView = nibs![0] as! ShareView
    self.view.addSubview(shareView)
Pratik
la source