Quand est appelé layoutSubviews?

264

J'ai une vue personnalisée qui ne reçoit pas de layoutSubviewmessages pendant l'animation.

J'ai une vue qui remplit l'écran. Il a une sous-vue personnalisée en bas de l'écran qui se redimensionne correctement dans Interface Builder si je change la hauteur de la barre de navigation. layoutSubviewsest appelé lorsque la vue est créée, mais plus jamais. Mes sous-vues sont correctement présentées. Si je désactive la barre d'état en cours d'appel, la sous-vue layoutSubviewsn'est pas appelée du tout, même si la vue principale anime son redimensionnement.

Dans quelles circonstances est layoutSubviewseffectivement appelé?

J'ai autoresizesSubviewsdéfini NOpour ma vue personnalisée. Et dans Interface Builder, j'ai les entretoises supérieure et inférieure et l'ensemble de flèches verticales.


Une autre partie du puzzle est que la fenêtre doit être rendue clé:

[window makeKeyAndVisible];

sinon, les sous-vues ne sont pas automatiquement redimensionnées.

Steve Weller
la source

Réponses:

492

J'avais une question similaire, mais je n'étais pas satisfait de la réponse (ou de toute autre chose que je pouvais trouver sur le net), alors je l'ai essayée en pratique et voici ce que j'ai obtenu:

  • initne fait pas layoutSubviewsappeler (duh)
  • addSubview:entraîne layoutSubviewsl'appel de la vue ajoutée, de la vue à laquelle elle est ajoutée (vue cible) et de toutes les sous-vues de la cible
  • view setFrame appelle intelligemment layoutSubviewsla vue dont le cadre est défini uniquement si le paramètre de taille du cadre est différent
  • le défilement d'un UIScrollView provoque layoutSubviewsl'appel sur le scrollView et sa vue d'ensemble
  • la rotation d'un appareil n'appelle que layoutSubviewla vue parent (la vue principale de viewControllers qui répond)
  • Le redimensionnement d'une vue fera appel layoutSubviewsà sa vue d'ensemble

Mes résultats - http://blog.logichigh.com/2011/03/16/when-does-layoutsubviews-get-called/

BadPirate
la source
1
Très bonne réponse. Je me suis toujours posé des questions layoutSubviews. Est -ce que la initWithFrame:cause layoutSubviewsà appeler?
Robert
2
@Robert - J'utilisais initWithFrame ... donc non.
BadPirate
8
@BadPirate: oui . D'après mes expériences, si vous redimensionnez, view1.1cela appelle layoutSubviewsde view1puis layoutSubviewsde view1.1. Cet appel ne se propage pas indéfiniment aux superviews, l'appelant view1.1.1uniquement layoutSubviewssur les appels sur view1.1et view1.1.1. Se déplacer sans changer sa taille ne fait appel layoutSubviewsà aucun d'entre eux.
João Portela
1
viewDidLoad n'est pas appelé sur UIView (mais UIViewController). View Did load est appelé après le lancement de UIView.
BadPirate
2
Sur la base de mon expérience, la deuxième règle peut être pas exacte: quand j'ajoute view1.2en view1, layoutSubviewsde view1.2et view1sont appelés, mais layoutSubviewsde view1.1n'est pas appelé. ( view1.1et view1.2sont des sous-vues de view1). Autrement dit, toutes les sous-vues de la vue cible ne sont pas appelées layoutSubviewsméthode .
HongchaoZhang
96

Sur la base de la réponse précédente de @BadPirate, j'ai expérimenté un peu plus loin et j'ai apporté quelques clarifications / corrections. J'ai trouvé que ce layoutSubviews:sera appelé sur une vue si et seulement si:

  • Ses propres limites (pas le cadre) ont changé.
  • Les limites de l'une de ses sous-vues directes ont changé.
  • Une sous-vue est ajoutée à la vue ou supprimée de la vue.

Quelques détails pertinents:

  • Les limites ne sont considérées comme modifiées que si la nouvelle valeur est différente, y compris une origine différente . Notez spécifiquement que c'est pourquoi layoutSubviews:est appelé chaque fois qu'un UIScrollView défile, car il effectue le défilement en changeant l'origine de ses limites.
  • Changer le cadre ne changera les limites que si la taille a changé, car c'est la seule chose propagée à la propriété des limites.
  • Un changement dans les limites d'une vue qui n'est pas encore dans une hiérarchie de vues entraînera un appel à layoutSubviews: quand la vue sera finalement ajoutée à une hiérarchie de vues .
  • Et juste pour être complet: ces déclencheurs n'appellent pas directement layoutSubviews, mais appellent plutôt setNeedsLayout, ce qui définit / lève un indicateur. À chaque itération de la boucle d'exécution, pour toutes les vues de la hiérarchie des vues , cet indicateur est vérifié. Pour chaque vue où le drapeau se trouve levé, layoutSubviews:est appelé dessus et le drapeau est réinitialisé. Les vues plus haut dans la hiérarchie seront vérifiées / appelées en premier.
Patrick Pijnappel
la source
5
ne peut pas voter assez pour cette réponse. ce devrait être la meilleure réponse. les trois règles données sont tout ce dont vous avez besoin. je n'ai jamais rencontré de comportement layoutSubview que ces règles ne décrivent pas parfaitement.
Pärserk
1
Je ne pense pas que layoutSubviews soit appelé lorsque les limites d'une sous-vue directe sont modifiées. Je pense que l'appel de layoutSubviews n'est qu'un effet secondaire de vos tests. Dans certains cas, layoutSubviews ne sera pas appelé lorsque les limites d'une sous-vue changent. Veuillez vérifier la réponse de frogcjn, car sa réponse est basée sur la documentation d'Apple au lieu de simples expériences.
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

Des modifications de mise en page peuvent se produire chaque fois que l'un des événements suivants se produit dans une vue:

une. La taille du rectangle des limites d'une vue change.
b. Un changement d'orientation de l'interface se produit, ce qui déclenche généralement un changement dans le rectangle des limites de la vue racine.
c. L'ensemble des sous-couches Core Animation associées au calque de la vue change et nécessite une disposition.
ré. Votre application force la présentation à apparaître en appelant la   méthode setNeedsLayout or  layoutIfNeededd'une vue.
e. Votre application force la mise en page en appelant la  setNeedsLayout méthode de l'objet de calque sous-jacent de la vue.

grenouille
la source
Il est également important de souligner qu'aucun de ces événements n'est appelé lorsque la vue n'a pas encore été ajoutée à la pile de vues. Vous l'incluez avec le mot "peut", mais en particulier, il est appelé dans le prochain cycle disponible du thread principal de l'application lorsqu'il a été marqué comme nécessitant une mise en page par l'un de ces événements.
user1122069
13

Certains des points de la réponse de BadPirate ne sont que partiellement vrais:

  1. Pour le addSubViewpoint

    addSubview provoque la mise en page des sous-vues lors de l'ajout de la vue, de la vue à laquelle elle est ajoutée (vue cible) et de toutes les sous-vues de la cible.

    Cela dépend du masque de redimensionnement automatique de la vue (vue cible). Si le masque de redimensionnement automatique est activé, layoutSubview sera appelé sur chacun addSubview. S'il n'a pas de masque de redimensionnement automatique, alors layoutSubview sera appelé uniquement lorsque la taille du cadre de la vue (vue cible) change.

    Exemple: si vous avez créé UIView par programme (il n'a pas de masque de redimensionnement automatique par défaut), LayoutSubview sera appelé uniquement lorsque le cadre UIView ne change pas tous les jours addSubview.

    C'est grâce à cette technique que les performances de l'application augmentent également.

  2. Pour le point de rotation de l'appareil

    La rotation d'un appareil appelle uniquement layoutSubview sur la vue parent (la vue principale de la vue répondante du contrôleur)

    Cela ne peut être vrai que lorsque votre VC est dans la hiérarchie VC (racine à window.rootViewController), et bien c'est le cas le plus courant. Dans iOS 5, si vous créez un VC, mais qu'il n'est ajouté à aucun autre VC, ce VC ne sera pas remarqué lorsque l'appareil pivotera. Par conséquent, sa vue ne serait pas remarquée en appelant layoutSubviews.

Mohit Nigam
la source
9

J'ai trouvé la solution jusqu'à l'insistance d'Interface Builder sur le fait que les ressorts ne peuvent pas être modifiés sur une vue sur laquelle les éléments d'écran simulés sont activés (barre d'état, etc.). Étant donné que les ressorts étaient désactivés pour la vue principale, cette vue ne pouvait pas changer de taille et était donc défilée dans son intégralité lorsque la barre d'appel était apparue.

La désactivation des fonctions simulées, puis le redimensionnement de la vue et le réglage correct des ressorts ont provoqué l'animation et l'appel de ma méthode.

Un problème supplémentaire lors du débogage est que le simulateur quitte l'application lorsque l'état en cours d'appel est basculé via le menu. Quittez l'application = pas de débogueur.

Steve Weller
la source
Voulez-vous dire que layoutSubviews est appelé lorsque la vue est redimensionnée? J'ai toujours supposé que ce n'était pas ...
Andrey Tarantsov
C'est. Lorsqu'une vue est redimensionnée, elle doit faire quelque chose avec ses sous-vues. Si vous ne le fournissez pas, il les déplace automatiquement à l'aide de ressorts, de jambes de force, etc.
Steve Weller,
8

appeler [self.view setNeedsLayout]; dans viewController permet d'appeler viewDidLayoutSubviews

bademi
la source
5

avez-vous regardé layoutIfNeeded?

L'extrait de documentation est ci-dessous. L'animation fonctionne-t-elle si vous appelez cette méthode explicitement pendant l'animation?

layoutIfNeeded Présente les sous-vues si nécessaire.

- (void)layoutIfNeeded

Discussion Utilisez cette méthode pour forcer la disposition des sous-vues avant de dessiner.

Disponibilité Disponible dans iPhone OS 2.0 et versions ultérieures.

Willi Ballenthin
la source
2

Lors de la migration d'une application OpenGL du SDK 3 vers 4, layoutSubviews n'était plus appelé. Après beaucoup d'essais et d'erreurs, j'ai finalement ouvert MainWindow.xib, sélectionné l'objet Window, dans l'inspecteur choisi l'onglet Attributs de fenêtre (le plus à gauche) et vérifié "Visible au lancement". Il semble que dans le SDK 3, il provoquait toujours un appel de layoutSubViews, mais pas dans 4.

6 heures de frustration terminées.

Tal Yaniv
la source
Avez-vous fait la clé de la fenêtre? Sinon, cela peut empêcher toutes sortes de choses intéressantes de se produire.
Steve Weller
-2

Un cas assez obscur, mais potentiellement important, quand il layoutSubviewsn'est jamais appelé est:

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
la source
1
Pourquoi cette réponse est ici?
Dominik Bucher