Práctica adecuada para subclasificar UIView?

158

Estoy trabajando en algunos controles de entrada personalizados basados ​​en UIView, y estoy tratando de determinar la práctica adecuada para configurar la vista. Cuando se trabaja con un UIViewController, es bastante simple de usar las loadViewy relacionados viewWill, viewDidmétodos, pero cuando la subclasificación de un UIView, los methosds más cercano que tengo son `awakeFromNib, drawRecty layoutSubviews. (Estoy pensando en términos de configuración y devolución de llamadas de desmontaje). En mi caso, estoy configurando mi marco y vistas internas layoutSubviews, pero no veo nada en la pantalla.

¿Cuál es la mejor manera de garantizar que mi vista tenga la altura y el ancho correctos que quiero que tenga? (Mi pregunta se aplica independientemente de si estoy usando autolayout, aunque puede haber dos respuestas). ¿Cuál es la "mejor práctica" adecuada?

Moshe
fuente

Respuestas:

298

Apple definió claramente cómo subclasificar UIViewen el documento.

Consulte la lista a continuación, especialmente eche un vistazo a initWithFrame:y layoutSubviews. El primero está destinado a configurar el marco de su UIViewmientras que el segundo está destinado a configurar el marco y el diseño de sus subvistas.

También recuerde que initWithFrame:se llama solo si está creando instancias UIViewprogramáticamente. Si lo está cargando desde un archivo plumín (o un guión gráfico), initWithCoder:se utilizará. Y en initWithCoder:el marco aún no se ha calculado, por lo que no puede modificar el marco que configuró en Interface Builder. Como se sugiere en esta respuesta , puede pensar en llamar initWithFrame:desde initWithCoder:para configurar el marco.

Finalmente, si carga su UIViewdesde una plumilla (o un guión gráfico), también tiene la awakeFromNiboportunidad de realizar inicializaciones de marcos y diseños personalizados, ya que cuando awakeFromNibse llama se garantiza que cada vista en la jerarquía no se haya archivado e inicializado.

Del documento de NSNibAwaking(ahora reemplazado por el documento de awakeFromNib):

Los mensajes a otros objetos se pueden enviar de forma segura desde dentro de wakekeFromNib, momento en el cual se garantiza que todos los objetos no están archivados e inicializados (aunque no necesariamente despiertos, por supuesto)

También vale la pena señalar que con la distribución automática no debe establecer explícitamente el marco de su vista. En su lugar, se supone que debe especificar un conjunto de restricciones suficientes, de modo que el motor de diseño calcule automáticamente el marco.

Directamente de la documentación :

Métodos para anular

Inicialización

  • initWithFrame:Se recomienda que implemente este método. También puede implementar métodos de inicialización personalizados además de, o en lugar de, este método.

  • initWithCoder: Implemente este método si carga su vista desde un archivo plum de Interface Builder y su vista requiere una inicialización personalizada.

  • layerClassImplemente este método solo si desea que su vista use una capa de animación principal diferente para su almacén de respaldo. Por ejemplo, si está utilizando OpenGL ES para hacer su dibujo, debería anular este método y devolver la clase CAEAGLLayer.

Dibujo e impresión

  • drawRect:Implemente este método si su vista dibuja contenido personalizado. Si su vista no hace ningún dibujo personalizado, evite anular este método.

  • drawRect:forViewPrintFormatter: Implemente este método solo si desea dibujar el contenido de su vista de manera diferente durante la impresión.

Restricciones

  • requiresConstraintBasedLayout Implemente este método de clase si su clase de vista requiere restricciones para funcionar correctamente.

  • updateConstraints Implemente este método si su vista necesita crear restricciones personalizadas entre sus subvistas.

  • alignmentRectForFrame:, frameForAlignmentRect:Poner en práctica estos métodos para anular cómo sus puntos de vista están alineados con otros puntos de vista.

Diseño

  • sizeThatFits:Implemente este método si desea que su vista tenga un tamaño predeterminado diferente al que normalmente tendría durante las operaciones de cambio de tamaño. Por ejemplo, puede usar este método para evitar que su vista se reduzca hasta el punto en que las subvistas no se puedan mostrar correctamente.

  • layoutSubviews Implemente este método si necesita un control más preciso sobre el diseño de sus subvistas que la restricción o los comportamientos de autorizar.

  • didAddSubview:, willRemoveSubview:Poner en práctica estos métodos según sea necesario para realizar un seguimiento de las adiciones y eliminaciones de subvistas.

  • willMoveToSuperview:, didMoveToSuperviewImplemente estos métodos según sea necesario para rastrear el movimiento de la vista actual en su jerarquía de vistas.

  • willMoveToWindow:, didMoveToWindowPoner en práctica estos métodos, según sea necesario para seguir el movimiento de la vista a una ventana diferente.

Manejo de eventos:

  • touchesBegan:withEvent:, touchesMoved:withEvent:, touchesEnded:withEvent:, touchesCancelled:withEvent:Poner en práctica estos métodos, si usted necesita para manejar eventos de toque directamente. (Para la entrada basada en gestos, use reconocedores de gestos).

  • gestureRecognizerShouldBegin: Implemente este método si su vista maneja eventos táctiles directamente y puede querer evitar que los reconocedores de gestos adjuntos activen acciones adicionales.

Gabriele Petronella
fuente
¿Qué pasa con - (nulo) setFrame: (CGRect) marco?
pfrank
bueno, definitivamente puedes anularlo, pero ¿para qué?
Gabriele Petronella
cambiar el diseño / dibujo en cualquier momento que cambie el tamaño del marco o la ubicación
pfrank
1
¿Qué hay de layoutSubviews?
Gabriele Petronella
De stackoverflow.com/questions/4000664/… , "el problema con esto es que las subvistas no solo pueden cambiar su tamaño, sino que también pueden animar ese cambio de tamaño. Cuando UIView ejecuta la animación, no llama a layoutSubviews cada vez". Sin embargo
pfrank
38

Esto todavía aparece en Google. A continuación se muestra un ejemplo actualizado para swift.

La didLoadfunción le permite poner todo su código de inicialización personalizado. Como otros han mencionado, didLoadse llamará cuando una vista se cree mediante programación init(frame:)o cuando el deserializador XIB combine una plantilla XIB en su vista a través deinit(coder:)

Aparte :layoutSubviews y updateConstraintsse llaman varias veces para la mayoría de las vistas. Esto está destinado a diseños y ajustes avanzados de múltiples pasadas cuando cambian los límites de una vista. Personalmente, evito los diseños de pasadas múltiples cuando es posible porque queman los ciclos de la CPU y hacen que todo sea un dolor de cabeza. Además, pongo código de restricción en los inicializadores, ya que rara vez los invalido.

import UIKit

class MyView: UIView {
  //-----------------------------------------------------------------------------------------------------
  //Constructors, Initializers, and UIView lifecycle
  //-----------------------------------------------------------------------------------------------------
  override init(frame: CGRect) {
      super.init(frame: frame)
      didLoad()
  }

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

  convenience init() {
    self.init(frame: CGRectZero)
  }

  func didLoad() {
    //Place your initialization code here

    //I actually create & place constraints in here, instead of in
    //updateConstraints
  }

  override func layoutSubviews() {
     super.layoutSubviews()

     //Custom manually positioning layout goes here (auto-layout pass has already run first pass)
  }

  override func updateConstraints() {
    super.updateConstraints()

    //Disable this if you are adding constraints manually
    //or you're going to have a 'bad time'
    //self.translatesAutoresizingMaskIntoConstraints = false

    //Add custom constraint code here
  }
}
seo
fuente
¿Puede explicar cuándo / por qué dividiría el código de restricción de diseño entre un método llamado desde su proceso de inicio y layoutSubviews y updateConstraints? Parece que son las tres posibles ubicaciones candidatas para colocar el código de diseño. Entonces, ¿cómo saber cuándo / qué / por qué dividir el código de diseño entre los tres?
Clay Ellis
3
Nunca uso updateConstraints; updateConstraints puede ser bueno porque sabe que su jerarquía de vistas se configuró completamente en init, por lo que no puede generar una excepción al agregar una restricción entre dos vistas que no están en la jerarquía :) layoutSubviews nunca debe tener modificaciones de restricción; puede causar fácilmente una recursión infinita ya que se llama a layoutSubviews si las restricciones se 'invalidan' durante el pase de diseño. La configuración de diseño manual (como en la configuración de marcos directamente, que rara vez necesita hacer más a menos que sea por razones de rendimiento) va en vistas de diseño. Personalmente, coloco la creación de restricciones en init
seo
Para el código de representación personalizado, ¿deberíamos anular el drawmétodo?
Petrus Theron
14

Hay un resumen decente en la documentación de Apple , y esto está bien cubierto en el curso gratuito de Stanford disponible en iTunes. Presento mi versión TL; DR aquí:

Si su clase se compone principalmente de subvistas, el lugar correcto para asignarlas es en los initmétodos. Para las vistas, hay dos initmétodos diferentes a los que se podría llamar, dependiendo de si su vista se instancia desde el código o desde una plumilla / guión gráfico. Lo que hago es escribir mi propio setupmétodo, y luego llamarlo desde los métodos initWithFrame:y initWithCoder:.

Si está haciendo un dibujo personalizado, de hecho desea anular drawRect:en su vista. Sin embargo, si su vista personalizada es principalmente un contenedor para subvistas, probablemente no necesite hacer eso.

Solo anule layoutSubViewssi desea hacer algo como agregar o eliminar una subvista dependiendo de si está en orientación vertical u horizontal. De lo contrario, debería poder dejarlo solo.

dpassage
fuente
Utilizo su respuesta para cambiar la vista (que está en el marco de subView de wakekeFromNib) layoutSubViews, funcionó.
avión
1

layoutSubviews está destinado a establecer el marco en vistas secundarias, no en la vista en sí.

Por lo general UIView, el constructor designado es initWithFrame:(CGRect)framey usted debe establecer el marco allí (o adentro initWithCoder:), posiblemente ignorando el valor del marco pasado. También puede proporcionar un constructor diferente y establecer el marco allí.

proxi
fuente
¿podrías tomar más detalles? No sabía su significado. ¿Cómo establecer el marco de una subvista de vista? la vista esawakeFromNib
avión
Avance rápido hasta 2016, probablemente no debería establecer marcos en absoluto y usar autolayout (restricciones). Si la vista proviene de XIB (o storyboard), la subvista ya debería estar configurada.
Proxi