Swift subclase UIView

95

Quiero crear UIViewuna subclase y mostrar una vista de inicio de sesión. Creé esto en Objective-C, pero ahora quiero portarlo a Swift. No uso guiones gráficos, así que creo toda mi interfaz de usuario en código.

Pero el primer problema es que debo implementar initWithCoder. Le di una implementación predeterminada ya que no se llamará. Ahora, cuando ejecuto el programa, se bloquea, porque también tengo que implementarlo initWithFrame. Ahora tengo esto:

override init() {
    super.init()
    println("Default init")
}

override init(frame: CGRect) {
    super.init(frame: frame)
    println("Frame init")
}

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

Mi pregunta es dónde debería crear mi campo de texto, etc. y si nunca implemento el marco y el codificador, ¿cómo puedo "ocultar" esto?

Haagenti
fuente

Respuestas:

174

Normalmente hago algo como esto, es un poco detallado.

class MyView: UIView {
    override init(frame: CGRect) {
        super.init(frame: frame)
        addBehavior()
    }

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

    required init(coder aDecoder: NSCoder) {
        fatalError("This class does not support NSCoding")
    }

    func addBehavior() {
        print("Add all the behavior here")
    }
}



let u = MyView(frame: CGRect.zero)
let v = MyView()

(Editar: he editado mi respuesta para que la relación entre los inicializadores sea más clara)

Raymond
fuente
4
Pero addBehavior se llama dos veces desde que se llama a initFrame e init. Si ejecuta mi código, se imprime el primer cuadro de inicialización, entonces se imprime el init predeterminado
Haagenti
6
Buen material, gracias. En lugar de usar CGRectZero, creo que se recomienda usar CGRect.zeroRect.
Mr Rogers
57
Este asunto del inicializador es muy complicado.
Ian Warburton
3
¿Hay alguna forma de hacer esto para el diseño automático? El marco está tan desactualizado.
devios1
8
Ésta no es una respuesta completa. UIView admite initWithCoding. Cualquier vista cargada desde una plumilla o guión gráfico llamará al método initWithCoding y se bloqueará.
Iain Delaney
17

Esto es más simple.

override init (frame : CGRect) {
    super.init(frame : frame)
    // Do what you want.
}

required init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
}
Jeff Gu Kang
fuente
10

Ejemplo personalizado de subclase de UIView

Normalmente creo aplicaciones de iOS sin usar guiones gráficos o plumillas. Compartiré algunas técnicas que he aprendido para responder a sus preguntas.

 

Ocultar initmétodos no deseados

Mi primera sugerencia es declarar una base UIViewpara ocultar inicializadores no deseados. He discutido este enfoque en detalle en mi respuesta a "Cómo ocultar los inicializadores específicos del guión gráfico y la punta en las subclases de la interfaz de usuario" . Nota: Este enfoque asume que no usará BaseViewo sus descendientes en guiones gráficos o plumillas, ya que provocará intencionalmente que la aplicación se bloquee.

class BaseView: UIView {

    // This initializer hides init(frame:) from subclasses
    init() {
        super.init(frame: CGRect.zero)
    }

    // This attribute hides `init(coder:)` from subclasses
    @available(*, unavailable)
    required init?(coder aDecoder: NSCoder) {
        fatalError("NSCoding not supported")
    }
}

Su subclase personalizada de UIView debe heredar BaseView. Debe llamar a super.init () en su inicializador. No es necesario implementarlo init(coder:). Esto se demuestra en el ejemplo siguiente.

 

Agregar un UITextField

Creo propiedades almacenadas para subvistas a las que se hace referencia fuera del initmétodo. Normalmente lo haría para un UITextField. Yo prefiero subvistas crear una instancia dentro de la declaración de la propiedad subvista como esto: let textField = UITextField().

UITextField no será visible a menos que lo agregue a la lista de subvista de la vista personalizada llamando a addSubview(_:). Esto se demuestra en el ejemplo siguiente.

 

Diseño programático sin diseño automático

UITextField no será visible a menos que establezca su tamaño y posición. A menudo hago el diseño en código (sin usar Auto Layout) dentro del método layoutSubviews . layoutSubviews()se llama inicialmente y siempre que ocurre un evento de cambio de tamaño. Esto permite ajustar el diseño según el tamaño de CustomView. Por ejemplo, si CustomView aparece en el ancho completo en varios tamaños de iPhones y iPads y se ajusta para la rotación, debe adaptarse a muchos tamaños iniciales y cambiar el tamaño de forma dinámica.

Puede consultar frame.heighty frame.widthdentro layoutSubviews()para obtener las dimensiones de CustomView como referencia. Esto se demuestra en el ejemplo siguiente.

 

Ejemplo de subclase de UIView

Una subclase de UIView personalizada que contiene un UITextField que no es necesario implementar init?(coder:).

class CustomView: BaseView {

    let textField = UITextField()

    override init() {
        super.init()

        // configure and add textField as subview
        textField.placeholder = "placeholder text"
        textField.font = UIFont.systemFont(ofSize: 12)
        addSubview(textField)
    }

    override func layoutSubviews() {
        super.layoutSubviews()

        // Set textField size and position
        textField.frame.size = CGSize(width: frame.width - 20, height: 30)
        textField.frame.origin = CGPoint(x: 10, y: 10)
    }
}

 

Diseño programático con diseño automático

También puede implementar el diseño utilizando el diseño automático en el código. Como no hago esto a menudo, no mostraré un ejemplo. Puede encontrar ejemplos de implementación de diseño automático en código en Stack Overflow y en otros lugares de Internet.

 

Marcos de diseño programático

Existen marcos de código abierto que implementan el diseño en código. Uno que me interesa pero que no he probado es LayoutKit . Fue escrito por el equipo de desarrollo de LinkedIn. Desde el repositorio de Github: "LinkedIn creó LayoutKit porque descubrimos que el diseño automático no tiene el rendimiento suficiente para jerarquías de vista complicadas en vistas desplazables".

 

¿Por qué poner fatalErroreninit(coder:)

Al crear subclases de UIView que nunca se usarán en un guión gráfico o plumilla, puede introducir inicializadores con diferentes parámetros y requisitos de inicialización que el init(coder:)método no podría llamar . Si no falló init (codificador :) con a fatalError, podría generar problemas muy confusos en el futuro si se usa accidentalmente en un guión gráfico / nib. El fatalError afirma estas intenciones.

required init?(coder aDecoder: NSCoder) {
    fatalError("NSCoding not supported")
}

Si desea ejecutar algún código cuando se crea la subclase, independientemente de si se crea en código o en un guión gráfico / plumilla, puede hacer algo como lo siguiente (según la respuesta de Jeff Gu Kang )

class CustomView: UIView {
    override init (frame: CGRect) {
        super.init(frame: frame)
        initCommon()
    }

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

    func initCommon() {
        // Your custom initialization code
    }
}
Dan móvil
fuente
¿y? al agregar fatalError, prohíbe iniciar esta vista con archivos xib
Vyachaslav Gerchicov
@VyachaslavGerchicov, Mi respuesta establece la suposición de que no está utilizando xibs o guiones gráficos como lo hace la respuesta aceptada y la pregunta. La pregunta señala "No uso guiones gráficos, así que creo toda mi interfaz de usuario en código".
Móvil Dan
La próxima vez que escriba fatalErrordentro del método dealloc y nos diga que no funciona porque esa clase debería ser un singleton. Si prefiere crear elementos de IU en código, no debería prohibir manualmente todas las demás formas. Finalmente, la pregunta es cómo crear "programáticamente sin guiones gráficos", pero no se mencionan xibs / nibs. En mi caso, necesito crear una matriz de celdas con programáticamente + xib y pasarlas, DropDownMenuKity de esta manera no funciona porque el autor de esta biblioteca también prohíbe xibs.
Vyachaslav Gerchicov
@VyachaslavGerchicov Parece que la respuesta de Jeff Gu Kang es lo que está buscando, ya que se adapta a Storyboards / Xibs
Mobile Dan
1
@VyachaslavGerchicov La pregunta también decía "y si nunca implemento el marco y el codificador, ¿cómo puedo" ocultar "esto?" Al crear subclases de UIView que nunca se usarán en un Xib / Storyboard, puede introducir inicializadores con diferentes parámetros que no podrían ser llamados por el método init (coder :). Si no falló init (coder :) con un fatalError, podría generar problemas muy confusos en el futuro si se usa accidentalmente en un Xib / Storyboard. El error fatal declara estas intenciones. Esta es una práctica intencional y común como se ve en la respuesta aceptada.
Móvil Dan
4

Es importante que su UIView pueda crearse mediante el constructor de interfaces / guiones gráficos o desde el código. Encuentro que es útil tener un setupmétodo para reducir la duplicación de cualquier código de configuración. p.ej

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

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

    func setup () {
        backgroundColor = .red
    }
}
Jason Moore
fuente
4

Swift 4.0, si desea utilizar la vista desde el archivo xib, entonces esto es para usted. Creé la clase Subclase CustomCalloutView de UIView. He creado un archivo xib y en IB simplemente seleccione el propietario del archivo, luego seleccione el nombre de clase del conjunto del inspector de atributos en CustomCalloutView, luego cree una salida en su clase.

    import UIKit
    class CustomCalloutView: UIView {

        @IBOutlet var viewCallout: UIView! // This is main view

        @IBOutlet weak var btnCall: UIButton! // subview of viewCallout
        @IBOutlet weak var btnDirection: UIButton! // subview of viewCallout
        @IBOutlet weak var btnFavourite: UIButton! // subview of viewCallout 

       // let nibName = "CustomCalloutView" this is name of xib file

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

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

        func nibSetup() {
            Bundle.main.loadNibNamed(String(describing: CustomCalloutView.self), owner: self, options: nil)
            guard let contentView = viewCallout else { return } // adding main view 
            contentView.frame = self.bounds //Comment this line it take default frame of nib view
           // custom your view properties here
            self.addSubview(contentView)
        }
    }

// Ahora agregándolo

    let viewCustom = CustomCalloutView.init(frame: CGRect.init(x: 120, y: 120, 50, height: 50))
    self.view.addSubview(viewCustom)
Gurjinder Singh
fuente
-1

Aquí hay un ejemplo de cómo suelo construir mis subclases (UIView). Tengo el contenido como variables para que se pueda acceder a ellas y modificarlas tal vez más tarde en alguna otra clase. También he mostrado cómo utilizo el diseño automático y agrego contenido.

Por ejemplo, en un ViewController tengo esta vista inicializada en ViewDidLoad () ya que solo se llama una vez cuando la vista es visible. Luego uso de estas funciones que hago aquí addContentToView()y después activateConstraints()de construir el contenido y las limitaciones establecidas. Si más tarde en un ViewController quiero que el color de, digamos, un botón sea rojo, simplemente lo hago en esa función específica en ese ViewController. Algo como:func tweaksome(){ self.customView.someButton.color = UIColor.red}

class SomeView: UIView {


var leading: NSLayoutConstraint!
var trailing: NSLayoutConstraint!
var bottom: NSLayoutConstraint!
var height: NSLayoutConstraint!


var someButton: UIButton = {
    var btn: UIButton = UIButton(type: UIButtonType.system)
    btn.setImage(UIImage(named: "someImage"), for: .normal)
    btn.translatesAutoresizingMaskIntoConstraints = false
    return btn
}()

var btnLeading: NSLayoutConstraint!
var btnBottom: NSLayoutConstraint!
var btnTop: NSLayoutConstraint!
var btnWidth: NSLayoutConstraint!

var textfield: UITextField = {
    var tf: UITextField = UITextField()
    tf.adjustsFontSizeToFitWidth = true
    tf.placeholder = "Cool placeholder"
    tf.translatesAutoresizingMaskIntoConstraints = false
    tf.backgroundColor = UIColor.white
    tf.textColor = UIColor.black
    return tf
}()
var txtfieldLeading: NSLayoutConstraint!
var txtfieldTrailing: NSLayoutConstraint!
var txtfieldCenterY: NSLayoutConstraint!

override init(frame: CGRect){
    super.init(frame: frame)
    self.translatesAutoresizingMaskIntoConstraints = false
}

required init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
    //fatalError("init(coder:) has not been implemented")
}



/*
// Only override draw() if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
override func draw(_ rect: CGRect) {
    // Drawing code

}
*/
func activateConstraints(){
    NSLayoutConstraint.activate([self.btnLeading, self.btnBottom, self.btnTop, self.btnWidth])
    NSLayoutConstraint.activate([self.txtfieldCenterY, self.txtfieldLeading, self.txtfieldTrailing])
}

func addContentToView(){
    //setting the sizes
    self.addSubview(self.userLocationBtn)

    self.btnLeading = NSLayoutConstraint(
        item: someButton,
        attribute: .leading,
        relatedBy: .equal,
        toItem: self,
        attribute: .leading,
        multiplier: 1.0,
        constant: 5.0)
    self.btnBottom = NSLayoutConstraint(
        item: someButton,
        attribute: .bottom,
        relatedBy: .equal,
        toItem: self,
        attribute: .bottom,
        multiplier: 1.0,
        constant: 0.0)
    self.btnTop = NSLayoutConstraint(
        item: someButton,
        attribute: .top,
        relatedBy: .equal,
        toItem: self,
        attribute: .top,
        multiplier: 1.0,
        constant: 0.0)
    self.btnWidth = NSLayoutConstraint(
        item: someButton,
        attribute: .width,
        relatedBy: .equal,
        toItem: self,
        attribute: .height,
        multiplier: 1.0,
        constant: 0.0)        


    self.addSubview(self.textfield)
    self.txtfieldLeading = NSLayoutConstraint(
        item: self.textfield,
        attribute: .leading,
        relatedBy: .equal,
        toItem: someButton,
        attribute: .trailing,
        multiplier: 1.0,
        constant: 5)
    self.txtfieldTrailing = NSLayoutConstraint(
        item: self.textfield,
        attribute: .trailing,
        relatedBy: .equal,
        toItem: self.doneButton,
        attribute: .leading,
        multiplier: 1.0,
        constant: -5)
    self.txtfieldCenterY = NSLayoutConstraint(
        item: self.textfield,
        attribute: .centerY,
        relatedBy: .equal,
        toItem: self,
        attribute: .centerY,
        multiplier: 1.0,
        constant: 0.0)
}
}
Starlord
fuente