Cocoa: ¿Cuál es la diferencia entre el marco y los límites?
UIView
y todas sus subclases tienen las propiedades frame
y bounds
. ¿Cual es la diferencia?
Los límites de una UIView son el rectángulo , expresado como una ubicación (x,y) y un tamaño (ancho,alto) en relación con su propio sistema de coordenadas (0,0).
El marco de una UIView es el rectángulo , expresado como una ubicación (x,y) y un tamaño (ancho,alto) en relación con la supervista que contiene.
Entonces, imagine una vista que tiene un tamaño de 100x100 (ancho x alto) ubicada en 25,25 (x,y) de su supervista. El siguiente código imprime los límites y el marco de esta vista:
// 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);
}
Y la salida de este código es:
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
Entonces, podemos ver que en ambos casos, el ancho y el alto de la vista son los mismos independientemente de si estamos mirando los límites o el marco. Lo que es diferente es el posicionamiento x,y de la vista. En el caso de los límites, las coordenadas xey están en 0,0 ya que estas coordenadas son relativas a la vista misma. Sin embargo, las coordenadas xey del marco son relativas a la posición de la vista dentro de la vista principal (que anteriormente dijimos que estaba en 25,25).
También hay una gran presentación que cubre UIViews. Vea las diapositivas 1 a 20, que no solo explican la diferencia entre marcos y límites, sino que también muestran ejemplos visuales.
Respuesta corta
marco = la ubicación y el tamaño de una vista usando el sistema de coordenadas de la vista principal
- Importante para: colocar la vista en el padre
límites = la ubicación y el tamaño de una vista usando su propio sistema de coordenadas
- Importante para: colocar el contenido o las subvistas de la vista dentro de sí misma
Respuesta detallada
Para ayudarme a recordar el marco , pienso en el marco de un cuadro en la pared . El marco del cuadro es como el borde de una vista. Puedo colgar el cuadro en cualquier lugar que quiera en la pared. De la misma manera, puedo colocar una vista en cualquier lugar que quiera dentro de una vista principal (también llamada supervista). La vista principal es como la pared. El origen del sistema de coordenadas en iOS está arriba a la izquierda. Podemos poner nuestra vista en el origen de la supervista estableciendo las coordenadas xy del marco de vista en (0, 0), que es como colgar nuestra imagen en la esquina superior izquierda de la pared. Para moverlo hacia la derecha, aumente x, para moverlo hacia abajo aumente y.
Para ayudarme a recordar los límites , pienso en una cancha de baloncesto donde a veces la pelota sale fuera de los límites . Estás driblando la pelota por toda la cancha de baloncesto, pero realmente no te importa dónde está la cancha. Podría ser en un gimnasio, afuera en una escuela secundaria o frente a tu casa. No importa. Sólo quieres jugar baloncesto. De la misma manera, el sistema de coordenadas de los límites de una vista sólo se preocupa por la vista misma. No sabe nada acerca de dónde se encuentra la vista en la vista principal. El origen de los límites (punto (0, 0) por defecto) es la esquina superior izquierda de la vista. Cualquier subvista que tenga esta vista se establece en relación con este punto. Es como llevar la pelota de baloncesto a la esquina delantera izquierda de la cancha.
Ahora la confusión surge cuando intentas comparar el marco y los límites. Aunque en realidad no es tan malo como parece al principio. Usemos algunas imágenes para ayudarnos a entender.
Marco versus límites
En la primera imagen de la izquierda tenemos una vista que se encuentra en la parte superior izquierda de su vista principal. El rectángulo amarillo representa el marco de la vista. A la derecha volvemos a ver la vista, pero esta vez no se muestra la vista principal. Esto se debe a que los límites no conocen la vista principal. El rectángulo verde representa los límites de la vista. El punto rojo en ambas imágenes representa el origen del marco o límites.
Frame
origin = (0, 0)
width = 80
height = 130
Bounds
origin = (0, 0)
width = 80
height = 130
Entonces el marco y los límites eran exactamente los mismos en esa imagen. Veamos un ejemplo en el que son diferentes.
Frame
origin = (40, 60) // That is, x=40 and y=60
width = 80
height = 130
Bounds
origin = (0, 0)
width = 80
height = 130
Entonces puede ver que cambiar las coordenadas xy del marco lo mueve en la vista principal. Pero el contenido de la vista en sí sigue siendo exactamente el mismo. Los límites no tienen idea de que algo es diferente.
Hasta ahora, el ancho y alto tanto del marco como de los límites han sido exactamente los mismos. Sin embargo, eso no siempre es cierto. Mira lo que pasa si giramos la vista 20 grados en el sentido de las agujas del reloj. (La rotación se realiza mediante transformaciones. Consulte la documentación y estos ejemplos de vistas y capas para obtener más información).
Frame
origin = (20, 52) // These are just rough estimates.
width = 118
height = 187
Bounds
origin = (0, 0)
width = 80
height = 130
Puedes ver que los límites siguen siendo los mismos. ¡Todavía no saben que ha pasado algo! Sin embargo, todos los valores del marco han cambiado.
Ahora es un poco más fácil ver la diferencia entre marco y límites, ¿no? El artículo Probablemente no entiendes los marcos y límites define un marco de visualización como
...el cuadro delimitador más pequeño de esa vista con respecto a su sistema de coordenadas principal, incluida cualquier transformación aplicada a esa vista.
Es importante tener en cuenta que si transforma una vista, el marco deja de estar definido. En realidad, el marco amarillo que dibujé alrededor de los límites verdes rotados en la imagen de arriba nunca existe. Eso significa que si rotas, escalas o haces alguna otra transformación, entonces no deberías usar más los valores del marco. Sin embargo, aún puedes usar los valores de los límites. Los documentos de Apple advierten:
Importante: si la propiedad de una vista
transform
no contiene la transformación de identidad, el marco de esa vista no está definido y tampoco lo están los resultados de sus comportamientos de tamaño automático.
Es bastante desafortunado lo del tamaño automático... Sin embargo, hay algo que puedes hacer.
Los documentos de Apple afirman:
Al modificar la
transform
propiedad de su vista, todas las transformaciones se realizan en relación con el punto central de la vista.
Entonces, si necesita mover una vista en el padre después de realizar una transformación, puede hacerlo cambiando las view.center
coordenadas. Me gusta frame
, center
utiliza el sistema de coordenadas de la vista principal.
Bien, deshagámonos de nuestra rotación y centrémonos en los límites. Hasta ahora, el origen de los límites siempre se ha mantenido en (0, 0). Aunque no es necesario. ¿Qué pasa si nuestra vista tiene una subvista grande que es demasiado grande para mostrarla toda a la vez? Lo haremos UIImageView
con una imagen grande. Aquí está nuevamente nuestra segunda imagen desde arriba, pero esta vez podemos ver cómo se vería todo el contenido de la subvista de nuestra vista.
Frame
origin = (40, 60)
width = 80
height = 130
Bounds
origin = (0, 0)
width = 80
height = 130
Sólo la esquina superior izquierda de la imagen puede caber dentro de los límites de la vista. Ahora mira lo que sucede si cambiamos las coordenadas de origen de los límites.
Frame
origin = (40, 60)
width = 80
height = 130
Bounds
origin = (280, 70)
width = 80
height = 130
El marco no se ha movido en la supervista, pero el contenido dentro del marco ha cambiado porque el origen del rectángulo de límites comienza en una parte diferente de la vista. Esta es la idea detrás de a UIScrollView
y sus subclases (por ejemplo, a UITableView
). Consulte Comprensión de UIScrollView para obtener más explicaciones.
Cuándo usar marco y cuándo usar límites
Dado que frame
relaciona la ubicación de una vista en su vista principal, la usa cuando realiza cambios externos , como cambiar su ancho o encontrar la distancia entre la vista y la parte superior de su vista principal.
Úselo bounds
cuando esté realizando cambios internos , como dibujar cosas u organizar subvistas dentro de la vista. Utilice también los límites para obtener el tamaño de la vista si ha realizado alguna transformación en ella.
Artículos para futuras investigaciones:
documentos de Apple
- Ver geometría
- Puntos de vista
- Arquitectura de vistas y ventanas
Preguntas relacionadas con StackOverflow
- Marco, límites y centro de UIView
- Marco, límites, centro, origen de UIView, ¿cuándo usar qué?
- Tamaño de marco/ventana "incorrecto" después de la reorientación en iPhone
Otros recursos
- Probablemente no entiendas los marcos y los límites
- Fundamentos de iOS: marcos, límites y CGGeometry
- CS193p Conferencia 5: Vistas, dibujo, animación
Practica tú mismo
In addition to reading the above articles, it helps me a lot to make a test app. You might want to try to do something similar. (I got the idea from this video course but unfortunately it isn't free.)
Here is the code for your reference:
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))"
}
}