Cargue una UIView desde la punta en Swift

148

Aquí está mi código Objective-C que estoy usando para cargar una punta para mi personalizado UIView:

-(id)init{

    NSArray *subviewArray = [[NSBundle mainBundle] loadNibNamed:@"myXib" owner:self options:nil];
    return [subviewArray objectAtIndex:0];

}

¿Cuál es el código equivalente en Swift?

Bagusflyer
fuente

Respuestas:

172

Solución original

  1. Creé un XIB y una clase llamada SomeView (usé el mismo nombre para mayor comodidad y legibilidad). Basé ambos en una UIView.
  2. En el XIB, cambié la clase "Propietario del archivo" a SomeView (en el inspector de identidad).
  3. Creé una salida UIView en SomeView.swift, vinculándola a la vista de nivel superior en el archivo XIB (lo denominé "vista" por conveniencia). Luego agregué otras salidas a otros controles en el archivo XIB según sea necesario.
  4. en SomeView.swift, cargué el XIB dentro del inicializador "init with code". No hay necesidad de asignar nada a "uno mismo". Tan pronto como se carga el XIB, se conectan todas las salidas, incluida la vista de nivel superior. Lo único que falta es agregar la vista superior a la jerarquía de vistas:

.

class SomeView: UIView {
   required init(coder aDecoder: NSCoder) {
      super.init(coder: aDecoder)
      NSBundle.mainBundle().loadNibNamed("SomeView", owner: self, options: nil)
      self.addSubview(self.view);    // adding the top level view to the view hierarchy
   }
   ...
}

Tenga en cuenta que de esta manera obtengo una clase que se carga desde nib. Entonces podría usar SomeView como clase siempre que UIView se pueda usar en el proyecto (en el generador de interfaces o mediante programación).

Actualización: uso de la sintaxis Swift 3

La carga de un xib en la siguiente extensión se escribe como un método de instancia, que luego puede ser utilizado por un inicializador como el anterior:

extension UIView {

    @discardableResult   // 1
    func fromNib<T : UIView>() -> T? {   // 2
        guard let contentView = Bundle(for: type(of: self)).loadNibNamed(String(describing: type(of: self)), owner: self, options: nil)?.first as? T else {    // 3
            // xib not loaded, or its top view is of the wrong type
            return nil
        }
        self.addSubview(contentView)     // 4
        contentView.translatesAutoresizingMaskIntoConstraints = false   // 5 
        contentView.layoutAttachAll(to: self)   // 6 
        return contentView   // 7
    }
}
  1. El uso de un valor de retorno descartable ya que la vista devuelta no tiene mayor interés para la persona que llama cuando todas las salidas ya están conectadas.
  2. Este es un método genérico que devuelve un objeto opcional de tipo UIView. Si no puede cargar la vista, devuelve nil.
  3. Intentando cargar un archivo XIB con el mismo nombre que la instancia de clase actual. Si eso falla, se devuelve nil.
  4. Agregar la vista de nivel superior a la jerarquía de vistas.
  5. Esta línea asume que estamos usando restricciones para diseñar la vista.
  6. Este método agrega restricciones superiores, inferiores, iniciales y finales - adjuntando la vista a "self" en todos los lados (Ver: https://stackoverflow.com/a/46279424/2274829 para más detalles)
  7. Volviendo a la vista de nivel superior

Y el método de llamada podría verse así:

final class SomeView: UIView {   // 1.
   required init?(coder aDecoder: NSCoder) {   // 2 - storyboard initializer
      super.init(coder: aDecoder)
      fromNib()   // 5.
   }
   init() {   // 3 - programmatic initializer
      super.init(frame: CGRect.zero)  // 4.
      fromNib()  // 6.
   }
   // other methods ...
}
  1. SomeClass es una subclase de UIView que carga su contenido desde un archivo SomeClass.xib. La palabra clave "final" es opcional.
  2. Un inicializador para cuando la vista se usa en un guión gráfico (recuerde usar SomeClass como la clase personalizada de su vista de guión gráfico).
  3. Un inicializador para cuando la vista se crea mediante programación (es decir, "let myView = SomeView ()").
  4. Usando un marco de todos ceros ya que esta vista se presenta usando el diseño automático. Tenga en cuenta que el método "init (frame: CGRect) {..}" no se crea de forma independiente, ya que el diseño automático se utiliza exclusivamente en nuestro proyecto.
  5. & 6. Cargando el archivo xib usando la extensión.

Crédito: El uso de una extensión genérica en esta solución se inspiró en la respuesta de Robert a continuación.

Editar Cambiando "ver" a "contentView" para evitar confusiones. También cambió el subíndice de matriz a ".first".

GK100
fuente
77
Configurando el nombre de la clase para File's Ownerdar en el clavo ... ¡Gracias!
Aviel Gross
13
UIView no tiene vista de propiedad, por lo que llamar a self.view causa un error
Nastya Gorban
44
@NastyaGorban self.view en realidad se refiere en este caso a la propiedad de salida (denominada "vista" que GK100 enlazó desde la vista de nivel superior en .xib a SomeView.swift. No agregar esa salida le dará un error ya que no hay "vista" "propiedad en clases NSView como usted dice.
Ausiàs
3
Me cuelgo al cargar nib (loadNibNamed). Usando Xcode 6.3 y Swift
karthikPrabhu Alagu
11
llamar fromNib()desde dentro init(coder aDecoder: NSCoder)crea un bucle infinito ya que al cargar el Nib dentro del fromNib()método se realiza una llamada a:init(coder aDecoder: NSCoder)
Matthew Cawley
334

Mi contribución:

extension UIView {
    class func fromNib<T: UIView>() -> T {
        return Bundle(for: T.self).loadNibNamed(String(describing: T.self), owner: nil, options: nil)![0] as! T
    }
}

Entonces llámalo así:

let myCustomView: CustomView = UIView.fromNib()

..o incluso:

let myCustomView: CustomView = .fromNib()
Robert Gummesson
fuente
20
La mejor respuesta con diferencia.
CodyMace
77
La mejor respuesta aquí. Limpio y simple
Marquavious Draggon
3
@YuchenZhong: prefiero [0] sobre .first ya que eso devolvería un opcional. Si fuerza desenvolverlo, no sería más seguro. ... y esto plantea la pregunta: ¿Por qué no devolver una opción como algunas de las soluciones anteriores? Respuesta: puedes. Nada de malo con eso. Pero ... si alguna vez devuelve nil, el nombre de la clase xib / no coincide. Este es un error del desarrollador y debe detectarse de inmediato y nunca llegar a producción. Aquí preferiría que la aplicación se bloquee en lugar de dejarla en un estado extraño. Solo mis 2 centavos / preferencia.
Robert Gummesson
1
@allenlinli: el método es una extensión estática de UIView como se supone que CustomView. Funciona porque el compilador infiere tipo usando la anotación de tipo explícito. Debido a que CustomView es una subclase de UIView y el tipo ya se ha inferido, no necesitamos inferirlo nuevamente, por lo tanto, UIView se puede omitir como se muestra en mi segundo ejemplo. Dicho esto, obviamente también puedes hacer la llamada de la manera en que la dejaste.
Robert Gummesson
3
Esta solución no me funcionó para el caso cuando había una vista personalizada dentro del .xib. Sugeriría arreglar esta parte para: return Bundle.main.loadNibNamed (String (describing: self), owner: nil, options: nil)! [0] as! T
Nadzeya
79

Ahora poder regresar -> Selfrápidamente ayuda a simplificar esto un poco. Última confirmación en Swift 5.

extension UIView {
    class func fromNib(named: String? = nil) -> Self {
        let name = named ?? "\(Self.self)"
        guard
            let nib = Bundle.main.loadNibNamed(name, owner: nil, options: nil)
            else { fatalError("missing expected nib named: \(name)") }
        guard
            /// we're using `first` here because compact map chokes compiler on
            /// optimized release, so you can't use two views in one nib if you wanted to
            /// and are now looking at this
            let view = nib.first as? Self
            else { fatalError("view of type \(Self.self) not found in \(nib)") }
        return view
    }
}

Si su .xibarchivo y subclase comparten el mismo nombre, puede usar:

let view = CustomView.fromNib()

Si tiene un nombre personalizado, use:

let view = CustomView.fromNib(named: "special-case")

NOTA:

Si obtiene el error "vista de tipo YourType no encontrada en ...", entonces no ha establecido la clase de vista en el .xibarchivo

Seleccione su vista en el .xibarchivo y presione cmd + opt + 4y en la classentrada, ingrese su clase

Logan
fuente
1
No puedo hacer que esto funcione con XCode 7.1 beta 3: no estoy seguro de si es una versión beta, pero básicamente he intentado todas las formas de crear una vista personalizada directamente desde una punta en Swift y siempre obtengo el mismo resultado: la clase que está creando no cumple con KVC con las salidas. No estoy seguro de si es algo que estoy haciendo mal, pero mi clase es bastante simple y el propietario del archivo es correcto. Solía ​​hacer esto todo el tiempo bajo Objective-C.
Echelon
1
@Logan no está realmente relacionado con su código, pero las vistas personalizadas de imo deberían admitir la carga desde Storyboard / XIB. Mi comentario fue solo una notificación para aquellos que desean crear tales puntos de vista
Nikita tomó el
1
Tenga en cuenta que todavía tengo problemas para usar la segunda forma de llamar a esta función, a saber let myCustomView = UIView.fromNib() as? CustomView. En este caso, se T.selfresuelve en UIViewlugar de CustomViewy no puede encontrar la punta. No estoy seguro de por qué es esto, ¿tal vez el tipo inferido para el letmedio significa que la función se llama como a UIView?
Echelon
2
Es importante señalar que intentar usar el Propietario del archivo para conectar los enchufes (como lo hicimos en los viejos tiempos) hará que esto se bloquee. En IB, el propietario del archivo debe ser nulo / vacío y las salidas deben estar conectadas a la vista.
Echelon
1
@Echelon me salvaste el día !!! Conecté mis puntos de venta usando el Propietario del archivo y no funcionó, en su lugar funcionó la vista.
Jan Schlorf
19

intente seguir el código.

var uiview :UIView?

self.uiview = NSBundle.mainBundle().loadNibNamed("myXib", owner: self, options: nil)[0] as? UIView

Editar:

import UIKit

class TestObject: NSObject {

     var uiview:UIView?

    init()  {
        super.init()
       self.uiview = NSBundle.mainBundle().loadNibNamed("myXib", owner: self, options: nil)[0] as? UIView
    }


}
Jazmín
fuente
Necesito llamar a este método dentro del método de inicialización de objetos que es init () en Swift.
Bagusflyer
12

Extensiones de protocolo Swift 4

public protocol NibInstantiatable {

    static func nibName() -> String

}

extension NibInstantiatable {

    static func nibName() -> String {
        return String(describing: self)
    }

}

extension NibInstantiatable where Self: UIView {

    static func fromNib() -> Self {

        let bundle = Bundle(for: self)
        let nib = bundle.loadNibNamed(nibName(), owner: self, options: nil)

        return nib!.first as! Self

    }

}

Adopción

class MyView: UIView, NibInstantiatable {

}

Esta implementación supone que el Nib tiene el mismo nombre que la clase UIView. Ex. MyView.xib. Puede modificar este comportamiento implementando nibName () en MyView para devolver un nombre diferente al de la implementación de extensión de protocolo predeterminada.

En xib, el propietario de los archivos es MyView y la clase de vista raíz es MyView.

Uso

let view = MyView.fromNib()
Brody Robertson
fuente
3
¡Esta es, con mucho, la solución más elegante y directa y no tengo idea de por qué no es la respuesta aceptada!
horseshoe7
@ horseshoe7 porque está escrito 4 años después de la pregunta.
Freeubi
11

Lo logré con Swift con el siguiente código:

class Dialog: UIView {
    @IBOutlet var view:UIView!

    override init(frame: CGRect) {
        super.init(frame: frame)
        self.frame = UIScreen.mainScreen().bounds
        NSBundle.mainBundle().loadNibNamed("Dialog", owner: self, options: nil)
        self.view.frame = UIScreen.mainScreen().bounds
        self.addSubview(self.view)
    }

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

No olvide conectar su toma de vista XIB para ver definida en swift. También puede configurar First Responder a su nombre de clase personalizado para comenzar a conectar cualquier salida adicional.

¡Espero que esto ayude!

Amro Shafie
fuente
10

Probado en Xcode 7 beta 4, Swift 2.0 y iOS9 SDK. El siguiente código asignará xib a la uiview. Puede usar esta vista xib personalizada en el guión gráfico y acceder también al objeto IBOutlet.

import UIKit

@IBDesignable class SimpleCustomView:UIView
{
    var view:UIView!;

    @IBOutlet weak var lblTitle: UILabel!

   @IBInspectable var lblTitleText : String?
        {
        get{
            return lblTitle.text;
        }
        set(lblTitleText)
        {
            lblTitle.text = lblTitleText!;
        }
    }

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

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        loadViewFromNib ()
    }
    func loadViewFromNib() {
        let bundle = NSBundle(forClass: self.dynamicType)
        let nib = UINib(nibName: "SimpleCustomView", bundle: bundle)
        let view = nib.instantiateWithOwner(self, options: nil)[0] as! UIView
        view.frame = bounds
        view.autoresizingMask = [.FlexibleWidth, .FlexibleHeight]
        self.addSubview(view);



    }


}

Acceda a la vista personalizada mediante programación

self.customView =  SimpleCustomView(frame: CGRectMake(100, 100, 200, 200))
        self.view.addSubview(self.customView!);

Código fuente: https://github.com/karthikprabhuA/CustomXIBSwift

karthikPrabhu Alagu
fuente
9

Si tiene muchas vistas personalizadas en su proyecto, puede crear clases como UIViewFromNib

Swift 2.3

class UIViewFromNib: UIView {

    var contentView: UIView!

    var nibName: String {
        return String(self.dynamicType)
    }

    //MARK:
    override init(frame: CGRect) {
        super.init(frame: frame)

        loadViewFromNib()
    }

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

        loadViewFromNib()
    }

    //MARK:
    private func loadViewFromNib() {
        contentView = NSBundle.mainBundle().loadNibNamed(nibName, owner: self, options: nil)[0] as! UIView
        contentView.autoresizingMask = [.FlexibleWidth, .FlexibleHeight]
        contentView.frame = bounds
        addSubview(contentView)
    }
}

Swift 3

class UIViewFromNib: UIView {

    var contentView: UIView!

    var nibName: String {
        return String(describing: type(of: self))
    }

    //MARK:
    override init(frame: CGRect) {
        super.init(frame: frame)

        loadViewFromNib()
    }

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

        loadViewFromNib()
    }

    //MARK:
    func loadViewFromNib() {
        contentView = Bundle.main.loadNibNamed(nibName, owner: self, options: nil)?[0] as! UIView
        contentView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        contentView.frame = bounds
        addSubview(contentView)
    }
}

Y en cada clase que acaba de heredar UIViewFromNib, también puede anular la nibNamepropiedad si el .xibarchivo tiene un nombre diferente:

class MyCustomClass: UIViewFromNib {

}
ChikabuZ
fuente
8

Sobre la base de las soluciones anteriores.

Esto funcionará en todos los paquetes de proyectos y no necesitará genéricos al llamar desde Nib ().

Swift 2

extension UIView {

    public class func fromNib() -> Self {
        return fromNib(nil)
    }

    public class func fromNib(nibName: String?) -> Self {

        func fromNibHelper<T where T : UIView>(nibName: String?) -> T {
            let bundle = NSBundle(forClass: T.self)
            let name = nibName ?? String(T.self)
            return bundle.loadNibNamed(name, owner: nil, options: nil)?.first as? T ?? T()
        }
        return fromNibHelper(nibName)
    }
}

Swift 3

extension UIView {

    public class func fromNib() -> Self {
        return fromNib(nibName: nil)
    }

    public class func fromNib(nibName: String?) -> Self {
        func fromNibHelper<T>(nibName: String?) -> T where T : UIView {
            let bundle = Bundle(for: T.self)
            let name = nibName ?? String(describing: T.self)
            return bundle.loadNibNamed(name, owner: nil, options: nil)?.first as? T ?? T()
        }
        return fromNibHelper(nibName: nibName)
    }
}

Se puede usar así:

let someView = SomeView.fromNib()

O así:

let someView = SomeView.fromNib("SomeOtherNibFileName")
Génesis
fuente
6

Swift 4

No olvides escribir ".primero como? CustomView".

if let customView = Bundle.main.loadNibNamed("myXib", owner: self, options: nil)?.first as? CustomView {    
    self.view.addSubview(customView)
    }

Si quieres usar en cualquier lugar

La mejor solución es la respuesta de Robert Gummesson .

extension UIView {
    class func fromNib<T: UIView>() -> T {
        return Bundle.main.loadNibNamed(String(describing: T.self), owner: nil, options: nil)![0] as! T
    }
}

Entonces llámalo así:

let myCustomView: CustomView = UIView.fromNib()
Ali Ihsan URAL
fuente
5
let subviewArray = NSBundle.mainBundle().loadNibNamed("myXib", owner: self, options: nil)
return subviewArray[0]
BaSha
fuente
Pero en init () de Swift, no hay valor devuelto. Olvidé mencionar que necesito llamar a loadNibNamed en la inicialización de una UIView.
Bagusflyer
¿Qué quiere decir "sin valor de retorno"? selfse devuelve implícitamente de todos los initmétodos ...
Grimxn
1
Lo que quiero decir es que llamo loadNibNamed dentro del método init. El UIView cargado se asigna a sí mismo en ObjC. Pero en rápido, no lo es.
Bagusflyer
5

Una buena manera de hacer esto con Swift es usar una enumeración.

enum Views: String {
    case view1 = "View1" // Change View1 to be the name of your nib
    case view2 = "View2" // Change View2 to be the name of another nib

    func getView() -> UIView? {
        return NSBundle.mainBundle().loadNibNamed(self.rawValue, owner: nil, options: nil).first as? UIView
    }
}

Luego, en su código, simplemente puede usar:

let view = Views.view1.getView()
TOTIG
fuente
2
Tenga en cuenta que si hace esto con un archivo de plumín vacío o un archivo de plumín con un nodo raíz sin UIView, se bloqueará ya que no está comprobando la cordura ni el tamaño de la matriz ni el elemento en la posición 0.
Matthew Cawley
4

Prefiero esta solución (basada en la respuesta if @ GK100):

  1. Creé un XIB y una clase llamada SomeView (usé el mismo nombre para mayor comodidad y legibilidad). Basé ambos en una UIView.
  2. En el XIB, cambié la clase "Propietario del archivo" a SomeView (en el inspector de identidad).
  3. Creé una salida UIView en SomeView.swift, vinculándola a la vista de nivel superior en el archivo XIB (lo denominé "vista" por conveniencia). Luego agregué otras salidas a otros controles en el archivo XIB según sea necesario.
  4. En SomeView.swift, cargué el XIB dentro del inicializador inito init:frame: CGRect. No hay necesidad de asignar nada a "uno mismo". Tan pronto como se carga el XIB, se conectan todas las salidas, incluida la vista de nivel superior. Lo único que falta es agregar la vista superior a la jerarquía de vistas:

    class SomeView: UIView {
      override init(frame: CGRect) {
        super.init(frame: frame)
        NSBundle.mainBundle().loadNibNamed("SomeObject", owner: self, options: nil)
        self.addSubview(self.view);    // adding the top level view to the view hierarchy
      }
    
      required init(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        NSBundle.mainBundle().loadNibNamed("SomeObject", owner: self, options: nil)
        self.addSubview(self.view);    // adding the top level view to the view hierarchy
      }
    
    
      ...
    }
confile
fuente
¡Prefiero usar init con frame así que lo desarraigé! una cosa a tener en cuenta ... agregue self.view.frame = frame si desea que la vista coincida con el marco que pasa
Mike E
3

Versión Swift 3 de la respuesta de Logan

extension UIView {
    public class func fromNib(nibName: String? = nil) -> Self {
        return fromNib(nibName: nibName, type: self)
    }

    public class func fromNib<T: UIView>(nibName: String? = nil, type: T.Type) -> T {
        return fromNib(nibName: nibName, type: T.self)!
    }

    public class func fromNib<T: UIView>(nibName: String? = nil, type: T.Type) -> T? {
        var view: T?
        let name: String

        if let nibName = nibName {
            name = nibName
        } else {
            name = self.nibName
        }

        if let nibViews = Bundle.main.loadNibNamed(name, owner: nil, options: nil) {
            for nibView in nibViews {
                if let tog = nibView as? T {
                    view = tog
                }
            }
        }

        return view
    }

    public class var nibName: String {
        return "\(self)".components(separatedBy: ".").first ?? ""
    }

    public class var nib: UINib? {
        if let _ = Bundle.main.path(forResource: nibName, ofType: "nib") {
            return UINib(nibName: nibName, bundle: nil)
        } else {
            return nil
        }
    }
}
Alexey Karetski
fuente
3

Aquí hay una forma limpia y declarativa de cargar mediante programación una vista usando un protocolo y una extensión de protocolo (Swift 4.2):

protocol XibLoadable {
    associatedtype CustomViewType
    static func loadFromXib() -> CustomViewType
}

extension XibLoadable where Self: UIView {
    static func loadFromXib() -> Self {
        let nib = UINib(nibName: "\(self)", bundle: Bundle(for: self))
        guard let customView = nib.instantiate(withOwner: self, options: nil).first as? Self else {
            // your app should crash if the xib doesn't exist
            preconditionFailure("Couldn't load xib for view: \(self)")
        }
        return customView
    }
}

Y puedes usar esto así:

// don't forget you need a xib file too
final class MyView: UIView, XibLoadable { ... }

// and when you want to use it
let viewInstance = MyView.loadFromXib()

Algunas consideraciones adicionales :

  1. Asegúrese de que el archivo xib de su vista personalizada tenga la vista de Custom Class conjunto (y las salidas / acciones establecidas desde allí), no el propietario del archivo.
  2. Puede usar este protocolo / extensión externa a su vista personalizada o interna. Es posible que desee usarlo internamente si tiene algún otro trabajo de configuración al inicializar su vista.
  3. Su clase de vista personalizada y su archivo xib deben tener el mismo nombre.
jason z
fuente
2

Solo hago esto:

if let myView = UINib.init(nibName: "MyView", bundle: nil).instantiate(withOwner: self)[0] as? MyView {
// Do something with myView
}

Este ejemplo utiliza la primera vista en la plumilla "MyView.xib" en el paquete principal. Pero puede variar el índice, el nombre de la punta o el paquete (principal de forma predeterminada).

Solía ​​despertar vistas en el método de inicio de vista o hacer métodos genéricos como en las soluciones anteriores (que por cierto son inteligentes), pero ya no lo hago.

De esta manera puedo usar diferentes diseños o rasgos manteniendo la misma vista lógica y código.

Me resulta más fácil dejar que un objeto de fábrica (generalmente el viewController que usará la vista) lo cree como lo necesita. A veces necesita un propietario (por lo general, cuando la vista creada tiene una salida conectada al creador), a veces no ...

Probablemente por eso Apple no incluyó un initFromNibmétodo en su clase UIView ...

Para tomar un ejemplo a nivel del suelo, no sabes cómo naces. Acabas de nacer. Así son las vistas;)

Alce
fuente
2

Todo lo que tiene que hacer es llamar al método init en su UIViewclase.

Hazlo de esa manera:

class className: UIView {

    @IBOutlet var view: UIView!

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

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

    func setup() {
        UINib(nibName: "nib", bundle: nil).instantiateWithOwner(self, options: nil)
        addSubview(view)
        view.frame = self.bounds
    }
}

Ahora, si desea agregar esta vista como una vista secundaria en el controlador de vista, hágalo de esa manera en el archivo view controller.swift:

self.view.addSubview(className())
Alap Anerao
fuente
es una gran respuesta pero hay algo mal, lo editaré.
C0mrade
Es la forma en que lo implementé. Pero puedes improvisarlo. Gracias de antemano @ C0mrade
Alap Anerao
1

Similar a algunas de las respuestas anteriores pero con una extensión más consistente de Swift3 UIView:

extension UIView {
    class func fromNib<A: UIView> (nibName name: String, bundle: Bundle? = nil) -> A? {
        let bundle = bundle ?? Bundle.main
        let nibViews = bundle.loadNibNamed(name, owner: self, options: nil)
        return nibViews?.first as? A
    }

    class func fromNib<T: UIView>() -> T? {
        return fromNib(nibName: String(describing: T.self), bundle: nil)
    }
}

Lo que brinda la conveniencia de poder cargar la clase desde una plumilla autodenominada, pero también desde otras plumillas / paquetes.

Mark Johnson
fuente
1

Puede hacerlo a través del guión gráfico, solo agregue las restricciones adecuadas para la vista. Puede hacerlo fácilmente subclasificando cualquier vista de su propia, digamos BaseView:

C objetivo

BaseView.h


/*!
 @class BaseView
 @discussion Base View for getting view from xibFile
 @availability ios7 and later
 */
@interface BaseView : UIView

@end


BaseView.m


#import "BaseView.h"

@implementation BaseView

#pragma mark - Public

- (instancetype)initWithCoder:(NSCoder *)coder
{
    self = [super initWithCoder:coder];
    if (self) {
        [self prepareView];
    }
    return self;
}

#pragma mark - LifeCycle

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

#pragma mark - Private

- (void)prepareView
{
    NSArray *nibsArray = [[NSBundle mainBundle] loadNibNamed:NSStringFromClass([self class]) owner:self options:nil];
    UIView *view = [nibsArray firstObject];

    view.translatesAutoresizingMaskIntoConstraints = NO;
    [self addSubview:view];
    [self addConstraintsForView:view];
}

#pragma mark - Add constraints

- (void)addConstraintsForView:(UIView *)view
{
    [self addConstraints:@[[NSLayoutConstraint constraintWithItem:view
                                                        attribute:NSLayoutAttributeBottom
                                                        relatedBy:NSLayoutRelationEqual
                                                           toItem:self attribute:NSLayoutAttributeBottom
                                                       multiplier:1.0
                                                         constant:0],
                           [NSLayoutConstraint constraintWithItem:view
                                                        attribute:NSLayoutAttributeTop
                                                        relatedBy:NSLayoutRelationEqual
                                                           toItem:self attribute:NSLayoutAttributeTop
                                                       multiplier:1.0
                                                         constant:0],
                           [NSLayoutConstraint constraintWithItem:view
                                                        attribute:NSLayoutAttributeLeft
                                                        relatedBy:NSLayoutRelationEqual
                                                           toItem:self attribute:NSLayoutAttributeLeft
                                                       multiplier:1.0
                                                         constant:0],
                           [NSLayoutConstraint constraintWithItem:view
                                                        attribute:NSLayoutAttributeRight
                                                        relatedBy:NSLayoutRelationEqual
                                                           toItem:self attribute:NSLayoutAttributeRight
                                                       multiplier:1.0
                                                         constant:0]
                           ]];
}

@end

Swift 4

import UIKit

class BaseView : UIView {

    // MARK: - LifeCycle

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

        prepareView()
    }

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

        prepareView()
    }

    internal class func xibName() -> String {
        return String(describing: self)
    }

    // MARK: - Private
    fileprivate func prepareView() {
        let nameForXib = BaseView.xibName()
        let nibs = Bundle.main.loadNibNamed(nameForXib, owner: self, options: nil)
        if let view = nibs?.first as? UIView {
            view.backgroundColor = UIColor.clear
            view.translatesAutoresizingMaskIntoConstraints = false
            addSubviewWithConstraints(view, offset: false)
        }
    }
}

UIView+Subview


public extension UIView {
    // MARK: - UIView+Extensions

    public func addSubviewWithConstraints(_ subview:UIView, offset:Bool = true) {
        subview.translatesAutoresizingMaskIntoConstraints = false
        let views = [
            "subview" : subview
        ]
        addSubview(subview)

        var constraints = NSLayoutConstraint.constraints(withVisualFormat: offset ? "H:|-[subview]-|" : "H:|[subview]|", options: [.alignAllLeading, .alignAllTrailing], metrics: nil, views: views)
        constraints.append(contentsOf: NSLayoutConstraint.constraints(withVisualFormat: offset ? "V:|-[subview]-|" : "V:|[subview]|", options: [.alignAllTop, .alignAllBottom], metrics: nil, views: views))
        NSLayoutConstraint.activate(constraints)
    }
}

Proporciono 2 variantes sobre cómo agregar restricciones, una común y dentro del lenguaje de formato visual, seleccione la que desee :)

Además, de forma predeterminada se supone que el xibnombre tiene el mismo nombre que el nombre de la clase de implementación. Si no, simplemente cambiexibName parámetro.

Si subclasifica su vista desde BaseView, puede poner fácilmente cualquier vista y especificar la clase en IB.

gbk
fuente
1
class func loadFromNib<T: UIView>() -> T {
    let nibName = String(describing: self)
    return Bundle.main.loadNibNamed(nibName, owner: nil, options: nil)![0] as! T
}
Nadzeya
fuente
0

Versión más potente basada en la respuesta de Logan

extension UIView {
    public class func fromNib(nibName: String? = nil) -> Self {
        return fromNib(nibName: nibName, type: self)
    }

    public class func fromNib<T: UIView>(nibName: String? = nil, type: T.Type) -> T {
        return fromNib(nibName: nibName, type: T.self)!
    }

    public class func fromNib<T: UIView>(nibName: String? = nil, type: T.Type) -> T? {
        var view: T?
        let name: String

        if let nibName = nibName {
            name = nibName
        } else {
            name = self.nibName
        }

        if let nibViews = nibBundle.loadNibNamed(name, owner: nil, options: nil) {
            if nibViews.indices.contains(nibIndex), let tog = nibViews[nibIndex] as? T {
                view = tog
            }
        }

        return view
    }

    public class var nibName: String {
        return "\(self)".components(separatedBy: ".").first ?? ""
    }

    public class var nibIndex: Int {
        return 0
    }

    public class var nibBundle: Bundle {
        return Bundle.main
    }
}

Y puedes usar como

class BaseView: UIView {
    override class var nibName: String { return "BaseView" }
    weak var delegate: StandardStateViewDelegate?
}

class ChildView: BaseView {
    override class var nibIndex: Int { return 1 }
}
usuario2790103
fuente
0

La implementación más conveniente. Aquí necesita dos métodos para regresar directamente al objeto de su clase, no a UIView.

  1. viewId marcado como una clase , lo que permite anular
  2. Su .xib puede contener más de una vista del nivel superior, esta situación también se maneja correctamente.

extension UIView {

class var viewId: String {
    return String(describing: self)
}

static func instance(from bundle: Bundle? = nil, nibName: String? = nil,
                    owner: Any? = nil, options: [AnyHashable : Any]? = nil) -> Self? {

    return instancePrivate(from: bundle ?? Bundle.main,
                           nibName: nibName ?? viewId,
                           owner: owner,
                           options: options)
}

private static func instancePrivate<T: UIView>(from bundle: Bundle, nibName: String,
                                              owner: Any?, options: [AnyHashable : Any]?) -> T? {

    guard
        let views = bundle.loadNibNamed(nibName, owner: owner, options: options),
        let view = views.first(where: { $0 is T }) as? T else { return nil }

    return view
}
}

Ejemplo:

guard let customView = CustomView.instance() else { return }

//Here customView has CustomView class type, not UIView.
print(customView is CustomView) // true
SeRG1k
fuente
0

Si desea que la subclase Swift UIView sea completamente autónoma y tenga la capacidad de instanciarse usando init o init (frame :) sin exponer los detalles de implementación de usar un Nib, entonces puede usar una extensión de protocolo para lograr esto. Esta solución evita la jerarquía de UIView anidada como lo sugieren muchas de las otras soluciones.

public class CustomView: UIView {

    @IBOutlet weak var nameLabel: UILabel!
    @IBOutlet weak var valueLabel: UILabel!

    public convenience init() {
        self.init(frame: CGRect.zero)
    }

    public override convenience init(frame: CGRect) {
        self.init(internal: nil)
        self.frame = frame
    }

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

    fileprivate func commonInit() {
    }
}

fileprivate protocol _CustomView {
}

extension CustomView: _CustomView {
}

fileprivate extension _CustomView {

    // Protocol extension initializer - has the ability to assign to self, unlike
    // class initializers. Note that the name of this initializer can be anything
    // you like, here we've called it init(internal:)

    init(internal: Int?) {
        self = Bundle.main.loadNibNamed("CustomView", owner:nil, options:nil)![0] as! Self;
    }
}
appfigurate
fuente
0
  let bundle = Bundle(for: type(of: self))
   let views = bundle.loadNibNamed("template", owner: self, options: nil)
    self.view.addSubview(views?[0] as! UIView)
Divesh singh
fuente
Las respuestas de solo código se desaconsejan. Agregue alguna explicación sobre cómo esto resuelve el problema o cómo difiere de las respuestas existentes. De la opinión
Nick
0

Prefiero la extensión a continuación

extension UIView {
    class var instanceFromNib: Self {
        return Bundle(for: Self.self)
            .loadNibNamed(String(describing: Self.self), owner: nil, options: nil)?.first as! Self
    }
}

La diferencia entre esta y la extensión con la respuesta más alta es que no necesita almacenarla de forma constante o variable.

class TitleView: UIView { }

extension UIView {
    class var instanceFromNib: Self {
        return Bundle(for: Self.self)
            .loadNibNamed(String(describing: Self.self), owner: nil, options: nil)?.first as! Self
    }
}

self.navigationItem.titleView = TitleView.instanceFromNib
iCoder
fuente
¿Qué versión de Xcode estás usando? Asegúrese de estar utilizando la última versión de XCode. Funciona bien para mí con XCode 11.5 (última versión como en la fecha).
iCoder
0
    let nibs = Bundle.main.loadNibNamed("YourView", owner: nil, options: nil)
    let shareView = nibs![0] as! ShareView
    self.view.addSubview(shareView)
Pratik
fuente