Reutilizar un uiview xib en el guión gráfico

81

Normalmente me gusta crear y diseñar mis uiviews en el generador de interfaces. A veces necesito crear una sola vista en un xib que se pueda reutilizar en múltiples controladores de vista en un guión gráfico.

Garfbargle
fuente

Respuestas:

132

Reutilice y renderice un xib en un guión gráfico.

Probado con Swift 2.2 y Xcode 7.3.1

1 ---- Cree una nueva UIView llamada 'DesignableXibView'

  • Archivo> Nuevo> Archivo> Fuente> Clase Cocoa Touch> UIView

2 ---- Cree un archivo xib coincidente llamado 'DesignableXibView'

  • Archivo> Nuevo> Archivo> Interfaz de usuario> Ver

3 ---- Establecer el propietario del archivo de la xib

  1. seleccione el xib
  2. seleccionar el propietario del archivo
  3. establezca la clase personalizada en 'DesignableXibView' en el Inspector de identidad.

Configuración del propietario de un Xib en una clase personalizada

  • Nota: No establezca la clase personalizada de la vista en el xib. ¡Solo el propietario del archivo!

4 ---- Implementación de DesignableXibView

//  DesignableXibView.swift

import UIKit

@IBDesignable

class DesignableXibView: UIView {

    var contentView : UIView?

    override init(frame: CGRect) {
        super.init(frame: frame)
        xibSetup()
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        xibSetup()
    }

    func xibSetup() {
        contentView = loadViewFromNib()

        // use bounds not frame or it'll be offset
        contentView!.frame = bounds

        // Make the view stretch with containing view
        contentView!.autoresizingMask = [UIViewAutoresizing.FlexibleWidth, UIViewAutoresizing.FlexibleHeight]

        // Adding custom subview on top of our view (over any custom drawing > see note below)
        addSubview(contentView!)
    }

    func loadViewFromNib() -> UIView! {

        let bundle = NSBundle(forClass: self.dynamicType)
        let nib = UINib(nibName: String(self.dynamicType), bundle: bundle)
        let view = nib.instantiateWithOwner(self, options: nil)[0] as! UIView

        return view
    }

}

5 ---- Pruebe su vista reutilizable en un guión gráfico

  1. Abre tu guión gráfico
  2. Agregar una vista
  3. Establecer la clase personalizada de esa vista
  4. espera un segundo ... BOOM !!

Renderizado Xib dentro de un controlador de vista de guión gráfico

Garfbargle
fuente
1
¡Gracias! Es posible que desee agregar un ejemplo utilizando restricciones con Swift, como lo hizo en su respuesta Obj-C (con y sin VFL).
Evan R
3
Respondí mi propia pregunta, parece que configuré los puntos de venta ANTES de leer estos artículos de SO que dicen configurar el propietario del archivo, no la vista. Funcionó después de que desconecté todos los enchufes, me aseguré de que el propietario del archivo fuera correcto y luego volví a agregar todos los enchufes.
SuperDuperTango
4
¿Cuál es la diferencia entre configurar el propietario del archivo y la clase personalizada?
Paul Brewczynski
9
Tampoco veo la vista previa en el generador de interfaces. Xcode 9
Jaap Weijland
3
El código funciona cuando se ejecuta, pero en mi Xcode 9 muestra 'Falló la construcción designada' debajo de la línea del módulo
ManuQiao
76

¡NUEVO! respuesta actualizada con capacidad para renderizar directamente en el guión gráfico (¡y rápido!)

Funciona en Xcode 6.3.1

Cree una nueva UIView llamada 'ReuseableView'

  • Archivo> Nuevo> Archivo> Fuente> Clase Cocoa Touch> UIView

Cree un archivo xib coincidente llamado 'ReuseableView'

  • Archivo> Nuevo> Archivo> Interfaz de usuario> Ver

Establecer el propietario del archivo de la xib

  1. seleccione el xib
  2. seleccionar el propietario del archivo
  3. establezca la clase personalizada en 'ReusableView' en el Inspector de identidad. ingrese la descripción de la imagen aquí

    • Nota: No establezca la clase personalizada de la vista en el xib. ¡Solo el propietario del archivo!

Haga una salida de la vista en ReuseableView.xib a su interfaz ReuseableView.h

  1. Editor asistente abierto
  2. Control + Arrastra desde la vista a tu interfaz

ingrese la descripción de la imagen aquí

Agregue la implementación initWithCoder para cargar la vista y agregarla como una subvista.

- (id)initWithCoder:(NSCoder *)aDecoder{
    self = [super initWithCoder:aDecoder];
    if (self) {

        // 1. load the interface
        [[NSBundle mainBundle] loadNibNamed:NSStringFromClass([self class]) owner:self options:nil];
        // 2. add as subview
        [self addSubview:self.view];
        // 3. allow for autolayout
        self.view.translatesAutoresizingMaskIntoConstraints = NO;
        // 4. add constraints to span entire view
        [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[view]|" options:0 metrics:nil views:@{@"view":self.view}]];
        [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[view]|" options:0 metrics:nil views:@{@"view":self.view}]];

    }
    return self;
}

ingrese la descripción de la imagen aquí

Pruebe su vista reutilizable en un guión gráfico

  1. Abre tu guión gráfico
  2. Agregar una vista
  3. Establecer la clase personalizada de esa vista

ingrese la descripción de la imagen aquí

¡Corre y observa! ingrese la descripción de la imagen aquí

Garfbargle
fuente
@Garfbargle Agregando además de esto ... ¿es posible mostrar en el guión gráfico los subelementos del xib? En su último paso, la vista agregada aparece como un cuadrado gris, ¿hay alguna forma de solucionarlo? ¡Gracias!
Andres C
2
@ andres.cianio Sí, sin embargo, debes adoptar un enfoque completamente diferente. Debe crear la vista mediante programación y usar la directiva IBDesignable. Sin embargo, esto fue específicamente para personas que querían construir vistas visualmente. No conozco una forma de crear una vista en un xib visualmente y hacer que se represente en un guión gráfico. No me sorprendería que esto fuera posible muy pronto, aunque aún no lo es.
Garfbargle
@Garfbargle gracias ... Esto me ha salvado la vida, pero tener que rinde en el SB al insertar la vista habría sido hermosa;)
Andres C
2
¡Gran explicación! Pero tengo una pista para ti: usa la UINib(nibName: nibName, bundle: nil).instantiateWithOwner(nil, options: nil)que es más rápida que la versión NSBundle.
blackjacx
@Garfbargle Esto parece satisfacer el requisito de que puede diseñar en IB y hacer que se represente en el guión gráfico. Básicamente, haces exactamente lo que hiciste para el XIB y también @IBDesignable on the class.tienes que implementarlo init(frame:), ¡pero aparte de eso, funciona bastante bien! supereasyapps.com/blog/2014/12/15/…
wyu
53

Actualización de Swift 3 y 4 a la respuesta aceptada

1. Cree una nueva UIView llamada 'DesignableXibView'

  • Archivo> Nuevo> Archivo> Fuente> Clase Cocoa Touch> UIView

2. Cree un archivo xib coincidente llamado 'DesignableXibView'

  • Archivo> Nuevo> Archivo> Interfaz de usuario> Ver

3. Establecer el propietario del archivo de la xib

Seleccione "DesignableXibView.xib"> "Propietario del archivo"> ​​establezca "Clase personalizada" en 'DesignableXibView' en el Inspector de identidad.

  • Nota: No establezca la clase personalizada de la vista en el xib. ¡Solo el propietario del archivo!

4. Implementación de DesignableXibView

    import UIKit

@IBDesignable

class DesignableXibView: UIView {

    var contentView : UIView!

    override init(frame: CGRect) {
        super.init(frame: frame)
        xibSetup()
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        xibSetup()
    }

    func xibSetup() {
        contentView = loadViewFromNib()

        // use bounds not frame or it'll be offset
        contentView.frame = bounds

        // Make the view stretch with containing view
        contentView.autoresizingMask = [UIViewAutoresizing.flexibleWidth, UIViewAutoresizing.flexibleHeight]

        // Adding custom subview on top of our view 
        addSubview(contentView)
    }

    func loadViewFromNib() -> UIView! {

        let bundle = Bundle(for: type(of: self))
        let nib = UINib(nibName: String(describing: type(of: self)), bundle: bundle)
        let view = nib.instantiate(withOwner: self, options: nil).first as! UIView

        return view
    }
} 

5 Pruebe su vista reutilizable en un guión gráfico

  • Abre tu guión gráfico

  • Agregar una vista

  • Establecer la clase personalizada de esa vista

harsh_v
fuente
2
No puedo hacer que funcione en Xcode8 / Swift3. Funciona bien cuando se ejecuta la aplicación, pero no se muestra en el guión gráfico.
shallowThought
Funciona bien para mi. Si está intentando ver los cambios en el xib, no se mostrará. Agregue una vista dentro de otro controlador y luego configure la clase de esa vista en DesignableXibView. Cree el proyecto para ver los cambios.
harsh_v
1
También funcionó para mí. Solo necesitaba agregar DesignableXibView a mi guión gráfico actual como se describe en el Paso 5 de la publicación de Garfbargle
Tinkerbell
¿Solo obtengo "No se pudo cargar NIB en el paquete" en tiempo de ejecución?
Jonny
1
@Jonny Creo que String(describing: type(of: self))debería hacer esto de forma segura
harsh_v
11

La función initWithCoder en swift 2 si alguien tiene problemas para traducirla:

required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        UINib(nibName: String(self.dynamicType), bundle: NSBundle.mainBundle()).instantiateWithOwner(self, options: nil)
        self.addSubview(view)
        self.view.translatesAutoresizingMaskIntoConstraints = false
        self.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|[view]|", options: NSLayoutFormatOptions.AlignAllCenterY , metrics: nil, views: ["view": self.view]))
        self.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|[view]|", options: NSLayoutFormatOptions.AlignAllCenterX , metrics: nil, views: ["view": self.view]))
    }
Ankit Goel
fuente
¡Gracias! Es posible que desee agregar un ejemplo que no use VFL, para aquellos que no quieran usarlo.
Evan R
3

Para cualquiera que intente adaptar la respuesta aceptada (por @Garfbargle) a Objective-C

Convertir Swifta Objective-Cno es suficiente para que funcione. He tenido dificultades para permitir la representación en vivo en Storyboard.

Después de traducir todo el código, la vista está bien cargada cuando se ejecuta en el dispositivo (o simulador), pero la representación en vivo en Storyboard no funciona. La razón de esto es que usé [NSBundle mainBundle]mientras que Interface Builder no tiene acceso a mainBundle. Lo que tienes que usar en su lugar es [NSBundle bundleForClass:self.classForCoder]. BOOM, ¡la renderización en vivo funciona ahora!

Nota: si tiene problemas con el diseño automático, intente deshabilitarlo Safe Area Layout Guidesen Xib.

Para su comodidad, dejo mi código completo aquí, para que solo tenga que copiar / pegar (para todo el proceso, siga la respuesta original ):

BottomBarView.h

#import <UIKit/UIKit.h>

IB_DESIGNABLE
@interface BottomBarView : UIView

@end

BottomBarView.m

#import "BottomBarView.h"

@interface BottomBarView() {
    UIView *contentView;
}

@end


@implementation BottomBarView

-(id) initWithFrame:(CGRect)frame {
    self = [super initWithFrame:frame];
    if (self) {
        [self xibSetup];
    }
    return self;
}

-(id) initWithCoder:(NSCoder *)aDecoder {
    self = [super initWithCoder:aDecoder];
    if (self) {
        [self xibSetup];
    }
    return self;
}

-(void) xibSetup {
    contentView = [self loadViewFromNib];

    contentView.frame = self.bounds;

    contentView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;

    [self addSubview:contentView];
}

-(UIView*) loadViewFromNib {
    NSBundle *bundle = [NSBundle bundleForClass:self.classForCoder]; //this is the important line for view to render in IB
    UINib *nib = [UINib nibWithNibName:NSStringFromClass([self class]) bundle:bundle];
    UIView *view = [nib instantiateWithOwner:self options:nil][0];

    return view;
}

@end

Dime si encuentras algunos problemas, pero casi debería funcionar fuera de la caja :)

AnthoPak
fuente
0

Si alguien está interesado, aquí está la versión Xamarin.iOS del código Paso 4 de @Garfbargle

public partial class CustomView : UIView
    {
        public ErrorView(IntPtr handle) : base(handle)
        {

        }

        [Export("awakeFromNib")]
        public override void AwakeFromNib()
        {
            var nibObjects = NSBundle.MainBundle.LoadNib("CustomView", this, null);
            var view = (UIView)Runtime.GetNSObject(nibObjects.ValueAt(0));
            view.Frame = Bounds;
            view.AutoresizingMask = UIViewAutoresizing.FlexibleWidth | UIViewAutoresizing.FlexibleHeight;

            AddSubview(rootView);
        }
    }
Yohan Dahmani
fuente
0

Aquí está la respuesta que siempre ha querido. Puede crear su CustomViewclase, tener la instancia maestra de la misma en un xib con todas las subvistas y salidas. Luego, puede aplicar esa clase a cualquier instancia en sus guiones gráficos u otros xibs.

No es necesario jugar con el propietario del archivo, ni conectar puntos de venta a un proxy o modificar el xib de una manera peculiar, o agregar una instancia de su vista personalizada como una subvista de sí mismo.

Solo haz esto:

  1. Importar marco BFWControls
  2. Cambia tu superclase de UIViewa NibView(o de UITableViewCella NibTableViewCell)

¡Eso es!

Incluso funciona con IBDesignable para representar su vista personalizada (incluidas las subvistas de xib) en el momento del diseño en el guión gráfico.

Puede leer más sobre esto aquí: https://medium.com/build-an-app-like-lego/embed-a-xib-in-a-storyboard-953edf274155

Y puede obtener el marco de código abierto BFWControls aquí: https://github.com/BareFeetWare/BFWControls

Tom 👣

barefeettom
fuente