Charger la vue à partir d'un fichier xib externe dans le storyboard

131

Je souhaite utiliser une vue sur plusieurs contrôleurs de vue dans un storyboard. Ainsi, j'ai pensé à concevoir la vue dans un xib externe afin que les changements soient reflétés dans chaque contrôleur de vue. Mais comment charger une vue depuis un xib externe dans un storyboard et est-ce même possible? Si ce n'est pas le cas, quelles autres alternatives sont disponibles pour convenir à la situation ci-dessus?

Sebastian Hoffmann
la source

Réponses:

132

Mon exemple complet est ici , mais je vais fournir un résumé ci-dessous.

Disposition

Ajoutez un fichier .swift et .xib portant chacun le même nom à votre projet. Le fichier .xib contient votre disposition de vue personnalisée (en utilisant de préférence des contraintes de disposition automatique).

Faites du fichier swift le propriétaire du fichier xib.

entrez la description de l'image ici Code

Ajoutez le code suivant au fichier .swift et raccordez les prises et les actions du fichier .xib.

import UIKit
class ResuableCustomView: UIView {

    let nibName = "ReusableCustomView"
    var contentView: UIView?

    @IBOutlet weak var label: UILabel!
    @IBAction func buttonTap(_ sender: UIButton) {
        label.text = "Hi"
    }

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

        guard let view = loadViewFromNib() else { return }
        view.frame = self.bounds
        self.addSubview(view)
        contentView = view
    }

    func loadViewFromNib() -> UIView? {
        let bundle = Bundle(for: type(of: self))
        let nib = UINib(nibName: nibName, bundle: bundle)
        return nib.instantiate(withOwner: self, options: nil).first as? UIView
    }
}

Utilise le

Utilisez votre vue personnalisée n'importe où dans votre storyboard. Ajoutez simplement un UIViewet définissez le nom de la classe sur votre nom de classe personnalisé.

entrez la description de l'image ici

Suragch
la source
3
N'est-ce pas que loadNibNamed appelle init (coder :)? J'ai un crash en essayant d'adapter votre approche.
Fishman
@Fishman, si vous essayez de charger la vue par programme (plutôt qu'à partir du storyboard), elle plantera car elle n'a pas actuellement de fichier init(frame:). Consultez ce didacticiel pour plus de détails.
Suragch
7
Une autre cause fréquente de plantage est de ne pas définir la vue personnalisée sur le propriétaire du fichier . Voyez le cercle rouge dans ma réponse.
Suragch le
5
Ouais, j'avais défini la classe de la vue racine au lieu du propriétaire du fichier et cela provoquait une boucle infinie.
devios1
2
Après avoir ajouté la ligne view.autoresizingMask = [.flexibleWidth, .flexibleHeight] avant self.addSubview (vue), cela fonctionne parfaitement.
Pramod Tapaniya
69

Pendant un certain temps, l'approche de Christopher Swasey a été la meilleure que j'ai trouvée. J'ai interrogé quelques développeurs seniors de mon équipe à ce sujet et l'un d'eux avait la solution parfaite ! Cela répond à chacune des préoccupations que Christopher Swasey a abordées avec tant d'éloquence et ne nécessite pas de code de sous-classe standard (ma principale préoccupation avec son approche). Il y a un piège , mais à part cela, il est assez intuitif et facile à mettre en œuvre.

  1. Créez une classe UIView personnalisée dans un fichier .swift pour contrôler votre xib. c'est à direMyCustomClass.swift
  2. Créez un fichier .xib et stylisez-le comme vous le souhaitez. c'est à direMyCustomClass.xib
  3. Définissez le File's Ownerfichier .xib comme votre classe personnalisée ( MyCustomClass)
  4. GOTCHA: laissez vide la classvaleur (sous le identity Inspector) de votre vue personnalisée dans le fichier .xib. Ainsi, votre vue personnalisée n'aura pas de classe spécifiée, mais elle aura un propriétaire de fichier spécifié.
  5. Branchez vos prises comme vous le feriez normalement en utilisant le Assistant Editor.
    • REMARQUE: si vous regardez le, Connections Inspectorvous remarquerez que vos points de vente de référencement ne référencent pas votre classe personnalisée (c'est-à-dire MyCustomClass), mais plutôt une référence File's Owner. Comme il File's Ownerest spécifié comme étant votre classe personnalisée, les points de vente se connecteront et fonctionneront proprement.
  6. Assurez-vous que votre classe personnalisée a @IBDesignable avant l'instruction de classe.
  7. Rendez votre classe personnalisée conforme au NibLoadableprotocole référencé ci-dessous.
    • REMARQUE: si votre .swiftnom de fichier de classe personnalisé est différent de votre .xibnom de fichier, définissez la nibNamepropriété sur le nom de votre .xibfichier.
  8. Implémentez required init?(coder aDecoder: NSCoder)et override init(frame: CGRect)appelez setupFromNib()comme dans l'exemple ci-dessous.
  9. Ajoutez un UIView à votre storyboard souhaité et définissez la classe comme votre nom de classe personnalisé (c'est-à-dire MyCustomClass).
  10. Regardez IBDesignable en action alors qu'il dessine votre .xib dans le storyboard avec toute sa crainte et son émerveillement.

Voici le protocole que vous voudrez référencer:

public protocol NibLoadable {
    static var nibName: String { get }
}

public extension NibLoadable where Self: UIView {

    public static var nibName: String {
        return String(describing: Self.self) // defaults to the name of the class implementing this protocol.
    }

    public static var nib: UINib {
        let bundle = Bundle(for: Self.self)
        return UINib(nibName: Self.nibName, bundle: bundle)
    }

    func setupFromNib() {
        guard let view = Self.nib.instantiate(withOwner: self, options: nil).first as? UIView else { fatalError("Error loading \(self) from nib") }
        addSubview(view)
        view.translatesAutoresizingMaskIntoConstraints = false
        view.leadingAnchor.constraint(equalTo: self.safeAreaLayoutGuide.leadingAnchor, constant: 0).isActive = true
        view.topAnchor.constraint(equalTo: self.safeAreaLayoutGuide.topAnchor, constant: 0).isActive = true
        view.trailingAnchor.constraint(equalTo: self.safeAreaLayoutGuide.trailingAnchor, constant: 0).isActive = true
        view.bottomAnchor.constraint(equalTo: self.safeAreaLayoutGuide.bottomAnchor, constant: 0).isActive = true
    }
}

Et voici un exemple d' MyCustomClassimplémentation du protocole (avec le fichier .xib nommé MyCustomClass.xib):

@IBDesignable
class MyCustomClass: UIView, NibLoadable {

    @IBOutlet weak var myLabel: UILabel!

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

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

}

REMARQUE: Si vous manquez le Gotcha et que vous définissez la classvaleur à l'intérieur de votre fichier .xib comme votre classe personnalisée, il ne sera pas dessiné dans le storyboard et vous obtiendrez une EXC_BAD_ACCESSerreur lorsque vous exécuterez l'application car elle reste bloquée dans une boucle infinie de essayer d'initialiser la classe à partir de la pointe en utilisant la init?(coder aDecoder: NSCoder)méthode qui appelle Self.nib.instantiateet appelle à initnouveau le.

Ben Patch
la source
2
Voici une autre excellente approche, mais je pense que celle ci-dessus est toujours meilleure: medium.com/zenchef-tech-and-product
Ben Patch
4
L'approche mentionnée ci-dessus fonctionne parfaitement et applique la prévisualisation en direct directement dans le storyboard. C'est absolument pratique et génial, grand!
Igor Leonovich
1
Pour info: cette solution, utilisant la définition de contrainte dans setupFromNib(), semble résoudre certains problèmes étranges de mise en page automatique avec les cellules de vue de tableau à dimensionnement automatique contenant des vues créées par XIB.
Gary
1
De loin la meilleure solution! J'adore la @IBDesignablecompatibilité. Je ne peux pas croire pourquoi Xcode ou UIKit ne fournit pas quelque chose comme ça par défaut lors de l'ajout d'un fichier UIView.
fl034
1
ne semble pas pouvoir faire fonctionner celui-ci. Chaque fois que je place le propriétaire du fichier dans mon fichier .xib, il définit AUSSI une classe personnalisée.
dessinateur
32

En supposant que vous ayez créé un xib que vous souhaitez utiliser:

1) Créez une sous-classe personnalisée d'UIView (vous pouvez aller dans Fichier -> Nouveau -> Fichier ... -> Cocoa Touch Class. Assurez-vous que "Sous-classe de:" est "UIView").

2) Ajoutez une vue basée sur xib comme sous-vue à cette vue lors de l'initialisation.

Dans Obj-C

-(id)initWithCoder:(NSCoder *)aDecoder{
    if (self = [super initWithCoder:aDecoder]) {
        UIView *xibView = [[[NSBundle mainBundle] loadNibNamed:@"YourXIBFilename"
                                                              owner:self
                                                            options:nil] objectAtIndex:0];
        xibView.frame = self.bounds;
        xibView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
        [self addSubview: xibView];
    }
    return self;
}

Dans Swift 2

required init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
    let xibView = NSBundle.mainBundle().loadNibNamed("YourXIBFilename", owner: self, options: nil)[0] as! UIView
    xibView.frame = self.bounds
    xibView.autoresizingMask = [.FlexibleWidth, .FlexibleHeight]
    self.addSubview(xibView)
}

Dans Swift 3

required init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
    let xibView = Bundle.main.loadNibNamed("YourXIBFilename", owner: self, options: nil)!.first as! UIView
    xibView.frame = self.bounds
    xibView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
    self.addSubview(xibView)
}

3) Où que vous souhaitiez l'utiliser dans votre storyboard, ajoutez un UIView comme vous le feriez normalement, sélectionnez la vue nouvellement ajoutée, allez dans l'inspecteur d'identité (la troisième icône en haut à droite qui ressemble à un rectangle avec des lignes), et entrez le nom de votre sous-classe en tant que «Classe» sous «Classe personnalisée».

utilisateur1021430
la source
xibView.frame = self.frame;devrait être xibView.frame = CGRectMake(0, 0, self.frame.size.width, self.frame.size.height);, sinon xibView aura un décalage lorsque la vue est ajoutée au storyboard.
BabyPanda
tard à la fête mais il semble qu'il l'a changé en xibView.frame = self.bounds, qui est une image sans décalage
Heavy_Bullets
20
Entraîne un crash dû à une récursion infinie. Le chargement de la pointe crée une autre instance de la sous-classe.
David
2
La classe de la vue xib ne doit pas être la même que cette nouvelle sous-classe. Si le xib est MyClass, vous pouvez créer cette nouvelle classe MyClassContainer.
user1021430
la solution ci-dessus plantera en récursion infinie.
JBarros35 le
27

J'ai toujours trouvé la solution «ajouter comme sous-vue» insatisfaisante, car elle se visse avec (1) la mise en page automatique, (2) @IBInspectableet (3) prises. Au lieu de cela, laissez-moi vous présenter la magie d' awakeAfter:une NSObjectméthode.

awakeAftervous permet d'échanger l'objet réellement réveillé d'un NIB / Storyboard avec un objet complètement différent. Cet objet est ensuite soumis au processus d'hydratation, l'a awakeFromNibappelé, est ajouté en tant que vue, etc.

Nous pouvons utiliser ceci dans une sous-classe "découpée en carton" de notre vue, dont le seul but sera de charger la vue depuis le NIB et de la renvoyer pour une utilisation dans le Storyboard. La sous-classe intégrable est ensuite spécifiée dans l'inspecteur d'identité de la vue Storyboard, plutôt que dans la classe d'origine. Il n'est pas nécessaire que ce soit une sous-classe pour que cela fonctionne, mais en faire une sous-classe est ce qui permet à IB de voir toutes les propriétés IBInspectable / IBOutlet.

Ce passe-partout supplémentaire peut sembler sous-optimal - et dans un sens, il l'est, car idéalement UIStoryboardle gérerait de manière transparente - mais il a l'avantage de laisser le NIB d'origine et la UIViewsous - classe complètement inchangés. Le rôle qu'il joue est essentiellement celui d'un adaptateur ou d'une classe de pont, et est parfaitement valable, du point de vue de la conception, en tant que classe supplémentaire, même si c'est regrettable. D'un autre côté, si vous préférez être parcimonieux avec vos classes, la solution de @ BenPatch fonctionne en implémentant un protocole avec quelques autres changements mineurs. La question de savoir quelle solution est la meilleure se résume à une question de style programmeur: si l'on préfère la composition d'objets ou l'héritage multiple.

Remarque: la classe définie sur la vue dans le fichier NIB reste la même. La sous - classe intégrable est uniquement utilisé dans le story - board. La sous-classe ne peut pas être utilisée pour instancier la vue dans le code, elle ne devrait donc pas avoir de logique supplémentaire elle-même. Il doit seulement contenir le awakeAftercrochet.

class MyCustomEmbeddableView: MyCustomView {
  override func awakeAfter(using aDecoder: NSCoder) -> Any? {
    return (UIView.instantiateViewFromNib("MyCustomView") as MyCustomView?)! as Any
  }
}

⚠️ Le seul inconvénient majeur ici est que si vous définissez des contraintes de largeur, de hauteur ou de rapport hauteur / largeur dans le storyboard qui ne sont pas liées à une autre vue, elles doivent être copiées manuellement. Les contraintes qui relient deux vues sont installées sur l'ancêtre commun le plus proche, et les vues sont réveillées du storyboard de l'intérieur vers l'extérieur, donc au moment où ces contraintes sont hydratées sur la supervision, l'échange a déjà eu lieu. Les contraintes qui n'impliquent que la vue en question sont installées directement sur cette vue, et sont donc rejetées lorsque l'échange se produit, à moins qu'elles ne soient copiées.

Notez que ce qui se passe ici, c'est que les contraintes installées sur la vue dans le storyboard sont copiées dans la vue nouvellement instanciée , qui peut déjà avoir ses propres contraintes, définies dans son fichier nib. Ceux-ci ne sont pas affectés.

class MyCustomEmbeddableView: MyCustomView {
  override func awakeAfter(using aDecoder: NSCoder) -> Any? {
    let newView = (UIView.instantiateViewFromNib("MyCustomView") as MyCustomView?)!

    for constraint in constraints {
      if constraint.secondItem != nil {
        newView.addConstraint(NSLayoutConstraint(item: newView, attribute: constraint.firstAttribute, relatedBy: constraint.relation, toItem: newView, attribute: constraint.secondAttribute, multiplier: constraint.multiplier, constant: constraint.constant))
      } else {
        newView.addConstraint(NSLayoutConstraint(item: newView, attribute: constraint.firstAttribute, relatedBy: constraint.relation, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: constraint.constant))
      }
    }

    return newView as Any
  }
}  

instantiateViewFromNibest une extension de type sécurisé de UIView. Tout ce qu'il fait est de parcourir les objets du NIB jusqu'à ce qu'il en trouve un qui correspond au type. Notez que le type générique est la valeur de retour , le type doit donc être spécifié sur le site d'appel.

extension UIView {
  public class func instantiateViewFromNib<T>(_ nibName: String, inBundle bundle: Bundle = Bundle.main) -> T? {
    if let objects = bundle.loadNibNamed(nibName, owner: nil) {
      for object in objects {
        if let object = object as? T {
          return object
        }
      }
    }

    return nil
  }
}
Christopher Swasey
la source
C'est ahurissant. Si je ne me trompe pas, cela ne fonctionne que "sur le storyboard" - si vous essayez de créer une telle classe dans le code au moment de l'exécution, je ne pense pas que cela fonctionne. Je crois.
Fattie le
La sous-classe doit fonctionner dans le code aussi bien que la classe d'origine à toutes fins utiles. Si vous souhaitez charger la vue à partir d'une pointe dans le code, vous devez simplement l'instancier directement en utilisant la même technique. Tout ce que fait la sous-classe est de prendre le code pour instancier la vue à partir d'une pointe et de le mettre dans un crochet pour que le storyboard l'utilise.
Christopher Swasey
En fait , je me suis trompé, il serait tout aussi bien si vous pouvez instancier, mais vous ne pouvez pas, parce que la vue dans le NIB aura la superclasse comme type donc instantiateViewFromNibne retournera rien. Pas un gros problème de toute façon IMO, la sous-classe est juste un artifice pour accrocher dans le storyboard, tout le code doit être sur la classe d'origine.
Christopher Swasey
1
Génial! Une chose m'a trébuché parce que j'ai peu d'expérience avec xibs (je n'ai jamais travaillé qu'avec des storyboards et des approches programmatiques), laissant ceci ici au cas où cela aiderait quelqu'un: dans le fichier .xib, vous devez sélectionner la vue de niveau supérieur et définir son type de classe à MyCustomView. Dans mon xib, la barre latérale gauche était manquante par défaut; pour l'activer, il y a un bouton à côté du contrôle des traits «Afficher comme: iPhone 7» près du côté inférieur / gauche.
xaphod
5
Il freine les contraintes lorsqu'il est remplacé par un autre objet. :(
invoodoo
6

La meilleure solution actuellement est d'utiliser simplement un contrôleur de vue personnalisé avec sa vue définie dans un xib, et de supprimer simplement la propriété "view" que Xcode crée à l'intérieur du storyboard lors de l'ajout du contrôleur de vue (n'oubliez pas de définir le nom de la classe personnalisée cependant).

Cela obligera le runtime à rechercher automatiquement le xib et à le charger. Vous pouvez utiliser cette astuce pour tout type de vue de conteneur ou de vue de contenu.

Ben G
la source
5

Je pense alternativeà utiliser XIB viewspour utiliser View Controller dans un storyboard séparé .

Puis , en story - board principal lieu d'utilisation de vue personnalisée container viewavec Embed Segueet ont StoryboardReferenceà ce contrôleur de vue personnalisée qui vue doit être placé dans un autre point de vue dans le story - board principale.

Ensuite, nous pouvons configurer la délégation et la communication entre ce ViewController intégré et le contrôleur de vue principal via la préparation de segue . Cette approche est différente de celle de l' affichage de UIView, mais beaucoup plus simple et plus efficace (du point de vue de la programmation) peut être utilisée pour atteindre le même objectif, c'est-à-dire avoir une vue personnalisée réutilisable qui est visible dans le storyboard principal

L'avantage supplémentaire est que vous pouvez implémenter votre logique dans la classe CustomViewController et y configurer toute la délégation et la préparation de vue sans créer de classes de contrôleur distinctes (plus difficiles à trouver dans le projet), et sans placer de code standard dans UIViewController principal à l'aide de Component. Je pense que c'est bon pour les composants réutilisables ex. Composant Music Player (comme un widget) qui peut être intégré dans d'autres vues.

Michał Ziobro
la source
En effet, une solution beaucoup plus simple que d'utiliser la vue personnalisée xib :(
Wilson
1
après avoir tourné en rond avec la chaîne de création du cycle de vie xib d'Apple sur UIView personnalisé, c'est ainsi que j'ai fini par aller.
LightningStryk
Dans le cas où vous souhaitez ajouter la vue personnalisée de manière dynamique, nous devons toujours utiliser un contrôleur de vue séparé (de préférence XIB)
Satyam
5

Bien que les réponses les plus populaires fonctionnent bien, elles sont conceptuellement erronées. Ils sont tous utilisés File's ownercomme connexion entre les prises de classe et les composants de l'interface utilisateur. File's ownerest censé être utilisé uniquement pour les objets de niveau supérieur et non pour UIViews. Consultez le document du développeur Apple . Avoir UIView comme File's ownerconduit à ces conséquences indésirables.

  1. Vous êtes obligé d'utiliser contentViewlà où vous êtes censé utiliser self. Ce n'est pas seulement moche, mais aussi structurellement incorrect car la vue intermédiaire empêche la structure de données de transmettre sa structure d'interface utilisateur. C'est comme l'opposé de l'interface utilisateur déclarative.
  2. Vous ne pouvez avoir qu'un seul UIView par Xib. Un Xib est censé avoir plusieurs UIViews.

Il existe une manière élégante de le faire sans utiliser File's owner. Veuillez consulter ce billet de blog . Il explique comment procéder de la bonne manière.

Ingun 전인 건
la source
peu importe, vous n'avez pas expliqué pourquoi c'est mauvais. Je ne vois pas pourquoi. il n'y a pas d'effets secondaires.
JBarros35 le
1

Voici la réponse que vous vouliez depuis le début. Vous pouvez simplement créer votre CustomViewclasse, en avoir l'instance principale dans un xib avec toutes les sous-vues et sorties. Ensuite, vous pouvez appliquer cette classe à toutes les instances de vos storyboards ou d'autres xibs.

Inutile de jouer avec le propriétaire du fichier, de connecter des prises à un proxy ou de modifier le xib d'une manière particulière, ou d'ajouter une instance de votre vue personnalisée en tant que sous-vue d'elle-même.

Faites juste ceci:

  1. Importer le framework BFWControls
  2. Changer votre superclasse de UIViewà NibView(ou de UITableViewCellà NibTableViewCell)

C'est tout!

Il fonctionne même avec IBDesignable pour référencer votre vue personnalisée (y compris les sous-vues du xib) au moment de la conception dans le storyboard.

Vous pouvez en savoir plus ici: https://medium.com/build-an-app-like-lego/embed-a-xib-in-a-storyboard-953edf274155

Et vous pouvez obtenir le framework open source BFWControls ici: https://github.com/BareFeetWare/BFWControls

Et voici un simple extrait du NibReplaceablecode qui le pilote, au cas où vous seriez curieux:
 https://gist.github.com/barefeettom/f48f6569100415e0ef1fd530ca39f5b4

Tom 👣

nu
la source
Solution astucieuse, merci!
avee le
Je ne veux pas installer un framework alors qu'il devrait être fourni nativement.
JBarros35 le
0

Cette solution peut être utilisée même si votre classe n'a pas le même nom que le XIB. Par exemple, si vous avez une classe de contrôleur de vue de base controllerA qui a un nom XIB controllerA.xib et que vous l'avez sous-classée avec controllerB et que vous souhaitez créer une instance de controllerB dans un storyboard, vous pouvez:

  • créer le contrôleur de vue dans le storyboard
  • définir la classe du contrôleur sur le contrôleurB
  • supprimer la vue du contrôleur B dans le storyboard
  • remplacer la vue de charge dans le contrôleurA pour:

*

- (void) loadView    
{
        //according to the documentation, if a nibName was passed in initWithNibName or
        //this controller was created from a storyboard (and the controller has a view), then nibname will be set
        //else it will be nil
        if (self.nibName)
        {
            //a nib was specified, respect that
            [super loadView];
        }
        else
        {
            //if no nib name, first try a nib which would have the same name as the class
            //if that fails, force to load from the base class nib
            //this is convenient for including a subclass of this controller
            //in a storyboard
            NSString *className = NSStringFromClass([self class]);
            NSString *pathToNIB = [[NSBundle bundleForClass:[self class]] pathForResource: className ofType:@"nib"];
            UINib *nib ;
            if (pathToNIB)
            {
                nib = [UINib nibWithNibName: className bundle: [NSBundle bundleForClass:[self class]]];
            }
            else
            {
                //force to load from nib so that all subclass will have the correct xib
                //this is convenient for including a subclass
                //in a storyboard
                nib = [UINib nibWithNibName: @"baseControllerXIB" bundle:[NSBundle bundleForClass:[self class]]];
            }

            self.view = [[nib instantiateWithOwner:self options:nil] objectAtIndex:0];
       }
}
otusweb
la source
0

Solution pour Objective-C selon les étapes décrites dans la réponse de Ben Patch .

Utilisez l'extension pour UIView:

@implementation UIView (NibLoadable)

- (UIView*)loadFromNib
{
    UIView *xibView = [[[NSBundle mainBundle] loadNibNamed:NSStringFromClass([self class]) owner:self options:nil] firstObject];
    xibView.translatesAutoresizingMaskIntoConstraints = NO;
    [self addSubview:xibView];
    [xibView.topAnchor constraintEqualToAnchor:self.topAnchor].active = YES;
    [xibView.bottomAnchor constraintEqualToAnchor:self.bottomAnchor].active = YES;
    [xibView.leftAnchor constraintEqualToAnchor:self.leftAnchor].active = YES;
    [xibView.rightAnchor constraintEqualToAnchor:self.rightAnchor].active = YES;
    return xibView;
}

@end

Créer des fichiers MyView.h, MyView.met MyView.xib.

Préparez d'abord votre MyView.xibcomme la réponse de Ben Patch le dit, définissez la classe MyViewpour le propriétaire du fichier au lieu de la vue principale dans ce XIB.

MyView.h:

#import <UIKit/UIKit.h>

IB_DESIGNABLE @interface MyView : UIView

@property (nonatomic, weak) IBOutlet UIView* someSubview;

@end

MyView.m:

#import "MyView.h"
#import "UIView+NibLoadable.h"

@implementation MyView

#pragma mark - Initializers

- (id)init
{
    self = [super init];
    if (self) {
        [self loadFromNib];
        [self internalInit];
    }
    return self;
}

- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        [self loadFromNib];
        [self internalInit];
    }
    return self;
}

- (id)initWithCoder:(NSCoder *)aDecoder
{
    self = [super initWithCoder:aDecoder];
    if (self) {
        [self loadFromNib];
    }
    return self;
}

- (void)awakeFromNib
{
    [super awakeFromNib];
    [self internalInit];
}

- (void)internalInit
{
    // Custom initialization.
}

@end

Et plus tard, créez simplement votre vue par programme:

MyView* view = [[MyView alloc] init];

Avertissement! L'aperçu de cette vue ne sera pas affiché dans Storyboard si vous utilisez l'extension WatchKit à cause de ce bogue dans Xcode> = 9.2: https://forums.developer.apple.com/thread/95616

Ariel Bogdziewicz
la source
Aurait dû fournir la version Swift. Très peu utilisent encore ObjC en ce moment
Satyam