web-dev-qa-db-fra.com

Quand appelle-t-on layoutSubviews?

J'ai une vue personnalisée qui ne reçoit pas les messages layoutSubview lors de l'animation.

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

Dans quelles circonstances layoutSubviews est-il appelé?

autoresizesSubviews est défini sur NO pour ma vue personnalisée. Et dans Interface Builder, j'ai les entretoises supérieure et inférieure et le jeu de flèches verticales.

242
Steve Weller

J'ai suivi la solution jusqu'à l'insistance d'Interer Builder sur le fait que les ressorts ne peuvent pas être modifiés dans 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 entièrement défilée vers le bas lorsque la barre In-Call est apparue.

La désactivation des fonctionnalités 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 de l'appel en cours est activé via le menu. Quittez app = pas de débogueur.

7
Steve Weller

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

  • init ne cause pas layoutSubviews àbe appelé (duh) 
  • addSubview: provoque l’appel de layoutSubviews sur la vue en cours d’ajout, la vue à laquelle elle est ajoutée (vue cible) et toutes les sous-vues de la cible.
  • view setFrame appelle intelligemment layoutSubviews sur la vue dont le cadre est défini uniquement si le paramètre de taille du cadre est différent
  • le défilement d'une UIScrollView provoque l'appel de layoutSubviews sur le scrollView et son aperçu
  • la rotation d'un périphérique appelle uniquement layoutSubview sur la vue parent (la vue primaire répondant à viewControllers )
  • Redimensionner une vue appellera layoutSubviews sur son aperçu

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

461
BadPirate

En me basant sur la réponse précédente de @BadPirate, j’ai essayé un peu plus loin et proposé quelques clarifications/corrections. J'ai trouvé que layoutSubviews: sera appelé sur une vue si et seulement si:

  • Son propre bounds (pas de cadre) a changé.
  • Les limites de l'une de ses sous-vues directes ont été modifiées.
  • 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, incluant une origine différente. Notez précisément que c'est pourquoi layoutSubviews: est appelé chaque fois qu'un UIScrollView défile, car il effectue le défilement en modifiant l'origine de ses limites.
  • La modification du cadre ne modifiera les limites que si la taille a été modifiée, car il s’agit de la seule chose transmise à la propriété bounds.
  • Une modification des limites d'une vue qui ne figure pas encore dans une hiérarchie de vues entraîne l'appel de layoutSubviews:lorsque la vue est finalement ajoutée à une hiérarchie de vues.
  • Et juste pour être complet: ces déclencheurs n’appellent pas directement appel à layoutSubviews, mais plutôt setNeedsLayout, ce qui définit/déclenche un indicateur. A chaque itération de la boucle d'exécution, cet indicateur est activé pour toutes les vues de la hiérarchie de vues. layoutSubviews: est appelé pour chaque vue où le drapeau est levé, et le drapeau est réinitialisé. Les vues situées plus haut dans la hiérarchie seront vérifiées/appelées en premier.
67
Patrick Pijnappel

_ { https://developer.Apple.com/library/prerelease/tvos/documentation/WindowsViews/Conceptual/ViewPG_iPhoneOS/CreatingViews/CreatingViews.html#//Apple_ref/doc/uid/TP40009503-CH5-SW1

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

une. La taille du rectangle de contour d’une vue change.
b. Un changement d'orientation de l'interface se produit, ce qui déclenche généralement une modification du rectangle des limites de la vue racine.
c. L'ensemble des sous-couches de Core Animation associé au calque de la vue change et nécessite une mise en page.
ré. Votre application force la mise en page en appelant la méthode setNeedsLayout ou layoutIfNeeded d'une vue.
e. Votre application force la présentation en appelant la méthode setNeedsLayout de l’objet calque sous-jacent de la vue.

14
frogcjn

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

  1. Pour addSubView point 

    addSubview provoque l'appel de layoutSubviews sur la vue ajoutée, la vue à laquelle elle est ajoutée (vue cible) et toutes les sous-vues de la cible.

    Cela dépend du masque de redimensionnement automatique de la vue (vue cible). Si Masque de taille automatique est activé, layoutSubview sera appelée à chaque addSubview. Si elle n’a pas de masque de redimensionnement automatique, layoutSubview ne sera appelée que lorsque la taille de l’image de la vue (Vue cible) sera modifiée.

    Exemple: si vous avez créé UIView par programme (il ne possède pas de masque de redimensionnement automatique par défaut), LayoutSubview sera appelée uniquement lorsque le cadre UIView ne sera pas modifié à chaque 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 périphérique appelle uniquement layoutSubview sur la vue parent (la vue principale du viewController qui répond)

    Cela ne peut être vrai que lorsque votre VC est dans la hiérarchie VC (racine à window.rootViewController), ce qui est le cas le plus courant. Dans iOS 5, si vous créez un VC, mais qu'il n'est pas ajouté à un autre VC, ce VC ne sera pas remarqué lors de la rotation du périphérique. Par conséquent, sa vue ne serait pas remarquée en appelant layoutSubviews.

9
Mohit Nigam

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

7
bademi

avez-vous regardé layoutIfNeeded?

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

layoutIfNeeded Décale les vues secondaires si nécessaire.

- (void)layoutIfNeeded

Discussion Utilisez cette méthode pour forcer la mise en page des vues secondaires avant le dessin.

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

4
Willi Ballenthin

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

6 heures de frustration terminées.

2
Tal Yaniv

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.

0
Steve Weller

Un cas plutôt obscur, mais potentiellement important, où layoutSubviews n'est jamais appelé, est le suivant:

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))
0
milos