Comment puis-je obtenir la largeur et la hauteur actuelles d'une vue lors de l'utilisation de contraintes de mise en page automatique?

108

Je ne parle pas de la propriété frame, car à partir de là, vous ne pouvez obtenir la taille de la vue que dans le xib. Je parle du moment où la vue est redimensionnée en raison de ses contraintes (peut-être après une rotation, ou en réponse à un événement). Existe-t-il un moyen d'obtenir sa largeur et sa hauteur actuelles?

J'ai essayé d'itérer ses contraintes à la recherche de contraintes de largeur et de hauteur, mais ce n'est pas très clair et échoue lorsqu'il y a des contraintes intrinsèques (car je ne peux pas faire la différence entre les deux). De plus, cela ne fonctionne que s'ils ont réellement des contraintes de largeur et de hauteur, ce qu'ils ne font pas s'ils s'appuient sur d'autres contraintes pour redimensionner.

Pourquoi est-ce si difficile pour moi. ARG!

yuf
la source
J'aurais pensé que les limites de la vue à un moment donné conservaient sa largeur et sa hauteur actuelles. C'est ce qui est ajusté pendant le processus de mise en page.
Phillip Mills
Hmm, je n'ai jamais pensé à vérifier les limites. Je vais le faire maintenant.
yuf

Réponses:

246

La réponse est [view layoutIfNeeded].

Voici pourquoi:

Vous obtenez toujours la largeur et la hauteur actuelles de la vue en inspectant view.bounds.size.widthet view.bounds.size.height(ou le cadre, qui est équivalent à moins que vous ne jouiez avec leview.transform ).

Si vous voulez la largeur et la hauteur impliquées par vos contraintes existantes, la réponse n'est pas d'inspecter les contraintes manuellement, car cela vous obligerait à réimplémenter toute la logique de résolution de contraintes du système de mise en page automatique afin de les interpréter. contraintes. Au lieu de cela, vous devez simplement demander à la disposition automatique de mettre à jour cette disposition , afin qu'elle résout les contraintes et mette à jour la valeur de view.bounds avec la solution correcte, puis vous inspectez la vue.bounds.

Comment demander à la mise en page automatique de mettre à jour la mise en page? Appelez [view setNeedsLayout]si vous voulez que la disposition automatique mette à jour la disposition au prochain tour de la boucle d'exécution.

Cependant, si vous souhaitez qu'il mette à jour immédiatement la mise en page, afin de pouvoir accéder immédiatement à la nouvelle valeur de limites ultérieurement dans votre fonction actuelle, ou à un autre moment avant le tour de la boucle d'exécution, vous devez appeler [view setNeedsLayout]et [view layoutIfNeeded].

Vous avez posé une deuxième question: "comment puis-je changer une contrainte hauteur / largeur si je n'y ai pas de référence directement?".

Si vous créez la contrainte dans IB, la meilleure solution consiste à créer un IBOutlet dans votre contrôleur de vue ou dans votre vue pour y faire directement référence. Si vous avez créé la contrainte dans le code, vous devez conserver une référence dans une propriété faible interne au moment où vous l'avez créée. Si quelqu'un d'autre a créé la contrainte, vous devez la trouver en examinant la propriété view.constraints sur la vue, et éventuellement toute la hiérarchie de vues, et en implémentant une logique qui trouve le NSLayoutConstraint crucial. Ce n'est probablement pas la bonne façon de procéder, car cela vous oblige également à déterminer quelle contrainte particulière a déterminé la taille des limites, alors qu'il n'est pas garanti qu'il y ait une réponse simple à cette question. La valeur finale des bornes pourrait être la solution à un système très compliqué de contraintes multiples,

algue
la source
16
Une excellente réponse et bien expliquée aussi. M'a sauvé après une heure ou deux à m'arracher les cheveux.
Ben Kreeger du
2
layoutIfNeededest génial, et rendra le cadre immédiatement disponible. Cependant, cela forcera également le rendu des contraintes sur toutes les vues dans les sous-arbres de la vue sur laquelle il est appelé. Si vous ajoutez des vues par programme, par exemple, et que vous les appelez layoutIfNeededtoutes dans votre routine récursive, vous constaterez peut-être que votre hiérarchie de vues s'affiche très lentement. (J'ai appris cela à la dure) Comme mentionné dans l'excellente réponse, «setNeedsLayout» est plus efficace et rendra le cadre disponible lors de la prochaine passe de mise en page, ce qui se produit idéalement en 1 / 60e de seconde environ.
shmim
1
Je sais que c'est vieux, mais où appelez-vous cette méthode? Dans initWithCoder?
MayNotBe
4
Pourquoi forcer la mise en page juste pour obtenir les limites? Cela semble inefficace et avec une interface complexe, cela va potentiellement coûter cher. Au lieu de cela, accédez aux dernières limites à partir de viewDidLayoutSubviews ou même de viewWillLayoutSubviews et laissez Cocoa gérer son propre timing de mise en page. Définissez une propriété si vous devez accéder à la valeur ou à l'indicateur pour éviter plusieurs appels en cas de problème.
smileBot
1
Oui, vous ne devez forcer la mise en page que si vous avez besoin d'accéder à la valeur calculée par mise en page automatique avant le tour de la boucle d'exécution - par exemple, dans l'étendue de l'appel de fonction en cours.
algal
8

J'ai eu un problème similaire où je devais ajouter une bordure supérieure et inférieure à un UITableViewqui se redimensionne en fonction de sa configuration de contraintes dans le UIStoryboard. J'ai pu accéder aux contraintes mises à jour avec - (void)viewDidLayoutSubviews. Ceci est utile pour ne pas avoir besoin de sous-classer une vue et de remplacer sa méthode de disposition.

/*** SET TOP AND BOTTOM BORDERS ON TABLE VIEW ***/
- (void)addBorders
{
    CALayer *topBorder           = [CALayer layer];
    topBorder.frame              = CGRectMake(0.0f, self.tableView.frame.origin.y, 320.0f, 0.5f);
    topBorder.backgroundColor    = [UIColor redColor].CGColor;

    CALayer *bottomBorder        = [CALayer layer];
    bottomBorder.frame           = CGRectMake(0.0f, (self.tableView.frame.origin.y + self.tableView.frame.size.height), 320.0f, 0.5f);
    bottomBorder.backgroundColor = [UIColor redColor].CGColor;

    [self.view.layer addSublayer:topBorder];
    [self.view.layer addSublayer:bottomBorder];
}

/*** GET AUTORESIZED FRAME DIMENSIONS ***/
- (void)viewDidLayoutSubviews{
    [self addBorders];
}

Sans appeler la méthode à partir de la viewDidLayoutSubviewméthode, seule la bordure supérieure est dessinée correctement, car la bordure inférieure est quelque part hors écran.

Brandon Lire
la source
3
viewDidLayoutSubviews étant appelé plusieurs fois, vous créez des tonnes de couches ... Vous devez ajouter quelques vérifications d'existence avant d'ajouter une autre couche.
Kalzem
Commentez quelques années de retard ... vous devriez également appeler cette méthode sur la superclasse lors de la substitution avant toute autre chose:[super viewDidLayoutSubviews];
Alejandro Iván
5

Pour ceux qui peuvent encore être confrontés à de tels problèmes, en particulier avec TableviewCell.

Remplacez simplement la méthode:

-(void)layoutSubviews
{
//your code here like drawing a shadow
}

Dans le cas de UITableViewCell ou UICollectionViewCell, créez une sous-classe de la cellule et remplacez la même méthode:

-(void)layoutSubviews
{
//your code here like drawing a shadow
}
Mayank Pahuja
la source
c'est le seul moyen pour moi d'obtenir la taille finale réelle de ma vue
Benoit Jadinon
C'était la seule façon dont j'ai pu obtenir la taille finale de l'une de mes vues contraintes, car avant j'essayais de les obtenir sur awakeFromNib, ce qui ne donnait pas le temps aux sous-vues de se redimensionner en premier. Je vous remercie!
Azin Mehrnoosh le
3

Utiliser -(void)viewWillAppear:(BOOL)animatedet appeler [self.view layoutIfNeeded];- Cela fonctionne, j'ai essayé.

car si vous l'utilisez, -(void)viewDidLayoutSubviewscela fonctionnera certainement, mais cette méthode est appelée chaque fois que votre interface utilisateur demande des mises à jour / modifications. Ce qui sera difficile à gérer. La touche programmable est que vous utilisez une variable booléenne pour éviter une telle boucle d'appels. meilleure utilisation viewWillAppear. Remember viewWillAppearsera également appelé si la vue est à nouveau chargée (sans réallocation).


la source
2

Le cadre est toujours valide. En fin de compte, la vue utilise sa propriété frame pour se mettre en page. Il calcule ce cadre en fonction de toutes les contraintes. Les contraintes ne sont utilisées que pour la mise en page initiale (et à tout moment, layoutSubviews est appelée sur une vue comme après une rotation). Après cela, les informations de position se trouvent dans la propriété frame. Ou voyez-vous autrement?

Borrden
la source
1
Je vois autrement. Les valeurs de trame ne changent pas, même après avoir changé les contraintes de largeur / hauteur directement (en changeant leurs constantes. J'ai un IBOutlet à eux dans le xib)
yuf
Mon problème est unique car je change la contrainte, puis je dois utiliser le cadre dans la même fonction. L'appel de setNeedsLayout ne le met pas à jour immédiatement. Je suppose que je dois d'abord attendre la mise en page, puis utiliser le cadre. Ma deuxième question est, comment puis-je changer une contrainte de hauteur / largeur si je n'ai pas de référence directe?
yuf
1
Vous devriez alors dispatch_async, et cela vous permettra de retarder votre code jusqu'à la trame suivante. Quant à la deuxième partie ... je ne sais pas ... il faudrait itérer toutes les contraintes et chercher laquelle est applicable ... Les IBOutlets sont beaucoup plus faciles.
borrrden
C'est vrai pour IBOutlets, mais je souhaite écrire une fonction dans une catégorie pour un UIView avec lequel je n'aurai pas d'IB pour travailler.
yuf