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:
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:
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?
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:
l'espace bleu est essentiellement un uivi qui va "tiens" ton xib.
C'est votre xib:
il y a une Action reliée à un bouton dessus qui imprimera du texte.
et voici le résultat final:
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.
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;
}
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):
-
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) } }
-
dans votre storyboard, créez une vue qui abritera le .fichier xib. Donnez-lui un nom de classe de
XibView
: -
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 snapkit pour faciliter les choses:
xibView.snp_makeConstraints(closure: { (make) -> Void in
make.edges.equalTo(self)
})
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]
Étape
- Ajouter Un Nouveau Fichier => Interface Utilisateur => UIView
- Ensemble de la Classe Personnalisée - yourUIViewClass
- Set Restoration ID-yourUIViewClass
-
yourUIViewClass *yourView = [[[NSBundle mainBundle] loadNibNamed:@"yourUIViewClass" owner:self options:nil] firstObject]; [self.view addSubview:yourView]
Maintenant vous pouvez personnaliser comme vous le souhaitez.
- 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:
surNSBundle.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 deself.view
'S cadre sauf si vous avez besoin de le rendre plus petit queself.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
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])
}
}
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;
}
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.
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.