Bonne façon de charger une Nib pour une sous-classe UIView

98

Je sais que cette question a déjà été posée mais les réponses sont contradictoires et je suis confuse, alors ne m'enflammez pas.

Je souhaite avoir une UIViewsous-classe réutilisable dans mon application. Je veux décrire l'interface à l'aide d'un fichier nib.

Disons maintenant que c'est une vue d'indicateur de chargement avec un indicateur d'activité. Je voudrais sur un événement pour instancier cette vue et l'animer dans la vue d'un contrôleur de vue. Je pourrais décrire l'interface de la vue sans problème par programme, en créant les éléments par programme et en définissant leur cadre dans une méthode init, etc.

Comment puis-je faire cela en utilisant une pointe? Conserver la taille indiquée dans le générateur d'interface sans avoir à définir un cadre.

J'ai réussi à le faire comme ça, mais je suis sûr que c'est faux (c'est juste une vue avec un sélecteur):

 - (id)initWithDataSource:(NSDictionary *)dataSource {
        self = [super init];
        if (self){
            self = [[[NSBundle mainBundle] loadNibNamed:[NSString stringWithFormat:@"%@", [self class]] owner:self options:nil] objectAtIndex:0];
            self.pickerViewData = dataSource;
            [self configurePickerView];
        }
        return self;
    }

Mais j'écrase moi-même et quand je l'instancie:

FSASelectView *selectView = [[FSASelectView alloc] initWithDataSource:selectViewDictionary];
    selectView.delegate = self;

    selectView.frame = CGRectMake(0, self.view.bottom + 50, [FSASelectView width], [FSASelectView height]);

Je dois régler manuellement le cadre plutôt que de le récupérer auprès d'IB.

EDIT: Je souhaite créer cette vue personnalisée dans un contrôleur de vue et avoir accès pour contrôler les éléments de la vue. Je ne veux pas de nouveau contrôleur de vue.

Merci

EDIT: Je ne sais pas si c'est la meilleure pratique, je suis sûr que ce n'est pas le cas, mais voici comment je l'ai fait:

FSASelectView *selectView = [[[NSBundle mainBundle] loadNibNamed:[NSString stringWithFormat:@"%@",[FSASelectView class]] owner:self options:nil] objectAtIndex:0];
    selectView.delegate = self;
    [selectView configurePickerViewWithData:ds];
    selectView.frame = CGRectMake(0, self.view.bottom + 50, selectView.width, selectView.height);
    selectView.alpha = 0.9;
    [self.view addSubview:selectView];
    [UIView animateWithDuration: 0.25 delay: 0 options:UIViewAnimationOptionAllowUserInteraction |UIViewAnimationOptionCurveEaseInOut animations:^{
                            selectView.frame = CGRectMake(0, self.view.bottom - selectView.height, selectView.width, selectView.height);
                            selectView.alpha = 1;
                        } completion:^(BOOL finished) {
                        }];

Une pratique correcte est toujours souhaitée

Cela aurait-il dû être fait en utilisant un contrôleur de vue et init avec le nom de la nib? Dois-je avoir défini la pointe dans une méthode d'initialisation UIView dans le code? Ou ce que j'ai fait est-il correct?

Adam Waite
la source
Mais la première ligne de code n'est-elle pas presque la même que celle que vous avez utilisée dans la question d'origine initWithDataSource? Quoi qu'il en soit, cela fonctionnera même si vous donnez au propriétaire la valeur «Nil».
Rakesh
Il convient de noter que ces jours-ci, dans 99,99999% des cas, on utiliserait simplement des vues de conteneur , qui sont désormais utilisées partout et toujours dans iOS. article pratique
Fattie
note latérale que vous pouvez remplacer [NSString stringWithFormat: @ "% @", [FSASelectView class]] par NSStringFromClass ([FSASelectView class])
naomimichiko

Réponses:

132
MyViewClass *myViewObject = [[[NSBundle mainBundle] loadNibNamed:@"MyViewClassNib" owner:self options:nil] objectAtIndex:0]

J'utilise ceci pour initialiser les vues personnalisées réutilisables que j'ai.


Notez que vous pouvez utiliser "firstObject" à la fin, c'est un peu plus propre. "firstObject" est une méthode pratique pour NSArray et NSMutableArray.

Voici un exemple typique de chargement d'un xib à utiliser comme en-tête de table. Dans votre fichier YourClass.m

- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
    return [[NSBundle mainBundle] loadNibNamed:@"TopArea" owner:self options:nil].firstObject;
}

Normalement, dans le TopArea.xib, vous cliqueriez sur le propriétaire du fichier et définissez le propriétaire du fichier sur YourClass . En fait, dans YourClass.h, vous auriez des propriétés IBOutlet. Dans TopArea.xib, vous pouvez faire glisser les contrôles vers ces prises.

N'oubliez pas que dans TopArea.xib, vous devrez peut-être cliquer sur la vue elle - même et la faire glisser vers une prise, pour en avoir le contrôle, si nécessaire. (Un conseil très utile est que lorsque vous faites cela pour les lignes de cellules de tableau, vous devez absolument le faire - vous devez connecter la vue elle-même à la propriété appropriée dans votre code.)

Premsuraj
la source
il n'y a pas de méthode initWithNibName: pour un UIView, c'est juste pour UIViewController
meronix
C'est pour un contrôleur de vue, je veux utiliser un UIView et avoir le contrôleur qui le crée pour le posséder.
Adam Waite
Je suis désolé, j'ai copié le mauvais code dans ma hâte, j'ai mis à jour la réponse, veuillez jeter un œil.
Premsuraj
Je vais marquer cela comme étant correct. Je ne sais pas si c'est la meilleure pratique mais ça marche.
Adam Waite
@Joe Blow Désolé pour la bosse, mais pourquoi devrait-on faire ceci: "N'oubliez pas que dans TopArea.xib, vous devrez peut-être cliquer sur la vue elle-même et la faire glisser vers une prise, pour en avoir le contrôle , si nécessaire." Pourriez-vous à la place simplement utiliser myViewObject pour accéder à la vue sous-jacente car il s'agit d'un UIView lui-même? Pourquoi une propriété est-elle nécessaire?
user1686342
39

Si vous souhaitez conserver votre CustomViewet ses xibindépendants de File's Owner, suivez ces étapes

  • Laissez le File's Ownerchamp vide.
  • Cliquez sur la vue réelle dans le xibfichier de votre CustomViewet définissez-la Custom Classcomme CustomView(nom de votre classe de vue personnalisée)
  • Ajouter IBOutletau .hfichier de votre vue personnalisée.
  • Dans .xib fichier de votre vue personnalisée, cliquez sur la vue et entrez Connection Inspector. Vous trouverez ici tous vos IBOutlets que vous définissez dans le .hfichier
  • Connectez-les avec leur vue respective.

dans le .mfichier de votre CustomViewclasse, remplacez leinit méthode comme suit

-(CustomView *) init{
    CustomView *result = nil;
    NSArray* elements = [[NSBundle mainBundle] loadNibNamed: NSStringFromClass([self class]) owner:self options: nil];
    for (id anObject in elements)
    {
        if ([anObject isKindOfClass:[self class]])
        {
            result = anObject;
            break;
        }
    }
    return result;
}

Maintenant, lorsque vous souhaitez charger votre CustomView, utilisez la ligne de code suivante [[CustomView alloc] init];

Ans
la source
1
Depuis iOS 9, c'est la seule solution répertoriée sur cette page qui fonctionne réellement pour moi (les autres solutions provoquent des plantages).
lifjoy
Notez simplement que cette approche n'est pas compatible avec le chargement de votre vue personnalisée à partir d'un XIB existant car elle n'appelle pas -init. Au lieu de cela, il appellera initWithFrame:et -awakeFromNib. Si vous écrivez le même code pour charger votre vue que dans la -initméthode, vous entrerez dans une boucle infinie.
Dustt
25

Suivez les étapes suivantes

  1. Créez une classe nommée MyView .h / .m de type UIView.
  2. Créez un xib du même nom MyView.xib.
  3. Maintenant, changez la classe File Owner en UIViewControllerfrom NSObjectin xib. Voir l'image ci-dessous entrez la description de l'image ici
  4. Connectez la vue du propriétaire de fichier à votre vue. Voir l'image ci-dessous entrez la description de l'image ici

  5. Changez la classe de votre vue en MyView. Identique à 3.

  6. Les contrôles de placement créent des IBOutlets.

Voici le code pour charger la vue:

UIViewController *controller=[[UIViewController alloc] initWithNibName:@"MyView" bundle:nil];
MyView* view=(MyView*)controller.view;
[self.view addSubview:myview];

J'espère que ça aide.

Clarification :

UIViewControllersert à charger votre xib et la vue que UIViewControllerpossède est en fait celle MyViewque vous avez assignée dans MyView xib.

Démo J'ai fait une démo ici

iphonic
la source
Pourquoi les gens votent-ils, mal? Je n'ai pas encore essayé, mais il semble que cela ne fera pas ce que je veux.
Adam Waite
J'avais voté à la baisse, mais c'est parce que j'ai mal lu votre réponse (je pensais que vous attribuiez une sous-classe UIView comme propriétaire du fichier). Désolé pour ça.
Rakesh
2
Dans votre fichier xib, ignorez le propriétaire des fichiers. Ensuite, vous pouvez éviter d'avoir à créer un UIViewController en faisant simplement un MyView * view = [[UINib instantiateWithOwner: nil options: nil] lastObject];
LightningStryk
1
@LightningStryk Oui bien sûr, mais c'est juste une autre façon de le faire.
iphonic
1
Il s'agit de créer une sous-classe UIView réutilisable, et non d'utiliser un UIViewController à cette fin.
arielyz
11

Répondre à ma propre question environ 2 ans ou quelque chose plus tard ici mais ...

Il utilise une extension de protocole afin que vous puissiez le faire sans aucun code supplémentaire pour toutes les classes.

/*

Prerequisites
-------------
- In IB set the view's class to the type hook up any IBOutlets
- In IB ensure the file's owner is blank

*/

public protocol CreatedFromNib {
    static func createFromNib() -> Self?
    static func nibName() -> String?
}

extension UIView: CreatedFromNib { }

public extension CreatedFromNib where Self: UIView {

    public static func createFromNib() -> Self? {
        guard let nibName = nibName() else { return nil }
        guard let view = NSBundle.mainBundle().loadNibNamed(nibName, owner: nil, options: nil).last as? Self else { return nil }
        return view
    }

    public static func nibName() -> String? {
        guard let n = NSStringFromClass(Self.self).componentsSeparatedByString(".").last else { return nil }
        return n
    }
}

// Usage:
let myView = MyView().createFromNib()
Adam Waite
la source
8

Dans Swift:

Par exemple, le nom de votre classe personnalisée est InfoView

Au début, vous créez des fichiers InfoView.xibet InfoView.swiftcomme ceci:

import Foundation
import UIKit

class InfoView: UIView {
    class func instanceFromNib() -> UIView {
    return UINib(nibName: "InfoView", bundle: nil).instantiateWithOwner(nil, options: nil)[0] as! UIView
}

Définissez ensuite File's Ownerà UIViewControlleraimer ceci:

entrez la description de l'image ici

Renommez votre Viewen InfoView:

entrez la description de l'image ici

Cliquez avec le bouton droit sur File's Owneret connectez votre viewchamp à votre InfoView:

entrez la description de l'image ici

Assurez-vous que le nom de la classe est InfoView:

entrez la description de l'image ici

Et après cela, vous pouvez ajouter l'action au bouton dans votre classe personnalisée sans aucun problème:

entrez la description de l'image ici

Et l'utilisation de cette classe personnalisée dans votre MainViewController:

func someMethod() {
    var v = InfoView.instanceFromNib()
    v.frame = self.view.bounds
    self.view.addSubview(v)
}
vkalit
la source
2

Eh bien, vous pouvez soit initialiser le xib en utilisant un contrôleur de vue et utiliser viewController.view. ou faites-le comme vous l'avez fait. Créer uniquement une UIViewsous - classe comme contrôleur pourUIView est une mauvaise idée.

Si vous ne disposez d'aucune prise dans votre vue personnalisée, vous pouvez directement utiliser une UIViewControllerclasse pour l'initialiser.

Mise à jour: dans votre cas:

UIViewController *genericViewCon = [[UIViewController alloc] initWithNibName:@"CustomView"];
//Assuming you have a reference for the activity indicator in your custom view class
CustomView *myView = (CustomView *)genericViewCon.view;
[parentView addSubview:myView];
//And when necessary
[myView.activityIndicator startAnimating]; //or stop

Sinon, vous devez faire une personnalisation UIViewController(pour en faire le propriétaire du fichier afin que les prises soient correctement câblées).

YourCustomController *yCustCon = [[YourCustomController alloc] initWithNibName:@"YourXibName"].

Où que vous souhaitiez ajouter la vue que vous pouvez utiliser.

[parentView addSubview:yCustCon.view];

Cependant, passer un autre contrôleur de vue (déjà utilisé pour une autre vue) en tant que propriétaire lors du chargement du xib n'est pas une bonne idée car la propriété de vue du contrôleur sera modifiée et lorsque vous souhaitez accéder à la vue d'origine, vous ne le ferez pas. y faire référence.

EDIT: Vous rencontrerez ce problème si vous avez configuré votre nouveau xib avec le propriétaire du fichier comme le même principalUIViewController classe et lié la propriété view à la nouvelle vue xib.

c'est à dire;

  • YourMainViewController - gère - mainView
  • CustomView - doit charger à partir de xib au fur et à mesure des besoins.

Le code ci-dessous causera de la confusion plus tard, si vous l'écrivez à l'intérieur de la vue a été chargée YourMainViewController. En effet, à self.viewpartir de ce moment, se référera à votre vue personnalisée

-(void)viewDidLoad:(){
  UIView *childView= [[[NSBundle mainBundle] loadNibNamed:@"YourXibName" owner:self options:nil] objectAtIndex:0];
}
Rakesh
la source
Ok, donc fondamentalement, la meilleure pratique stipule que chaque UIView devrait avoir un contrôleur associé?
Adam Waite
Pas nécessairement. Mais lors du chargement d'une vue à partir d'un xib séparé, ce serait le moyen sans problème. La documentation Apple pré-IOS5 indique qu'un contrôleur de vue ne doit pas être utilisé pour contrôler plus d'une scène (xib) et vice-versa.
Rakesh
Le xib n'a en fait que la taille d'une vue d'alerte
Adam Waite
Personnellement, je pense que cela dépend de la complexité du code / xib si vous devez créer un nouveau contrôleur ou non. Si vous pouvez gérer les problèmes causés avec succès et si vous ne regardez pas les modifications futures, ce n'est pas un problème. Mais créer un contrôleur de vue séparé résoudrait beaucoup de problèmes pour vous. Et comme dans votre cas, il ne s'agit que d'un indicateur d'activité, vous pouvez facilement utiliser un objet UIViewController (comme mentionné dans la réponse). Je mettrai à jour ma réponse pour le faire en conséquence.
Rakesh
1
Je peux donc dessiner l'interface dans le générateur d'interface plutôt que de le faire par programme. Ce n'est pas un problème de le faire par programme, je veux juste savoir comment sous-classer un UIView et lui donner une plume! Cela ne devrait pas être difficile.
Adam Waite