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

587

UIViewet ses sous-classes ont toutes les propriétés frameet bounds. Quelle est la différence?

mk12
la source
1
Comprendre le cadre macoscope.com/blog/understanding-frame
onmyway133
pour moi l'explication la plus concise est ici: videos.raywenderlich.com/courses/99-scroll-view-school/lessons/…
Vlad
1
Il n'y a rien de concis dans une vidéo de 2h30. Je peux lire une phrase ou deux beaucoup plus vite que 2h30.
dsjoerg
Pour moi, c'est beaucoup plus clair si je pense de cette façon: Frame = Bounds & Position
Serg

Réponses:

902

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

Le cadre d'une vue UIV est le rectangle , exprimé en un emplacement (x, y) et une taille (largeur, hauteur) par rapport à la vue d'ensemble dans laquelle il est contenu.

Imaginez 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, c'est le positionnement x, y de la vue. Dans le cas des limites, les coordonnées x et y sont à 0,0 car ces coordonnées sont relatives à la vue elle-même. Cependant, les coordonnées x et y du cadre sont relatives à la position de la vue dans la vue parent (qui, nous l'avons dit plus tôt, était à 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 cadres et les limites, mais montrent également des exemples visuels.

chek
la source
37
Ainsi, les x, y pour les bornes ne seront pas toujours 0,0 puisque c'est l'emplacement de l'objet en ... lui-même. Ou pourriez-vous donner un scénario où il ne serait pas?
mk12
5
Pour autant que je sache, l'origine des limites sera toujours 0,0
shek
77
En fait, le bounds.origin peut être quelque chose en plus de 0,0. Utilisez setBoundsOrigin: pour déplacer / traduire l'origine. Voir "View Geometry" dans le "View Programming Guide for Cocoa" pour plus d'informations.
Meltemi
127
UIScrollView est un exemple où les limites peuvent changer pour afficher différentes régions de contenu dans une vue.
Brad Larson
92
Un petit conseil: l'utilisation de NSStringFromCGRect peut vous faire gagner du temps pour enregistrer les rects.
béryllium
647

Réponse courte

frame = emplacement et taille d'une vue à l'aide du système de coordonnées de la vue parent

  • Important pour: placer la vue dans le parent

limites = emplacement et taille d'une vue à l'aide de son propre système de coordonnées

  • Important pour: placer le contenu ou les sous-vues de la vue en soi

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 la photo où je veux au mur. De la même manière, je peux placer une vue n'importe où dans une vue parent (également appelée superview). La vue parent est comme le mur. L'origine du système de coordonnées dans iOS est en haut à gauche. Nous pouvons placer notre vue à l'origine de la vue d'ensemble 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, augmenter x, pour le déplacer vers le bas, augmenter y.

Pour m'aider à me souvenir des limites , je pense à un terrain de basket où parfois le basket-ball est frappé hors des limites . Vous dribblez le ballon partout sur le terrain de basket, mais peu vous importe où se trouve le terrain lui-même. Cela pourrait être dans une salle de sport, à l'extérieur dans un lycée ou devant votre maison. Ça 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 ne se soucie que de la vue elle-même. Il ne sait rien de l'emplacement de la vue dans la vue parent. L'origine des limites (point (0, 0) par défaut) est le coin supérieur gauche de la vue. Toutes les sous-vues de cette vue sont présentées par rapport à ce point. C'est comme amener le ballon de basket dans le coin avant gauche du terrain.

Maintenant, la confusion survient lorsque vous essayez de comparer le cadre et les limites. En fait, ce n'est pas aussi mauvais qu'il n'y paraît au premier abord. Utilisons quelques images pour nous aider à comprendre.

Frame vs Bounds

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

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

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

entrez la description de l'image ici

Le cadre et les limites étaient donc exactement les mêmes sur 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

entrez la description de l'image ici

Vous pouvez donc voir que la modification des coordonnées xy du cadre le déplace dans la vue parent. Mais le contenu de la vue elle-même est toujours identique. 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 étaient exactement les mêmes. Ce n'est pas toujours vrai, cependant. Regardez ce qui se passe si nous tournons la vue de 20 degrés dans le sens des aiguilles d'une montre. (La rotation est effectuée à l'aide de transformations. Consultez la documentation et ces exemples de vue 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

entrez la description de l'image ici

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

Maintenant, il est un peu plus facile de voir la différence entre le cadre et les limites, n'est-ce pas? L'article que 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 toutes 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 pivoter, mettez à l'échelle ou effectuez une autre transformation, vous ne devriez plus utiliser les valeurs de cadre. Cependant, vous pouvez toujours utiliser les valeurs des limites. Les documents Apple préviennent:

Important: Si la transformpropriété d' une vue ne contient pas la transformation d'identité, le cadre de cette vue n'est pas défini, tout comme les résultats de ses comportements de redimensionnement automatique.

Plutôt malheureux à propos du redimensionnement automatique ... Il y a cependant quelque chose que vous pouvez faire.

Les documents Apple indiquent:

Lors de la modification de la transformpropriété de votre vue, toutes les transformations sont effectuées par rapport au point central de la vue.

Donc, si vous devez déplacer une vue dans le parent après avoir effectué une transformation, vous pouvez le faire en modifiant les view.centercoordonnées. Comme frame, centerutilise le système de coordonnées de la vue parent.

Ok, débarrassons-nous de notre rotation et concentrons-nous sur les limites. Jusqu'à présent, l'origine des bornes est toujours restée à (0, 0). Ce n'est pas nécessaire, 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 UIImageViewavec une grande image. Voici à nouveau notre deuxième image d'en haut, mais cette fois, nous pouvons voir à quoi ressemblerait tout 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

entrez la description de l'image ici

Seul le coin supérieur gauche de l'image peut tenir dans les limites de la vue. Maintenant, regardez ce qui se passe si nous modifions les coordonnées d'origine des bornes.

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

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

entrez la description de l'image ici

Le cadre n'a pas bougé dans la vue d'ensemble, 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 toute l'idée derrière a UIScrollViewet ses sous-classes (par exemple, a UITableView). Voir Comprendre UIScrollView pour plus d'explications.

Quand utiliser un cadre et quand utiliser des limites

Étant donné frameque l'emplacement d'une vue est lié à sa vue parent, vous l'utilisez lorsque vous effectuez des modifications vers l'extérieur , comme changer sa largeur ou trouver la distance entre la vue et le haut de sa vue parent.

Utilisez le boundslorsque 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 à approfondir:

Documents Apple

Questions StackOverflow connexes

Autres ressources

Pratiquez-vous

En plus de lire les articles ci-dessus, cela m'aide beaucoup à faire une application de test. Vous voudrez peut-être 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.)

entrez la description de l'image ici

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))"

    }

}
Suragch
la source
Il semble que la largeur et la hauteur de la borne soient redondantes et qu'une propriété "origine" aurait été suffisante (comme c'était le cas avec Flash).
Ian Warburton
utilisez des limites pour "comme dessiner des choses ou organiser des sous-vues dans la vue". vous voulez dire que si j'ai un viewController qui a deux boutons, je devrais placer les boutons en utilisant les «limites» de la vue du viewController?! J'ai toujours utilisé le cadre de la vue ...
Honey
@Honey, je suis un peu rouillé sur mes compétences iOS depuis que je travaille sur Android depuis un an. Je crois que la vue racine d'un View Controller remplit l'écran, donc son cadre et ses limites devraient être les mêmes.
Suragch
5
Création du référentiel github.com/maniramezan/FrameVsBounds.git basé sur l'exemple d'application donné ici pour aider à mieux comprendre boundsvs framed'une vue.
manman
1
@IanWarburton Lorsque la vue pivote, la largeur et la hauteur de la limite ne correspondent pas à celles du cadre et ne sont donc pas redondantes.
Edward Brey il y a
43

essayez d'exécuter le code ci-dessous

- (void)viewDidLoad {
    [super viewDidLoad];
    UIWindow *w = [[UIApplication sharedApplication] keyWindow];
    UIView *v = [w.subviews objectAtIndex:0];

    NSLog(@"%@", NSStringFromCGRect(v.frame));
    NSLog(@"%@", NSStringFromCGRect(v.bounds));
}

la sortie de ce code est:

l'orientation du périphérique du boîtier est Portrait

{{0, 0}, {768, 1024}}
{{0, 0}, {768, 1024}}

l'orientation du périphérique du boîtier est Paysage

{{0, 0}, {768, 1024}}
{{0, 0}, {1024, 768}}

évidemment, vous pouvez voir la différence entre le cadre et les limites

tristan
la source
2
ce n'est plus vrai (iOS 8)
Julian Król
13

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

La limite rect est la plage de valeurs qui définit le système de coordonnées de NSView.

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

jorik
la source
10

le cadre 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 d'autre part sont la taille et l'origine dans son 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 que la vue a été inclinée de sorte qu'il se trouve dans son coin inférieur droit, alors les limites seront toujours ((0,0), (200,250)), mais le cadre ne l'est pas.

entrez la description de l'image ici

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 une superView dont les limites sont ((0,0), (200,200)) et que cette superView a une subView dont la trame est ((20,20), (100,100)) et que vous avez changé les limites de superView à ((20,20), (200,200)), la trame de sous-vue sera toujours ((20,20), (100,100)) mais compensée par (20,20) car son système de coordonnées de vue d'ensemble a été compensé par (20, 20).

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

m.eldehairy
la source
concernant le dernier paragraphe: le subView cadre est relatif à lui superView. changer les superViewlimites de n'importe quoi ne fera pas subViewcoïncider l' origine avec elle superView.
staticVoidMan
5

Cadrez son par rapport à son SuperView tandis que Bounds par rapport à son NSView.

Exemple: X = 40, Y = 60. Contient également 3 vues. Ce diagramme vous montre une idée claire.

CADRE

BORNES

RAGHUNATH
la source
4

Permettez-moi d'ajouter mes 5 cents.

Le cadre est utilisé par la vue parent de la vue pour la placer dans la vue parent.

Bounds est utilisé par la vue elle-même pour placer son propre contenu (comme le fait une vue de défilement pendant le défilement). Voir aussi clipsToBounds . Les limites peuvent également être utilisées pour effectuer un zoom avant / arrière sur le contenu de la vue.

Analogie:
Cadre ~
Limites de l' écran du téléviseur ~ Appareil photo (zoom, déplacement, rotation)

Serg
la source
2

Les réponses ci-dessus ont très bien expliqué la différence entre les limites et les cadres.

Limites: taille et emplacement de la vue selon son propre système de coordonnées.

Cadre: taille et emplacement de la vue par rapport à sa SuperView.

Ensuite, il y a confusion en cas de limites, le X, Y sera toujours "0". Ce n'est pas vrai . Cela peut également être compris dans UIScrollView et UICollectionView.

Lorsque les bornes 'x, y ne sont pas 0.
Supposons que nous ayons un UIScrollView. Nous avons implémenté la pagination. L'UIScrollView a 3 pages et la largeur de sa ContentSize est trois fois la largeur de l'écran (supposons que ScreenWidth est de 320). La hauteur est constante (supposons 200).

scrollView.contentSize = CGSize(x:320*3, y : 200)

Ajoutez trois UIImageViews en tant que sous-vues et surveillez de près la valeur x du cadre

let imageView0 = UIImageView.init(frame: CGRect(x:0, y: 0 , width : scrollView.frame.size.width, height : scrollView.frame.size.height))
let imageView1 :  UIImageView.init( frame: CGRect(x:320, y: 0 , width : scrollView.frame.size.width, height : scrollView.frame.size.height))
let imageView2 :  UIImageView.init(frame: CGRect(x:640, y: 0 , width : scrollView.frame.size.width, height : scrollView.frame.size.height))
scrollView.addSubview(imageView0)
scrollView.addSubview(imageView0)
scrollView.addSubview(imageView0)
  1. Page 0: Lorsque le ScrollView est sur la page 0, les limites seront (x: 0, y: 0, largeur: 320, hauteur: 200)

  2. Page 1: faites défiler et passez à la page 1.
    Maintenant, les limites seront (x: 320, y: 0, largeur: 320, hauteur: 200) N'oubliez pas que nous avons dit en ce qui concerne son propre système de coordonnées. Alors maintenant, la "partie visible" de notre ScrollView a son "x" à 320. Regardez le cadre de l'imageView1.

  3. Page 2: faites défiler et passez à la page 2 Limites: (x: 640, y: 0, largeur: 320, hauteur: 200) Encore une fois, regardez le cadre de l'imageView2

Idem pour le cas de UICollectionView. La façon la plus simple de regarder collectionView est de la faire défiler et d'imprimer / enregistrer ses limites et vous aurez l'idée.

Muhammad Nayab
la source
2

Toutes les réponses ci-dessus sont correctes et voici mon opinion à ce sujet:

Pour faire la différence entre le cadre et les limites, le développeur CONCEPTS doit lire:

  1. par rapport à la vue d'ensemble (une vue parent), elle est contenue dans = FRAME
  2. par rapport à son propre système de coordonnées, détermine son emplacement de sous-vue = BOUNDS

"limites" est déroutant car il donne l'impression que les coordonnées sont la position de la vue pour laquelle il est défini. Mais ceux-ci sont en relations et ajustés en fonction des constantes de trame.

écran iPhone

Marina
la source
1

Encore une illustration pour montrer une différence entre le cadre et les limites. Dans cet exemple:

  • View B est une sous-vue de View A
  • View B a été déplacé vers x:60, y: 20
  • View B a été tourné 45 degrees

entrez la description de l'image ici

yoAlex5
la source
0

Cadre vs lié

  • Si vous créez une vue à X: 0, Y: 0, largeur: 400, hauteur: 400, son cadre et ses limites sont identiques.
  • Si vous déplacez cette vue vers X: 400, son cadre reflétera ce changement, mais pas ses limites. N'oubliez pas que les limites sont relatives au propre espace de la vue et, en interne, à la vue, rien n'a changé.
  • Si vous transformez la vue, par exemple en la faisant pivoter ou en l'agrandissant, le cadre changera pour refléter cela, mais les limites ne changeront toujours pas - en ce qui concerne la vue en interne, elle n'a pas changé.
  • Si vous modifiez les limites, cela modifiera le contenu à l'intérieur du cadre car l'origine du rectangle des limites commence à une partie différente de la vue.
Anit Kumar
la source
-1

frame = emplacement et taille d'une vue à l'aide du système de coordonnées de la vue parent

limites = emplacement et taille d'une vue à l'aide de son propre système de coordonnées

Une vue suit sa taille et son emplacement à l'aide de deux rectangles: un rectangle de cadre et un rectangle de limites. Le rectangle du cadre définit l'emplacement et la taille de la vue dans la vue d'ensemble à l'aide du système de coordonnées de la vue d'ensemble. Le rectangle de délimitation définit le système de coordonnées intérieures utilisé lors du dessin du contenu de la vue, y compris l'origine et la mise à l'échelle. La figure 2-1 montre la relation entre le rectangle du cadre, à gauche, et le rectangle des limites, à droite. »

En bref, le cadre est l'idée d'une vue de superview, et les limites est l'idée propre de la vue. Le fait d'avoir plusieurs systèmes de coordonnées, un pour chaque vue, fait partie de la hiérarchie des vues.

Xab Ion
la source
Où est la figure 2-1?
Alexander Mayatsky