cocoa - videos - xcode 10 wwdc




Cocoa: Quelle est la différence entre le cadre et les limites? (5)

UIView et ses sous-classes ont toutes les propriétés frame et bounds . Quelle est la différence?



Réponse courte

frame = emplacement et taille d'une vue en utilisant le système de coordonnées de la vue parente

  • Important pour: placer la vue dans le parent

bounds = emplacement et taille d'une vue utilisant son propre système de coordonnées

  • Important pour: placer le contenu ou les sous-vues de la vue en elle-même

Réponse détaillée

Pour m'aider à me souvenir du cadre , je pense à un cadre photo sur un mur . Le cadre photo est comme la bordure d'une vue. Je peux accrocher l'image partout où je veux sur le mur. De la même manière, je peux mettre une vue n'importe où je veux dans une vue parent (aussi appelée une vue d'ensemble). La vue des parents est comme le mur. L'origine du système de coordonnées dans iOS est le coin supérieur gauche. Nous pouvons placer notre vue à l'origine de la superposition en définissant les coordonnées xy du cadre de vue sur (0, 0), ce qui revient à accrocher notre image dans le coin supérieur gauche du mur. Pour le déplacer vers la droite, augmentez x, pour le diminuer, augmentez y.

Pour m'aider à me souvenir des limites , je pense à un terrain de basket-ball où parfois le basketball est assommé . Vous dribble la balle partout sur le terrain de basketball, mais vous ne vous souciez pas vraiment de la cour elle-même. Ce pourrait être dans un gymnase, ou à l'extérieur dans une école secondaire, ou devant votre maison. Cela n'a pas d'importance. Vous voulez juste jouer au basket. De la même manière, le système de coordonnées pour les limites d'une vue se soucie uniquement de la vue elle-même. Il ne sait rien sur l'emplacement de la vue dans la vue parente. L'origine des bornes (le point (0, 0) par défaut) est le coin supérieur gauche de la vue. Toutes les sous-vues que cette vue a sont présentées en relation avec ce point. C'est comme prendre le ballon de basket dans le coin avant gauche du terrain.

Maintenant, la confusion vient quand vous essayez de comparer le cadre et les limites. En fait, il n'est pas aussi mauvais qu'il semble au premier abord, cependant. Utilisons quelques images pour nous aider à comprendre.

Cadre vs limites

Dans la première image à gauche, nous avons une vue située en haut à gauche de la vue parente. Le rectangle jaune représente le cadre de la vue. Sur la droite, nous voyons à nouveau la vue, mais cette fois la vue parente n'est pas montrée. C'est parce que les limites ne connaissent pas la vue parente. Le rectangle vert représente les limites de la vue. Le point rouge dans les deux images représente l' origine de l'image ou des limites.

Frame
    origin = (0, 0)
    width = 80
    height = 130

Bounds 
    origin = (0, 0)
    width = 80
    height = 130

Donc, le cadre et les limites étaient exactement les mêmes dans cette image. Regardons un exemple où ils sont différents.

Frame
    origin = (40, 60)  // That is, x=40 and y=60
    width = 80
    height = 130

Bounds 
    origin = (0, 0)
    width = 80
    height = 130

Vous pouvez donc voir que modifier les coordonnées xy du cadre le déplace dans la vue parente. Mais le contenu de la vue elle-même semble toujours le même. Les limites n'ont aucune idée que quelque chose est différent.

Jusqu'à présent, la largeur et la hauteur du cadre et des limites ont été exactement les mêmes. Ce n'est pas toujours vrai, cependant. Regardez ce qui se passe si nous faisons pivoter la vue de 20 degrés dans le sens des aiguilles d'une montre. (La rotation est effectuée à l'aide de transformations.Voir la documentation et ces exemples de view et de couche pour plus d'informations.)

Frame
    origin = (20, 52)  // These are just rough estimates.
    width = 118
    height = 187

Bounds 
    origin = (0, 0)
    width = 80
    height = 130

Vous pouvez voir que les limites sont toujours les mêmes. Ils ne savent toujours pas que quelque chose s'est passé! Les valeurs de cadre ont toutes changé, cependant.

Maintenant, il est un peu plus facile de voir la différence entre le cadre et les limites, n'est-ce pas? L'article Vous ne comprenez probablement pas les cadres et les limites définit un cadre de vue comme

... la plus petite boîte englobante de cette vue par rapport au système de coordonnées de ses parents, y compris les transformations appliquées à cette vue.

Il est important de noter que si vous transformez une vue, le cadre devient indéfini. Donc, en fait, le cadre jaune que j'ai dessiné autour des limites vertes tournées dans l'image ci-dessus n'existe jamais réellement. Cela signifie que si vous faites une rotation, une mise à l'échelle ou une autre transformation, vous ne devez plus utiliser les valeurs d'image. Vous pouvez toujours utiliser les valeurs des limites, cependant. Les docs d'Apple préviennent:

Important: Si la propriété transform une vue ne contient pas la transformation d'identité, le cadre de cette vue est indéfini et les résultats de ses comportements d'autoresalisation sont également définis.

Plutôt malheureux à propos de l'autoresizing .... Il y a quelque chose que vous pouvez faire, cependant.

L'état des docs Apple:

Lorsque vous modifiez la propriété de transform de votre vue, toutes les transformations sont effectuées par rapport au point central de la vue.

Donc, si vous avez besoin de déplacer une vue dans le parent après une transformation, vous pouvez le faire en changeant les coordonnées view.center . Comme frame , le center utilise le système de coordonnées de la vue parente.

Ok, débarrassons-nous de notre rotation et concentrons-nous sur les limites. Jusqu'à présent, l'origine des limites est toujours restée à (0, 0). Cela ne doit pas, cependant. Que se passe-t-il si notre vue a une grande sous-vue trop grande pour être affichée en même temps? Nous allons en faire un UIImageView avec une grande image. Voici à nouveau notre deuxième image d'en haut, mais cette fois nous pouvons voir à quoi ressemblerait le contenu de la sous-vue de notre vue.

Frame
    origin = (40, 60)
    width = 80
    height = 130

Bounds 
    origin = (0, 0)
    width = 80
    height = 130

Seul le coin supérieur gauche de l'image peut tenir dans les limites de la vue. Regardez maintenant ce qui se passe si nous changeons les coordonnées d'origine des limites.

Frame
    origin = (40, 60)
    width = 80
    height = 130

Bounds 
    origin = (280, 70)
    width = 80
    height = 130

Le cadre n'a pas été déplacé dans la vue supérieure, mais le contenu à l'intérieur du cadre a changé car l'origine du rectangle des limites commence à une partie différente de la vue. C'est l'idée derrière un UIScrollView et ses sous-classes (par exemple, un UITableView ). Voir Comprendre UIScrollView pour plus d'explications.

Quand utiliser le cadre et quand utiliser les limites

Étant donné que l' frame associe l'emplacement d'une vue à sa vue parente, vous l'utilisez lorsque vous effectuez des modifications à l'extérieur , par exemple en changeant sa largeur ou en trouvant la distance entre la vue et le haut de sa vue parente.

Utilisez les bounds lorsque vous effectuez des modifications internes , comme dessiner des objets ou organiser des sous-vues dans la vue. Utilisez également les limites pour obtenir la taille de la vue si vous avez effectué une transfomation dessus.

Articles pour plus de recherche:

Apple docs

Questions connexes

Autres ressources

Pratique toi

En plus de lire les articles ci-dessus, cela m'aide beaucoup à faire une application de test. Vous pourriez vouloir essayer de faire quelque chose de similaire. (J'ai eu l'idée de ce cours vidéo mais malheureusement ce n'est pas gratuit.)

Voici le code pour votre référence:

import UIKit

class ViewController: UIViewController {


    @IBOutlet weak var myView: UIView!

    // Labels
    @IBOutlet weak var frameX: UILabel!
    @IBOutlet weak var frameY: UILabel!
    @IBOutlet weak var frameWidth: UILabel!
    @IBOutlet weak var frameHeight: UILabel!
    @IBOutlet weak var boundsX: UILabel!
    @IBOutlet weak var boundsY: UILabel!
    @IBOutlet weak var boundsWidth: UILabel!
    @IBOutlet weak var boundsHeight: UILabel!
    @IBOutlet weak var centerX: UILabel!
    @IBOutlet weak var centerY: UILabel!
    @IBOutlet weak var rotation: UILabel!

    // Sliders
    @IBOutlet weak var frameXSlider: UISlider!
    @IBOutlet weak var frameYSlider: UISlider!
    @IBOutlet weak var frameWidthSlider: UISlider!
    @IBOutlet weak var frameHeightSlider: UISlider!
    @IBOutlet weak var boundsXSlider: UISlider!
    @IBOutlet weak var boundsYSlider: UISlider!
    @IBOutlet weak var boundsWidthSlider: UISlider!
    @IBOutlet weak var boundsHeightSlider: UISlider!
    @IBOutlet weak var centerXSlider: UISlider!
    @IBOutlet weak var centerYSlider: UISlider!
    @IBOutlet weak var rotationSlider: UISlider!

    // Slider actions
    @IBAction func frameXSliderChanged(sender: AnyObject) {
        myView.frame.origin.x = CGFloat(frameXSlider.value)
        updateLabels()
    }
    @IBAction func frameYSliderChanged(sender: AnyObject) {
        myView.frame.origin.y = CGFloat(frameYSlider.value)
        updateLabels()
    }
    @IBAction func frameWidthSliderChanged(sender: AnyObject) {
        myView.frame.size.width = CGFloat(frameWidthSlider.value)
        updateLabels()
    }
    @IBAction func frameHeightSliderChanged(sender: AnyObject) {
        myView.frame.size.height = CGFloat(frameHeightSlider.value)
        updateLabels()
    }
    @IBAction func boundsXSliderChanged(sender: AnyObject) {
        myView.bounds.origin.x = CGFloat(boundsXSlider.value)
        updateLabels()
    }
    @IBAction func boundsYSliderChanged(sender: AnyObject) {
        myView.bounds.origin.y = CGFloat(boundsYSlider.value)
        updateLabels()
    }
    @IBAction func boundsWidthSliderChanged(sender: AnyObject) {
        myView.bounds.size.width = CGFloat(boundsWidthSlider.value)
        updateLabels()
    }
    @IBAction func boundsHeightSliderChanged(sender: AnyObject) {
        myView.bounds.size.height = CGFloat(boundsHeightSlider.value)
        updateLabels()
    }
    @IBAction func centerXSliderChanged(sender: AnyObject) {
        myView.center.x = CGFloat(centerXSlider.value)
        updateLabels()
    }
    @IBAction func centerYSliderChanged(sender: AnyObject) {
        myView.center.y = CGFloat(centerYSlider.value)
        updateLabels()
    }
    @IBAction func rotationSliderChanged(sender: AnyObject) {
        let rotation = CGAffineTransform(rotationAngle: CGFloat(rotationSlider.value))
        myView.transform = rotation
        updateLabels()
    }

    private func updateLabels() {

        frameX.text = "frame x = \(Int(myView.frame.origin.x))"
        frameY.text = "frame y = \(Int(myView.frame.origin.y))"
        frameWidth.text = "frame width = \(Int(myView.frame.width))"
        frameHeight.text = "frame height = \(Int(myView.frame.height))"
        boundsX.text = "bounds x = \(Int(myView.bounds.origin.x))"
        boundsY.text = "bounds y = \(Int(myView.bounds.origin.y))"
        boundsWidth.text = "bounds width = \(Int(myView.bounds.width))"
        boundsHeight.text = "bounds height = \(Int(myView.bounds.height))"
        centerX.text = "center x = \(Int(myView.center.x))"
        centerY.text = "center y = \(Int(myView.center.y))"
        rotation.text = "rotation = \((rotationSlider.value))"

    }

}

Le cadre est le rectangle qui définit l'UIView par rapport à sa vue d'ensemble .

Le rectangle bound est la plage de valeurs qui définit le système de coordonnées de NSView.

c'est-à-dire que tout ce qui est dans ce rectangle s'affichera réellement dans l'UIView.


Les limites d'un UIView sont le rectangle , exprimé comme un emplacement (x, y) et une taille (largeur, hauteur) par rapport à son propre système de coordonnées (0,0).

Le cadre d'un UIView est le rectangle , exprimé sous la forme d'un emplacement (x, y) et d'une taille (largeur, hauteur) par rapport à l'aperçu dans lequel il est contenu.

Imaginons donc une vue d'une taille de 100x100 (largeur x hauteur) positionnée à 25,25 (x, y) de sa vue d'ensemble. Le code suivant imprime les limites et le cadre de cette vue:

// This method is in the view controller of the superview
- (void)viewDidLoad {
    [super viewDidLoad];

    NSLog(@"bounds.origin.x: %f", label.bounds.origin.x);
    NSLog(@"bounds.origin.y: %f", label.bounds.origin.y);
    NSLog(@"bounds.size.width: %f", label.bounds.size.width);
    NSLog(@"bounds.size.height: %f", label.bounds.size.height);

    NSLog(@"frame.origin.x: %f", label.frame.origin.x);
    NSLog(@"frame.origin.y: %f", label.frame.origin.y);
    NSLog(@"frame.size.width: %f", label.frame.size.width);
    NSLog(@"frame.size.height: %f", label.frame.size.height);
}

Et la sortie de ce code est:

bounds.origin.x: 0
bounds.origin.y: 0
bounds.size.width: 100
bounds.size.height: 100

frame.origin.x: 25
frame.origin.y: 25
frame.size.width: 100
frame.size.height: 100

Ainsi, nous pouvons voir que dans les deux cas, la largeur et la hauteur de la vue sont les mêmes, que nous regardions les limites ou le cadre. Ce qui est différent est le positionnement x, y de la vue. Dans le cas des bornes, les coordonnées x et y sont à 0,0 car ces coordonnées sont relatives à la vue elle-même. Cependant, les coordonnées du cadre x et y sont relatives à la position de la vue dans la vue parente (ce que nous avons dit précédemment à 25,25).

Il y a aussi une excellente présentation qui couvre UIViews. Voir les diapositives 1-20 qui expliquent non seulement la différence entre les images et les limites, mais montrent aussi des exemples visuels.


frame est l'origine (coin supérieur gauche) et la taille de la vue dans le système de coordonnées de sa super vue, cela signifie que vous traduisez la vue dans sa super vue en changeant l'origine du cadre, les limites sont la taille et l'origine de propre système de coordonnées, donc par défaut l'origine des bornes est (0,0).

la plupart du temps le cadre et les limites sont congruents, mais si vous avez une vue du cadre ((140,65), (200,250)) et des limites ((0,0), (200,250)) par exemple et la vue a été inclinée de sorte qu'il se trouve sur son coin inférieur droit, alors les limites seront toujours ((0,0), (200,250)), mais le cadre ne l'est pas.

le cadre sera le plus petit rectangle qui encapsule / entoure la vue, donc le cadre (comme sur la photo) sera ((140,65), (320,320)).

une autre différence est par exemple si vous avez un superView dont les limites sont ((0,0), (200,200)) et cette superView a une sous-vue dont le cadre est ((20,20), (100,100)) et vous avez changé les limites superView à ((20,20), (200,200)), alors la trame de sous-image sera encore ((20,20), (100,100)) mais décalée de (20,20) parce que son système de coordonnées de présentation a été décalé de (20, 20).

J'espère que cela aide quelqu'un.





uiview