Forma correcta de cargar un Nib para una subclase de UIView

98

Soy consciente de que esta pregunta se ha hecho antes, pero las respuestas son contradictorias y estoy confundido, así que no me llames.

Quiero tener una UIViewsubclase reutilizable en toda mi aplicación. Quiero describir la interfaz usando un archivo nib.

Ahora digamos que es una vista de indicador de carga con un indicador de actividad. Me gustaría en algún evento crear una instancia de esta vista y animarla en la vista de un controlador de vista. Podría describir la interfaz de la vista sin problemas mediante programación, creando los elementos mediante programación y estableciendo su marco dentro de un método de inicio, etc.

Sin embargo, ¿cómo puedo hacer esto usando una punta? Mantener el tamaño dado en el generador de interfaces sin tener que establecer un marco.

Me las arreglé para hacerlo así, pero estoy seguro de que está mal (es solo una vista con un selector):

 - (id)initWithDataSource:(NSDictionary *)dataSource {
        self = [super init];
        if (self){
            self = [[[NSBundle mainBundle] loadNibNamed:[NSString stringWithFormat:@"%@", [self class]] owner:self options:nil] objectAtIndex:0];
            self.pickerViewData = dataSource;
            [self configurePickerView];
        }
        return self;
    }

Pero me estoy sobrescribiendo a mí mismo, y cuando lo instancia:

FSASelectView *selectView = [[FSASelectView alloc] initWithDataSource:selectViewDictionary];
    selectView.delegate = self;

    selectView.frame = CGRectMake(0, self.view.bottom + 50, [FSASelectView width], [FSASelectView height]);

Tengo que configurar manualmente el marco en lugar de que lo recojan de IB.

EDITAR: Quiero crear esta vista personalizada en un controlador de vista y tener acceso para controlar los elementos de la vista. No quiero un nuevo controlador de vista.

Gracias

EDITAR: No sé si esta es la mejor práctica, estoy seguro de que no lo es, pero así es como lo hice:

FSASelectView *selectView = [[[NSBundle mainBundle] loadNibNamed:[NSString stringWithFormat:@"%@",[FSASelectView class]] owner:self options:nil] objectAtIndex:0];
    selectView.delegate = self;
    [selectView configurePickerViewWithData:ds];
    selectView.frame = CGRectMake(0, self.view.bottom + 50, selectView.width, selectView.height);
    selectView.alpha = 0.9;
    [self.view addSubview:selectView];
    [UIView animateWithDuration: 0.25 delay: 0 options:UIViewAnimationOptionAllowUserInteraction |UIViewAnimationOptionCurveEaseInOut animations:^{
                            selectView.frame = CGRectMake(0, self.view.bottom - selectView.height, selectView.width, selectView.height);
                            selectView.alpha = 1;
                        } completion:^(BOOL finished) {
                        }];

Todavía se quiere la práctica correcta

¿Debería haberse hecho esto usando un controlador de vista e init con el nombre de nib? ¿Debería haber configurado la punta en algún método de inicialización de UIView en el código? ¿O está bien lo que he hecho?

Adam Waite
fuente
Pero, ¿no es la primera línea de código casi la misma que usó en la pregunta original initWithDataSource? De todos modos, esto funcionará incluso si le da propietario como 'Nil'.
Rakesh
Vale la pena señalar que en estos días, en el 99,99999% de los casos, uno solo estaría usando vistas de contenedor , que ahora se usan en todas partes y siempre en iOS. artículo
instructivo
tenga en cuenta que puede reemplazar [NSString stringWithFormat: @ "% @", [FSASelectView class]] con NSStringFromClass ([FSASelectView class])
naomimichiko

Respuestas:

132
MyViewClass *myViewObject = [[[NSBundle mainBundle] loadNibNamed:@"MyViewClassNib" owner:self options:nil] objectAtIndex:0]

Estoy usando esto para inicializar las vistas personalizadas reutilizables que tengo.


Tenga en cuenta que puede usar "firstObject" al final, es un poco más limpio. "firstObject" es un método útil para NSArray y NSMutableArray.

Aquí hay un ejemplo típico de cargar un xib para usarlo como encabezado de tabla. En su archivo YourClass.m

- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
    return [[NSBundle mainBundle] loadNibNamed:@"TopArea" owner:self options:nil].firstObject;
}

Normalmente, en el TopArea.xib, haría clic en Propietario del archivo y establecería el propietario del archivo en YourClass . Entonces, en realidad, en YourClass.h tendrías propiedades IBOutlet. En TopArea.xib, puede arrastrar los controles a esos puntos de venta.

No olvides que en TopArea.xib, es posible que tengas que hacer clic en la Vista y arrastrarla a alguna salida, para que tengas el control de ella, si es necesario. (Un consejo que vale la pena es que cuando está haciendo esto para las filas de celdas de la tabla, es absolutamente necesario que lo haga: debe conectar la vista a la propiedad relevante en su código).

Premsuraj
fuente
no hay método initWithNibName: para una UIView, es solo para UIViewController
meronix
Eso es para un controlador de vista, quiero usar un UIView y tener el controlador que lo crea como propietario.
Adam Waite
Lo siento, copié el código incorrecto en mi prisa, he actualizado la respuesta, por favor échale un vistazo.
Premsuraj
Voy a marcar esto como correcto. No sé si es la mejor práctica, pero funciona.
Adam Waite
@Joe Blow Perdón por el golpe, pero ¿por qué tendríamos que hacer esto? "No olvides que en TopArea.xib, es posible que tengas que hacer clic en la Vista y arrastrarla a alguna salida, para que tengas el control de ella. , si necesario." En su lugar, ¿podría usar myViewObject para acceder a la vista subyacente, ya que es una UIView en sí? ¿Por qué se necesita una propiedad?
user1686342
39

Si desea mantener su CustomViewy su xibindependiente File's Owner, siga estos pasos

  • Deje el File's Ownercampo vacío.
  • Haga clic en la vista real en su xibarchivo CustomViewy establezca su Custom Classcomo CustomView(nombre de su clase de vista personalizada)
  • Añadir IBOutleten el .harchivo de la vista personalizada.
  • En el .xibarchivo de su vista personalizada, haga clic en Ver y acceda Connection Inspector. Aquí tendrás todos tus IBOutlets que definas en .harchivo
  • Conéctelos con su vista respectiva.

en el .marchivo de su CustomViewclase, anule el initmétodo de la siguiente manera

-(CustomView *) init{
    CustomView *result = nil;
    NSArray* elements = [[NSBundle mainBundle] loadNibNamed: NSStringFromClass([self class]) owner:self options: nil];
    for (id anObject in elements)
    {
        if ([anObject isKindOfClass:[self class]])
        {
            result = anObject;
            break;
        }
    }
    return result;
}

Ahora, cuando quieras cargar tu CustomView, usa la siguiente línea de código [[CustomView alloc] init];

Ans
fuente
1
A partir de iOS 9, esta es la única solución enumerada en esta página que realmente funciona para mí (las otras soluciones causan fallas).
lifjoy
Solo tenga en cuenta que este enfoque no es compatible con la carga de su vista personalizada desde un XIB existente, ya que no llama -init. En su lugar, llamará initWithFrame:y -awakeFromNib. Si escribe el mismo código para cargar su vista que en el -initmétodo, ingresará un bucle infinito.
Dustt
25

Siga los siguientes pasos

  1. Cree una clase denominada MyView .h / .m de tipo UIView.
  2. Cree una xib del mismo nombre MyView.xib.
  3. Ahora cambie la clase File Owner a UIViewControllerfrom NSObjectin xib. Ver la imagen de abajo ingrese la descripción de la imagen aquí
  4. Conecte la vista del propietario del archivo a su vista. Ver la imagen de abajo ingrese la descripción de la imagen aquí

  5. Cambie la clase de su Vista a MyView. Igual que 3.

  6. Los controles de lugar crean IBOutlets.

Aquí está el código para cargar la Vista:

UIViewController *controller=[[UIViewController alloc] initWithNibName:@"MyView" bundle:nil];
MyView* view=(MyView*)controller.view;
[self.view addSubview:myview];

Espero eso ayude.

Aclaración :

UIViewControllerse usa para cargar su xib y la vista que UIViewControllertiene es realmente la MyViewque ha asignado en el xib de MyView.

Demostración he hecho un robo de demostración aquí

iphonic
fuente
¿Por qué la gente vota esto mal? No lo he intentado todavía, pero parece que esto no hará lo que quiero.
Adam Waite
Había votado en contra, pero eso es porque leí mal su respuesta (pensé que estaba asignando una subclase UIView como propietario del archivo). Lo siento por eso.
Rakesh
2
En su archivo xib ignore el propietario del archivo. Entonces puede evitar tener que crear un UIViewController simplemente haciendo un MyView * view = [[UINib instantiateWithOwner: nil options: nil] lastObject];
LightningStryk
1
@LightningStryk Sí, por supuesto, pero es solo otra forma de hacerlo.
iphonic
1
El es crear una subclase UIView reutilizable, no usar un UIViewController para ese propósito.
arielyz
11

Respondiendo a mi propia pregunta acerca de 2 años más tarde aquí, pero ...

Utiliza una extensión de protocolo para que pueda hacerlo sin ningún código adicional para todas las clases.

/*

Prerequisites
-------------
- In IB set the view's class to the type hook up any IBOutlets
- In IB ensure the file's owner is blank

*/

public protocol CreatedFromNib {
    static func createFromNib() -> Self?
    static func nibName() -> String?
}

extension UIView: CreatedFromNib { }

public extension CreatedFromNib where Self: UIView {

    public static func createFromNib() -> Self? {
        guard let nibName = nibName() else { return nil }
        guard let view = NSBundle.mainBundle().loadNibNamed(nibName, owner: nil, options: nil).last as? Self else { return nil }
        return view
    }

    public static func nibName() -> String? {
        guard let n = NSStringFromClass(Self.self).componentsSeparatedByString(".").last else { return nil }
        return n
    }
}

// Usage:
let myView = MyView().createFromNib()
Adam Waite
fuente
8

En Swift:

Por ejemplo, el nombre de su clase personalizada es InfoView

Al principio, creas archivos InfoView.xiby InfoView.swiftasí:

import Foundation
import UIKit

class InfoView: UIView {
    class func instanceFromNib() -> UIView {
    return UINib(nibName: "InfoView", bundle: nil).instantiateWithOwner(nil, options: nil)[0] as! UIView
}

A continuación, establezca File's Ownera UIViewControllerdesea:

ingrese la descripción de la imagen aquí

Cambie el nombre de su Viewa InfoView:

ingrese la descripción de la imagen aquí

Haga clic derecho File's Ownery conecte su viewcampo con su InfoView:

ingrese la descripción de la imagen aquí

Asegúrese de que el nombre de la clase sea InfoView:

ingrese la descripción de la imagen aquí

Y después de esto, puede agregar la acción al botón en su clase personalizada sin ningún problema:

ingrese la descripción de la imagen aquí

Y el uso de esta clase personalizada en su MainViewController:

func someMethod() {
    var v = InfoView.instanceFromNib()
    v.frame = self.view.bounds
    self.view.addSubview(v)
}
vkalit
fuente
2

Bueno, podría inicializar el xib usando un controlador de vista y usar viewController.view. o hazlo de la forma en que lo hiciste. Hacer solo una UIViewsubclase como controlador UIViewes una mala idea.

Si no tiene salidas desde su vista personalizada, puede usar directamente una UIViewControllerclase para inicializarla.

Actualización: En su caso:

UIViewController *genericViewCon = [[UIViewController alloc] initWithNibName:@"CustomView"];
//Assuming you have a reference for the activity indicator in your custom view class
CustomView *myView = (CustomView *)genericViewCon.view;
[parentView addSubview:myView];
//And when necessary
[myView.activityIndicator startAnimating]; //or stop

De lo contrario, tiene que hacer un personalizado UIViewController(para convertirlo en el propietario del archivo para que las salidas estén correctamente conectadas).

YourCustomController *yCustCon = [[YourCustomController alloc] initWithNibName:@"YourXibName"].

Donde quiera que desee agregar la vista, puede usarla.

[parentView addSubview:yCustCon.view];

Sin embargo, pasar el otro controlador de vista (que ya se está utilizando para otra vista) como propietario mientras se carga el xib no es una buena idea, ya que la propiedad de vista del controlador cambiará y cuando desee acceder a la vista original, no lo hará. tener una referencia a él.

EDITAR: Se enfrentará a este problema si ha configurado su nuevo xib con el propietario del archivo como la misma UIViewControllerclase principal y ha vinculado la propiedad de la vista a la nueva vista xib.

es decir;

  • YourMainViewController - administra - mainView
  • CustomView: debe cargarse desde xib cuando sea necesario.

El siguiente código causará confusión más adelante, si lo escribe dentro de la vista se cargó YourMainViewController. Eso se debe a que a self.viewpartir de este punto se referirá a su vista personalizada

-(void)viewDidLoad:(){
  UIView *childView= [[[NSBundle mainBundle] loadNibNamed:@"YourXibName" owner:self options:nil] objectAtIndex:0];
}
Rakesh
fuente
Bien, básicamente, ¿las mejores prácticas establecen que cada UIView debería tener un controlador asociado?
Adam Waite
No necesariamente. Pero al cargar una vista desde un xib separado, esa sería la forma sin problemas. Los documentos de Apple anteriores a IOS5 dicen que un controlador de vista no debe usarse para controlar más de una escena (xib) y viceversa.
Rakesh
El xib es en realidad solo del tamaño de una vista de alerta
Adam Waite
Personalmente, creo que depende de la complejidad del código / xib si tienes que crear un nuevo controlador o no. Si puede manejar los problemas causados ​​con éxito y si no está considerando modificaciones futuras, no es un problema. Pero crear un controlador de vista separado le resolvería muchos problemas. Y dado que en su caso es solo un indicador de actividad, puede usar un objeto UIViewController (como se menciona en la respuesta) fácilmente. Actualizaré mi respuesta para hacer esto en consecuencia.
Rakesh
1
Entonces puedo dibujar la interfaz en el generador de interfaces en lugar de hacerlo mediante programación. No es un problema hacerlo programáticamente, solo quiero saber cómo subclasificar una UIView y darle una punta. Esto no debería ser difícil.
Adam Waite