Comment intégrer une vue personnalisée xib dans une scène de storyboard?

je suis relativement nouveau dans le monde de XCode/iOS; j'ai fait quelques applications basées sur des storyboards de taille décente, mais je ne me suis jamais coupé les dents sur toute cette chose nib/xib. Je veux utiliser les mêmes outils pour les scènes pour concevoir/mettre en page une vue/contrôle réutilisable. J'ai donc créé mon tout premier xib pour ma sous-classe de vue et je l'ai peint:

enter image description here

j'ai mes prises connectées et mes contraintes configurées, tout comme je suis habitué à le faire dans le storyboard. J'ai placé la classe de mon File Owner à celle de ma sous-classe UIView personnalisée. Donc je suppose que je peux instancier cette vue sous-classe avec une API, et elle sera configurée / connectée comme indiqué.

maintenant de retour dans mon storyboard, je veux intégrer/réutiliser ceci. Je le fais dans une cellule prototype vue de table:

enter image description here

j'ai une vue. J'ai mis la classe de mon sous-classe. J'ai créé un exutoire pour elle donc, je peux la manipuler.

la question $ 64 est où/Comment puis-je indiquer qu'il ne suffit pas de mettre une instance vide/non configurée de ma sous-classe de vue là, mais d'utiliser le .xib j'ai créé pour configurer/instancier? Ce serait vraiment cool, si dans XCode6, je pouvais simplement entrer le fichier XIB à utiliser pour un uivi donné, mais je ne vois pas de champ pour le faire, donc je suppose que je dois faire quelque chose en code quelque part.

(je ne vois d'autres des questions comme celle-ci sur SO, mais n'ont pas trouvé de demander juste cette partie du puzzle, ou à jour avec XCode6/2015)

mise à Jour

je suis en mesure d'obtenir ce genre de travail en mettant en œuvre ma cellule de table awakeFromNib comme suit:

- (void)awakeFromNib
{
    // gather all of the constraints pointing to the uncofigured instance
    NSArray* progressConstraints = [self.contentView.constraints filteredArrayUsingPredicate: [NSPredicate predicateWithBlock:^BOOL(id each, NSDictionary *_) {
        return (((NSLayoutConstraint*)each).firstItem == self.progressControl) || (((NSLayoutConstraint*)each).secondItem == self.progressControl);
    }]];
    // fetch the fleshed out variant
    ProgramProgressControl *fromXIB = [[[NSBundle mainBundle] loadNibNamed:@"ProgramProgressControl" owner:self options:nil] objectAtIndex:0];
    // ape the current placeholder's frame
    fromXIB.frame = self.progressControl.frame;
    // now swap them
    [UIView transitionFromView: self.progressControl toView: fromXIB duration: 0 options: 0 completion: nil];
    // recreate all of the constraints, but for the new guy
    for (NSLayoutConstraint *each in progressConstraints) {
        id firstItem = each.firstItem == self.progressControl ? fromXIB : each.firstItem;
        id secondItem = each.secondItem == self.progressControl ? fromXIB : each.secondItem;
        NSLayoutConstraint *constraint = [NSLayoutConstraint constraintWithItem: firstItem attribute: each.firstAttribute relatedBy: each.relation toItem: secondItem attribute: each.secondAttribute multiplier: each.multiplier constant: each.constant];
        [self.contentView addConstraint: constraint];
    }
    // update our outlet
    self.progressControl = fromXIB;
}

est-ce aussi simple que ça? Ou est-ce que je travaille trop dur pour ça?

40
demandé sur Travis Griggs 2015-01-07 01:01:05

9 réponses

vous y êtes presque. Vous devez outrepasser initwcoder dans votre classe personnalisée à laquelle vous avez assigné la vue.

- (id)initWithCoder:(NSCoder *)aDecoder {
    if ((self = [super initWithCoder:aDecoder])) {
        [self addSubview:[[[NSBundle mainBundle] loadNibNamed:@"ViewYouCreated" owner:self options:nil] objectAtIndex:0]];
    }
    return self; }

une fois que ce sera fait, le StoryBoard saura charger le xib à l'intérieur de cet uivi.

Voici une explication plus détaillée:

c'est à ça que ressemble votre UIViewController sur votre panneau d'histoire: enter image description here

l'espace bleu est essentiellement un uivi qui va "tiens" ton xib.

C'est votre xib:

enter image description here

il y a une Action reliée à un bouton dessus qui imprimera du texte.

et voici le résultat final:

enter image description here

la différence entre le premier clickMe et le second est que le premier a été ajouté au UIViewController en utilisant le StoryBoard . Le le deuxième a été ajouté en utilisant le code.

26
répondu Segev 2017-03-23 22:25:23

vous devez implémenter awakeAfterUsingCoder: dans votre sous-classe personnalisée UIView . Cette méthode vous permet d'échanger l'objet décodé (du storyboard) avec un autre objet (de votre XIB réutilisable), comme ceci:

- (id) awakeAfterUsingCoder: (NSCoder *) aDecoder
{
    // without this check you'll end up with a recursive loop - we need to know that we were loaded from our view xib vs the storyboard.
    // set the view tag in the MyView xib to be -999 and anything else in the storyboard.
    if ( self.tag == -999 )
    {
        return self;
    }

    // make sure your custom view is the first object in the nib
    MyView* v = [[[UINib nibWithNibName: @"MyView" bundle: nil] instantiateWithOwner: nil options: nil] firstObject];

    // copy properties forward from the storyboard-decoded object (self)
    v.frame = self.frame;
    v.autoresizingMask = self.autoresizingMask;
    v.translatesAutoresizingMaskIntoConstraints = self.translatesAutoresizingMaskIntoConstraints;
    v.tag = self.tag;

    // copy any other attribtues you want to set in the storyboard

    // possibly copy any child constraints for width/height

    return v;
}

Il ya une assez bonne écriture ici discussion de cette technique et quelques alternatives.

en outre, si vous ajoutez IB_DESIGNABLE à votre déclaration @interface, et fournir un initWithFrame: méthode vous pouvez obtenir l'aperçu design-time pour travailler dans IB (Xcode 6 requis!):

IB_DESIGNABLE @interface MyView : UIView
@end

@implementation MyView

- (id) initWithFrame: (CGRect) frame
{
    self = [[[UINib nibWithNibName: @"MyView"
                            bundle: [NSBundle bundleForClass: [MyView class]]]

             instantiateWithOwner: nil
             options: nil] firstObject];

    self.frame = frame;

    return self;
}
15
répondu TomSwift 2015-01-15 21:52:20

une façon assez cool et réutilisable de faire ce constructeur D'Interface et Swift 3 (Swift 2 est similaire, mais vous devrez le modifier vous-même):

  1. créer une nouvelle classe comme ceci:

    import Foundation
    import UIKit
    
    @IBDesignable class XibView: UIView {
    
        @IBInspectable var xibName: String?
    
        override func awakeFromNib() { 
            guard let name = self.xibName, 
                  let xib = Bundle.main.loadNibNamed(name, owner: self), 
                  let views = xib as? [UIView], views.count > 0 else { return }    
            self.addSubview(views[0] as! UIView)
        }
    
    }
    
  2. dans votre storyboard, créez une vue qui abritera le .fichier xib. Donnez-lui un nom de classe de XibView :

  3. dans l'Inspecteur des biens du nouveau XibView, mettez le nom de votre .xib (sans l'extension de fichier):

Une chose à noter: Cela suppose que vous êtes correspondant à l' .cadre xib à son container. Si vous ne le faites pas, ou si vous avez besoin qu'il soit redimensionnable, vous devrez ajouter quelques contraintes programmatiques ou modifier le cadre de subview pour l'adapter. J'utilise pour faciliter les choses:

xibView.snp_makeConstraints(closure: { (make) -> Void in
    make.edges.equalTo(self)
})
15
répondu brandonscript 2018-02-19 17:06:03

vous avez juste à glisser et déposer UIView dans votre IB et de sortie et de mettre

yourUIViewClass  *yourView =   [[[NSBundle mainBundle] loadNibNamed:@"yourUIViewClass" owner:self options:nil] firstObject];
[self.view addSubview:yourView]

enter image description here

Étape

  1. Ajouter Un Nouveau Fichier => Interface Utilisateur => UIView
  2. Ensemble de la Classe Personnalisée - yourUIViewClass
  3. Set Restoration ID-yourUIViewClass
  4. yourUIViewClass *yourView = [[[NSBundle mainBundle] loadNibNamed:@"yourUIViewClass" owner:self options:nil] firstObject]; [self.view addSubview:yourView]

Maintenant vous pouvez personnaliser comme vous le souhaitez.

2
répondu Yuyutsu 2015-01-15 05:58:03
  • créer un fichier xib File > New > New File > iOS > User Interface > View
  • créer la classe custom UIView File > New > New File > iOS > Source > CocoaTouch
  • Assignez l'identité du fichier xib à la classe custom view
  • dans viewDidLoad du contrôleur de vue initialise le xib et son fichier associé en utilisant loadNibNamed: sur NSBundle.mainBundle et la première vue retournée peut être ajoutée comme sous-vue de soi.vue.
  • La vue personnalisée chargé de le nib peut être sauvegardé dans une propriété pour définir le cadre dans viewDidLayoutSubviews . Il suffit de définir le cadre de self.view 'S cadre sauf si vous avez besoin de le rendre plus petit que self.view .

    class ViewController: UIViewController {
    
        weak var customView: MyView!
    
        override func viewDidLoad() {
            super.viewDidLoad()
            self.customView = NSBundle.mainBundle().loadNibNamed("MyView", owner: self, options: nil)[0] as! MyView
            self.view.addSubview(customView)
            addButtonHandlerForCustomView()
        }
    
        private func addButtonHandlerForCustomView() {
            customView.buttonHandler = {
                [weak self] (sender:UIButton) in
                guard let welf = self else {
                    return
                }
                welf.buttonTapped(sender)
            }
        }
    
        override func viewDidLayoutSubviews() {
            self.customView.frame = self.view.frame
        }
    
        private func buttonTapped(button:UIButton) {
    
        }
    }
    
  • aussi, si vous voulez répondre à partir du xib à votre instance UIViewController alors créer une propriété faible sur la classe de la vue personnalisée.

    class MyView: UIView {
    
        var buttonHandler:((sender:UIButton)->())!
    
        @IBAction func buttonTapped(sender: UIButton) {
            buttonHandler(sender:sender)
        }
    }
    

voici le projet sur GitHub

2
répondu smileBot 2017-05-17 04:32:37

une version un peu plus rapide de l'idée de @brandonscript avec un retour anticipé:

override func awakeFromNib() {

    guard let xibName = xibName,
          let xib = Bundle.main.loadNibNamed(xibName, owner: self, options: nil),
          let views = xib as? [UIView] else {
        return
    }

    if views.count > 0 {
        self.addSubview(views[0])
    }

}
1
répondu Sasha Prokhorenko 2016-12-07 15:14:27

j'utilise ce code depuis des années. Si vous prévoyez d'avoir des vues de classe personnalisées dans votre XIB il suffit de laisser tomber ceci dans le .m fichier de votre classe personnalisée.

comme un effet secondaire, il se traduit par un appel de awakeFromNib pour que vous puissiez y laisser tout votre code d'initialisation.

- (id)awakeAfterUsingCoder:(NSCoder*)aDecoder {
    if ([[self subviews] count] == 0) {
        UIView *view = [[NSBundle mainBundle] loadNibNamed:NSStringFromClass([self class]) owner:nil options:nil][0];
        view.frame = self.frame;
        view.autoresizingMask = self.autoresizingMask;
        view.alpha = self.alpha;
        view.translatesAutoresizingMaskIntoConstraints = self.translatesAutoresizingMaskIntoConstraints;
        return view;
    }
    return self;
}
0
répondu alexgophermix 2017-11-01 19:57:57

bien que je ne recommande pas le chemin que vous empruntez, vous pouvez le faire en plaçant une" vue du contrôleur de vue intégré " où vous voulez que la vue apparaisse.

intègre un contrôleur de vue qui contient une vue unique -- la vue que vous voulez réutiliser.

0
répondu Dustin 2018-07-26 03:55:12

la "bonne" réponse est que vous n'êtes pas censé rendre les vues réutilisables avec les nibs correspondants. Si une sous-classe de vue est utile comme objet réutilisable, elle aura rarement besoin d'un nib pour aller avec elle. Prenez par exemple chaque vue sous-classe fournie par UIKit. Une partie de cette pensée est une vue sous-classe qui est réellement valable ne sera pas mis en œuvre en utilisant un nib, qui est la vue générale chez Apple.

habituellement, lorsque vous utilisez une vue en nib ou storyboard, vous voudrez la modifier graphiquement pour le cas d'utilisation donné de toute façon.

vous pourriez envisager d'utiliser" copier coller " pour recréer des vues identiques ou similaires au lieu de faire des nibs séparés. Je ce crois accomplit les mêmes conditions et il vous permettra d'être plus ou moins en ligne avec ce que fait Apple.

-1
répondu Dustin 2015-01-11 11:22:56