Cómo cargar una UIView usando un archivo nib creado con Interface Builder


241

Estoy tratando de hacer algo un poco elaborado, pero algo que debería ser posible. Así que aquí hay un desafío para todos ustedes expertos (este foro es un paquete de muchos de ustedes :)).

Estoy creando un "componente" del Cuestionario, que quiero cargar en un NavigationContoller(mi QuestionManagerViewController). El "componente" es un "vacío" UIViewController, que puede cargar diferentes vistas dependiendo de la pregunta que deba responderse.

La forma en que lo hago es:

  1. Cree el objeto Question1View como una UIViewsubclase, definiendo algunos IBOutlets.
  2. Cree (usando Interface Builder) el Question1View.xib (AQUÍ ES DONDE ESTÁ MI PROBLEMA PROBABLEMENTE ). Configuré tanto el UIViewControllery el UIViewpara ser de clase Question1View.
  3. Enlace los puntos de venta con el componente de la vista (usando IB).
  4. Anulo el initWithNibde mi QuestionManagerViewControllerpara lucir así:

    - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
        if (self = [super initWithNibName:@"Question1View" bundle:nibBundleOrNil]) {
            // Custom initialization
        }
        return self;
    }

Cuando ejecuto el código, recibo este error:

2009-05-14 15: 05: 37.152 iMobiDines [17148: 20b] *** Finalización de la aplicación debido a una excepción no detectada ' NSInternalInconsistencyException', razón: ' -[UIViewController _loadViewFromNibNamed:bundle:]cargó la plumilla "Question1View" pero el punto de vista no estaba configurado ".

Estoy seguro de que hay una manera de cargar la vista usando el archivo nib, sin necesidad de crear una clase viewController.

Respuestas:


224

También hay una manera más fácil de acceder a la vista en lugar de tratar con la punta como una matriz.

1 ) Cree una subclase de Vista personalizada con cualquier salida a la que desee tener acceso más tarde. --Mi vista

2 ) en el UIViewController que desea cargar y manejar la punta, cree una propiedad IBOutlet que contendrá la vista de la punta cargada, por ejemplo

en MyViewController (una subclase UIViewController)

  @property (nonatomic, retain) IBOutlet UIView *myViewFromNib;

(no olvides sintetizarlo y liberarlo en tu archivo .m)

3) abra su plumín (lo llamaremos 'myViewNib.xib') en IB, configure el Propietario de su archivo en MyViewController

4) ahora conecte la salida de Propietario de su archivo myViewFromNib a la vista principal en la plumilla.

5) Ahora en MyViewController, escriba la siguiente línea:

[[NSBundle mainBundle] loadNibNamed:@"myViewNib" owner:self options:nil];

Ahora, tan pronto como lo haga, llamar a su propiedad "self.myViewFromNib" le dará acceso a la vista desde su plumín.


44
¿Funcionará para la vista del controlador principal (self.view)? Por alguna razón, necesito cargar la vista principal desde la punta después del método de inicio del controlador estándar. Motivo: estructura de subclasificación compleja
Lukasz

@Lukasz ¿Cuánto logras hacerlo? Incluso estoy buscando algo como tú.
Ajay Sharma

Lo siento, podría estar siendo grueso, pero estoy confundido en el primer punto. ¿Cómo creo salidas en una subclase de vista personalizada? ¿Pensé que los puntos de venta eran solo para UIViewControllers?
jowie

1
¿Qué pasa con un caso cuando esta vista aparece en varias vistas principales? Entonces, ¿propone editar xib para cada uno de ellos manualmente? ¡¡¡Es muy malo!!!
user2083364

2
Esto funciona solo si desea utilizar la punta personalizada en un solo controlador de vista. En el caso en que desee usarlo en diferentes controladores de vista, cada uno creando una instancia de la punta personalizada de forma dinámica, esto no funcionará.
thgc

120

Gracias a todos. Encontré una manera de hacer lo que quería.

  1. Crea tu UIViewcon los IBOutlets que necesitas.
  2. Cree el xib en IB, diséñelo a su gusto y enlácelo de esta manera: el propietario del archivo es de clase UIViewController(no hay una subclase personalizada, sino la "real"). La vista del propietario del archivo está conectada a la vista principal y su clase se declara como la del paso 1).
  3. Conecte sus controles con el IBOutlets.
  4. El DynamicViewControllerpuede ejecutar su lógica para decidir qué vista / xib cargar. Una vez que ha tomado la decisión, en el loadViewmétodo ponga algo como esto:

    NSArray* nibViews = [[NSBundle mainBundle] loadNibNamed:@"QPickOneView"
                                                      owner:self
                                                    options:nil];
    
    QPickOneView* myView = [ nibViews objectAtIndex: 1];
    
    myView.question = question;

¡Eso es!

El loadNibNamedmétodo del paquete principal se encargará de inicializar la vista y crear las conexiones.

Ahora el ViewController puede mostrar una vista u otra dependiendo de los datos en la memoria, y la pantalla "principal" no necesita molestarse con esta lógica.


2
Tengo un problema muy similar: simplemente quiero cargar una UIView desde un archivo xib. Estos pasos no funcionan para mí: la aplicación se bloquea con "mal acceso" poco después de agregar la vista cargada como una subvista.
Justicle

2
Para iPhone 3.0, use objectAtIndex: 0 para obtener el primer elemento. Esto se bloquea para mí exactamente como lo describe Justicle. ¿Alguna idea de por qué?
tba

1
+1 funcionó perfectamente para mí. Quería agregar varias vistas que tuvieran un controlador de vista (estoy usando un escenario de vista invertida donde una sección de la vista gira) Tuve que hacer el índice en la matriz 0 (iPhone 3.0)
Aran Mulholland

42
Esta es la respuesta correcta, sin embargo, el código debe modificarse para que recorra nibViews y realice una verificación de clase en cada objeto, de modo que ciertamente obtenga el objeto correcto. objectAtIndex: 1 es una suposición peligrosa.
M. Ryan

2
Jasconius tiene razón. Debe recorrer la matriz nibViews de esta manera: gist.github.com/769539
leviatán

71

No estoy seguro de qué están hablando algunas de las respuestas, pero necesito poner esta respuesta aquí para cuando busque en Google la próxima vez. Palabras clave: "Cómo cargar una UIView desde una punta" o "Cómo cargar una UIView desde un NSBundle".

Aquí está el código casi al 100% directamente del libro Apress Beginning iPhone 3 (página 247, "Uso de la nueva celda de vista de tabla"):

- (void)viewDidLoad {
    [super viewDidLoad];
    NSArray *bundle = [[NSBundle mainBundle] loadNibNamed:@"Blah"
                                                 owner:self options:nil];
    Blah *blah;
    for (id object in bundle) {
        if ([object isKindOfClass:[Blah class]]) {
            blah = (Blah *)object;
            break;
        }
    }   
    assert(blah != nil && "blah can't be nil");
    [self.view addSubview: blah];
} 

Esto supone que tiene una UIViewsubclase llamada Blah, una punta llamada Blahque contiene una UIViewque tiene su clase establecida Blah.


Categoría: NSObject + LoadFromNib

#import "NSObject+LoadFromNib.h"

@implementation NSObject (LoadFromNib)

+ (id)loadFromNib:(NSString *)name classToLoad:(Class)classToLoad {
    NSArray *bundle = [[NSBundle mainBundle] loadNibNamed:name owner:self options:nil];
    for (id object in bundle) {
        if ([object isKindOfClass:classToLoad]) {
            return object;
        }
    }
    return nil;
}

@end

Extensión rápida

extension UIView {
    class func loadFromNib<T>(withName nibName: String) -> T? {
        let nib  = UINib.init(nibName: nibName, bundle: nil)
        let nibObjects = nib.instantiate(withOwner: nil, options: nil)
        for object in nibObjects {
            if let result = object as? T {
                return result
            }
        }
        return nil
    }
}

Y un ejemplo en uso:

class SomeView: UIView {
    class func loadFromNib() -> SomeView? {
        return self.loadFromNib(withName: "SomeView")
    }
}

2
Si utiliza el isKindOfestá protegido de los cambios de estructura del paquete.
Dan Rosenstark

1
Una assertsería probablemente una mejor solución. De esta manera solo ocultarás el problema.
Sulthan

1
@Sulthan una afirmación es inherentemente diferente. Quiero el objeto de tipo Blah donde sea que se encuentre en el Nib . No me importa si ha sido promovido o degradado. Sin embargo, en respuesta a su comentario que he añadido una aserción, pero es no en lugar del forbucle. Lo complementa.
Dan Rosenstark

Puede hacer loadFromNib sin parámetros: + (id) loadFromNib {NSArray * bundle = [[NSBundle mainBundle] loadNibNamed: NSStringFromClass ([self class]) propietario: self options: nil]; for (objeto id en paquete) {if ([object isKindOfClass: [self class]]) {return object; }} return nil; }
JVC

22

Para todos aquellos que necesitan administrar más de una instancia de la vista personalizada, es decir, una Colección Outlet , fusioné y personalicé las respuestas @Gonso, @AVeryDev y @Olie de esta manera:

  1. Cree un personalizado MyView : UIViewy configúrelo como " Clase personalizada " de la raíz UIViewen el XIB deseado ; clase personalizada

  2. Cree todos los puntos de venta que necesite MyView(hágalo ahora porque después del punto 3 el IB le propondrá conectar los puntos de venta a la UIViewControllervista personalizada y no a la vista personalizada como queramos); salida de clase personalizada

  3. Establezca su UIViewControllercomo " Propietario del archivo " de la vista personalizada XIB ; ingrese la descripción de la imagen aquí

  4. En el UIViewControllerañadir nuevo UIViewspara cada instancia de MyViewque desee, y conectarlos a UIViewControllerla creación de una colección de salida : estos puntos de vista actuarán como puntos de vista "contenedor" para las instancias de vista personalizada; ingrese la descripción de la imagen aquí

  5. Finalmente, en el viewDidLoadde su UIViewControlleragregue las siguientes líneas:

NSArray *bundleObjects;
MyView *currView;
NSMutableArray *myViews = [NSMutableArray arrayWithCapacity:myWrapperViews.count];
for (UIView *currWrapperView in myWrapperViews) {
    bundleObjects = [[NSBundle mainBundle] loadNibNamed:@"MyView" owner:self options:nil];
    for (id object in bundleObjects) {
        if ([object isKindOfClass:[MyView class]]){
            currView = (MyView *)object;
            break;
        }
    }

    [currView.myLabel setText:@"myText"];
    [currView.myButton setTitle:@"myTitle" forState:UIControlStateNormal];
    //...

    [currWrapperView addSubview:currView];
    [myViews addObject:currView];
}
//self.myViews = myViews; if need to access them later..

Hola ... sabes cómo hacer todo esto programáticamente, es decir. no utilizando el archivo xib en absoluto?
El X-Coder

19

Usaría UINib para crear una instancia de UIView personalizada para ser reutilizada

UINib *customNib = [UINib nibWithNibName:@"MyCustomView" bundle:nil];
MyCustomViewClass *customView = [[customNib instantiateWithOwner:self options:nil] objectAtIndex:0];
[self.view addSubview:customView];

Los archivos necesarios en este caso son MyCustomView.xib , MyCustomViewClass.h y MyCustomViewClass.m Tenga en cuenta que [UINib instantiateWithOwner]devuelve una matriz, por lo que debe usar el elemento que refleja la UIView que desea reutilizar. En este caso es el primer elemento.


1
No quiere decir "customNib" en la línea 2 en lugar de "rankingNib"
Danny

11

No debe configurar la clase de su controlador de vista para que sea una subclase de UIViewInterface Builder. Eso es definitivamente al menos parte de su problema. Déjalo como UIViewControlleruna subclase o alguna otra clase personalizada que tengas.

En cuanto a cargar solo una vista desde un xib, suponía que tenía que tener algún tipo de controlador de vista (incluso si no se extiende UIViewController, que puede ser demasiado pesado para sus necesidades) configurado como Propietario del archivo en la interfaz Builder si quieres usarlo para definir tu interfaz. Investigué un poco para confirmar esto también. Esto se debe a que, de lo contrario, no habría forma de acceder a ninguno de los elementos de la interfaz en el UIView, ni habría una forma de tener sus propios métodos en el código desencadenados por eventos.

Si usa a UIViewControllercomo Propietario de su archivo para sus vistas, puede usarlo initWithNibName:bundle:para cargarlo y recuperar el objeto del controlador de vista. En IB, asegúrese de configurar la viewsalida a la vista con su interfaz en el xib. Si usa algún otro tipo de objeto como propietario de su archivo, deberá usar NSBundleel loadNibNamed:owner:options:método de '' para cargar la punta, pasando una instancia del propietario de archivo al método. Todas sus propiedades se establecerán correctamente de acuerdo con los puntos de venta que defina en IB.


Estoy leyendo el libro de Apress, "Iniciando el desarrollo de iPhone: Explorando el SDK de iPhone" y discutieron este método en el capítulo 9: Controladores de navegación y Vistas de tabla. No parecía demasiado complicado.
Lyndsey Ferguson

Me daría curiosidad ver cómo dicen que se hace. No tengo una copia de ese libro en este momento.
Marc W

10

También puede usar initWithNibName de UIViewController en lugar de loadNibNamed. Es más simple, me parece.

UIViewController *aViewController = [[UIViewController alloc] initWithNibName:@"MySubView" bundle:nil];
[self.subview addSubview:aViewController.view];
[aViewController release];  // release the VC

Ahora solo tiene que crear MySubView.xib y MySubView.h / m. En MySubView.xib establezca la clase Propietario del archivo en UIViewController y vea la clase en MySubView.

Puede posicionar y dimensionar el subviewarchivo xib principal.


8

Esta es una gran pregunta (+1) y las respuestas fueron casi útiles;) Lo siento muchachos, pero tuve un montón de tiempo trabajando en esto, aunque tanto Gonso como AVeryDev dieron buenos consejos. Con suerte, esta respuesta ayudará a otros.

MyVC es el controlador de vista que contiene todo esto.

MySubview es la vista que queremos cargar desde un xib

  • En MyVC.xib, cree una vista de tipo MySubViewque tenga el tamaño y la forma correctos y se coloque donde desee.
  • En MyVC.h, tengo

    IBOutlet MySubview *mySubView
    // ...
    @property (nonatomic, retain) MySubview *mySubview;
  • En MyVC.m, @synthesize mySubView;y no olvides lanzarlo dealloc.

  • En MySubview.h, tenga una salida / propiedad para UIView *view(puede ser innecesario, pero funcionó para mí). Sintetizar y liberarlo en .m
  • En MySubview.xib
    • establezca el tipo de propietario del archivo en MySubviewy vincule la propiedad de vista a su vista.
    • Coloque todos los bits y conéctese a los IBOutlet's como desee
  • De vuelta en MyVC.m, tengo

    NSArray *xibviews = [[NSBundle mainBundle] loadNibNamed: @"MySubview" owner: mySubview options: NULL];
    MySubview *msView = [xibviews objectAtIndex: 0];
    msView.frame = mySubview.frame;
    UIView *oldView = mySubview;
    // Too simple: [self.view insertSubview: msView aboveSubview: mySubview];
    [[mySubview superview] insertSubview: msView aboveSubview: mySubview]; // allows nesting
    self.mySubview = msView;
    [oldCBView removeFromSuperview];

Lo más difícil para mí fue: las sugerencias en las otras respuestas cargaron mi vista desde el xib, pero NO reemplazaron la vista en MyVC (¡duh!) - Tuve que cambiar eso por mi cuenta.

Además, para obtener acceso a mySubviewlos métodos de, la viewpropiedad en el archivo .xib debe establecerse en MySubview. De lo contrario, vuelve como un viejo UIView.

Si hay una manera de cargar mySubviewdirectamente desde su propio xib, eso sería genial, pero esto me llevó a donde necesitaba estar.


1
Usé este método, gracias. Un cambio que hice para permitir que admitiera vistas anidadas fue cambiar [ self.view insertSubview: msView aboveSubview: mySubview]; a [ mySubview.superview insertSubview: msView aboveSubview: mySubview];
Jason

8

Esto es algo que debería ser más fácil. Terminé extendiendo UIViewControllery agregando un loadNib:inPlaceholder:selector. Ahora puedo decir

self.mySubview = (MyView *)[self loadNib:@"MyView" inPlaceholder:mySubview];

Aquí está el código para la categoría (hace el mismo rigamarole descrito por Gonso):

@interface UIViewController (nibSubviews)

- (UIView *)viewFromNib:(NSString *)nibName;
- (UIView *)loadNib:(NSString *)nibName inPlaceholder:(UIView *)placeholder;

@end

@implementation UIViewController (nibSubviews)

- (UIView *)viewFromNib:(NSString *)nibName
{
  NSArray *xib = [[NSBundle mainBundle] loadNibNamed:nibName owner:self options:nil]; 
  for (id view in xib) { // have to iterate; index varies
    if ([view isKindOfClass:[UIView class]]) return view;
  }
  return nil;
}

- (UIView *)loadNib:(NSString *)nibName inPlaceholder:(UIView *)placeholder
{
  UIView *nibView = [self viewFromNib:nibName];
  [nibView setFrame:placeholder.frame];
  [self.view insertSubview:nibView aboveSubview:placeholder];
  [placeholder removeFromSuperview];
  return nibView;
}

@end

7

En rápido

En realidad, mi resolución a este problema fue cargar la vista en viewDidLoad en mi CustonViewController donde quería usar la vista así:

myAccessoryView = NSBundle.mainBundle().loadNibNamed("MyAccessoryView", owner: self, options: nil)[0] as! MyAccessoryView

¡No cargue la vista en un loadView()método! El método loadView sirve para cargar la vista para su ViewController personalizado.


6

Yo también quería hacer algo similar, esto es lo que encontré: (SDK 3.1.3)

Tengo un controlador de vista A (propiedad de un controlador Nav) que carga VC B al presionar un botón:

En AViewController.m

BViewController *bController = [[BViewController alloc] initWithNibName:@"Bnib" bundle:nil];
[self.navigationController pushViewController:bController animated:YES];
[bController release];

Ahora VC B tiene su interfaz de Bnib, pero cuando se presiona un botón, quiero ir a un 'modo de edición' que tiene una interfaz de usuario separada de una punta diferente, pero no quiero un nuevo VC para el modo de edición, Quiero que la nueva punta se asocie con mi B VC existente.

Entonces, en BViewController.m (en el método de presionar un botón)

NSArray *nibObjects = [[NSBundle mainBundle] loadNibNamed:@"EditMode" owner:self options:nil];
UIView *theEditView = [nibObjects objectAtIndex:0];
self.editView = theEditView;
[self.view addSubview:theEditView];

Luego, en otro botón, presione (para salir del modo de edición):

[editView removeFromSuperview];

y vuelvo a mi Bnib original.

Esto funciona bien, pero tenga en cuenta que mi EditMode.nib solo tiene 1 obj de nivel superior, un obj UIView. No importa si el propietario del archivo en este plumín está configurado como BViewController o el NSObject predeterminado, PERO asegúrese de que la salida de visualización en el propietario del archivo NO esté configurada en nada. Si es así, recibo un bloqueo exc_bad_access y xcode procede a cargar 6677 cuadros de pila que muestran un método interno UIView repetidamente llamado ... por lo que parece un bucle infinito. (Sin embargo, View Outlet está configurado en mi Bnib original)

Espero que esto ayude.


Leyendo entre líneas, esto también me ayudó.
petert

De acuerdo con la información en el enlace, no debe manipular los controladores de vista de esta manera.
T. Markle

6

Hice una categoría que me gusta:

UIView+NibInitializer.h

#import <UIKit/UIKit.h>

@interface UIView (NibInitializer)
- (instancetype)initWithNibNamed:(NSString *)nibNameOrNil;
@end

UIView+NibInitializer.m

#import "UIView+NibInitializer.h"

@implementation UIView (NibInitializer)

- (instancetype)initWithNibNamed:(NSString *)nibNameOrNil
{
    if (!nibNameOrNil) {
        nibNameOrNil = NSStringFromClass([self class]);
    }
    NSArray *viewsInNib = [[NSBundle mainBundle] loadNibNamed:nibNameOrNil
                                                        owner:self
                                                      options:nil];
    for (id view in viewsInNib) {
        if ([view isKindOfClass:[self class]]) {
            self = view;
            break;
        }
    }
    return self;
}

@end

Entonces, llame así:

MyCustomView *myCustomView = [[MyCustomView alloc] initWithNibNamed:nil];

Use un nombre de plumín si su plumín tiene un nombre diferente al de su clase.

Para anularlo en sus subclases para un comportamiento adicional, podría verse así:

- (instancetype)initWithNibNamed:(NSString *)nibNameOrNil
{
    self = [super initWithNibNamed:nibNameOrNil];
    if (self) {
        self.layer.cornerRadius = CGRectGetHeight(self.bounds) / 2.0;
    }
    return self;
}

5

Para usuarios de Swift con opción designable:

  1. Cree una UIViewsubclase personalizada y un archivo xib , que nombraremos después de nuestro propio nombre de clase: en nuestro caso, MemeView. Dentro de la clase Meme View, recuerde definirla como designable con el @IBDesignableatributo antes de la declaración de clase
  2. Recuerde configurar el Propietario del archivo en el xib con nuestra UIViewsubclase personalizada en el panel del Inspector de independencia

    ingrese la descripción de la imagen aquí

  3. En el archivo xib ahora podemos construir nuestra interfaz, hacer restricciones, crear salidas, acciones, etc. ingrese la descripción de la imagen aquí

  4. Necesitamos implementar algunos métodos en nuestra clase personalizada para abrir el xib una vez inicializado

    class XibbedView: UIView {

    weak var nibView: UIView!
    
    override convenience init(frame: CGRect) {
        let nibName = NSStringFromClass(self.dynamicType).componentsSeparatedByString(".").last!
        self.init(nibName: nibName)
    }
    
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        let nibName = NSStringFromClass(self.dynamicType).componentsSeparatedByString(".").last!
        let nib = loadNib(nibName)
        nib.frame = bounds
        nib.translatesAutoresizingMaskIntoConstraints = false
        addSubview(nib)
        nibView = nib
        setUpConstraints()
    }
    
    init(nibName: String) {
        super.init(frame: CGRectZero)
        let nibName = NSStringFromClass(self.dynamicType).componentsSeparatedByString(".").last!
        let nib = loadNib(nibName)
        nib.frame = bounds
        nib.translatesAutoresizingMaskIntoConstraints = false
        addSubview(nib)
        nibView = nib
        setUpConstraints()
    }
    
    func setUpConstraints() {
        ["V","H"].forEach { (quote) -> () in
            let format = String(format:"\(quote):|[nibView]|")
            addConstraints(NSLayoutConstraint.constraintsWithVisualFormat(format, options: [], metrics: nil, views: ["nibView" : nibView]))
        }
    }
    
    func loadNib(name: String) -> UIView {
        let bundle = NSBundle(forClass: self.dynamicType)
        let nib = UINib(nibName: name, bundle: bundle)
        let view = nib.instantiateWithOwner(self, options: nil)[0] as! UIView
    
        return view
    }

    }

  5. En nuestra clase personalizada también podemos definir algunas propiedades inspeccionables para tener control total sobre ellas desde el generador de interfaces

    @IBDesignable class MemeView: XibbedView {

    @IBInspectable var memeImage: UIImage = UIImage() {
        didSet {
            imageView.image = memeImage
        }
    }
    @IBInspectable var textColor: UIColor = UIColor.whiteColor() {
        didSet {
            label.textColor = textColor
        }
    }
    @IBInspectable var text: String = "" {
        didSet {
            label.text = text
        }
    }
    @IBInspectable var roundedCorners: Bool = false {
        didSet {
            if roundedCorners {
                layer.cornerRadius = 20.0
                clipsToBounds = true
            }
            else {
                layer.cornerRadius = 0.0
                clipsToBounds = false
            }
        }
    }
    
    @IBOutlet weak var label: UILabel!
    @IBOutlet weak var imageView: UIImageView!

}

Pocos ejemplos:
ingrese la descripción de la imagen aquí ingrese la descripción de la imagen aquí

Si necesitamos agregar más información a la vista mientras se muestra dentro de un guión gráfico u otro xib, para hacer eso podemos implementar prepareForInterfaceBuilder(), este método se ejecutará solo al abrir el archivo en el generador de interfaces. Si hizo todo lo que escribí pero nada funciona, esta es una forma de depurar una vista sigle agregando puntos de interrupción en su implementación. Aquí está la jerarquía de vistas. ingrese la descripción de la imagen aquí
Ver jerarquía

Espero que esto ayude a descargar una muestra completa aquí


El artículo completo se puede ver en mi blog cloudintouch.it/2016/04/21/designable-xib-in-xcode-hell-yeah pero ya publiqué la parte relevante.
Andrea

let nib = loadNib(nibName)esta es una instancia de XibbedView, si llama `` `addSubview (nib)` `` ¿entonces está agregando una instancia de XibbedView a otra instancia de XibbedView?
William Hu

Estoy de acuerdo en este punto. Cuando intenta verlo desde "Debug View Hierarchy" parece que XibbedView contiene un XibbedView. Puedes verlo.
William Hu

@WilliamHu si descarga el ejemplo proporcionado, puede ver que MemeView.xib es solo una instancia de UIView con un montón de subvistas, el titular del archivo es un MemeView que es una subclase de XibbedView. Por lo tanto, la única instancia de XibbedView es solo MemeView que carga un archivo xib donde la vista principal es solo una vista
Andrea

Oh, bien entonces. Lo probaré ¡Gracias!
William Hu

4

Encontré que esta publicación de blog de Aaron Hillegass (autor, instructor, Cocoa ninja) es muy esclarecedora. Incluso si no adopta su enfoque modificado para cargar archivos NIB a través de un inicializador designado, probablemente al menos obtendrá una mejor comprensión del proceso que está sucediendo. ¡He estado usando este método últimamente con gran éxito!


Link está muerto. Creo que ahora se encuentra en bignerdranch.com/blog/…
joelliusp

3

La respuesta anterior no tiene en cuenta un cambio en la estructura NIB (XIB) que se produjo entre 2.0 y 2.1 del SDK de iPhone. El contenido del usuario ahora comienza en el índice 0 en lugar de 1.

Puede usar la macro 2.1 que es válida para todas las versiones 2.1 y superiores (son dos guiones bajos antes de IPHONE:

 // Cited from previous example
 NSArray* nibViews =  [[NSBundle mainBundle] loadNibNamed:@"QPickOneView" owner:self options:nil];
 int startIndex;
 #ifdef __IPHONE_2_1
 startIndex = 0;
 #else
 startIndex = 1;
 #endif
 QPickOneView* myView = [ nibViews objectAtIndex: startIndex];
 myView.question = question;

Utilizamos una técnica similar a esta para la mayoría de nuestras aplicaciones.

Barney


2
Barney: "La respuesta anterior" no tiene sentido en Stack Overflow porque las respuestas cambian de posición según la votación. Es mejor referirse a "La respuesta de Fred" o "La respuesta de Barney" o "La respuesta de Olie".
Olie

3

Tenía razones para hacer lo mismo (cargar programáticamente una vista desde un archivo XIB), pero necesitaba hacerlo completamente desde el contexto de una subclase de una subclase de a UIView(es decir, sin involucrar al controlador de vista de ninguna manera). Para hacer esto, creé este método de utilidad:

+ (id) initWithNibName:(NSString *)nibName withSelf:(id)myself {

    NSArray *bundle = [[NSBundle mainBundle] loadNibNamed:nibName
                                                    owner:myself options:nil];
    for (id object in bundle) {
        if ([object isKindOfClass:[myself class]]) {
            return object;
        }
    }  

    return nil;
} 

Luego lo llamo desde el initWithFramemétodo de mi subclase así:

- (id)initWithFrame:(CGRect)frame {

    self = [Utilities initWithNibName:@"XIB1" withSelf:self];
    if (self) {
        // Initialization code.
    }
    return self;
}

Publicado para interés general; Si alguien ve algún problema sin hacerlo de esta manera, hágamelo saber.


He implementado su código, pero sigue fuera de alcance en init. Hice su método estático en una clase de Utilidades. Hice archivos h / m llamados MyView (subclase de UIView) y un xib llamado igual. En IB configuré el propietario de los archivos xib Y la vista en "MyView". En el depurador está fuera de alcance. ¿Me estoy perdiendo algo en el IB?
TigerCoding

Eche un vistazo a mi nueva pregunta SO aquí: stackoverflow.com/questions/9224857/…
TigerCoding

self = [Utilidades initWithNibName: @ "XIB1" withSelf: self]; Te estás pasando cuando aún no está configurado. ¿No es ese código el mismo que este: self = [Utilities initWithNibName: @ "XIB1" withSelf: nil]; ?
Ken Van Hoeylandt

@Javy: mi ejemplo de código aquí puede no funcionar con ARC; no lo he probado con ARC habilitado.
MusiGenesis

3

Aquí hay una manera de hacerlo en Swift (actualmente escribiendo Swift 2.0 en XCode 7 beta 5).

Desde su UIViewsubclase que establezca como "Clase personalizada" en el Creador de interfaces, cree un método como este (mi subclase se llama RecordingFooterView):

class func loadFromNib() -> RecordingFooterView? {
    let nib = UINib(nibName: "RecordingFooterView", bundle: nil)
    let nibObjects = nib.instantiateWithOwner(nil, options: nil)
    if nibObjects.count > 0 {
        let topObject = nibObjects[0]
        return topObject as? RecordingFooterView
    }
    return nil
}

Entonces puedes llamarlo así:

let recordingFooterView = RecordingFooterView.loadFromNib()


2

Ninguna de las respuestas explica cómo crear el XIB independiente que es la raíz de esta pregunta. No hay Xcode 4opción para "Crear nuevo archivo XIB".

Para hacer esto

1) Choose "New File..."
2) Choose the "User Interface" category under the iOS section
3) Choose the "View" item
4) You will then be prompted to choose an iPhone or iPad format

Esto puede parecer simple, pero puede ahorrarle unos minutos buscando porque la palabra "XIB" no aparece en ningún lado.


1

@AVeryDev

6) Para adjuntar la vista cargada a la vista del controlador de vista:

[self.view addSubview:myViewFromNib];

Presumiblemente, es necesario eliminarlo de la vista para evitar pérdidas de memoria.

Para aclarar: el controlador de vista tiene varios IBOutlets, algunos de los cuales están conectados a elementos en el archivo de plumín original (como de costumbre), y algunos están conectados a elementos en el plumín cargado. Ambas puntas tienen la misma clase de propietario. La vista cargada se superpone a la original.

Sugerencia: establezca la opacidad de la vista principal en la punta cargada a cero, luego no ocultará los elementos de la punta original.


Es bueno tener cuidado, pero al agregar una vista como subvista, se elimina automáticamente de su padre anterior para que no se filtre.
averydev

1

Después de pasar muchas horas, forjé la siguiente solución. Siga estos pasos para crear personalizadosUIView .

1) Crear clase myCustomView heredada de UIView .
ingrese la descripción de la imagen aquí

2) Crear .xib con el nombre myCustomView .
ingrese la descripción de la imagen aquí

3) Cambie la clase de UIView dentro de su archivo .xib, asigne myCustomView Class allí.
ingrese la descripción de la imagen aquí

4) Crear IBOutlets
ingrese la descripción de la imagen aquí

5) Cargue .xib en myCustomView * customView . Use el siguiente código de muestra.

myCustomView * myView = [[[NSBundle mainBundle] loadNibNamed:@"myCustomView" owner:self options:nil] objectAtIndex:0];
[myView.description setText:@"description"];

Nota: Para aquellos que aún enfrentan problemas pueden comentar, les proporcionaré un enlace del proyecto de muestra con myCustomView


1

Versión más corta:

RSFavoritePlaceholderView *favoritePlaceholderView = [[[NSBundle mainBundle] loadNibNamed:@"RSFavoritePlaceholderView" owner:self options:nil] firstObject];

0

Tengo una convención de nombrar xibs con vistas en ellos igual que la vista. Lo mismo que uno haría para un controlador de vista. Entonces, no tengo que escribir los nombres de las clases en el código. Cargo una UIView desde un archivo plumín con el mismo nombre.

Ejemplo para una clase llamada MyView.

  • Cree un archivo nib llamado MyView.xib en Interface Builder
  • En Interface Builder, agregue una UIView. Establezca su clase en MyView. Personalice el contenido de su corazón, conecte variables de instancia de MyView a subvistas a las que desee acceder más adelante.
  • En su código, cree un nuevo MyView como este:

    MyView * myView = [MyView nib_viewFromNibWithOwner: propietario];

Aquí está la categoría para esto:

@implementation UIView (nib)

+ (id) nib_viewFromNib {
    return [self nib_viewFromNibWithOwner:nil];
}

+ (id) nib_viewFromNibWithOwner:(id)owner {
    NSString *className = NSStringFromClass([self class]);
    NSArray *nib = [[NSBundle mainBundle] loadNibNamed:className owner:owner options:nil];
    UIView *view = nil;
    for(UIView *v in nib) {
        if ([v isKindOfClass:[self class]]) {
            view = v;
            break;
        }
    }
    assert(view != nil && "View for class not found in nib file");
    [view nib_viewDidLoad];
    return view;
}

// override this to do custom setup
-(void)nib_viewDidLoad {

}

Luego conectaba los botones con acciones del controlador que estoy usando, y establecía cosas en las etiquetas usando los puntos de venta en mi subclase de vista personalizada.


0

Para cargar mediante programación una vista desde una punta / xib en Swift 4:

// Load a view from a Nib given a placeholder view subclass
//      Placeholder is an instance of the view to load.  Placeholder is discarded.
//      If no name is provided, the Nib name is the same as the subclass type name
//
public func loadViewFromNib<T>(placeholder placeholderView: T, name givenNibName: String? = nil) -> T {

    let nib = loadNib(givenNibName, placeholder: placeholderView)
    return instantiateView(fromNib: nib, placeholder: placeholderView)
}

// Step 1: Returns a Nib
//
public func loadNib<T>(_ givenNibName: String? = nil, placeholder placeholderView: T) -> UINib {
    //1. Load and unarchive nib file
    let nibName = givenNibName ?? String(describing: type(of: placeholderView))

    let nib = UINib(nibName: nibName, bundle: Bundle.main)
    return nib
}

// Step 2: Instantiate a view given a nib
//
public func instantiateView<T>(fromNib nib: UINib, placeholder placeholderView: T) -> T {
    //1. Get top level objects
    let topLevelObjects = nib.instantiate(withOwner: placeholderView, options: nil)

    //2. Have at least one top level object
    guard let firstObject = topLevelObjects.first else {
        fatalError("\(#function): no top level objects in nib")
    }

    //3. Return instantiated view, placeholderView is not used
    let instantiatedView = firstObject as! T
    return instantiatedView
}

-1

Terminé agregando una categoría a UIView para esto:

 #import "UIViewNibLoading.h"

 @implementation UIView (UIViewNibLoading)

 + (id) loadNibNamed:(NSString *) nibName {
    return [UIView loadNibNamed:nibName fromBundle:[NSBundle mainBundle] retainingObjectWithTag:1];
 }

 + (id) loadNibNamed:(NSString *) nibName fromBundle:(NSBundle *) bundle retainingObjectWithTag:(NSUInteger) tag {
    NSArray * nib = [bundle loadNibNamed:nibName owner:nil options:nil];
    if(!nib) return nil;
    UIView * target = nil;
    for(UIView * view in nib) {
        if(view.tag == tag) {
            target = [view retain];
            break;
        }
    }
    if(target && [target respondsToSelector:@selector(viewDidLoad)]) {
        [target performSelector:@selector(viewDidLoad)];
    }
    return [target autorelease];
 }

 @end

explicación aquí: viewcontroller es menos carga de vista en ios y mac


Hrm. ¿Qué pasa con la etiqueta?
n13

para saber qué objeto de nivel superior retener. en ese momento era necesario, pero probablemente podría sacar las retenciones para cumplir con ARC ahora.
gngrwzrd
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.