95 votes

Depuis Xcode 8 et iOS10, les vues ne sont pas dimensionnées correctement sur viewDidLayoutSubviews

Il semble qu'avec Xcode 8, sur viewDidLoad Si l'on utilise l'outil de gestion des vues, toutes les vues secondaires du contrôleur de vues ont la même taille de 1000x1000. C'est étrange, mais ça va, viewDidLoad n'a jamais été le meilleur endroit pour dimensionner correctement les vues.

Mais viewDidLayoutSubviews est !

Et sur mon projet actuel, j'essaie d'imprimer la taille d'un bouton :

- (void)viewDidLayoutSubviews {
    [super viewDidLayoutSubviews];

    NSLog(@"%@", self.myButton);
}

Le journal indique une taille de (1000x1000) pour myButton ! Ensuite, si j'enregistre un clic sur un bouton, par exemple, le journal affiche une taille normale.

J'utilise autolayout.

C'est un bug ?

1 votes

J'ai le même problème avec UIImageView - lorsque j'imprime, j'obtiens un étrange frame = (0 0 ; 1000 1000) ;. Je suis à l'intérieur d'un UITableViewCell, et une fois que j'ai rafraîchi le tableau, le cadre est ce que je m'attends à ce qu'il soit (également lorsque la cellule sort de la fenêtre et revient). Quelqu'un a-t-il une idée de la raison pour laquelle cela se produit (cadre bizarre par défaut) ?

4 votes

Je pense que le (0, 0, 1000, 1000) bound initialization est la nouvelle façon dont Xcode instancie les vues de IB. Avant Xcode8, les vues étaient créées avec leur taille configurée dans le xib, puis redimensionnées en fonction de l'écran juste après. Mais maintenant, il n'y a pas de taille configurée dans le document IB puisque la taille dépend de la sélection de votre appareil (en bas de l'écran). La vraie question est donc la suivante : existe-t-il un endroit fiable où l'on peut vérifier la taille finale des vues ?

4 votes

Utilisez-vous des coins arrondis pour votre bouton ? Essayez d'appeler layoutIfNeeded() avant.

98voto

Martin Points 3170

Désormais, Interface Builder permet à l'utilisateur de modifier dynamiquement la taille de chaque contrôleur de vue dans le storyboard, afin de simuler la taille d'un certain appareil.

Avant cette fonctionnalité, l'utilisateur doit définir manuellement la taille de chaque contrôleur de vue. Ainsi, le contrôleur de vue était sauvegardé avec une certaine taille, qui était utilisée dans la fonction initWithCoder pour définir le cadre initial.

Maintenant, il semble que initWithCoder n'utilisez pas la taille définie dans le storyboard, et définissez une taille de 1000x1000 px pour la vue viewcontroller et toutes ses sous-vues.

Ce n'est pas un problème, car les vues doivent toujours utiliser l'une ou l'autre de ces solutions de mise en page :

  • autolayout, et toutes les contraintes disposeront correctement vos vues.

  • autoresizingMask, qui mettra en page chaque vue qui n'a pas de contrainte attachée à ( note les contraintes d'autolayout et de marge sont maintenant compatibles dans la même vue \o / ! )

Mais cette est un problème pour tous les éléments de mise en page liés à la couche de visualisation, tels que cornerRadius puisque ni l'un ni l'autre autolayout ni masque de redimensionnement s'applique aux propriétés des couches.

Pour répondre à ce problème, la méthode courante est d'utiliser viewDidLayoutSubviews si vous êtes dans le contrôleur, ou layoutSubview si vous êtes dans une vue. A ce stade (n'oubliez pas d'appeler leur super méthodes relatives), vous êtes à peu près sûr que tout le travail de mise en page a été fait !

Pretty sûr ? Hum... pas totalement, j'ai remarqué, et c'est pourquoi j'ai posé cette question, dans certains cas la vue a toujours sa taille 1000x1000 avec cette méthode. Je pense qu'il n'y a pas de réponse à ma propre question. Pour donner le maximum d'informations à ce sujet :

1- cela ne se produit que lors de la mise en place des cellules ! Dans UITableViewCell & UICollectionViewCell sous-classes, layoutSubview ne sera pas appelé après les sous-vues seraient correctement disposées.

2- Comme l'a fait remarquer @EugenDimboiu (merci de upvoter sa réponse si elle vous est utile), appeler [myView layoutIfNeeded] sur la vue secondaire qui n'a pas été mise en page, la mettra en page correctement juste à temps.

- (void)layoutSubviews {
    [super layoutSubviews];
    NSLog (self.myLabel); // 1000x1000 size 
    [self.myLabel layoutIfNeeded];
    NSLog (self.myLabel); // normal size
}

3- A mon avis, c'est définitivement un bug. Je l'ai soumis au radar (id 28562874).

PS : Je ne suis pas de langue maternelle anglaise, donc n'hésitez pas à éditer mon post si ma grammaire doit être corrigée ;)

PS2 : Si vous avez une meilleure solution, n'hésitez pas à écrire une autre réponse. Je déplacerai la réponse acceptée.

3 votes

Thx, mais je n'ai pas pu trouver le radar avec l'id 28562874, puis-je avoir l'URL du radar ?

0 votes

Cela a fonctionné comme un charme pour moi, également pour les couches de la vue !

0 votes

@Joey, je ne sais pas si je peux obtenir un lien de mon bug. Je n'ai pas trouvé d'URL directe, et il semble que les autres utilisateurs ne puissent pas voir mes rapports. D'après cette réponse de SO stackoverflow.com/a/145223/127493 il semble que la meilleure façon d'augmenter la priorité d'un bogue soit de le dupliquer.

41voto

Eugen Dimboiu Points 2160

Utilisez-vous des coins arrondis pour votre bouton ? Essayez d'appeler layoutIfNeeded() avant.

1 votes

Ahah, tu essaies d'avoir plus de représentants ? Comme je l'ai dit dans mon commentaire, cela ne répond pas à la question. Cependant, cela m'a aidé donc tu as gagné un +1 :)

4 votes

Cela peut aider quelqu'un dans le futur, et c'est plus facile à repérer par rapport aux commentaires.

1 votes

Il m'a aidé à l'instant !

25voto

Derek Soike Points 3634

Solución: Emballez tout à l'intérieur viewDidLayoutSubviews sur DispatchQueue.main.async .

// swift 3

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()

    DispatchQueue.main.async {
        // do stuff here
    }
}

1 votes

Cela fonctionne même si on le met dans -viewDidLoad ... une solution de rechange si étrange

1 votes

Probablement parce que lorsque viewDidLayoutSubviews le système n'avait pas eu le temps de faire ce qui devait être fait. Appeler dispatch vous fait sauter un runloop, permettant au système de finir son mot. Si je ne me trompe pas, vous pourriez obtenir le même comportement avec un sleep de quelques millisecondes.

0 votes

Parce que toutes les tâches de l'interface utilisateur doivent être exécutées au sein du thread principal, merci pour votre solution !

18voto

Je sais que ce n'était pas votre question exacte, mais j'ai rencontré un problème similaire où, lors de la mise à jour, certaines de mes vues étaient désordonnées malgré la taille correcte du cadre dans viewDidLayoutSubviews. Selon les notes de mise à jour d'iOS 10 :

"L'envoi de layoutIfNeeded à une vue n'est pas censé déplacer la vue, mais dans les versions précédentes, si la vue avait translatesAutoresizingMaskIntoConstraints était réglé sur NO, et si elle était [ ] pour correspondre au moteur de mise en page avant d'envoyer la mise en page au sous-arbre. Ces changements corrigent ce comportement, et la position du récepteur et généralement sa taille ne seront pas affectés par layoutIfNeeded.

Certains codes existants peuvent s'appuyer sur cette incorporation. maintenant corrigé. Il n'y a pas de changement de comportement pour binari iOS 10, mais lors de la construction sous iOS 10, vous devrez peut-être corriger certaines situations en envoyant -layoutIfNeed à une vue supérieure de la fonction translatesAutoresizingMaskIntoConstraints qui était le précédent récepteur précédente, ou bien en la positionnant et en la redimensionnant avant (ou après, selon le comportement que vous souhaitez) layoMaker, selon le comportement souhaité) layoutIfNeeded.

Applications tierces avec sous-classes UIView personnalisées utilisant Auto surchargent layoutSubviews et dirty layout on self avant d'appeler super risquent de déclencher une boucle de rétroaction de mise en page lorsqu'elles seront reconstruites sur iOS 10. Lorsqu'ils envoient correctement les appels ultérieurs à layoutSubviews ils doivent s'assurer d'arrêter de salir la mise en page sur soi à un moment donné (notez que cet appel a été ignoré dans les versions antérieures à iOS 10). "

Essentiellement, vous ne pouvez pas appeler layoutIfNeeded sur un objet enfant de la vue si vous utilisez translatesAutoresizingMaskIntoConstraints - maintenant, l'appel de layoutIfNeeded doit se faire sur la super-vue, et vous pouvez toujours l'appeler dans viewDidLayoutSubviews.

3voto

Nerdhappy Points 93

Cela a réglé le problème (ridiculement ennuyeux) pour moi :

- (void) viewDidLayoutSubviews {

    [super viewDidLayoutSubviews];

    self.view.frame = CGRectMake(0,0,[[UIScreen mainScreen] bounds].size.width,[[UIScreen mainScreen] bounds].size.height);

}

Edit/Note : Ceci est pour un ViewController plein écran.

0 votes

L'utilisation des limites de l'écran principal n'est pas une bonne solution car, dans de nombreux cas, le viewController n'occupe pas tout l'espace de l'écran. En outre, vous devez appeler [super viewDidLayoutSubviews]; dans cette méthode à cause des nombreux trucs d'autolayout faits par la vue elle-même.

0 votes

@Martin que recommandez-vous ? Je suis d'accord, cela ne semble pas idéal.

0 votes

@Crashalot comme je l'ai dit dans ma réponse, l'utilisation d'autolayout ou autoresizingMask mettrait correctement en forme votre UIView s. Mais si vous devez effectuer un calcul spécial sur un certain cadre de vue, la réponse d'Eugen fonctionne : appelez layoutIfNeeded sur elle. J'ai le sentiment de ce n'est pas la meilleure solution, mais je n'ai pas encore trouvé mieux.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X