Cómo inicializar / instanciar una clase UIView personalizada con un archivo XIB en Swift

139

Tengo una clase llamada MyClassque es una subclase de UIView, que quiero inicializar con un XIBarchivo. No estoy seguro de cómo inicializar esta clase con el archivo xib llamadoView.xib

class MyClass: UIView {

    // what should I do here? 
    //init(coder aDecoder: NSCoder) {} ?? 
}
Stephen Fox
fuente
55
consulte el ejemplo de código fuente completo para iOS9 Swift 2.0 github.com/karthikprabhuA/CustomXIBSwift y el hilo relacionado stackoverflow.com/questions/24857986/…
karthikPrabhu Alagu

Respuestas:

266

Probé este código y funciona muy bien:

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

Inicialice la vista y úsela como a continuación:

var view = MyClass.instanceFromNib()
self.view.addSubview(view)

O

var view = MyClass.instanceFromNib
self.view.addSubview(view())

ACTUALIZACIÓN Swift> = 3.xy Swift> = 4.x

class func instanceFromNib() -> UIView {
    return UINib(nibName: "nib file name", bundle: nil).instantiate(withOwner: nil, options: nil)[0] as! UIView
}
Ezimet
fuente
1
Debería ser var view = MyClass.instanceFromNib()& self.view.addSubview(view)en oposición a var view = MyClass.instanceFromNib& self.view.addSubview(view()). Solo una pequeña sugerencia para mejorar la respuesta :)
Logan
1
En mi caso, la vista se inicializó más tarde. si es self.view.addSubview (view), view debería ser var view = MyClass.instanceFromNib ()
Ezimet
2
@Ezimet, ¿qué pasa con las IBActions dentro de esa vista? donde manejarlos. Como si tuviera un botón en mi vista (xib), ¿cómo manejar el evento de clic IBAction de ese botón?
Qadir Hussain
66
¿Debería devolver "MyClass" en lugar de solo UIView?
Kesong Xie
2
Los IBoutlets no funcionan en este enfoque ... Estoy obteniendo: "esta clase no cumple con la codificación de valores clave para la clave"
Radek Wilczak
82

La solución de Sam ya es excelente, a pesar de que no tiene en cuenta diferentes paquetes (NSBundle: forClass viene al rescate) y requiere una carga manual, también conocido como código de mecanografía.

Si desea soporte completo para sus tomas Xib, diferentes paquetes (¡use en marcos!) Y obtenga una buena vista previa en Storyboard intente esto:

// NibLoadingView.swift
import UIKit

/* Usage: 
- Subclass your UIView from NibLoadView to automatically load an Xib with the same name as your class
- Set the class name to File's Owner in the Xib file
*/

@IBDesignable
class NibLoadingView: UIView {

    @IBOutlet weak var view: UIView!

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

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

    private func nibSetup() {
        backgroundColor = .clearColor()

        view = loadViewFromNib()
        view.frame = bounds
        view.autoresizingMask = [.FlexibleWidth, .FlexibleHeight]
        view.translatesAutoresizingMaskIntoConstraints = true

        addSubview(view)
    }

    private func loadViewFromNib() -> UIView {
        let bundle = NSBundle(forClass: self.dynamicType)
        let nib = UINib(nibName: String(self.dynamicType), bundle: bundle)
        let nibView = nib.instantiateWithOwner(self, options: nil).first as! UIView

        return nibView
    }

}

Use su xib como de costumbre, es decir, conecte los tomacorrientes al propietario del archivo y establezca la clase del propietario del archivo en su propia clase.

Uso: simplemente subclasifique su propia clase View desde NibLoadingView y configure el nombre de la clase como Propietario del archivo en el archivo Xib

No se requiere código adicional más.

Créditos donde vence el crédito: Bifurcó esto con cambios menores de DenHeadless en GH. Mi esencia: https://gist.github.com/winkelsdorf/16c481f274134718946328b6e2c9a4d8

Frederik Winkelsdorf
fuente
8
Esta solución frena las conexiones de salida (porque agrega la vista cargada como una subvista) y la llamada nibSetupdesde init?(coder:)causará una recursión infinita si se incrusta NibLoadingViewen un XIB.
redent84
3
@ redent84 Gracias por tu comentario y el voto negativo. Si tiene un segundo aspecto, debe reemplazar el SubView anterior (no se ha proporcionado una nueva variable de instancia). Tiene razón acerca de la recursión infinita, que debe omitirse si tiene problemas con IB.
Frederik Winkelsdorf
1
Como se mencionó, "conecte las tomas al propietario del archivo y establezca la clase del propietario del archivo en su propia clase". conecte las salidas al propietario del archivo
Yusuf X
1
Siempre me incomoda usar este método para cargar la vista desde xib. Básicamente estamos agregando una vista que es una subclase de Clase A, en una vista que también es una subclase de Clase A. ¿No hay alguna forma de evitar esta iteración?
Prajeet Shrestha
1
@PrajeetShrestha Eso probablemente se deba a que nibSetup () reemplaza el color de fondo .clearColor()después de cargarlo desde el guión gráfico. Pero debería funcionar si lo hace por código después de que se instancia. De todos modos, como se dijo, un enfoque aún más elegante es el basado en el protocolo. Así que seguro, aquí hay un enlace para usted: github.com/AliSoftware/Reusable . Estoy usando un enfoque similar ahora con respecto a UITableViewCells (que implementé antes de descubrir ese proyecto realmente útil). hth!
Frederik Winkelsdorf el
77

A partir de Swift 2.0, puede agregar una extensión de protocolo. En mi opinión, este es un mejor enfoque porque el tipo de retorno es Selfmás que UIView, por lo que la persona que llama no necesita emitir a la clase de vista.

import UIKit

protocol UIViewLoading {}
extension UIView : UIViewLoading {}

extension UIViewLoading where Self : UIView {

  // note that this method returns an instance of type `Self`, rather than UIView
  static func loadFromNib() -> Self {
    let nibName = "\(self)".characters.split{$0 == "."}.map(String.init).last!
    let nib = UINib(nibName: nibName, bundle: nil)
    return nib.instantiateWithOwner(self, options: nil).first as! Self
  }

}
Sam
fuente
44
Esta es una mejor solución que la respuesta seleccionada, ya que no hay necesidad de emitir y también será reutilizable en cualquier otra subclase de UIView que cree en el futuro.
user3344977
3
Intenté esto con Swift 2.1 y Xcode 7.2.1. Funcionó algunas veces y colgaría impredeciblemente a otros con un bloqueo de exclusión mutua. Las dos últimas líneas utilizadas directamente en el código funcionaron cada vez con la última línea modificada para servar myView = nib.instantiate... as! myViewType
Paul Linsay
@ jr-root-cs Su edición contenía errores tipográficos / errores, tuve que revertirla. Y de todos modos, no agregue código a las respuestas existentes. En cambio, haga un comentario; o agregue su versión en su propia respuesta . Gracias.
Eric Aya
Publiqué el código que había abierto y probado en mi proyecto usando Swift 3(XCode 8.0 beta 6) sin problemas. El error tipográfico estaba adentro Swift 2. ¿Por qué debería ser otra respuesta cuando esta respuesta es buena y los usuarios probablemente quieran buscar cuáles son los cambios cuando usarán XC8
Jr.root.cs
1
@ jr.root.cs Sí, esta respuesta es buena, por eso nadie debería alterarla. Esta es la respuesta de Sam, no la tuya. Si quieres comentarlo, deja un comentario; si desea publicar una versión nueva / actualizada, hágalo en su propia publicación . Las ediciones están destinadas a corregir errores tipográficos / sangrías / etiquetas para no agregar sus versiones en las publicaciones de otras personas. Gracias.
Eric Aya
37

Y esta es la respuesta de Frederik en Swift 3.0

/*
 Usage:
 - make your CustomeView class and inherit from this one
 - in your Xib file make the file owner is your CustomeView class
 - *Important* the root view in your Xib file must be of type UIView
 - link all outlets to the file owner
 */
@IBDesignable
class NibLoadingView: UIView {

    @IBOutlet weak var view: UIView!

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

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

    private func nibSetup() {
        backgroundColor = .clear

        view = loadViewFromNib()
        view.frame = bounds
        view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        view.translatesAutoresizingMaskIntoConstraints = true

        addSubview(view)
    }

    private func loadViewFromNib() -> UIView {
        let bundle = Bundle(for: type(of: self))
        let nib = UINib(nibName: String(describing: type(of: self)), bundle: bundle)
        let nibView = nib.instantiate(withOwner: self, options: nil).first as! UIView

        return nibView
    }
}
Mahmood S
fuente
31

Manera universal de cargar la vista desde xib:

Ejemplo:

let myView = Bundle.loadView(fromNib: "MyView", withType: MyView.self)

Implementación:

extension Bundle {

    static func loadView<T>(fromNib name: String, withType type: T.Type) -> T {
        if let view = Bundle.main.loadNibNamed(name, owner: nil, options: nil)?.first as? T {
            return view
        }

        fatalError("Could not load view with type " + String(describing: type))
    }
}
Максим Мартынов
fuente
La mejor respuesta para mí como la vista de salida como el Tipo de la subclase
UIView
26

Respuesta de Swift 3: en mi caso, quería tener una salida en mi clase personalizada que pudiera modificar:

class MyClassView: UIView {
    @IBOutlet weak var myLabel: UILabel!

    class func createMyClassView() -> MyClass {
        let myClassNib = UINib(nibName: "MyClass", bundle: nil)
        return myClassNib.instantiate(withOwner: nil, options: nil)[0] as! MyClassView
    }
}

Cuando esté en .xib, asegúrese de que el campo Clase personalizada sea MyClassView. No te molestes con el propietario del archivo.

Asegúrese de que la clase personalizada sea MyClassView

Además, asegúrese de conectar la salida en MyClassView a la etiqueta: Salida para myLabel

Para instanciarlo:

let myClassView = MyClassView.createMyClassView()
myClassView.myLabel.text = "Hello World!"
Jeremy
fuente
Vuelvo "cargué la plumilla" MyClas "pero el punto de vista no estaba configurado". Si el propietario no está configurado
jcharch
22

Swift 4

Aquí, en mi caso, tengo que pasar datos a esa vista personalizada, por lo que creo una función estática para instanciar la vista.

  1. Crear extensión UIView

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

    class MyCustomView: UIView {
    
        @IBOutlet weak var messageLabel: UILabel!
    
        static func instantiate(message: String) -> MyCustomView {
            let view: MyCustomView = initFromNib()
            view.messageLabel.text = message
            return view
        }
    }
  3. Establezca la clase personalizada en MyCustomView en el archivo .xib. Conecte el tomacorriente si es necesario como de costumbre. ingrese la descripción de la imagen aquí

  4. Vista de instancia

    let view = MyCustomView.instantiate(message: "Hello World.")
mnemónico23
fuente
Si hay botones en la vista personalizada, ¿cómo podemos manejar sus acciones en diferentes controladores de vista?
jayant rawat
puedes usar delegado de protocolo. echa un vistazo aquí stackoverflow.com/questions/29602612/… .
mnemonic23
No se pudo cargar una imagen en xib, obteniendo el siguiente error. No se pudo cargar la imagen " IBBrokenImage " referenciada desde una punta en el paquete con el identificador "test.Testing"
Ravi Raja Jangid
-4
override func draw(_ rect: CGRect) 
{
    AlertView.layer.cornerRadius = 4
    AlertView.clipsToBounds = true

    btnOk.layer.cornerRadius = 4
    btnOk.clipsToBounds = true   
}

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

@IBAction func okBtnDidClicked(_ sender: Any) {

    removeAlertViewFromWindow()

    UIView.animate(withDuration: 0.4, delay: 0.0, options: .allowAnimatedContent, animations: {() -> Void in
        self.AlertView.transform = CGAffineTransform(scaleX: 0.1, y: 0.1)

    }, completion: {(finished: Bool) -> Void in
        self.AlertView.transform = CGAffineTransform.identity
        self.AlertView.transform = CGAffineTransform(scaleX: 0.0, y: 0.0)
        self.AlertView.isHidden = true
        self.AlertView.alpha = 0.0

        self.alpha = 0.5
    })
}


func removeAlertViewFromWindow()
{
    for subview  in (appDel.window?.subviews)! {
        if subview.tag == 500500{
            subview.removeFromSuperview()
        }
    }
}


public func openAlertView(title:String , string : String ){

    lblTital.text  = title
    txtView.text  = string

    self.frame = CGRect(x: 0, y: 0, width: screenWidth, height: screenHeight)
    appDel.window!.addSubview(self)


    AlertView.alpha = 1.0
    AlertView.isHidden = false

    UIView.animate(withDuration: 0.2, animations: {() -> Void in
        self.alpha = 1.0
    })
    AlertView.transform = CGAffineTransform(scaleX: 0.0, y: 0.0)

    UIView.animate(withDuration: 0.3, delay: 0.2, options: .allowAnimatedContent, animations: {() -> Void in
        self.AlertView.transform = CGAffineTransform(scaleX: 1.1, y: 1.1)

    }, completion: {(finished: Bool) -> Void in
        UIView.animate(withDuration: 0.2, animations: {() -> Void in
            self.AlertView.transform = CGAffineTransform(scaleX: 1.0, y: 1.0)

        })
    })


}
Madan Kumawat
fuente
código con formato incorrecto, sin explicación, de ahí el voto negativo.
J. Doe
¿Qué tiene que ver todo esto con la pregunta?
Ashley Mills