Subclase UIView con su propio XIB

Creé una subclase UIView personalizada, y preferiría no diseñar la UI en código en la subclase UIView. Me gustaría usar un xib para eso. Entonces lo que hice fue lo siguiente.

Creé una clase “ShareView” que subclasifica UIView. Creé un archivo XIB con el propietario del archivo establecido en “ShareView”. Luego conecto algunos puntos de venta que declare en mi “ShareView.h”.

A continuación, tengo ViewController, MainViewController, que agrega ShareView como una subvista. con este código:

NSArray *arr = [[NSBundle mainBundle] loadNibNamed:@"ShareView" owner:nil options:nil]; UIView *fv = [[arr objectAtIndex:0] retain]; fv.frame = CGRectMake(0, 0, 320, 407); [self.view addSubview:fv]; 

Pero ahora obtengo los errores NSUnknownKeyException en los puntos de venta que declaro en mi ShareView.

La razón por la que hice todo esto es porque quiero un UIView, con su propia lógica en un archivo XIB separado. Leí en varios lugares que los ViewControllers solo se usan para administrar una pantalla completa, es decir, no partes de una pantalla … Entonces, ¿qué estoy haciendo mal? Quiero mi lógica para ShareView en una clase separada, por lo que mi clase MainController no se hincha con la lógica de ShareView (que creo que es una aplicación para resolver este problema).

ThomasM,

Teníamos ideas similares sobre el comportamiento de encapsulado dentro de una vista personalizada (por ejemplo, un control deslizante con tags complementarias para valores mínimos / máximos / actuales, con eventos de cambio de valores también manejados por el control internamente).

En nuestra mejor práctica actual, diseñamos el ShareView en el Interface Builder ( ShareView.xib ), como lo describe Eimantas en su respuesta. A continuación, incorporamos ShareView a la jerarquía de vistas en MainViewController.xib .

Escribí cómo insertamos Nibs personalizados en otros Nibs en nuestro blog para desarrolladores de iOS. La crux está anulando -awakeAfterUsingCoder: en su vista personalizada, reemplazando el objeto cargado desde MainViewController.xib con el cargado desde el Nib “embedded” (ShareView.xib).

Algo en esta línea:

 // ShareView.m - (id) awakeAfterUsingCoder:(NSCoder*)aDecoder { BOOL theThingThatGotLoadedWasJustAPlaceholder = ([[self subviews] count] == 0); if (theThingThatGotLoadedWasJustAPlaceholder) { // load the embedded view from its Nib ShareView* theRealThing = [[[NSBundle mainBundle] loadNibNamed:NSStringFromClass([ShareView class]) owner:nil options:nil] objectAtIndex:0]; // pass properties through theRealThing.frame = self.frame; theRealThing.autoresizingMask = self.autoresizingMask; [self release]; self = [theRealThing retain]; } return self; } 

Usted definió el propietario del xib cargado como nil . Dado que el propietario del archivo en xib tiene sockets conectados y se define como instancia de ShareView se obtiene la excepción sobre las claves desconocidas ( nil no tiene las propiedades sobresalientes que definió para ShareView ).

Debe definir el cargador del xib como propietario (es decir, ver el controlador responsable de cargar el xib). A continuación, agregue el objeto UIView por separado a xib y defínalo como instancia de ShareView . Luego, cuando cargue el xib.

 ShareView *shareView = [[[[NSBundle mainBundle] loadNibNamed:@"ShareView" owner:self options:nil] objectAtIndex:0] retain]; 

También puede definir shareView como un IBOutlet en la interfaz del controlador de vista (y conectar la salida del propietario del archivo a esa vista en el propio xib). Luego, cuando cargue el xib, no habrá necesidad de reasignar la variable de instancia shareView , ya que el proceso de carga xib volverá a conectar la vista a la variable de instancia directamente.

Me gustaría agregar a la respuesta. Espero que la gente mejore esta respuesta sin embargo.

En primer lugar, funciona.

XIB:

enter image description here

Resultado:

enter image description here

Me gustaría hacer una subclase de UIView durante mucho tiempo, especialmente para tableViewCell.

Así es como lo hice.

Es exitoso, pero una parte sigue siendo “incómoda” en mi opinión.

Primero creé un archivo .h, .m y xib habitual. Tenga en cuenta que Apple no tiene la checkbox para crear automáticamente un xib si la subclase que creó no es una subclase de UIViewController. Bueno, crea esos de todos modos.

 #import  #import "Business.h" @interface BGUIBusinessCellForDisplay : UITableViewCell + (NSString *) reuseIdentifier; - (BGUIBusinessCellForDisplay *) initWithBiz: (Business *) biz; @end 

UITableViewCell realmente simple, que quiero inicializar último con biz.

Puse reuseidentifier que deberías hacer para UITableViewCell

 //#import "Business.h" @interface BGUIBusinessCellForDisplay () @property (weak, nonatomic) IBOutlet UILabel *Title; @property (weak, nonatomic) IBOutlet UIImageView *Image; @property (weak, nonatomic) IBOutlet UILabel *Address; @property (weak, nonatomic) IBOutlet UILabel *DistanceLabel; @property (weak, nonatomic) IBOutlet UILabel *PinNumber; @property (strong, nonatomic) IBOutlet BGUIBusinessCellForDisplay *view; @property (weak, nonatomic) IBOutlet UIImageView *ArrowDirection; @property (weak, nonatomic) Business * biz; @end @implementation BGUIBusinessCellForDisplay - (NSString *) reuseIdentifier { return [[self class] reuseIdentifier]; }; + (NSString *) reuseIdentifier { return NSStringFromClass([self class]); }; 

Luego eliminé la mayoría de los códigos de inicio y puse esto en su lugar:

 - (BGUIBusinessCellForDisplay *) initWithBiz: (Business *) biz { if (self.biz == nil) //First time set up { self = [super init]; //If use dequeueReusableCellWithIdentifier then I shouldn't change the address self points to right NSString * className = NSStringFromClass([self class]); //PO (className); [[NSBundle mainBundle] loadNibNamed:className owner:self options:nil]; [self addSubview:self.view]; //What is this for? self.view is of type BGCRBusinessForDisplay2. That view should be self, not one of it's subview Things don't work without it though } if (biz==nil) { return self; } self.biz = biz; self.Title.text = biz.Title; //Let's set this one thing first self.Address.text=biz.ShortenedAddress; //if([self.distance isNotEmpty]){ self.DistanceLabel.text=[NSString stringWithFormat:@"%dm",[biz.Distance intValue]]; self.PinNumber.text =biz.StringPinLineAndNumber; 

Tenga en cuenta que es realmente incómodo.

En primer lugar, el init se puede usar de 2 maneras.

  1. Se puede usar inmediatamente después de aloc
  2. Se puede usar teniendo otra clase existente y luego solo queremos iniciar esa celda existente en otro negocio.

Así que lo hice:

 if (self.biz == nil) //First time set up { self = [super init]; //If use dequeueReusableCellWithIdentifier then I shouldn't change the address self points to right NSString * className = NSStringFromClass([self class]); //PO (className); [[NSBundle mainBundle] loadNibNamed:className owner:self options:nil]; [self addSubview:self.view]; //What is this for? self.view is of type BGCRBusinessForDisplay2. That view should be self, not one of it's subview Things don't work without it though } 

Otra cosa repulsiva que hice fue cuando lo hice [self addSubview: self.view];

La cuestión es que quiero que el yo sea la vista. No self.view. De alguna manera funciona sin embargo. Así que sí, ayúdenme a mejorar, pero esa es esencialmente la manera de implementar su propia subclase de UIView.

Puede crear su UIView personalizado diseñado en xib e incluso crear Interface Builder para mostrarlo dentro de otros archivos xib o storyboards en el nuevo Xcode 6 utilizando IB_DESIGNABLE. En xib, establezca el propietario del archivo en su clase personalizada pero no configure la clase UIView para evitar problemas de carga de recurrencia. Simplemente deje la clase UIView predeterminada y agregará esta UIView como una subvista de su vista de clase personalizada. Conecte todas sus salidas al propietario del archivo y en su clase personalizada cargue su xib como en el siguiente código. Puedes consultar mi video tutorial aquí: https://www.youtube.com/watch?v=L97MdpaF3Xg

 IB_DESIGNABLE @interface CustomControl : UIView @end @implementation CustomControl - (instancetype)initWithCoder:(NSCoder *)aDecoder { if (self = [super initWithCoder:aDecoder]) { [self load]; } return self; } - (instancetype)initWithFrame:(CGRect)frame { if (self = [super initWithFrame:frame]) { [self load]; } return self; } - (void)load { UIView *view = [[[NSBundle bundleForClass:[self class]] loadNibNamed:@"CustomControl" owner:self options:nil] firstObject]; [self addSubview:view]; view.frame = self.bounds; } @end 

Si está utilizando el diseño automático, es posible que desee cambiar: view.frame = self.bounds; a:

 [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[view]|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(view)]]; [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[view]|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(view)]]; 

Para usar el patrón de Yang con Diseño automático, debe agregar lo siguiente en algún lugar del método -awakeWithCoder :.

  theRealThing.translatesAutoresizingMaskIntoConstraints = NO; 

Si no apaga -traduce AutoResizingMaskIntoConstraints puede causar que su diseño sea incorrecto y también que cause MUCHA depuración en la consola.

EDITAR: el diseño automático todavía puede ser un problema. Ciertas restricciones no se respetan, pero otras son (por ejemplo, fijar en la parte inferior no funciona, pero anclar en la parte superior sí). No estamos exactamente seguros de por qué, pero puede evitar esto al pasar manualmente las restricciones del marcador de posición a la Realimentación.

También vale la pena señalar que este patrón funciona de la misma manera con Storyboards que con los .xibs regulares (es decir, puede crear un elemento UI en .xib y soltarlo en un controlador StoryBoard View siguiendo sus pasos).

En lugar de subclasificar a UIView, ¿por qué no subclasifica UIViewController? Echa un vistazo al siguiente enlace. En eso hicieron un UIViewControllers “RedView” y “BlueView” con sus xibs y los agregaron a la vista MultipleViewsController al crear una instancia de las dos primeras clases y agregar [self.view addSubview:red.view] y [self.view addSubview:blue.view] en el método viewDidLoad de viewDidLoad

MultipleControllers en una vista

Simplemente agregue el remitente (id) al botón presionado en RedView y BlueView en el código del enlace anterior.