Quelqu'un peut-il donner une explication définitive sur la relation entre UIView's
setNeedsLayout
, layoutIfNeeded
et layoutSubviews
méthodes? Et un exemple d'implémentation où les trois seraient utilisés. Merci.
Ce qui me rend confus, c'est que si j'envoie à ma vue personnalisée un message setNeedsLayout
, la prochaine chose qu'il invoque après cette méthode est layoutSubviews
, en sautant directement sur layoutIfNeeded
. D'après les documents, je m'attendrais à ce que le flux soit setNeedsLayout
> provoque layoutIfNeeded
à être appelé> provoque layoutSubviews
à être appelé.
J'essaie toujours de comprendre cela moi-même, alors prenez cela avec un certain scepticisme et pardonnez-moi s'il contient des erreurs.
setNeedsLayout
est simple: il définit simplement un indicateur quelque part dans l'UIView qui le marque comme ayant besoin d'une mise en page. Cela forcera layoutSubviews
à être appelé dans la vue avant le prochain redessin. Notez que dans de nombreux cas, vous n'avez pas besoin d'appeler cela explicitement, en raison de la propriété autoresizesSubviews
. Si ce paramètre est défini (ce qu'il est par défaut), toute modification du cadre d'une vue entraînera la présentation de ses sous-vues.
layoutSubviews
est la méthode dans laquelle vous faites toutes les choses intéressantes. C'est l'équivalent de drawRect
pour la mise en page, si vous voulez. Un exemple trivial pourrait être:
-(void)layoutSubviews {
// Child's frame is always equal to our bounds inset by 8px
self.subview1.frame = CGRectInset(self.bounds, 8.0, 8.0);
// It seems likely that this is incorrect:
// [self.subview1 layoutSubviews];
// ... and this is correct:
[self.subview1 setNeedsLayout];
// but I don't claim to know definitively.
}
AFAIK layoutIfNeeded
n'est généralement pas destiné à être remplacé dans votre sous-classe. C'est une méthode que vous êtes censé appeler lorsque vous souhaitez disposer une vue en ce moment. L'implémentation d'Apple pourrait ressembler à ceci:
-(void)layoutIfNeeded {
if (self._needsLayout) {
UIView *sv = self.superview;
if (sv._needsLayout) {
[sv layoutIfNeeded];
} else {
[self layoutSubviews];
}
}
}
Vous appelleriez layoutIfNeeded
sur une vue pour la forcer (et ses superviews si nécessaire) à être mis en page immédiatement.
Je voudrais ajouter sur la réponse de n8gray que dans certains cas, vous devrez appeler setNeedsLayout
suivi de layoutIfNeeded
.
Supposons, par exemple, que vous ayez écrit une vue personnalisée étendant UIView, dans laquelle le positionnement des sous-vues est complexe et ne peut pas être effectué avec le redimensionnement automatiqueMask ou iOS6 AutoLayout. Le positionnement personnalisé peut être effectué en remplaçant layoutSubviews
.
Par exemple, disons que vous avez une vue personnalisée qui a une propriété contentView
et une propriété edgeInsets
qui permet de définir les marges autour de contentView. layoutSubviews
ressemblerait à ceci:
- (void) layoutSubviews {
self.contentView.frame = CGRectMake(
self.bounds.Origin.x + self.edgeInsets.left,
self.bounds.Origin.y + self.edgeInsets.top,
self.bounds.size.width - self.edgeInsets.left - self.edgeInsets.right,
self.bounds.size.height - self.edgeInsets.top - self.edgeInsets.bottom);
}
Si vous souhaitez pouvoir animer le changement de cadre à chaque fois que vous modifiez la propriété edgeInsets
, vous devez remplacer le setter edgeInsets
comme suit et appeler setNeedsLayout
suivi de layoutIfNeeded
:
- (void) setEdgeInsets:(UIEdgeInsets)edgeInsets {
_edgeInsets = edgeInsets;
[self setNeedsLayout]; //Indicates that the view needs to be laid out
//at next update or at next call of layoutIfNeeded,
//whichever comes first
[self layoutIfNeeded]; //Calls layoutSubviews if flag is set
}
De cette façon, si vous procédez comme suit, si vous modifiez la propriété edgeInsets à l'intérieur d'un bloc d'animation, le changement d'image de contentView sera animé.
[UIView animateWithDuration:2 animations:^{
customView.edgeInsets = UIEdgeInsetsMake(45, 17, 18, 34);
}];
Si vous n'ajoutez pas l'appel à layoutIfNeeded dans la méthode setEdgeInsets, l'animation ne fonctionnera pas car les layoutSubviews seront appelés au prochain cycle de mise à jour, ce qui équivaut à l'appeler en dehors du bloc d'animation.
Si vous appelez uniquement layoutIfNeeded dans la méthode setEdgeInsets, rien ne se passera car l'indicateur setNeedsLayout n'est pas défini.