105 votes

Quelle est la relation entre UIView ' s setNeedsLayout, layoutIfNeeded et layoutSubviews ?

Quelqu'un peut-il donner une explication définitive sur la relation entre UIView de setNeedsLayout, layoutIfNeeded et layoutSubviews méthodes? Et un exemple de mise en œuvre où tous les trois seraient utilisés. Merci.

Ce qui me confond, c'est que si j'envoie mon affichage personnalisé un setNeedsLayout message de la très prochaine chose qu'il appelle après cette méthode est layoutSubviews, de sauter à droite sur layoutIfNeeded. Depuis les docs que j'ai attendons à ce que le flux de setNeedsLayout > les causes layoutIfNeeded d'être appelé > les causes layoutSubviews à être appelé.

103voto

n8gray Points 2923

Je suis encore à essayer de comprendre cela par moi-même, afin de prendre cela avec un certain scepticisme et pardonnez-moi si il contient des erreurs.

setNeedsLayout est facile: elle ne fait qu'un drapeau quelque part dans le UIView qui marque comme ayant besoin de mise en page. Qui va les obliger layoutSubviews d'être appelé sur la vue avant le prochain dessin qui se passe. Notez que dans de nombreux cas, vous n'avez pas besoin d'appeler explicitement, en raison de l' autoresizesSubviews de la propriété. Si c'est activé (ce qui est fait par défaut) toute modification à la vue de l'image sera la cause de la vue, pour exposer son sous-vues.

layoutSubviews est la méthode dans laquelle vous faites toutes les choses intéressantes. C'est l'équivalent d' drawRect pour la présentation, si vous voulez. Un exemple trivial:

-(void)layoutSubviews {
    // Child's frame is always equal to ours inset by 8px
    self.subview1.frame = CGRectInset(self.frame, 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.
}

Autant que je sache, layoutIfNeeded n'est généralement pas destiné à être remplacée dans votre sous-classe. C'est une méthode que vous êtes destiné à appeler quand vous voulez une vue de droite maintenant. Apple mise en œuvre pourrait ressembler à quelque chose comme ceci:

-(void)layoutIfNeeded {
    if (self._needsLayout) {
        UIView *sv = self.superview;
        if (sv._needsLayout) {
            [sv layoutIfNeeded];
        } else {
            [self layoutSubviews];
        }
    }
}

Vous appelez layoutIfNeeded sur une vue à la force (et de ses superviews si nécessaire) pour être mis en oeuvre immédiatement.

34voto

goldmine Points 661

Je tiens à ajouter sur n8gray de répondre que, dans certains cas, vous aurez besoin de faire appel setNeedsLayout suivie par layoutIfNeeded.

Par exemple, disons que vous avez écrit une vue personnalisée de l'extension de UIView, dans lequel le positionnement des sous-vues est complexe et ne peut être fait avec autoresizingMask ou iOS6 mise en page automatique. La coutume de positionnement peut être réalisé en remplaçant layoutSubviews.

Comme un exemple, disons que vous avez une vue personnalisée qui a un contentView de la propriété et un edgeInsets propriété qui permet de définir les marges autour de la 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 voulez être capable d'animer l'image changent chaque fois que vous modifiez l' edgeInsets de la propriété, vous devez remplacer la edgeInsets setter comme suit et appelez - setNeedsLayout suivie par 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 faites ce qui suit, si vous modifiez le edgeInsets de la propriété à l'intérieur d'un bloc d'animation, le cadre, le changement de la contentView seront animées.

[UIView animateWithDuration:2 animations:^{
    customView.edgeInsets = UIEdgeInsetsMake(45, 17, 18, 34);
}];

Si vous n'ajoutez pas l'appel à layoutIfNeeded dans le setEdgeInsets méthode, l'animation ne fonctionne pas car la layoutSubviews sera appelée lors du prochain cycle de mise à jour, ce qui équivaut à appeler à l'extérieur du bloc d'animation.

Si vous n'appelez layoutIfNeeded dans le setEdgeInsets méthode, rien ne se passera comme la setNeedsLayout indicateur n'est pas défini.

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