128 votes

Comment utiliser un seul storyboard uiviewcontroller pour plusieurs sous-classes

Supposons que j'ai une storyboard qui contient UINavigationController en tant que contrôleur de vue initial. Son contrôleur de vue racine est une sous-classe de UITableViewController, qui est BasicViewController. Il a un IBAction qui est connecté au bouton de navigation droit de la barre de navigation

À partir de là, je voudrais utiliser la storyboard comme un modèle pour d'autres vues sans avoir à créer de storyboards supplémentaires. Disons que ces vues auront exactement la même interface mais avec le contrôleur de vue racine de la classe SpecificViewController1 et SpecificViewController2 qui sont des sous-classes de BasicViewController.
Ces 2 contrôleurs de vue auraient la même fonctionnalité et interface sauf pour la méthode IBAction.
Ce serait comme suit :

@interface BasicViewController : UITableViewController

@interface SpecificViewController1 : BasicViewController

@interface SpecificViewController2 : BasicViewController

Puis-je faire quelque chose comme ça?
Puis-je simplement instancier la storyboard de BasicViewController mais avoir le contrôleur de vue racine comme une sous-classe de SpecificViewController1 et SpecificViewController2?

Merci.

3 votes

Il pourrait être utile de mentionner que vous pouvez le faire avec Nib. Mais si vous êtes comme moi et que vous voulez certaines fonctionnalités intéressantes que seul le storyboard a (cellule statique/prototype, par exemple), alors je suppose que nous n'avons pas de chance.

0 votes

0 votes

Est-ce que cela répond à votre question? Jeter à une sous-classe depuis Storyboard

5voto

Aaron Brager Points 16394

Bien que ce ne soit pas strictement une sous-classe, vous pouvez:

  1. option-glisser le contrôleur de vue de la classe de base dans le plan du document pour en faire une copie
  2. Déplacez la nouvelle copie du contrôleur de vue à un endroit séparé sur le storybord
  3. Changez Classe pour le contrôleur de vue de sous-classe dans l'inspecteur d'identité

Voici un exemple d'un tutoriel Bloc que j'ai écrit, en sous-classant ViewController avec WhiskeyViewController:

animation des trois étapes ci-dessus

Cela vous permet de créer des sous-classes de sous-classes de contrôleurs de vue dans le storyboard. Vous pouvez ensuite utiliser instantiateViewControllerWithIdentifier: pour créer des sous-classes spécifiques.

Cette approche est un peu rigide : les modifications ultérieures dans le storyboard apportées au contrôleur de classe de base ne se propagent pas à la sous-classe. Si vous avez beaucoup de sous-classes, vous seriez peut-être mieux avec l'une des autres solutions, mais cela fera l'affaire en cas de besoin.

12 votes

Ce n’est pas une sous-classe, mon pote, c’est juste la duplication d’un ViewController.

1 votes

Ce n'est pas juste. Cela devient une sous-classe lorsque vous changez Classe en sous-classe (étape 3). Ensuite, vous pouvez apporter les modifications que vous souhaitez et vous connecter aux prises/actions dans votre sous-classe.

6 votes

Je ne pense pas que vous compreniez le concept de sous-classification.

4voto

user8044830 Points 41

La méthode Objc_setclass ne crée pas une instance de childvc. Mais lors de la sortie de childvc, le deinit de childvc est appelé. Comme il n'y a pas de mémoire allouée séparément pour childvc, l'application plante. Basecontroller a une instance, tandis que childvc n'en a pas.

2voto

nitrous Points 21

Si vous n'êtes pas trop dépendant des storyboards, vous pouvez créer un fichier .xib séparé pour le contrôleur.

Définissez le propriétaire de fichier approprié et les sorties vers MainViewController et remplacez init(nibName:bundle:) dans le Main VC afin que ses enfants puissent accéder au même Nib et à ses sorties.

Votre code devrait ressembler à ceci :

class MainViewController: UIViewController {
    @IBOutlet weak var button: UIButton!

    override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
        super.init(nibName: "MainViewController", bundle: nil)
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        button.tintColor = .red
    }
}

Et votre VC enfant pourra réutiliser le nœud parent :

class ChildViewController: MainViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        button.tintColor = .blue
    }
}

2voto

Joe Blow Points 3618

Il existe une solution simple, évidente, quotidienne.

Il suffit de mettre le storyboard/contrôleur existant à l'intérieur du nouveau storyboard/contrôleur. C'est-à-dire comme une vue conteneur.

C'est exactement le même concept que "l'héritage", pour les contrôleurs de vue.

Tout fonctionne exactement comme dans une classe fille.

Tout comme vous mettez communément une sous-vue à l'intérieur d'une autre vue, naturellement vous mettez communément un contrôleur de vue à l'intérieur d'un autre contrôleur de vue.

Comment pourrait-on faire autrement ?

C'est une partie fondamentale d'iOS, aussi simple que le concept de "sous-vue".

C'est aussi simple que ça...

/*

L'écran de recherche est juste une modification de notre écran Liste.

*/

import UIKit

class Recherche: UIViewController {

    var liste: Liste!

    override func viewDidLoad() {
        super.viewDidLoad()

        liste = (_sb("List") as! List
        addChild(liste)
        view.addSubview(liste.view)
        liste.view.bindEdgesToSuperview()
        liste.didMove(toParent: self)
    }
}

Vous avez maintenant évidemment liste pour faire ce que vous voulez avec

liste.mode = .blah
liste.tableview.reloadData()
liste.heading = 'Recherche!'
liste.searchBar.isHidden = false

etc etc.

Les vues conteneur sont "similaires" à l'héritage de la même manière que les "sous-vues" sont "similaires" à l'héritage.

Évidemment, vous ne pouvez pas "hériter d'une mise en page" - que voudrait dire cela ?

("L'héritage" se rapporte au logiciel orienté objet et n'a aucun lien avec les "layouts".)

Évidemment, lorsque vous voulez réutiliser une vue, vous la mettez en tant que sous-vue à l'intérieur d'une autre vue.

Lorsque vous voulez réutiliser une mise en page de contrôleur, vous la mettez en tant que vue conteneur à l'intérieur d'un autre contrôleur.

C'est comme le mécanisme le plus basique d'iOS !!


Remarque - depuis des années, il est trivial de charger dynamiquement un autre contrôleur de vue en tant que vue conteneur. Expliqué dans la dernière section: https://stackoverflow.com/a/23403979/294884

Remarque - "_sb" est juste un macro évident que nous utilisons pour ne pas avoir à taper autant,

func _sb(_ s: String)->UIViewController {
    // par convention, pour un écran "NomEcran.storyboard" l'ID du storyboard doit être NomEcranID
    return UIStoryboard(name: s, bundle: nil)
       .instantiateViewController(withIdentifier: s + "ID")
}

2voto

CocoaBob Points 61

Merci pour la réponse inspirante de @Jirí Zahálka, j'ai répondu à ma solution il y a 4 ans ici, mais @Sayka m'a suggéré de le publier en tant que réponse, donc le voici.

Dans mes projets, normalement, si j'utilise Storyboard pour une sous-classe de UIViewController, je prépare toujours une méthode statique appelée instantiate() dans cette sous-classe, pour créer facilement une instance de Storyboard. Ainsi, pour résoudre la question de l'OP, si nous voulons partager le même Storyboard pour différentes sous-classes, nous pouvons simplement setClass() sur cette instance avant de la retourner.

class func instantiate() -> SubClass {
    let instance = (UIStoryboard(name: "Main", bundle: nil).instantiateViewControllerWithIdentifier("SuperClass") as? SuperClass)!
    object_setClass(instance, SubClass.self)
    return (instance as? SubClass)!
}

0 votes

J'ai essayé ceci et parfois obtenu des débordements de tampon de tas lorsque j'accède aux attributs de la sous-classe. Il semble que lorsque vous définissez la classe de cette manière, la mémoire n'a pas été réaffectée correctement à la sous-classe, donc je dirais .... cette méthode n'est probablement pas une bonne idée.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X