web-dev-qa-db-fra.com

Comment créer une classe de vue iOS personnalisée et en instancier plusieurs copies (dans IB)?

Je suis en train de créer une application qui aura plusieurs minuteries, qui sont fondamentalement toutes identiques.

Je souhaite créer une classe personnalisée qui utilise tout le code pour les minuteries, ainsi que pour l'agencement/les animations, afin de pouvoir disposer de 5 minuteries identiques fonctionnant indépendamment les unes des autres.

J'ai créé la mise en page en utilisant IB (xcode 4.2) et tout le code pour les minuteries est actuellement juste dans la classe viewcontroller.

J'ai de la difficulté à comprendre comment encapsuler le tout dans une classe personnalisée, puis l'ajouter au contrôleur de vue, toute aide serait la bienvenue.

104
Michael Campsall

Bien pour répondre de façon conceptuelle, votre minuterie devrait probablement être une sous-classe de UIView au lieu de NSObject.

Pour instancier une instance de votre minuterie dans IB, faites simplement glisser un UIView sur la vue de votre contrôleur de vue et définissez sa classe sur le nom de classe de votre minuterie.

enter image description here

N'oubliez pas de #import Votre classe de temporisateur dans votre contrôleur de vue.

Edit: pour la conception IB (pour l’instanciation du code, voir historique des révisions)

Je ne connais pas du tout le storyboard, mais je sais que vous pouvez construire votre interface dans IB en utilisant un fichier .xib Qui est presque identique à celui de la version du storyboard. Vous devriez même pouvoir copier et coller vos vues dans leur ensemble depuis votre interface existante vers le fichier .xib.

Pour tester cela, j'ai créé un nouveau .xib Vide nommé "MyCustomTimerView.xib". Ensuite, j'ai ajouté une vue, et à celle-ci, une étiquette et deux boutons. Ainsi:

enter image description here

J'ai créé un nouveau sous-classement de classe Object-C UIView nommé "MyCustomTimer". Dans mon .xib, J'ai défini ma classe propriétaire du fichier sur MyCustomTimer . Maintenant, je suis libre de connecter des actions et des prises comme n'importe quel autre contrôleur/vue. Le fichier .h Résultant a l'aspect suivant:

@interface MyCustomTimer : UIView
@property (strong, nonatomic) IBOutlet UILabel *displayLabel;
@property (strong, nonatomic) IBOutlet UIButton *startButton;
@property (strong, nonatomic) IBOutlet UIButton *stopButton;
- (IBAction)startButtonPush:(id)sender;
- (IBAction)stopButtonPush:(id)sender;
@end

Le seul obstacle qui reste à franchir est d'obtenir ce .xib Sur ma sous-classe UIView. L'utilisation d'un .xib Réduit considérablement la configuration requise. Et puisque vous utilisez des storyboards pour charger les minuteries, nous savons que -(id)initWithCoder: est le seul initialiseur appelé. Voici donc à quoi ressemble le fichier d'implémentation:

#import "MyCustomTimer.h"
@implementation MyCustomTimer
@synthesize displayLabel;
@synthesize startButton;
@synthesize stopButton;
-(id)initWithCoder:(NSCoder *)aDecoder{
    if ((self = [super initWithCoder:aDecoder])){
        [self addSubview:
         [[[NSBundle mainBundle] loadNibNamed:@"MyCustomTimerView" 
                                        owner:self 
                                      options:nil] objectAtIndex:0]];
    }
    return self;
}
- (IBAction)startButtonPush:(id)sender {
    self.displayLabel.backgroundColor = [UIColor greenColor];
}
- (IBAction)stopButtonPush:(id)sender {
    self.displayLabel.backgroundColor = [UIColor redColor];
}
@end

La méthode nommée loadNibNamed:owner:options: Fait exactement ce que cela ressemble. Il charge le Nib et définit la propriété "File's Owner" sur self. Nous extrayons le premier objet du tableau et c’est la vue racine du Nib. Nous ajoutons la vue en tant que sous-vue et voila, elle est à l'écran.

Évidemment, cela ne fait que changer la couleur d'arrière-plan de l'étiquette lorsque les boutons sont enfoncés, mais cet exemple devrait vous faciliter la tâche.

Notes basées sur les commentaires:

Il est à noter que si vous rencontrez des problèmes de récursion infinis, vous avez probablement manqué le truc subtil de cette solution. Ce n'est pas ce que vous pensez faire. La vue qui est placée dans le storyboard n'est pas vue, mais elle en charge une autre vue en tant que sous-vue. La vue qu'elle charge est la vue définie dans la nib. Le "propriétaire du fichier" dans la nib est cette vue invisible. La partie intéressante est que cette vue invisible est toujours une classe Objective-C qui peut être utilisée comme un contrôleur de vues pour la vue qu’elle apporte de la nib. Par exemple, les méthodes IBAction de la classe MyCustomTimer sont plus attendues dans un contrôleur de vue que dans une vue.

Comme note de côté, certains pourraient dire que cela casse MVC et je suis plutôt d’accord. De mon point de vue, il est plus étroitement lié à un UITableViewCell personnalisé, qui doit aussi parfois être partiellement contrôleur.

Il convient également de noter que cette réponse visait à fournir une solution très spécifique; créer une plume qui peut être instanciée plusieurs fois sur le même vue telle que présentée sur un story-board. Par exemple, vous pouvez facilement imaginer six de ces minuteries sur un écran iPad à la fois. Si vous devez uniquement spécifier une vue pour un contrôleur de vue devant être utilisé plusieurs fois dans votre application, alors la solution fournie par jyavenard à cette question est certainement une meilleure solution pour vous.

139
NJones

Exemple rapide

Mise à jour pour Xcode 10 et Swift 4

Voici une promenade de base à travers. Au départ, j’en ai appris beaucoup en regardant cette série de vidéos Youtube . Plus tard, j'ai mis à jour ma réponse sur la base de cet article .

Ajouter des fichiers de vue personnalisés

Les deux fichiers suivants formeront votre vue personnalisée:

  • Fichier .xib contenant la mise en page
  • Fichier .Swift en tant que sous-classe UIView

Les détails pour les ajouter sont ci-dessous.

Fichier Xib

Ajouter un fichier .xib à votre projet (Fichier> Nouveau> Fichier ...> Interface utilisateur> Afficher). J'appelle le mien ReusableCustomView.xib.

Créez la mise en page que vous souhaitez que votre vue personnalisée ait. Par exemple, je vais faire une mise en page avec un UILabel et un UIButton. C'est une bonne idée d'utiliser la mise en page automatique afin que les éléments soient redimensionnés automatiquement, quelle que soit la taille que vous définissiez ultérieurement. (J'ai utilisé Freeform pour la taille de xib dans l'inspecteur Attributs afin de pouvoir ajuster les métriques simulées, mais cela n'est pas nécessaire.)

enter image description here

Fichier Swift

Ajouter un fichier .Swift à votre projet (Fichier> Nouveau> Fichier ...> Source> Swift)). C'est une sous-classe de UIView et j'appelle le mien ReusableCustomView.Swift.

import UIKit
class ResuableCustomView: UIView {

}

Faites du Swift file le propriétaire

Retournez à votre fichier xib et cliquez sur "Propriétaire du fichier" dans la structure du document. Dans l'inspecteur d'identité, écrivez le nom de votre fichier . Swift en tant que nom de classe personnalisé.

enter image description here

Ajouter un code d'affichage personnalisé

Remplace le ReusableCustomView.Swift contenu du fichier avec le code suivant:

import UIKit

@IBDesignable
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)
        commonInit()
    }

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

    func commonInit() {
        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
    }
}

Assurez-vous de bien orthographier le nom de votre fichier .xib.

Branchez les prises et les actions

Reliez les prises et les actions en faisant glisser le contrôle de l'étiquette et du bouton de la présentation xib vers le code Swift).

Utilisez votre vue personnalisée

Votre vue personnalisée est terminée maintenant. Tout ce que vous avez à faire est d’ajouter un UIView où vous le souhaitez dans votre scénarimage principal. Définissez le nom de classe de la vue sur ReusableCustomView dans l'inspecteur d'identité.

enter image description here

120
Suragch

Réponse pour les contrôleurs de vue, pas les vues:

Il existe un moyen plus facile de charger un xib à partir d'un storyboard. Supposons que votre contrôleur soit de type MyClassController, qui hérite de UIViewController.

Vous ajoutez un UIViewController en utilisant IB dans votre storyboard; changez le type de classe en MyClassController. Supprimez la vue qui a été ajoutée automatiquement dans le storyboard.

Assurez-vous que le fichier XIB que vous souhaitez appeler s'appelle MyClassController.xib.

Lorsque la classe sera instanciée lors du chargement du storyboard, le fichier xib sera automatiquement chargé. La raison en est due à l'implémentation par défaut de UIViewController qui appelle le XIB nommé avec le nom de la classe.

39
jyavenard

Ce n'est pas vraiment une réponse, mais je pense qu'il est utile de partager cette approche.

Objective-C

  1. Importer CustomViewWithXib.h et CustomViewWithXib.m dans votre projet
  2. Créez les fichiers de vue personnalisés avec le même nom (.h/.m/.xib)
  3. Hériter de votre classe personnalisée de CustomViewWithXib

Rapide

  1. Importer CustomViewWithXib.Swift dans votre projet
  2. Créez les fichiers de vue personnalisés avec le même nom (.Swift et .xib)
  3. Hériter de votre classe personnalisée de CustomViewWithXib

Facultatif:

  1. Allez dans votre fichier xib, définissez le propriétaire avec votre nom de classe personnalisé si vous avez besoin de connecter certains éléments (pour plus de détails, voir la section Créer le Swift le propriétaire de la réponse de @Suragch))

C'est tout, maintenant vous pouvez ajouter votre vue personnalisée dans votre storyboard et elle sera affichée :)

CustomViewWithXib.h :

 #import <UIKit/UIKit.h>

/**
 *  All classes inherit from CustomViewWithXib should have the same xib file name and class name (.h and .m)
 MyCustomView.h
 MyCustomView.m
 MyCustomView.xib
 */

// This allows seeing how your custom views will appear without building and running your app after each change.
IB_DESIGNABLE
@interface CustomViewWithXib : UIView

@end

CustomViewWithXib.m :

#import "CustomViewWithXib.h"

@implementation CustomViewWithXib

#pragma mark - init methods

- (instancetype)initWithFrame:(CGRect)frame {
    self = [super initWithFrame:frame];
    if (self) {
        // load view frame XIB
        [self commonSetup];
    }
    return self;
}

- (instancetype)initWithCoder:(NSCoder *)aDecoder {
    self = [super initWithCoder:aDecoder];
    if (self) {
        // load view frame XIB
        [self commonSetup];
    }
    return self;
}

#pragma mark - setup view

- (UIView *)loadViewFromNib {
    NSBundle *bundle = [NSBundle bundleForClass:[self class]];

    //  An exception will be thrown if the xib file with this class name not found,
    UIView *view = [[bundle loadNibNamed:NSStringFromClass([self class])  owner:self options:nil] firstObject];
    return view;
}

- (void)commonSetup {
    UIView *nibView = [self loadViewFromNib];
    nibView.frame = self.bounds;
    // the autoresizingMask will be converted to constraints, the frame will match the parent view frame
    nibView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
    // Adding nibView on the top of our view
    [self addSubview:nibView];
}

@end

CustomViewWithXib.Swift :

import UIKit

@IBDesignable
class CustomViewWithXib: UIView {

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

        commonSetup()
    }

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

        commonSetup()
    }

    // MARK: setup view 

    private func loadViewFromNib() -> UIView {
        let viewBundle = NSBundle(forClass: self.dynamicType)
        //  An exception will be thrown if the xib file with this class name not found,
        let view = viewBundle.loadNibNamed(String(self.dynamicType), owner: self, options: nil)[0]
        return view as! UIView
    }

    private func commonSetup() {
        let nibView = loadViewFromNib()
        nibView.frame = bounds
        // the autoresizingMask will be converted to constraints, the frame will match the parent view frame
        nibView.autoresizingMask = [.FlexibleWidth, .FlexibleHeight]
        // Adding nibView on the top of our view
        addSubview(nibView)
    }
}

Vous pouvez trouver quelques exemples ici .

J'espère que ça t'as aidé.

15
Hamza