¿Cómo escribo un init personalizado para una subclase de UIView en Swift?

125

Digamos que quiero inituna UIViewsubclase con ay Stringan Int.

¿Cómo haría esto en Swift si solo estoy subclasificando UIView? Si solo hago una init()función personalizada pero los parámetros son una Cadena y una Int, me dice que "no se llama a super.init () antes de regresar del inicializador".

Y si llamo super.init()me dicen que debo usar un inicializador designado. ¿Qué debería estar usando allí? La versión del marco? La versión del codificador? ¿Ambos? ¿Por qué?

Doug Smith
fuente

Respuestas:

207

La init(frame:)versión es el inicializador predeterminado. Debe llamarlo solo después de inicializar sus variables de instancia. Si esta vista se está reconstituyendo desde un Nib, no se llamará a su inicializador personalizado, y en su lugar init?(coder:)se llamará a la versión. Como Swift ahora requiere una implementación de lo requerido init?(coder:), actualicé el siguiente ejemplo y cambié las letdeclaraciones de variables a vary opcional. En este caso, los inicializaría awakeFromNib()en algún momento posterior.

class TestView : UIView {
    var s: String?
    var i: Int?
    init(s: String, i: Int) {
        self.s = s
        self.i = i
        super.init(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }
}
Wolf McNally
fuente
55
Entonces, por supuesto, hazlos var. Pero la mejor práctica predeterminada en Swift es declarar variables a letmenos que haya una razón para declararlas var. No había tal razón para hacerlo en mi ejemplo de código anterior, por lo tanto let.
Wolf McNally
2
Este código no se compila. Debe implementar el inicializador requerido init(coder:).
Decade Moon
3
Es interesante cómo se compiló hace años. Hoy en día se queja bajo init (codificador :) de que "la propiedad no está inicializada en la llamada super.init"
mafiOSo
Ejemplo fijo para Swift 3.1. Compila debajo de un patio de juegos importando UIKit
Wolf McNally
1
@LightNight que hice sy iopcional para mantener las cosas simples aquí. Si no fueran opcionales, también tendrían que inicializarse en el inicializador requerido. Hacerlos opcionales significa que pueden ser nilcuando super.init()se llama. Si no fueran opcionales, de hecho tendrían que asignarse antes de llamar a super.init ().
Wolf McNally
32

Creo un init común para el designado y requerido. Para su comodidad, delego init(frame:)con un marco de cero.

Tener cero cuadros no es un problema porque normalmente la vista está dentro de la vista de un ViewController; su vista personalizada tendrá una buena y segura oportunidad de diseñar sus subvistas cuando su supervista llame layoutSubviews()o updateConstraints(). El sistema llama a estas dos funciones de forma recursiva en toda la jerarquía de vistas. Puedes usar cualquiera updateContstraints()o layoutSubviews(). updateContstraints()se llama primero, luego layoutSubviews(). En updateConstraints()asegúrate de llamar super último . En layoutSubviews(), llame a Super primero .

Esto es lo que hago:

@IBDesignable
class MyView: UIView {

      convenience init(args: Whatever) {
          self.init(frame: CGRect.zero)
          //assign custom vars
      }

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

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

      override func prepareForInterfaceBuilder() {
           super.prepareForInterfaceBuilder()
           commonInit()
      }

      private func commonInit() {
           //custom initialization
      }

      override func updateConstraints() {
           //set subview constraints here
           super.updateConstraints()
      }

      override func layoutSubviews() {
           super.layoutSubviews()
           //manually set subview frames here
      }

}
MH175
fuente
1
No debería funcionar: el uso de 'self' en el método llama a 'commonInit' antes de que super.init inicialice self
surfrider el
1
Inicialice argumentos personalizados después de la llamada self.init. Actualicé mi respuesta.
MH175
1
Pero, ¿qué commonInitpasa si desea inicializar algunas propiedades en el método, pero no puede colocarlo después superen este caso porque debe inicializar todas las propiedades ANTES de la superllamada? Jajaja parece un bucle muerto.
surfrider
1
Así es como a menudo funciona la inicialización Swift: busque "inicialización en dos fases". Puede usar opciones implícitas sin envolver, pero recomiendo no hacerlo. Su arquitectura, especialmente cuando se trata de vistas, debe inicializar todas las propiedades locales. He usado este método commonInit () para cientos de vistas ahora. Funciona
MH175
17

Así es como lo hago en iOS 9 en Swift:

import UIKit

class CustomView : UIView {

    init() {
        super.init(frame: UIScreen.mainScreen().bounds);

        //for debug validation
        self.backgroundColor = UIColor.blueColor();
        print("My Custom Init");

        return;
    }

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

Aquí hay un proyecto completo con ejemplo:

J-Dizzle
fuente
2
esto usaría toda la pantalla para la Vista
RaptoX
1
¡Sí! Si está interesado en una subvista parcial, hágamelo saber y lo publicaré también
J-Dizzle
1
Me gusta más esta respuesta, porque tener fatalError significa que no tengo que poner ningún código en el init requerido.
Carter Medlin
1
@ J-Dizzle, me gustaría ver la solución para vistas parciales.
Ari Lacenski
¿No dice su respuesta lo contrario de la respuesta aceptada? Quiero decir que estás haciendo la personalización después super.init, pero él dijo que debería hacerse antes super.init...
Miel
11

Así es como hago una subvista en iOS en Swift:

class CustomSubview : UIView {

    init() {
        super.init(frame: UIScreen.mainScreen().bounds);

        let windowHeight : CGFloat = 150;
        let windowWidth  : CGFloat = 360;

        self.backgroundColor = UIColor.whiteColor();
        self.frame = CGRectMake(0, 0, windowWidth, windowHeight);
        self.center = CGPoint(x: UIScreen.mainScreen().bounds.width/2, y: 375);

        //for debug validation
        self.backgroundColor = UIColor.grayColor();
        print("My Custom Init");

        return;
    }

    required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented"); }
}
J-Dizzle
fuente
44
Buen uso de la llamada fatalError (). Tenía que usar opcionales solo para silenciar las advertencias de un inicializador que ni siquiera se estaba usando. ¡Esto lo callo! Gracias.
Mike Critchley