Disons que j'ai un storyboard qui contient UINavigationController
comme contrôleur de vue initial. Son contrôleur de vue racine est une sous-classe de UITableViewController
, qui est BasicViewController
. Il a IBAction
qui est connecté au bouton de navigation droit de la barre de navigation
À partir de là, j'aimerais utiliser le storyboard comme modèle pour d'autres vues sans avoir à créer de storyboards supplémentaires. Supposons 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 la même interface à l'exception de 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 le storyboard de BasicViewController
mais avoir le contrôleur de vue racine dans la sous-classe SpecificViewController1
et SpecificViewController2
?
Merci.
grande question - mais malheureusement seulement une réponse boiteuse. Je ne pense pas qu'il soit actuellement possible de faire ce que vous proposez car il n'y a pas d'initialiseurs dans UIStoryboard qui permettent de remplacer le contrôleur de vue associé au storyboard tel que défini dans les détails de l'objet dans le storyboard lors de l'initialisation. C'est à l'initialisation que tous les éléments de l'interface utilisateur dans le stoaryboard sont liés à leurs propriétés dans le contrôleur de vue.
Il s'initialise par défaut avec le contrôleur de vue spécifié dans la définition du storyboard.
Si vous essayez de réutiliser les éléments d'interface utilisateur que vous avez créés dans le storyboard, ils doivent toujours être liés ou associés à des propriétés dans lesquelles jamais le contrôleur de vue les utilise pour qu'ils puissent "informer" le contrôleur de vue des événements.
Ce n'est pas très important de copier une mise en page de storyboard, surtout si vous n'avez besoin que d'un design similaire pour 3 vues, mais si vous le faites, vous devez vous assurer que toutes les associations précédentes sont effacées, sinon il se bloquera lorsqu'il essaiera pour communiquer avec le contrôleur de vue précédent. Vous pourrez les reconnaître comme des messages d'erreur KVO dans la sortie du journal.
Quelques approches que vous pourriez adopter:
stocker les éléments de l'interface utilisateur dans une vue UIV - dans un fichier xib et l'instancier à partir de votre classe de base et l'ajouter en tant que sous-vue dans la vue principale, généralement self.view. Ensuite, vous utiliseriez simplement la disposition du storyboard avec des contrôleurs de vue fondamentalement vides tenant leur place dans le storyboard mais avec la sous-classe de contrôleur de vue appropriée qui leur est affectée. Puisqu'ils hériteraient de la base, ils obtiendraient cette vue.
créer la disposition dans le code et l'installer à partir de votre contrôleur de vue de base. De toute évidence, cette approche va à l'encontre de l'objectif d'utiliser le storyboard, mais peut être la voie à suivre dans votre cas. Si vous avez d'autres parties de l'application qui bénéficieraient de l'approche du storyboard, il est possible de dévier ici et là si nécessaire. Dans ce cas, comme ci-dessus, vous devez simplement utiliser des contrôleurs de vue de banque avec votre sous-classe affectée et laisser le contrôleur de vue de base installer l'interface utilisateur.
Ce serait bien si Apple a trouvé un moyen de faire ce que vous proposez, mais le problème d'avoir les éléments graphiques pré-liés à la sous-classe du contrôleur serait toujours un problème.
excellente nouvelle année!! être bien
Le code de ligne que nous recherchons est:
object_setClass(AnyObject!, AnyClass!)
Dans Storyboard -> ajoutez UIViewController, donnez-lui un nom de classe ParentVC.
class ParentVC: UIViewController {
var type: Int?
override func awakeFromNib() {
if type = 0 {
object_setClass(self, ChildVC1.self)
}
if type = 1 {
object_setClass(self, ChildVC2.self)
}
}
override func viewDidLoad() { }
}
class ChildVC1: ParentVC {
override func viewDidLoad() {
super.viewDidLoad()
println(type)
// Console prints out 0
}
}
class ChildVC2: ParentVC {
override func viewDidLoad() {
super.viewDidLoad()
println(type)
// Console prints out 1
}
}
Comme l'indique la réponse acceptée, il ne semble pas possible de faire avec des storyboards.
Ma solution est d'utiliser Nib's - tout comme les développeurs les utilisaient avant les storyboards. Si vous voulez avoir un contrôleur de vue réutilisable et sous-classable (ou même une vue), ma recommandation est d'utiliser Nibs.
SubclassMyViewController *myViewController = [[SubclassMyViewController alloc] initWithNibName:@"MyViewController" bundle:nil];
Lorsque vous connectez tous vos points de vente au "Propriétaire du fichier" dans le MyViewController.xib
vous ne spécifiez PAS dans quelle classe le Nib doit être chargé, vous spécifiez simplement des paires clé-valeur: " cette vue doit être connectée à ce nom de variable d'instance . " Lors de l'appel [SubclassMyViewController alloc] initWithNibName:
le processus d'initialisation spécifie quel contrôleur de vue sera utilisé pour " contrôler " la vue que vous avez créée dans la plume.
Il est possible qu'un storyboard instancie différentes sous-classes d'un contrôleur de vue personnalisé, bien qu'il implique une technique légèrement peu orthodoxe: remplacer la méthode alloc
pour le contrôleur de vue. Lorsque le contrôleur de vue personnalisé est créé, la méthode alloc remplacée renvoie en fait le résultat de l'exécution de alloc
sur la sous-classe.
Je devrais préfacer la réponse à la condition que, même si je l'ai testée dans divers scénarios et que je n'ai reçu aucune erreur, je ne peux pas m'assurer qu'elle s'adaptera à des configurations plus complexes (mais je ne vois aucune raison pour que cela ne fonctionne pas) . De plus, je n'ai soumis aucune application en utilisant cette méthode, il y a donc une chance extérieure qu'elle soit rejetée par le processus de révision d'Apple (bien que je ne vois encore aucune raison pour laquelle cela devrait).
À des fins de démonstration, j'ai une sous-classe de UIViewController
appelée TestViewController
, qui a un UILabel IBOutlet et un IBAction. Dans mon storyboard, j'ai ajouté un contrôleur de vue et modifié sa classe en TestViewController
, et connecté l'IBOutlet à un UILabel et l'IBAction à un UIButton. Je présente le TestViewController au moyen d'une séquence modale déclenchée par un UIButton sur le viewController précédent.
Pour contrôler quelle classe est instanciée, j'ai ajouté une variable statique et des méthodes de classe associées afin d'obtenir/définir la sous-classe à utiliser (je suppose que l'on pourrait adopter d'autres façons de déterminer quelle sous-classe doit être instanciée):
TestViewController.m:
#import "TestViewController.h"
@interface TestViewController ()
@end
@implementation TestViewController
static NSString *_classForStoryboard;
+(NSString *)classForStoryboard {
return [_classForStoryboard copy];
}
+(void)setClassForStoryBoard:(NSString *)classString {
if ([NSClassFromString(classString) isSubclassOfClass:[self class]]) {
_classForStoryboard = [classString copy];
} else {
NSLog(@"Warning: %@ is not a subclass of %@, reverting to base class", classString, NSStringFromClass([self class]));
_classForStoryboard = nil;
}
}
+(instancetype)alloc {
if (_classForStoryboard == nil) {
return [super alloc];
} else {
if (NSClassFromString(_classForStoryboard) != [self class]) {
TestViewController *subclassedVC = [NSClassFromString(_classForStoryboard) alloc];
return subclassedVC;
} else {
return [super alloc];
}
}
}
Pour mon test, j'ai deux sous-classes de TestViewController
: RedTestViewController
et GreenTestViewController
. Les sous-classes ont chacune des propriétés supplémentaires et chacune remplace viewDidLoad
pour changer la couleur d'arrière-plan de la vue et mettre à jour le texte de l'UILabel IBOutlet:
RedTestViewController.m:
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
self.view.backgroundColor = [UIColor redColor];
self.testLabel.text = @"Set by RedTestVC";
}
GreenTestViewController.m:
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor greenColor];
self.testLabel.text = @"Set by GreenTestVC";
}
À certaines occasions, je pourrais vouloir instancier TestViewController
lui-même, à d'autres occasions RedTestViewController
ou GreenTestViewController
. Dans le contrôleur de vue précédent, je le fais au hasard comme suit:
NSInteger vcIndex = arc4random_uniform(4);
if (vcIndex == 0) {
NSLog(@"Chose TestVC");
[TestViewController setClassForStoryBoard:@"TestViewController"];
} else if (vcIndex == 1) {
NSLog(@"Chose RedVC");
[TestViewController setClassForStoryBoard:@"RedTestViewController"];
} else if (vcIndex == 2) {
NSLog(@"Chose BlueVC");
[TestViewController setClassForStoryBoard:@"BlueTestViewController"];
} else {
NSLog(@"Chose GreenVC");
[TestViewController setClassForStoryBoard:@"GreenTestViewController"];
}
Notez que la méthode setClassForStoryBoard
vérifie que le nom de classe demandé est bien une sous-classe de TestViewController, pour éviter toute confusion. La référence ci-dessus à BlueTestViewController
est là pour tester cette fonctionnalité.
essayez ceci, après instantiateViewControllerWithIdentifier.
- (void)setClass:(Class)c {
object_setClass(self, c);
}
comme :
SubViewController *vc = [sb instantiateViewControllerWithIdentifier:@"MainViewController"];
[vc setClass:[SubViewController class]];
Bien qu'il ne s'agisse pas strictement d'une sous-classe, vous pouvez:
Voici un exemple d'un tutoriel Bloc que j'ai écrit, sous-classant ViewController
avec WhiskeyViewController
:
Cela vous permet de créer des sous-classes de sous-classes de contrôleur 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 au contrôleur de classe de base ne se propagent pas à la sous-classe. Si vous avez un lot de sous-classes, vous pouvez être mieux avec l'une des autres solutions, mais cela fera l'affaire.
Si vous ne dépendez pas trop des storyboards, vous pouvez créer un fichier .xib distinct pour le contrôleur.
Définissez le propriétaire du fichier et les prises appropriés sur MainViewController
et remplacez init(nibName:bundle:)
dans Main VC afin que ses enfants puissent accéder à la même Nib et à ses prises.
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 enfant VC pourra réutiliser la plume de son parent:
class ChildViewController: MainViewController {
override func viewDidLoad() {
super.viewDidLoad()
button.tintColor = .blue
}
}
La méthode objc_setclass ne crée pas d'instance de childvc. Mais en sortant de childvc, deinit de childvc est appelé. Comme il n'y a pas de mémoire allouée séparément pour childvc, l'application se bloque. Basecontroller a une instance, alors que child vc n'en a pas.
En me basant particulièrement sur nickgzzjr et Jiří Zahálka réponses plus commentaire sous le deuxième de CocoaBob, j'ai préparé une méthode générique courte faisant exactement ce dont OP a besoin. Il vous suffit de vérifier le nom du storyboard et l'ID du storyboard des contrôleurs de vue
class func instantiate<T: BasicViewController>(as _: T.Type) -> T? {
let storyboard = UIStoryboard(name: "StoryboardName", bundle: nil)
guard let instance = storyboard.instantiateViewController(withIdentifier: "Identifier") as? BasicViewController else {
return nil
}
object_setClass(instance, T.self)
return instance as? T
}
Des options sont ajoutées pour éviter le débouclage forcé, mais la méthode renvoie des objets corrects.
Prenant des réponses d'ici et là, j'ai trouvé cette solution soignée.
Créez un contrôleur de vue parent avec cette fonction.
class ParentViewController: UIViewController {
func convert<T: ParentViewController>(to _: T.Type) {
object_setClass(self, T.self)
}
}
Cela permet au compilateur de s'assurer que le contrôleur de vue enfant hérite du contrôleur de vue parent.
Ensuite, chaque fois que vous souhaitez vous connecter à ce contrôleur à l'aide d'une sous-classe, vous pouvez faire:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
super.prepare(for: segue, sender: sender)
if let parentViewController = segue.destination as? ParentViewController {
ParentViewController.convert(to: ChildViewController.self)
}
}
La partie intéressante est que vous pouvez ajouter une référence de storyboard à lui-même, puis continuer d'appeler le contrôleur de vue enfant "suivant".
Le moyen probablement le plus flexible consiste à utiliser des vues réutilisables.
(Créez une vue dans un fichier XIB distinct ou Container view
et l'ajouter à chaque scène de contrôleur de vue de sous-classe dans le storyboard)