Comment charger une UIView à l'aide d'un fichier nib créé avec Interface Builder

241

J'essaie de faire quelque chose d'un peu élaboré, mais quelque chose qui devrait être possible. Voici donc un défi pour tous vos experts (ce forum est un pack de beaucoup d'entre vous :)).

Je crée un "composant" Questionnaire, que je veux charger sur un NavigationContoller(mon QuestionManagerViewController). Le "composant" est un "vide" UIViewController, qui peut charger différentes vues en fonction de la question à laquelle il faut répondre.

La façon dont je le fais est:

  1. Créez l'objet Question1View en tant que UIViewsous - classe, en définissant certains IBOutlets.
  2. Créez (en utilisant Interface Builder) le Question1View.xib (ICI EST O IS MON PROBLÈME EST PROBABLEMENT ). J'ai défini le UIViewControlleret le UIViewpour être de la classe Question1View.
  3. Je relie les prises avec le composant de la vue (en utilisant IB).
  4. Je remplace le initWithNibmon QuestionManagerViewControllerpour ressembler à ceci:

    - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
        if (self = [super initWithNibName:@"Question1View" bundle:nibBundleOrNil]) {
            // Custom initialization
        }
        return self;
    }
    

Lorsque j'exécute le code, j'obtiens cette erreur:

2009-05-14 15: 05: 37.152 iMobiDines [17148: 20b] *** Arrêt de l'application en raison d'une exception non interceptée ' NSInternalInconsistencyException', raison: 'a -[UIViewController _loadViewFromNibNamed:bundle:]chargé la plume "Question1View" mais la sortie de la vue n'a pas été définie.'

Je suis sûr qu'il existe un moyen de charger la vue à l'aide du fichier nib, sans avoir besoin de créer une classe viewController.

Alexander Farber
la source

Réponses:

224

Il existe également un moyen plus simple d'accéder à la vue au lieu de traiter la plume sous forme de tableau.

1 ) Créez une sous-classe View personnalisée avec toutes les sorties auxquelles vous souhaitez avoir accès ultérieurement. --Mon avis

2 ) dans l'UIViewController que vous souhaitez charger et gérer la pointe, créez une propriété IBOutlet qui contiendra la vue de la pointe chargée, par exemple

dans MyViewController (une sous-classe UIViewController)

  @property (nonatomic, retain) IBOutlet UIView *myViewFromNib;

(n'oubliez pas de le synthétiser et de le publier dans votre fichier .m)

3) ouvrez votre plume (nous l'appellerons 'myViewNib.xib') dans IB, définissez le propriétaire de votre fichier sur MyViewController

4) connectez maintenant la sortie propriétaire de votre fichier myViewFromNib à la vue principale de la plume.

5) Maintenant, dans MyViewController, écrivez la ligne suivante:

[[NSBundle mainBundle] loadNibNamed:@"myViewNib" owner:self options:nil];

Maintenant, dès que vous faites cela, appeler votre propriété "self.myViewFromNib" vous donnera accès à la vue depuis votre plume!

averydev
la source
4
Cela fonctionnera-t-il pour la vue du contrôleur principal (self.view)? Pour certaines raisons, je dois charger la vue principale à partir de nib après la méthode d'initialisation du contrôleur standard. Motif - structure complexe de sous-classement
Lukasz
@Lukasz Combien tu réussis à le faire? Même moi je cherche quelque chose comme toi.
Ajay Sharma
Désolé d'être peut-être épais, mais je suis confus au premier point. Comment créer des points de vente dans une sous-classe de vue personnalisée? Je pensais que les prises étaient réservées aux UIViewControllers?
jowie
1
Qu'en est-il d'un cas où cette vue apparaît sur plusieurs vues parent? Proposez-vous donc d'éditer manuellement xib pour chacun d'eux? C'est dommage!!!
user2083364
2
Cela ne fonctionne que si vous souhaitez utiliser la plume personnalisée dans un seul contrôleur de vue. Dans le cas où vous souhaitez l'utiliser sur différents contrôleurs de vue, chacun créant dynamiquement une instance de la plume personnalisée, cela ne fonctionnera pas.
thgc
120

Merci à tous. J'ai trouvé un moyen de faire ce que je voulais.

  1. Créez votre UIViewavec les IBOutlets dont vous avez besoin.
  2. Créez le xib dans IB, concevez-le à votre guise et liez-le comme ceci: Le propriétaire du fichier est de classe UIViewController(pas de sous-classe personnalisée, mais la "vraie"). La vue du propriétaire du fichier est connectée à la vue principale et sa classe est déclarée comme celle de l'étape 1).
  3. Connectez vos commandes avec le IBOutlets.
  4. Le DynamicViewControllerpeut exécuter sa logique pour décider quelle vue / xib charger. Une fois sa décision prise, dans la loadViewméthode, mettez quelque chose comme ceci:

    NSArray* nibViews = [[NSBundle mainBundle] loadNibNamed:@"QPickOneView"
                                                      owner:self
                                                    options:nil];
    
    QPickOneView* myView = [ nibViews objectAtIndex: 1];
    
    myView.question = question;

C'est tout!

La loadNibNamedméthode du bundle principal se chargera d'initialiser la vue et de créer les connexions.

Le ViewController peut maintenant afficher une vue ou une autre en fonction des données en mémoire, et l'écran "parent" n'a plus besoin de s'embêter avec cette logique.

geedubb
la source
2
J'ai un problème très similaire - je veux simplement charger un UIView à partir d'un fichier xib. Ces étapes ne fonctionnent pas pour moi - l'application se bloque avec un "mauvais accès" peu de temps après l'ajout de la vue chargée en tant que sous-vue.
Justicle
2
Pour iPhone 3.0, utilisez objectAtIndex: 0 pour obtenir le premier élément. Cela se bloque pour moi exactement comme décrit par Justicle. Une idée pourquoi?
confirmer
1
+1 cela a parfaitement fonctionné pour moi. Je voulais ajouter plusieurs vues qui avaient un seul contrôleur de vue (im utilisant un scénario de vue inversée où une partie de la vue tourne) J'ai dû faire l'index sur le tableau 0 (iPhone 3.0)
Aran Mulholland
42
C'est la bonne réponse, cependant, le code doit être modifié pour qu'il passe en revue nibViews et effectue une vérification de classe sur chaque objet, de cette façon, vous obtenez certainement l'objet correct. objectAtIndex: 1 est une hypothèse dangereuse.
M. Ryan
2
Jasconius a raison. Vous devez en boucle le tableau nibViews comme ceci: gist.github.com/769539
Léviathan
71

Je ne sais pas de quoi parlent certaines des réponses, mais je dois mettre cette réponse ici lorsque je ferai une recherche dans Google la prochaine fois. Mots-clés: "Comment charger une UIView à partir d'une pointe" ou "Comment charger une UIView à partir d'un NSBundle".

Voici le code presque à 100% directement du livre Apress Beginning iPhone 3 (page 247, «Utilisation de la nouvelle cellule de vue de table»):

- (void)viewDidLoad {
    [super viewDidLoad];
    NSArray *bundle = [[NSBundle mainBundle] loadNibNamed:@"Blah"
                                                 owner:self options:nil];
    Blah *blah;
    for (id object in bundle) {
        if ([object isKindOfClass:[Blah class]]) {
            blah = (Blah *)object;
            break;
        }
    }   
    assert(blah != nil && "blah can't be nil");
    [self.view addSubview: blah];
} 

Cela suppose que vous ayez une UIViewsous - classe appelée Blah, une nib appelée Blahqui contient un UIViewdont la classe est définie sur Blah.


Catégorie: NSObject + LoadFromNib

#import "NSObject+LoadFromNib.h"

@implementation NSObject (LoadFromNib)

+ (id)loadFromNib:(NSString *)name classToLoad:(Class)classToLoad {
    NSArray *bundle = [[NSBundle mainBundle] loadNibNamed:name owner:self options:nil];
    for (id object in bundle) {
        if ([object isKindOfClass:classToLoad]) {
            return object;
        }
    }
    return nil;
}

@end

Extension rapide

extension UIView {
    class func loadFromNib<T>(withName nibName: String) -> T? {
        let nib  = UINib.init(nibName: nibName, bundle: nil)
        let nibObjects = nib.instantiate(withOwner: nil, options: nil)
        for object in nibObjects {
            if let result = object as? T {
                return result
            }
        }
        return nil
    }
}

Et un exemple d'utilisation:

class SomeView: UIView {
    class func loadFromNib() -> SomeView? {
        return self.loadFromNib(withName: "SomeView")
    }
}
Dan Rosenstark
la source
2
Si vous utilisez le, isKindOfvous êtes protégé des modifications de la structure du Bundle.
Dan Rosenstark
1
Une assertserait probablement une meilleure solution. De cette façon, vous ne masquerez que le problème.
Sulthan
1
@Sulthan an assert est intrinsèquement différent. Je veux l'objet de type Blah partout où il se trouve dans la plume . Je me fiche que ce soit promu ou rétrogradé. Cependant, en réponse à votre commentaire, j'ai ajouté une assertion, mais elle ne remplace pas la forboucle. Il le complète.
Dan Rosenstark
Vous pouvez faire le loadFromNib sans paramètres: + (id) loadFromNib {NSArray * bundle = [[NSBundle mainBundle] loadNibNamed: NSStringFromClass ([self class]) propriétaire: self options: nil]; for (id object in bundle) {if ([object isKindOfClass: [self class]]) {return object; }} return nil; }
JVC
22

Pour tous ceux qui ont besoin de gérer plus d'une instance de la vue personnalisée, c'est-à-dire une collection de sorties, j'ai fusionné et personnalisé les réponses @Gonso, @AVeryDev et @Olie de cette manière:

  1. Créez un personnalisé MyView : UIViewet définissez-le comme " Classe personnalisée " de la racine UIViewdans le XIB souhaité ; classe personnalisée

  2. Créez toutes les prises dont vous avez besoin MyView(faites-le maintenant car après le point 3, l'IB vous proposera de connecter les prises à la UIViewControllervue personnalisée et non à la vue personnalisée); sortie de classe personnalisée

  3. Définissez votre en UIViewControllertant que « propriétaire du fichier » de la vue personnalisée XIB ; entrez la description de l'image ici

  4. Dans l' UIViewControllerajout d'un nouveau UIViewspour chaque instance de MyViewvous voulez, et connectez-les à la UIViewControllercréation d'une collection de sorties : ces vues agiront comme des vues «wrapper» pour les instances de vue personnalisées; entrez la description de l'image ici

  5. Enfin, dans le viewDidLoadvotre UIViewControllerajoutez les lignes suivantes:

NSArray *bundleObjects;
MyView *currView;
NSMutableArray *myViews = [NSMutableArray arrayWithCapacity:myWrapperViews.count];
for (UIView *currWrapperView in myWrapperViews) {
    bundleObjects = [[NSBundle mainBundle] loadNibNamed:@"MyView" owner:self options:nil];
    for (id object in bundleObjects) {
        if ([object isKindOfClass:[MyView class]]){
            currView = (MyView *)object;
            break;
        }
    }

    [currView.myLabel setText:@"myText"];
    [currView.myButton setTitle:@"myTitle" forState:UIControlStateNormal];
    //...

    [currWrapperView addSubview:currView];
    [myViews addObject:currView];
}
//self.myViews = myViews; if need to access them later..
Francesco Vadicamo
la source
Salut ... tu sais comment faire tout cela par programmation, c'est-à-dire. pas en utilisant du tout le fichier xib?
Le X-Coder du
19

J'utiliserais UINib pour instancier une UIView personnalisée à réutiliser

UINib *customNib = [UINib nibWithNibName:@"MyCustomView" bundle:nil];
MyCustomViewClass *customView = [[customNib instantiateWithOwner:self options:nil] objectAtIndex:0];
[self.view addSubview:customView];

Les fichiers nécessaires dans ce cas sont MyCustomView.xib , MyCustomViewClass.h et MyCustomViewClass.m Notez que [UINib instantiateWithOwner]renvoie un tableau, vous devez donc utiliser l'élément qui reflète l'UIView que vous souhaitez réutiliser. Dans ce cas, c'est le premier élément.

thgc
la source
1
Ne voulez-vous pas dire "customNib" dans la ligne 2 au lieu de "rankingNib"
Danny
11

Vous ne devez pas définir la classe de votre contrôleur de vue comme une sous-classe d' UIViewInterface Builder. C'est certainement au moins une partie de votre problème. Laissez-le comme UIViewControllerune sous-classe ou une autre classe personnalisée que vous avez.

En ce qui concerne le chargement seulement une vue d'un xib, j'étais sous l'hypothèse que vous avait d'avoir une sorte de contrôleur de vue (même si elle ne dépasse pas UIViewController, ce qui peut être trop lourd pour vos besoins) ensemble en tant que propriétaire dans l' interface du fichier Builder si vous souhaitez l'utiliser pour définir votre interface. J'ai également fait quelques recherches pour le confirmer. En effet, sinon, il n'y aurait aucun moyen d'accéder à l'un des éléments d'interface dans le UIView, ni aucun moyen de déclencher vos propres méthodes dans le code par des événements.

Si vous utilisez un en UIViewControllertant que propriétaire de votre fichier pour vos vues, vous pouvez simplement l'utiliser initWithNibName:bundle:pour le charger et récupérer l'objet du contrôleur de vue. Dans IB, assurez-vous de régler la viewsortie sur la vue avec votre interface dans le xib. Si vous utilisez un autre type d'objet en tant que propriétaire de votre fichier, vous devrez utiliser NSBundlela loadNibNamed:owner:options:méthode de pour charger la plume, en passant une instance du propriétaire du fichier à la méthode. Toutes ses propriétés seront définies correctement en fonction des prises que vous définissez dans IB.

Marc W
la source
Je lis le livre Apress, "Début du développement iPhone: exploration du SDK iPhone" et ils ont discuté de cette méthode au chapitre 9: Contrôleurs de navigation et vues de table. Cela ne semblait pas trop compliqué.
Lyndsey Ferguson,
Je serais curieux de voir comment ils disent que c'est fait. Je n'ai pas de copie de ce livre pour le moment.
Marc W
10

Vous pouvez également utiliser initWithNibName de UIViewController au lieu de loadNibNamed. C'est plus simple, je trouve.

UIViewController *aViewController = [[UIViewController alloc] initWithNibName:@"MySubView" bundle:nil];
[self.subview addSubview:aViewController.view];
[aViewController release];  // release the VC

Il ne vous reste plus qu'à créer MySubView.xib et MySubView.h / m. Dans MySubView.xib, définissez la classe Propriétaire du fichier sur UIViewController et la classe d'affichage sur MySubView.

Vous pouvez positionner et dimensionner le subviewà l'aide du fichier xib parent.

Ying
la source
8

C'est une excellente question (+1) et les réponses ont été presque utiles;) Désolé les gars, mais j'ai eu un sacré temps à parcourir cela, bien que Gonso et AVeryDev aient donné de bons indices. Espérons que cette réponse aidera les autres.

MyVC est le contrôleur de vue détenant tout cela.

MySubview est la vue que nous voulons charger à partir d'un xib

  • Dans MyVC.xib, créez une vue de type de MySubViewla bonne taille et de la bonne forme et positionnée où vous le souhaitez.
  • Dans MyVC.h, ayez

    IBOutlet MySubview *mySubView
    // ...
    @property (nonatomic, retain) MySubview *mySubview;
  • Dans MyVC.m, @synthesize mySubView;et n'oubliez pas de le publier dealloc.

  • Dans MySubview.h, avoir une sortie / propriété pour UIView *view(peut être inutile, mais a fonctionné pour moi.) Synthétisez et libérez-le dans .m
  • Dans MySubview.xib
    • définissez le type de propriétaire du fichier sur MySubview et liez la propriété view à votre vue.
    • Disposez tous les bits et connectez-vous à ceux IBOutletque vous souhaitez
  • De retour dans MyVC.m, ayez

    NSArray *xibviews = [[NSBundle mainBundle] loadNibNamed: @"MySubview" owner: mySubview options: NULL];
    MySubview *msView = [xibviews objectAtIndex: 0];
    msView.frame = mySubview.frame;
    UIView *oldView = mySubview;
    // Too simple: [self.view insertSubview: msView aboveSubview: mySubview];
    [[mySubview superview] insertSubview: msView aboveSubview: mySubview]; // allows nesting
    self.mySubview = msView;
    [oldCBView removeFromSuperview];

Le plus difficile pour moi était: les indices dans les autres réponses chargeaient ma vue depuis le xib, mais ne remplaçaient PAS la vue dans MyVC (duh!) - J'ai dû échanger cela par moi-même.

De plus, pour accéder aux mySubviewméthodes de, la viewpropriété du fichier .xib doit être définie sur MySubview. Sinon, il revient comme un vieuxUIView .

S'il y avait un moyen de charger mySubviewdirectement à partir de son propre xib, ça basculerait, mais cela m'a amené là où je devais être.

Olie
la source
1
J'ai utilisé cette méthode, merci. Une modification que j'ai apportée pour lui permettre de prendre en charge les vues imbriquées a été de changer [ self.view insertSubview: msView aboveSubview: mySubview]; à [ mySubview.superview insertSubview: msView aboveSubview: mySubview];
Jason
8

C'est quelque chose qui devrait être plus facile. J'ai fini par étendre UIViewControlleret ajouter un loadNib:inPlaceholder:sélecteur. Maintenant je peux dire

self.mySubview = (MyView *)[self loadNib:@"MyView" inPlaceholder:mySubview];

Voici le code de la catégorie (il fait le même rigamarole comme décrit par Gonso):

@interface UIViewController (nibSubviews)

- (UIView *)viewFromNib:(NSString *)nibName;
- (UIView *)loadNib:(NSString *)nibName inPlaceholder:(UIView *)placeholder;

@end

@implementation UIViewController (nibSubviews)

- (UIView *)viewFromNib:(NSString *)nibName
{
  NSArray *xib = [[NSBundle mainBundle] loadNibNamed:nibName owner:self options:nil]; 
  for (id view in xib) { // have to iterate; index varies
    if ([view isKindOfClass:[UIView class]]) return view;
  }
  return nil;
}

- (UIView *)loadNib:(NSString *)nibName inPlaceholder:(UIView *)placeholder
{
  UIView *nibView = [self viewFromNib:nibName];
  [nibView setFrame:placeholder.frame];
  [self.view insertSubview:nibView aboveSubview:placeholder];
  [placeholder removeFromSuperview];
  return nibView;
}

@end
skot
la source
7

En rapide

En fait, ma résolution de ce problème était de charger la vue dans un viewDidLoad dans mon CustonViewController où je voulais utiliser la vue comme ça:

myAccessoryView = NSBundle.mainBundle().loadNibNamed("MyAccessoryView", owner: self, options: nil)[0] as! MyAccessoryView

Ne chargez pas la vue dans une loadView()méthode! La méthode loadView sert à charger la vue de votre ViewController personnalisé.

kalafun
la source
6

Moi aussi, je voulais faire quelque chose de similaire, voici ce que j'ai trouvé: (SDK 3.1.3)

J'ai un contrôleur de vue A (lui-même détenu par un contrôleur Nav) qui charge VC B sur une pression de bouton:

Dans AViewController.m

BViewController *bController = [[BViewController alloc] initWithNibName:@"Bnib" bundle:nil];
[self.navigationController pushViewController:bController animated:YES];
[bController release];

Maintenant, VC B a son interface de Bnib, mais quand un bouton est enfoncé, je veux aller dans un "mode d'édition" qui a une interface utilisateur distincte d'une autre pointe, mais je ne veux pas de nouveau VC pour le mode d'édition, Je veux que la nouvelle pointe soit associée à mon B VC existant.

Donc, dans BViewController.m (dans la méthode de pression de bouton)

NSArray *nibObjects = [[NSBundle mainBundle] loadNibNamed:@"EditMode" owner:self options:nil];
UIView *theEditView = [nibObjects objectAtIndex:0];
self.editView = theEditView;
[self.view addSubview:theEditView];

Ensuite, sur un autre bouton, appuyez sur (pour quitter le mode édition):

[editView removeFromSuperview];

et je suis de retour à mon Bnib d'origine.

Cela fonctionne bien, mais notez que mon EditMode.nib n'a qu'un seul obj de niveau supérieur, un obj UIView. Peu importe que le propriétaire du fichier dans cette pointe soit défini comme BViewController ou NSObject par défaut, MAIS assurez-vous que la sortie d'affichage dans le propriétaire du fichier n'est PAS définie sur quoi que ce soit. Si c'est le cas, j'obtiens un crash exc_bad_access et xcode procède au chargement de 6677 trames de pile montrant une méthode UIView interne appelée à plusieurs reprises ... ressemble donc à une boucle infinie. (Le View Outlet EST défini dans mon Bnib d'origine cependant)

J'espère que cela t'aides.

Brynjar
la source
Lire entre les lignes, cela m'a aussi aidé.
petert
Selon les informations du lien, vous ne devez pas manipuler les contrôleurs de vue de cette manière.
T. Markle
6

J'ai fait une catégorie que j'aime:

UIView+NibInitializer.h

#import <UIKit/UIKit.h>

@interface UIView (NibInitializer)
- (instancetype)initWithNibNamed:(NSString *)nibNameOrNil;
@end

UIView+NibInitializer.m

#import "UIView+NibInitializer.h"

@implementation UIView (NibInitializer)

- (instancetype)initWithNibNamed:(NSString *)nibNameOrNil
{
    if (!nibNameOrNil) {
        nibNameOrNil = NSStringFromClass([self class]);
    }
    NSArray *viewsInNib = [[NSBundle mainBundle] loadNibNamed:nibNameOrNil
                                                        owner:self
                                                      options:nil];
    for (id view in viewsInNib) {
        if ([view isKindOfClass:[self class]]) {
            self = view;
            break;
        }
    }
    return self;
}

@end

Ensuite, appelez comme ceci:

MyCustomView *myCustomView = [[MyCustomView alloc] initWithNibNamed:nil];

Utilisez un nom de plume si votre plume est nommée autre chose que le nom de votre classe.

Pour le remplacer dans vos sous-classes pour un comportement supplémentaire, il pourrait ressembler à ceci:

- (instancetype)initWithNibNamed:(NSString *)nibNameOrNil
{
    self = [super initWithNibNamed:nibNameOrNil];
    if (self) {
        self.layer.cornerRadius = CGRectGetHeight(self.bounds) / 2.0;
    }
    return self;
}
Logan
la source
5

Pour les utilisateurs de Swift avec option personnalisable:

  1. Créez une UIViewsous - classe personnalisée et un fichier xib , que nous nommerons d'après notre propre nom de classe: dans notre cas MemeView. Dans la classe Meme View, n'oubliez pas de la définir comme pouvant être conçue avec le@IBDesignable attribut avant la déclaration de classe
  2. N'oubliez pas de définir le propriétaire du fichier dans le xib avec notre UIViewsous-classe personnalisée dans le panneau Inspecteur d'indétité

    entrez la description de l'image ici

  3. Dans le fichier xib, nous pouvons maintenant construire notre interface, créer des contraintes, créer des débouchés, des actions, etc. entrez la description de l'image ici

  4. Nous devons implémenter quelques méthodes dans notre classe personnalisée pour ouvrir le xib une fois initialisé

    class XibbedView: UIView {

    weak var nibView: UIView!
    
    override convenience init(frame: CGRect) {
        let nibName = NSStringFromClass(self.dynamicType).componentsSeparatedByString(".").last!
        self.init(nibName: nibName)
    }
    
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        let nibName = NSStringFromClass(self.dynamicType).componentsSeparatedByString(".").last!
        let nib = loadNib(nibName)
        nib.frame = bounds
        nib.translatesAutoresizingMaskIntoConstraints = false
        addSubview(nib)
        nibView = nib
        setUpConstraints()
    }
    
    init(nibName: String) {
        super.init(frame: CGRectZero)
        let nibName = NSStringFromClass(self.dynamicType).componentsSeparatedByString(".").last!
        let nib = loadNib(nibName)
        nib.frame = bounds
        nib.translatesAutoresizingMaskIntoConstraints = false
        addSubview(nib)
        nibView = nib
        setUpConstraints()
    }
    
    func setUpConstraints() {
        ["V","H"].forEach { (quote) -> () in
            let format = String(format:"\(quote):|[nibView]|")
            addConstraints(NSLayoutConstraint.constraintsWithVisualFormat(format, options: [], metrics: nil, views: ["nibView" : nibView]))
        }
    }
    
    func loadNib(name: String) -> UIView {
        let bundle = NSBundle(forClass: self.dynamicType)
        let nib = UINib(nibName: name, bundle: bundle)
        let view = nib.instantiateWithOwner(self, options: nil)[0] as! UIView
    
        return view
    }

    }

  5. Dans notre classe personnalisée, nous pouvons également définir certaines propriétés pouvant être inspectées pour avoir un contrôle total sur celles-ci à partir du générateur d'interface

    @IBDesignable class MemeView: XibbedView {

    @IBInspectable var memeImage: UIImage = UIImage() {
        didSet {
            imageView.image = memeImage
        }
    }
    @IBInspectable var textColor: UIColor = UIColor.whiteColor() {
        didSet {
            label.textColor = textColor
        }
    }
    @IBInspectable var text: String = "" {
        didSet {
            label.text = text
        }
    }
    @IBInspectable var roundedCorners: Bool = false {
        didSet {
            if roundedCorners {
                layer.cornerRadius = 20.0
                clipsToBounds = true
            }
            else {
                layer.cornerRadius = 0.0
                clipsToBounds = false
            }
        }
    }
    
    @IBOutlet weak var label: UILabel!
    @IBOutlet weak var imageView: UIImageView!

}

Quelques exemples:
entrez la description de l'image ici entrez la description de l'image ici

Si nous devons ajouter plus d'informations pendant que la vue est affichée dans un storyboard ou un autre xib, pour ce faire, nous pouvons l'implémenter prepareForInterfaceBuilder(), cette méthode sera exécutée uniquement lors de l'ouverture du fichier dans le générateur d'interface. Si vous avez fait tout ce que j'ai écrit mais que rien ne fonctionne, c'est un moyen de déboguer une vue sigle en ajoutant des points d'arrêt dans sa mise en œuvre. Voici la hiérarchie des vues. entrez la description de l'image ici
Voir la recherche

J'espère que cela aide un échantillon complet peut être téléchargé ici

Andrea
la source
L'article complet peut être consulté sur mon blog cloudintouch.it/2016/04/21/designable-xib-in-xcode-hell-yeah mais j'ai déjà posté la partie pertinente.
Andrea
let nib = loadNib(nibName)il s'agit d'une instance XibbedView, si vous appelez `` addSubview (nib) '', alors vous ajoutez une instance XibbedView à une autre instance XibbedView?
William Hu
je suis d'accord sur ce point. Lorsque vous essayez de le voir à partir de "Debug View Hierarchy" semble XibbedView contient un XibbedView. Tu peux le voir.
William Hu
@WilliamHu si vous téléchargez l'exemple de fourniture, vous pouvez voir que le MemeView.xib est juste une instance UIView avec un tas de sous-vues, le détenteur du fichier est un MemeView qui est une sous-classe de XibbedView. Ainsi, la seule instance de XibbedView est juste MemeView qui charge un fichier xib dont la vue principale n'est qu'une vue
Andrea
Oh ok alors. Je vais le tester. Je vous remercie!
William Hu
4

J'ai trouvé cet article de blog d'Aaron Hillegass (auteur, instructeur, Cocoa ninja) très instructif. Même si vous n'adoptez pas son approche modifiée du chargement des fichiers NIB via un initialiseur désigné, vous aurez probablement au moins une meilleure compréhension du processus en cours. J'ai utilisé cette méthode récemment avec beaucoup de succès!

Meltemi
la source
Le lien est mort. Je crois qu'il est maintenant situé sur bignerdranch.com/blog/…
joelliusp
3

La réponse précédente ne prend pas en compte une modification de la structure NIB (XIB) survenue entre 2.0 et 2.1 du SDK iPhone. Le contenu utilisateur commence maintenant à l'index 0 au lieu de 1.

Vous pouvez utiliser la macro 2.1 qui est valable pour toutes les versions 2.1 et supérieures (c'est deux soulignements avant IPHONE:

 // Cited from previous example
 NSArray* nibViews =  [[NSBundle mainBundle] loadNibNamed:@"QPickOneView" owner:self options:nil];
 int startIndex;
 #ifdef __IPHONE_2_1
 startIndex = 0;
 #else
 startIndex = 1;
 #endif
 QPickOneView* myView = [ nibViews objectAtIndex: startIndex];
 myView.question = question;

Nous utilisons une technique similaire à celle-ci pour la plupart de nos applications.

Barney

Barney Mattox
la source
2
Barney: "La réponse précédente" n'a aucun sens sur Stack Overflow car les réponses changent de position en fonction du vote. Mieux vaut se référer à "la réponse de Fred" ou à la "réponse de Barney" ou à la "réponse d'Olie".
Olie
3

J'avais des raisons de faire la même chose (charger par programme une vue à partir d'un fichier XIB), mais je devais le faire entièrement à partir du contexte d'une sous-classe d'une sous-classe de UIView(c'est-à-dire sans impliquer le contrôleur de vue de quelque manière que ce soit). Pour ce faire, j'ai créé cette méthode utilitaire:

+ (id) initWithNibName:(NSString *)nibName withSelf:(id)myself {

    NSArray *bundle = [[NSBundle mainBundle] loadNibNamed:nibName
                                                    owner:myself options:nil];
    for (id object in bundle) {
        if ([object isKindOfClass:[myself class]]) {
            return object;
        }
    }  

    return nil;
} 

Ensuite, je l'appelle à partir de la initWithFrameméthode de ma sous-classe comme suit:

- (id)initWithFrame:(CGRect)frame {

    self = [Utilities initWithNibName:@"XIB1" withSelf:self];
    if (self) {
        // Initialization code.
    }
    return self;
}

Publié pour l'intérêt général; si quelqu'un voit des problèmes sans le faire de cette façon, faites-le moi savoir.

MusiGenesis
la source
J'ai implémenté votre code, mais il reste hors de portée sur init. J'ai créé votre méthode statique dans une classe Utilities. J'ai créé des fichiers h / m appelés MyView (sous-classés à partir de UIView) et un xib nommé le même. Dans IB, j'ai défini le propriétaire des fichiers xib ET la vue sur "MyView". Dans le débogueur, c'est hors de portée. Suis-je en train de manquer quelque chose dans IB?
TigerCoding
Veuillez jeter un œil à ma nouvelle question SO ici: stackoverflow.com/questions/9224857/…
TigerCoding
self = [Utilitaires initWithNibName: @ "XIB1" withSelf: self]; Vous vous passez quand ce n'est pas encore réglé. Ce code n'est-il pas le même que celui-ci: self = [Utilitaires initWithNibName: @ "XIB1" withSelf: nil]; ?
Ken Van Hoeylandt
@Javy: mon exemple de code ici peut ne pas fonctionner avec ARC - je ne l'ai pas testé avec ARC activé.
MusiGenesis
3

Voici un moyen de le faire dans Swift (en train d'écrire Swift 2.0 dans XCode 7 beta 5).

À partir de votre UIViewsous-classe que vous définissez comme "Classe personnalisée" dans le générateur d'interface, créez une méthode comme celle-ci (ma sous-classe s'appelle RecordingFooterView):

class func loadFromNib() -> RecordingFooterView? {
    let nib = UINib(nibName: "RecordingFooterView", bundle: nil)
    let nibObjects = nib.instantiateWithOwner(nil, options: nil)
    if nibObjects.count > 0 {
        let topObject = nibObjects[0]
        return topObject as? RecordingFooterView
    }
    return nil
}

Ensuite, vous pouvez simplement l'appeler comme ceci:

let recordingFooterView = RecordingFooterView.loadFromNib()

Johannes
la source
2

Aucune des réponses n'explique comment créer le XIB autonome qui est à l'origine de cette question. Il n'y a pas d' Xcode 4option pour "Créer un nouveau fichier XIB".

Pour faire ça

1) Choose "New File..."
2) Choose the "User Interface" category under the iOS section
3) Choose the "View" item
4) You will then be prompted to choose an iPhone or iPad format

Cela peut sembler simple, mais cela peut vous faire économiser quelques minutes, car le mot "XIB" n'apparaît nulle part.

Whitneyland
la source
1

@AVeryDev

6) Pour attacher la vue chargée à la vue de votre contrôleur de vue:

[self.view addSubview:myViewFromNib];

Vraisemblablement, il est nécessaire de le retirer de la vue pour éviter les fuites de mémoire.

Pour clarifier: le contrôleur de vue a plusieurs IBOutlets, dont certains sont connectés aux éléments du fichier nib d'origine (comme d'habitude), et certains sont connectés aux éléments de la nib chargée. Les deux plumes ont la même classe de propriétaire. La vue chargée recouvre la vue d'origine.

Astuce: définissez l'opacité de la vue principale dans la plume chargée à zéro, cela n'obscurcira pas les éléments de la plume d'origine.

Mat
la source
C'est une bonne chose à surveiller, mais l'ajout d'une vue en tant que sous-vue la supprime automatiquement de son parent précédent afin qu'elle ne fuit pas.
averydev
1

Après avoir passé plusieurs heures, j'ai forgé la solution suivante. Suivez ces étapes pour créer une personnalisationUIView .

1) Créez la classe myCustomView héritée de UIView .
entrez la description de l'image ici

2) Créez .xib avec le nom myCustomView .
entrez la description de l'image ici

3) Modifiez la classe d' UIView dans votre fichier .xib, affectez- y la classe myCustomView .
entrez la description de l'image ici

4) Créer des IBOutlets
entrez la description de l'image ici

5) Chargez .xib dans myCustomView * customView . Utilisez l'exemple de code suivant.

myCustomView * myView = [[[NSBundle mainBundle] loadNibNamed:@"myCustomView" owner:self options:nil] objectAtIndex:0];
[myView.description setText:@"description"];

Remarque: pour ceux qui rencontrent toujours des problèmes, je peux leur fournir un lien vers un exemple de projet avec myCustomView.

Aamir
la source
1

Version la plus courte:

RSFavoritePlaceholderView *favoritePlaceholderView = [[[NSBundle mainBundle] loadNibNamed:@"RSFavoritePlaceholderView" owner:self options:nil] firstObject];
Denis Kutlubaev
la source
0

J'ai une convention de nommer xibs avec des vues identiques à celles de la vue. Même chose que pour un contrôleur de vue. Ensuite, je n'ai pas à écrire les noms de classe dans le code. Je charge une UIView à partir d'un fichier nib du même nom.

Exemple pour une classe appelée MyView.

  • Créez un fichier nib appelé MyView.xib dans Interface Builder
  • Dans Interface Builder, ajoutez une UIView. Définissez sa classe sur MyView. Personnalisez selon le contenu de votre cœur, connectez les variables d'instance de MyView aux sous-vues auxquelles vous voudrez peut-être accéder plus tard.
  • Dans votre code, créez un nouveau MyView comme ceci:

    MyView * myView = [MyView nib_viewFromNibWithOwner: propriétaire];

Voici la catégorie pour cela:

@implementation UIView (nib)

+ (id) nib_viewFromNib {
    return [self nib_viewFromNibWithOwner:nil];
}

+ (id) nib_viewFromNibWithOwner:(id)owner {
    NSString *className = NSStringFromClass([self class]);
    NSArray *nib = [[NSBundle mainBundle] loadNibNamed:className owner:owner options:nil];
    UIView *view = nil;
    for(UIView *v in nib) {
        if ([v isKindOfClass:[self class]]) {
            view = v;
            break;
        }
    }
    assert(view != nil && "View for class not found in nib file");
    [view nib_viewDidLoad];
    return view;
}

// override this to do custom setup
-(void)nib_viewDidLoad {

}

Je câblerais ensuite les boutons avec les actions du contrôleur que j'utilise et je définirais les choses sur les étiquettes à l'aide des prises de ma sous-classe de vue personnalisée.

n13
la source
0

Pour charger par programme une vue à partir d'un nib / xib dans Swift 4:

// Load a view from a Nib given a placeholder view subclass
//      Placeholder is an instance of the view to load.  Placeholder is discarded.
//      If no name is provided, the Nib name is the same as the subclass type name
//
public func loadViewFromNib<T>(placeholder placeholderView: T, name givenNibName: String? = nil) -> T {

    let nib = loadNib(givenNibName, placeholder: placeholderView)
    return instantiateView(fromNib: nib, placeholder: placeholderView)
}

// Step 1: Returns a Nib
//
public func loadNib<T>(_ givenNibName: String? = nil, placeholder placeholderView: T) -> UINib {
    //1. Load and unarchive nib file
    let nibName = givenNibName ?? String(describing: type(of: placeholderView))

    let nib = UINib(nibName: nibName, bundle: Bundle.main)
    return nib
}

// Step 2: Instantiate a view given a nib
//
public func instantiateView<T>(fromNib nib: UINib, placeholder placeholderView: T) -> T {
    //1. Get top level objects
    let topLevelObjects = nib.instantiate(withOwner: placeholderView, options: nil)

    //2. Have at least one top level object
    guard let firstObject = topLevelObjects.first else {
        fatalError("\(#function): no top level objects in nib")
    }

    //3. Return instantiated view, placeholderView is not used
    let instantiatedView = firstObject as! T
    return instantiatedView
}
Eng Yew
la source
-1

J'ai fini par ajouter une catégorie à UIView pour cela:

 #import "UIViewNibLoading.h"

 @implementation UIView (UIViewNibLoading)

 + (id) loadNibNamed:(NSString *) nibName {
    return [UIView loadNibNamed:nibName fromBundle:[NSBundle mainBundle] retainingObjectWithTag:1];
 }

 + (id) loadNibNamed:(NSString *) nibName fromBundle:(NSBundle *) bundle retainingObjectWithTag:(NSUInteger) tag {
    NSArray * nib = [bundle loadNibNamed:nibName owner:nil options:nil];
    if(!nib) return nil;
    UIView * target = nil;
    for(UIView * view in nib) {
        if(view.tag == tag) {
            target = [view retain];
            break;
        }
    }
    if(target && [target respondsToSelector:@selector(viewDidLoad)]) {
        [target performSelector:@selector(viewDidLoad)];
    }
    return [target autorelease];
 }

 @end

explication ici: viewcontroller est moins de chargement de vue dans ios et mac

gngrwzrd
la source
Hrm. Quel est le tag?
n13
pour savoir quel objet de niveau supérieur conserver. à l'époque, c'était nécessaire, mais vous pourriez probablement retirer les conserves pour vous conformer à l'ARC maintenant.
gngrwzrd