Cómo utilizar el uiviewcontroller de guion único para múltiples subclases

Digamos que tengo un guión gráfico que contiene UINavigationController como controlador de vista inicial. Su controlador de vista raíz es la subclase de UITableViewController , que es BasicViewController . Tiene IBAction que está conectado al botón derecho de navegación de la barra de navegación

A partir de ahí, me gustaría usar el guión gráfico como una plantilla para otras vistas sin tener que crear guiones gráficos adicionales. Supongamos que estas vistas tendrán exactamente la misma interfaz pero con el controlador de vista raíz de la clase SpecificViewController1 y SpecificViewController2 que son subclases de BasicViewController .
Esos 2 controladores de vista tendrían la misma funcionalidad e interfaz a excepción del método IBAction .
Sería como el siguiente:

 @interface BasicViewController : UITableViewController @interface SpecificViewController1 : BasicViewController @interface SpecificViewController2 : BasicViewController 

¿Puedo hacer algo así?
¿Puedo simplemente crear una instancia del guión gráfico de BasicViewController pero tener el controlador de vista raíz a la subclase SpecificViewController1 y SpecificViewController2 ?

Gracias.

gran pregunta, pero desafortunadamente solo una respuesta floja. No creo que actualmente sea posible hacer lo que propones porque no hay inicializadores en UIStoryboard que permitan anular el controlador de vista asociado con el guión gráfico tal como se define en los detalles del objeto en el guión gráfico durante la inicialización. Es durante la inicialización que todos los elementos de la interfaz de usuario en la tabla guardada están vinculados a sus propiedades en el controlador de vista.

Se inicializará por defecto con el controlador de vista que se especifica en la definición del guión gráfico.

Si está tratando de obtener la reutilización de los elementos de la interfaz de usuario que creó en el guión gráfico, todavía deben estar vinculados o asociados a propiedades en las que el controlador de vista los esté usando para que puedan “decirle” al controlador de vista sobre los eventos.

No es un gran problema copiar sobre un diseño de guión gráfico, especialmente si solo necesitas un diseño similar para 3 vistas, sin embargo, si lo haces, debes asegurarte de que todas las asociaciones anteriores estén desactivadas, o se bloqueará cuando intente. para comunicarse con el controlador de vista anterior. Podrá reconocerlos como mensajes de error KVO en la salida de registro.

Un par de enfoques que podría tomar:

  • almacene los elementos de UI en una UIView – en un archivo xib y ejemplínelo desde su clase base y agréguelo como una subvista en la vista principal, típicamente self.view. Luego, simplemente usaría el diseño del guión gráfico con controladores de vista básicamente en blanco que ocuparían su lugar en el guión gráfico pero con la subclase de controlador de vista correcta asignada a ellos. Como heredarían de la base, tendrían esa vista.

  • crea el diseño en el código e instálalo desde tu controlador de vista base. Obviamente, este enfoque frustra el propósito de usar el guión gráfico, pero puede ser el camino a seguir en su caso. Si tiene otras partes de la aplicación que se beneficiarían del enfoque del guión gráfico, está bien desviarse aquí y allá si corresponde. En este caso, como en el ejemplo anterior, simplemente usaría los controladores de vista bancaria con su subclase asignada y dejaría que el controlador de vista base instale la UI.

Sería bueno que a Apple se le ocurriera una manera de hacer lo que usted propone, pero la cuestión de tener los elementos gráficos pre-vinculados con la subclase del controlador todavía sería un problema.

¡Tengan un gran año nuevo! cuidate

El código de línea que estamos buscando es:

 object_setClass(AnyObject!, AnyClass!) 

En Storyboard -> agregue UIViewController, asígnele un nombre de clase 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 } } 

Como dice la respuesta aceptada, no parece que sea posible con guiones gráficos.

Mi solución es usar Nib, al igual que los desarrolladores los usaron antes de los guiones gráficos. Si desea tener un controlador de vista reutilizable y subclassable (o incluso una vista), mi recomendación es usar Nibs.

 SubclassMyViewController *myViewController = [[SubclassMyViewController alloc] initWithNibName:@"MyViewController" bundle:nil]; 

Cuando conecta todas sus salidas al “Propietario del archivo” en MyViewController.xib NO está especificando en qué clase debe cargarse el Nib, solo está especificando pares clave-valor: ” esta vista debe estar conectada a este nombre de variable de instancia ” Al llamar [SubclassMyViewController alloc] initWithNibName: el proceso de inicialización especifica qué controlador de vista se usará para ” controlar ” la vista que ha creado en el plumín.

Es posible hacer que un storyboard instanciar diferentes subclases de un controlador de vista personalizado, aunque implica una técnica poco ortodoxa: anulando el método alloc para el controlador de vista. Cuando se crea el controlador de vista personalizado, el método de alloc reemplazado de hecho devuelve el resultado de ejecutar alloc en la subclase.

Debo preceder la respuesta con la condición de que, aunque la he probado en varios escenarios y no he recibido errores, no puedo asegurarme de que pueda hacer frente a configuraciones más complejas (pero no veo ninguna razón por la que no debería funcionar). . Además, no he enviado ninguna aplicación con este método, por lo que existe la posibilidad de que el proceso de revisión de Apple la rechace (aunque, de nuevo, no veo ninguna razón por la que debería hacerlo).

Para fines de demostración, tengo una subclase de UIViewController llamada TestViewController , que tiene un UILabel IBOutlet y un IBAction. En mi guión gráfico, he agregado un controlador de vista y enmendado su clase a TestViewController , y conecté el IBOutlet a un UILabel y el IBAction a un UIButton. Presento el TestViewController a través de una transición modal activada por un UIButton en el viewController anterior.

Imagen del guión gráfico

Para controlar qué clase se crea una instancia, he agregado una variable estática y los métodos de clase asociados para obtener / establecer la subclase que se utilizará (supongo que se podrían adoptar otras formas de determinar qué subclase se va a instanciar):

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]; } } } 

Para mi prueba, tengo dos subclases de TestViewController : RedTestViewController y GreenTestViewController . Las subclases tienen propiedades adicionales y cada una anula viewDidLoad para cambiar el color de fondo de la vista y actualizar el texto de 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"; } 

En algunas ocasiones, me gustaría crear una instancia de TestViewController , en otras ocasiones, RedTestViewController o GreenTestViewController . En el controlador de vista anterior, hago esto al azar de la siguiente manera:

 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"]; } 

Tenga en cuenta que el método setClassForStoryBoard comprueba que el nombre de la clase solicitada sea de hecho una subclase de TestViewController para evitar confusiones. La referencia anterior a BlueTestViewController está ahí para probar esta funcionalidad.

intente esto, después de instanciar ViewControllerWithIdentifier.

 - (void)setClass:(Class)c { object_setClass(self, c); } 

me gusta :

 SubViewController *vc = [sb instantiateViewControllerWithIdentifier:@"MainViewController"]; [vc setClass:[SubViewController class]]; 

Aunque no es estrictamente una subclase, puede:

  1. opción: arrastrar el controlador de vista de clase base en el contorno del documento para hacer una copia
  2. Mueva la nueva copia del controlador de vista a un lugar separado en el guión gráfico
  3. Cambiar clase al controlador de vista de subclase en el Inspector de identidad

Aquí hay un ejemplo de un tutorial Bloc que escribí, subclasificando ViewController con WhiskeyViewController :

animación de los tres pasos anteriores

Esto le permite crear subclases de subclases de controlador de vista en el guión gráfico. A continuación, puede usar instantiateViewControllerWithIdentifier: para crear subclases específicas.

Este enfoque es un poco inflexible: las modificaciones posteriores dentro del guión gráfico al controlador de la clase base no se propagan a la subclase. Si tiene muchas subclases, es posible que esté mejor con una de las otras soluciones, pero esto le servirá en caso de necesidad.

El método Objc_setclass no crea una instancia de childvc. Pero mientras sale de childvc, se está convocando la deinit de childvc. Como no hay memoria asignada por separado para childvc, se bloquea la aplicación. Basecontroller tiene una instancia, mientras que child vc no tiene.

Probablemente la forma más flexible es usar vistas reutilizables.

(Cree una vista en un archivo XIB separado o en una Container view y agréguela a cada escena de controlador de vista de subclase en el guión gráfico)

Si no confías demasiado en los guiones gráficos, puedes crear un archivo .xib por separado para el controlador.

Establezca el propietario y las salidas del archivo adecuados en MainViewController y anule init(nibName:bundle:) en el VC principal para que sus hijos puedan acceder al mismo Nib y a sus puntos de venta.

Tu código debería verse así:

 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 } } 

Y su Child VC podrá reutilizar el plumín de sus padres:

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