¿Cuándo se llama a layoutSubviews?

264

Tengo una vista personalizada que no recibe layoutSubviewmensajes durante la animación.

Tengo una vista que llena la pantalla. Tiene una subvista personalizada en la parte inferior de la pantalla que cambia de tamaño correctamente en Interface Builder si cambio la altura de la barra de navegación. layoutSubviewsse llama cuando se crea la vista, pero nunca más. Mis subvistas están correctamente distribuidas. Si desactivo la barra de estado de la llamada, la subvista layoutSubviewsno se llama en absoluto, aunque la vista principal anima su cambio de tamaño.

¿Bajo qué circunstancias se layoutSubviewsllama realmente?

He autoresizesSubviewsconfigurado NOpara mi vista personalizada. Y en Interface Builder tengo los puntales superior e inferior y el conjunto de flechas verticales.


Otra parte del rompecabezas es que la ventana debe hacerse clave:

[window makeKeyAndVisible];

de lo contrario, las subvistas no se redimensionan automáticamente.

Steve Weller
fuente

Respuestas:

492

Tenía una pregunta similar, pero no estaba satisfecho con la respuesta (o cualquier otra que pudiera encontrar en la red), así que lo intenté en la práctica y esto es lo que obtuve:

  • initno hace layoutSubviewsque se llame (duh)
  • addSubview:hace layoutSubviewsque se invoque la vista que se agrega, la vista a la que se agrega (vista de destino) y todas las subvistas del objetivo
  • ver de forma setFrame inteligente las llamadas layoutSubviewsen la vista que tiene su marco establecido solo si el parámetro de tamaño del marco es diferente
  • desplazar un UIScrollView provoca layoutSubviewsque se invoque en scrollView y su supervista
  • la rotación de un dispositivo solo invoca layoutSubviewen la vista principal (la vista principal Vista de control Controladores)
  • Cambiar el tamaño de una vista llamará layoutSubviewsa su supervista

Mis resultados: http://blog.logichigh.com/2011/03/16/when-does-layoutsubviews-get-called/

BadPirate
fuente
1
Gran respuesta. Siempre me he preguntado layoutSubviews. ¿ initWithFrame:Causa layoutSubviewsser llamado?
Robert
2
@Robert - Estaba usando initWithFrame ... así que no.
BadPirate
8
@BadPirate: . De acuerdo con mis experimentos, si cambias el tamaño view1.1se llama layoutSubviewsde view1y luego layoutSubviewsde view1.1. Esta llamada no se propaga indefinidamente a las supervistas, view1.1.1solo se llama layoutSubviewsa las llamadas view1.1y view1.1.1. Simplemente moverse sin cambiar su tamaño no llama layoutSubviewsa ninguno de ellos.
João Portela
1
viewDidLoad no se llama en UIView (sino en UIViewController). Ver ¿Se cargó la carga cuando se inicia la UIView?
BadPirate
2
Según mi experimento, la segunda regla puede no ser precisa: cuando agrego view1.2a view1, layoutSubviewsde view1.2y view1se llama, pero layoutSubviewsde view1.1no se llama. ( view1.1y view1.2son subvistas de view1). Es decir, no todas las subvistas de la vista de destino se denominan layoutSubviewsmétodo .
HongchaoZhang
96

Partiendo de la respuesta anterior de @BadPirate, experimenté un poco más y obtuve algunas aclaraciones / correcciones. Descubrí que layoutSubviews:se invocará en una vista si y solo si:

  • Sus propios límites (no marco) cambiaron.
  • Los límites de una de sus subvistas directas cambiaron.
  • Se agrega una subvista a la vista o se elimina de la vista.

Algunos detalles relevantes:

  • Los límites se consideran modificados solo si el nuevo valor es diferente, incluido un origen diferente . Tenga en cuenta específicamente que es por eso que layoutSubviews:se llama cada vez que se desplaza un UIScrollView, ya que realiza el desplazamiento cambiando el origen de sus límites.
  • Cambiar el marco solo cambiará los límites si el tamaño ha cambiado, ya que esto es lo único que se propaga a la propiedad de límites.
  • Un cambio en los límites de una vista que aún no se encuentra en una jerarquía de vistas generará una llamada layoutSubviews: cuando la vista finalmente se agregue a una jerarquía de vistas .
  • Y solo para completar: estos disparadores no llaman directamente a layoutSubviews, sino que llaman setNeedsLayout, lo que establece / levanta una bandera. Cada iteración del ciclo de ejecución, para todas las vistas en la jerarquía de vistas , se marca este indicador. Para cada vista donde se encuentra la bandera levantada, layoutSubviews:se le llama y se restablece la bandera. Las vistas más arriba en la jerarquía se verificarán / llamarán primero.
Patrick Pijnappel
fuente
55
No puedo votar esta respuesta lo suficiente. Debería ser la mejor respuesta. las tres reglas dadas son todo lo que necesitas. Nunca me he encontrado con ningún comportamiento de Layout Subview que estas reglas no describieran perfectamente.
Pärserk
1
No creo que se llame a layoutSubviews cuando se cambian los límites de una subvista directa. Creo que la llamada de layoutSubviews es solo un efecto secundario de sus pruebas. En algunos casos, layoutSubviews no se llamará cuando los límites de una subvista cambien. Verifique la respuesta de frogcjn, porque su respuesta se basa en la documentación de Apple en lugar de solo experimentos.
Simon Backx
19

https://developer.apple.com/library/prerelease/tvos/documentation/WindowsViews/Conceptual/ViewPG_iPhoneOS/CreatingViews/CreatingViews.html#//apple_ref/doc/uid/TP40009503-CH5-SW1

Los cambios de diseño pueden ocurrir cada vez que ocurre uno de los siguientes eventos en una vista:

a. El tamaño del rectángulo de límites de una vista cambia.
si. Se produce un cambio de orientación de la interfaz, que generalmente desencadena un cambio en el rectángulo de límites de la vista raíz.
C. El conjunto de subcapas de animación central asociadas con la capa de la vista cambia y requiere diseño.
re. Su aplicación obliga a que se produzca el diseño llamando al   método setNeedsLayout o  layoutIfNeededde una vista.
mi. Su aplicación fuerza el diseño llamando al  setNeedsLayout método del objeto de capa subyacente de la vista.

frogcjn
fuente
Además, es importante señalar que ninguno de esos eventos se llama cuando la vista aún no se ha agregado a la pila de vistas. Incluyes esto con la palabra "can", pero específicamente se llama en el siguiente ciclo disponible del hilo principal de la aplicación cuando uno de esos eventos ha marcado que necesita diseño.
user1122069
13

Algunos de los puntos en la respuesta de BadPirate son solo parcialmente ciertos:

  1. Por addSubViewpunto

    addSubview hace que se invoque layoutSubviews en la vista que se agrega, la vista a la que se agrega (vista de destino) y todas las subvistas del objetivo.

    Depende de la máscara de tamaño automático de la vista (vista de destino). Si tiene la máscara de tamaño automático activada, se llamará a layoutSubview en cada una addSubview. Si no tiene máscara de tamaño automático, se llamará a layoutSubview solo cuando cambie el tamaño del marco de la vista (Vista de destino).

    Ejemplo: si creó UIView mediante programación (no tiene máscara de tamaño automático de forma predeterminada), LayoutSubview solo se llamará cuando el marco de UIView no cambie en todos addSubview.

    Es a través de esta técnica que el rendimiento de la aplicación también aumenta.

  2. Para el punto de rotación del dispositivo

    Girar un dispositivo solo llama a layoutSubview en la vista principal (la vista que responde Vista principal del controlador)

    Esto puede ser cierto solo cuando su VC está en la jerarquía de VC (raíz en window.rootViewController), bueno, este es el caso más común. En iOS 5, si crea un VC, pero no se agrega a ningún otro VC, entonces este VC no se notará cuando el dispositivo gire. Por lo tanto, su vista no se notará llamando a layoutSubviews.

Mohit Nigam
fuente
9

Rastreé la solución hasta la insistencia de Interface Builder de que los resortes no se pueden cambiar en una vista que tiene los elementos de pantalla simulados activados (barra de estado, etc.). Dado que los resortes estaban fuera de la vista principal, esa vista no podía cambiar de tamaño y, por lo tanto, se desplazó hacia abajo en su totalidad cuando apareció la barra de llamada.

Desactivar las características simuladas, luego cambiar el tamaño de la vista y configurar los resortes correctamente hizo que se produjera la animación y se llamara a mi método.

Un problema adicional en la depuración es que el simulador cierra la aplicación cuando el estado de la llamada se alterna a través del menú. Salga de la aplicación = sin depurador.

Steve Weller
fuente
¿Está diciendo que se llama a layoutSubviews cuando se cambia el tamaño de la vista? Siempre he asumido que no es ...
Andrey Tarantsov
Es. Cuando se cambia el tamaño de una vista, debe hacer algo con sus subvistas. Si usted no proporciona entonces los mueve automáticamente utilizando resortes, puntales, etc.
Steve Weller
8

llamando [self.view setNeedsLayout]; a viewController hace que llame a viewDidLayoutSubviews

bademi
fuente
5

has mirado layoutIfNeeded?

El fragmento de documentación está debajo. ¿Funciona la animación si llamas a este método explícitamente durante la animación?

layoutIfNeeded Presenta las subvistas si es necesario.

- (void)layoutIfNeeded

Discusión Use este método para forzar el diseño de las subvistas antes de dibujar.

Disponibilidad Disponible en iPhone OS 2.0 y posterior.

Willi Ballenthin
fuente
2

Al migrar una aplicación OpenGL de SDK 3 a 4, ya no se llamaba a layoutSubviews. Después de muchas pruebas y errores, finalmente abrí MainWindow.xib, seleccioné el objeto Window, en el inspector elegí la pestaña Atributos de ventana (más a la izquierda) y marqué "Visible en el lanzamiento". Parece que en el SDK 3 todavía solía causar una llamada a layoutSubViews, pero no en 4.

Se acabaron 6 horas de frustración.

Tal Yaniv
fuente
¿Hiciste la llave de la ventana? Si no, esto puede hacer que no ocurran todo tipo de cosas interesantes.
Steve Weller
-2

Un caso bastante oscuro pero potencialmente importante cuando layoutSubviewsnunca se llama es:

import UIKit

class View: UIView {

    override class var layerClass: AnyClass { return Layer.self }

    class Layer: CALayer {
        override func layoutSublayers() {
            // if we don't call super.layoutSublayers()...
            print(type(of: self), #function)
        }
    }

    override func layoutSubviews() {
        // ... this method never gets called by the OS!
        print(type(of: self), #function)
    }
}

let view = View(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
milos
fuente
1
¿Por qué esta respuesta aquí?
Dominik Bucher el