cocoa:框架和界限有什么区别?

UIView及其子类都具有属性framebounds 。 有什么不同?

UIView的边界是矩形 ,表示为相对于其自身坐标系(0,0)的位置(x,y)和大小(宽度,高度)。

UIView的框架是矩形 ,表示为相对于包含在其中的超级视图的位置(x,y)和大小(宽度,高度)。

因此,想象一个尺寸为100×100(宽x高)的视图,位于其超视图的25,25(x,y)处。 下面的代码打印出这个视图的边界和框架:

 // 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); } 

而这个代码的输出是:

 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 

所以,我们可以看到在这两种情况下,视图的宽度和高度是相同的,无论我们是在看边界还是框架。 不同的是视图的x,y定位。 在边界的情况下,x和y坐标是0,0,因为这些坐标是相对于视图本身的。 然而,框架x和y坐标是相对于父视图内的视图的位置(我们之前说过的是25,25)。

还有一个很好的介绍 ,涵盖UIViews。 请参阅第1-20张幻灯片,这些幻灯片不仅解释了帧和边界之间的区别,还显示了视觉示例。

简答

frame =使用父视图的坐标系统的视图的位置和大小

  • 重要的是:将视图放在父项中

边界 =视图的位置和大小使用自己的坐标系统

  • 重要的是:将视图的内容或子视图放置在其自身内

详细的答案

为了帮助我记住框架 ,我想起了墙上一个相框 。 相框就像一个视图的边框。 我可以把照片挂在墙上的任何地方。 以同样的方式,我可以在父视图(也称为超级视图)内的任何位置放置视图。 父母的观点就像墙。 iOS中坐标系的原点是左上angular。 我们可以通过将视图框架的xy坐标设置为(0,0)来将我们的视图放在超视图的起源处,这就像把我们的图片挂在墙的最左上angular一样。 要正确地移动它,增加x,把它向下移动增加y。

为了帮助我记住界限 ,我想到了一个篮球场 ,有时候篮球会被打破 。 你在篮球场上运球,但你真的不在乎球场在哪里。 它可以在健身房里,也可以在高中外面,或者在你家门前。 没关系。 你只想打篮球。 同样,视图边界的坐标系统也只关心视图本身。 它不知道视图在父视图中的位置。 边界的原点(默认点(0,0))是视图的左上angular。 这个观点所具有的任何子视图都是与这一点相关的。 这就像把篮球带到球场的左上angular。

现在当你试图比较帧和边界时,就会出现混乱。 但实际上并不像起初那样糟糕。 让我们用一些图片来帮助我们理解。

框架与边界

在左边的第一张照片中,我们有一个视图位于其父视图的左上angular。 黄色矩形表示视图的框架。 在右侧,我们再次看到视图,但这次不显示父视图。 那是因为边界不知道父视图。 绿色矩形表示视图的边界。 两幅图像中的红点代表框架的起源或界限。

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

在这里输入图像描述

所以画面中的框架和边界是完全一样的。 我们来看一个他们不同的例子。

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

在这里输入图像描述

所以你可以看到改变框架的xy坐标在父视图中移动它。 但是视图本身的内容看起来还是一模一样的。 界限不知道什么是不同的。

到目前为止,框架和边界的宽度和高度已经完全相同。 但是,这并非总是如此。 看看如果顺时针旋转视angular20度会发生什么。 (使用转换完成旋转。有关详细信息,请参阅文档以及这些视图和图层示例 。)

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

在这里输入图像描述

你可以看到,边界仍然是一样的。 他们还不知道发生了什么事! 虽然帧值已经全部改变了。

现在看到框架和框架之间的区别有点容易了,不是吗? 您可能不了解帧和边界的文章将视图帧定义为

…该视图的最小边界框相对于其父系坐标系,包括应用于该视图的任何转换。

重要的是要注意,如果你转换一个视图,那么该框架变得不确定。 所以实际上,我在上面的图像中围绕旋转的绿色边界画的黄色框架从来不存在。 这意味着如果您旋转,缩放或做其他转换,那么您不应该再使用帧值。 不过,您仍然可以使用边界值。 苹果文档警告:

重要提示:如果视图的transform属性不包含标识转换,那么该视图的框架是未定义的,其自动调整行为的结果也是如此。

相当不幸的autoresizing ….有一些你可以做,虽然。

苹果文档陈述:

修改视图的transform属性时,所有的转换都是相对于视图的中心点执行的。

所以,如果在完成转换后需要在父视图中移动视图,则可以通过更改view.center坐标来实现。 像frame一样, center使用父视图的坐标系。

好吧,让我们摆脱我们的轮换,把重点放在边界上。 到目前为止,边界起源一直停留在(0,0)。 不过,这并不是必须的。 如果我们的观点有一个很大的子视图太大而不能一次显示呢? 我们将使它成为一个大图像的UIImageView 。 这是我们的第二张图片,但是这一次我们可以看到我们视图子视图的全部内容是什么样的。

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

在这里输入图像描述

只有图像的左上angular可以放在视图的边界内。 现在看看如果我们改变边界的原点坐标会发生什么。

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

在这里输入图像描述

框架没有在超级视图中移动,但框架内的内容已经改变,因为界限矩形的起点在视图的不同部分开始。 这是UIScrollView和它的子类(例如,一个UITableView )的整个想法。 请参阅了解UIScrollView以获取更多解释。

何时使用框架以及何时使用界限

由于frame将视图的位置与其父视图相关联,所以当您进行外部更改时 (如更改其宽度或查找视图与其父视图顶部之间的距离),可以使用frame

在进行内部更改时使用bounds ,例如在视图内绘制东西或安排子视图。 如果你已经做了一些转换,也可以使用边界来获得视图的大小。

进一步研究的文章:

苹果文档

  • 查看几何
  • 查看
  • 视图和窗口结构

相关的StackOverflow问题

  • UIView框架,边界和中心
  • UIView的框架,边界,中心,出处,什么时候用什么?
  • 在iPhone重新定向后,“错误的”框架/窗口大小

其他资源

  • 你可能不理解框架和界限
  • iOS基础知识:框架,边界和CGGeometry
  • CS193p讲座5 – 视图,绘画,animation

练习自己

除了阅读上述文章之外,它还能帮助我制作一个testing应用程序。 你可能想尝试做类似的事情。 (我从这个video课程中获得了这个想法,但不幸的是它不是免费的。)

在这里输入图像描述

以下是供您参考的代码:

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

尝试运行下面的代码

 - (void)viewDidLoad { [super viewDidLoad]; UIWindow *w = [[UIApplication sharedApplication] keyWindow]; UIView *v = [w.subviews objectAtIndex:0]; NSLog(@"%@", NSStringFromCGRect(v.frame)); NSLog(@"%@", NSStringFromCGRect(v.bounds)); } 

这个代码的输出是:

案例设备方向是肖像

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

案例设备方向是风景

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

显然,你可以看到框架和边界之间的区别

框架相对于其超视图来定义UIView的矩形。

边界矩形是定义NSView坐标系的值的范围

即任何在这个矩形将实际显示在UIView。

框架是其超视图坐标系中视图的原点(左上angular)和视图的大小,这意味着您通过更改框架原点来翻译其超视图中的视图,另一方面, 边界是其大小和原点自己的坐标系,所以默认边界原点是(0,0)。

大多数情况下框架和边界是一致的,但是如果你有框架((140,65),(200,250))和边界((0,0),(200,250))的视图,并且视图是倾斜的所以它站在它的右下angular,那么界限仍然是((0,0),(200,250)),但是框架不是。

在这里输入图像描述

框架将是封装/包围视图的最小的矩形,所以框架(如照片中)将是((140,65),(320,320))。

另一个区别是,例如,如果你有一个超界面的边界是((0,0),(200,200)),并且这个超视图有一个子视图的框架是((20,20),(100,100)),你改变超视图边界到((20,20),(200,200)),那么子视图框架将仍然是((20,20),(100,100)),但由(20,20)偏移,因为其超视图坐标系被(20, 20)。

我希望这有助于某人。

框架相对于其SuperView而界限相对于其NSView。

例如:X = 40,Y = 60。还包含3个视图。此图显示了清晰的想法。

帧

界