可可:框架和边界之间有什么区别?

时间:2009-07-31 00:07:05

标签: cocoa cocoa-touch uiview

UIView及其子类都具有属性framebounds。有什么区别?

12 个答案:

答案 0 :(得分:858)

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

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

因此,想象一个大小为100x100(宽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的great presentation。请参阅幻灯片1-20,其中不仅解释了帧和边界之间的差异,还显示了可视化示例。

答案 1 :(得分:581)

简答

框架 =使用父视图的坐标系

的视图的位置和大小
  • 重要的是:将视图放在父级

界限 =使用自己的坐标系

的视图的位置和大小
  • 重要的是:将视图的内容或子视图置于其中

详细答案

为了帮助我记住框架,我想到墙上的相框。相框就像一个视图的边框。我可以将照片挂在墙上的任何地方。以同样的方式,我可以在父视图(也称为superview)中的任何地方放置视图。父视图就像墙。 iOS中坐标系的原点是左上角。我们可以通过将视图框的x-y坐标设置为(0,0)来将我们的视图放在超视图的原点上,这就像将我们的图片悬挂在墙的左上角一样。要向右移动,请增加x,将其向下移动增加y。

为了帮助我记住界限,我想到了一个篮球场,有时篮球会被淘汰出界。你正在篮球场上运球,但你并不关心球场本身的位置。它可能在健身房,高中以外,或在你家门前。这没关系。你只想打篮球。同样,视图边界的坐标系统只关心视图本身。它对父视图中视图的位置一无所知。界限' origin(默认为point(0,0))是视图的左上角。此视图具有的任何子视图都与此相关。就像把篮球带到球场的左前角一样。

现在,当您尝试比较帧和边界时,会出现混乱。尽管如此,它实际上并不像看起来那么糟糕。让我们使用一些图片来帮助我们理解。

框架与边界

在左边的第一张图片中,我们有一个位于其父视图左上角的视图。 黄色矩形表示视图的框架。在右侧,我们再次看到视图,但这次没有显示父视图。这是因为边界不了解父视图。 绿色矩形表示视图的边界。两个图像中的红点表示帧或边界的原点

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

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

enter image description here

因此该图片中的边框和边界完全相同。让我们看看它们不同的例子。

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

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

enter image description here

因此,您可以看到更改框架的x-y坐标会在父视图中移动它。但是视图本身的内容看起来仍然完全一样。边界不知道有什么不同。

到目前为止,框架和边界的宽度和高度完全相同。但是,这并不总是正确的。看看如果我们顺时针旋转视图20度会发生什么。 (使用转换完成轮换。有关详细信息,请参阅documentation以及这些viewlayer examples。)

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

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

enter image description here

您可以看到界限仍然相同。他们仍然不知道发生了什么事!但是,帧值已经全部改变了。

现在可以更容易地看到框架和边界之间的区别,不是吗?文章You Probably Don't Understand frames and bounds将视图框定义为

  

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

重要的是要注意,如果转换视图,则框架将变为未定义。实际上,我在上图中旋转的绿色边界周围绘制的黄色框架实际上并不存在。这意味着如果您旋转,缩放或进行其他转换,则不应再使用帧值。但是,您仍然可以使用边界值。 Apple文档警告说:

  

重要说明:如果视图的transform属性不包含标识转换,则该视图的框架未定义,因此   其自动化行为的结果。

相当不幸的是自动化......但是你可以做些什么。

The Apple docs state:

  

修改视图的transform属性时,全部   相对于中心点执行变换   图。

因此,如果您确实需要在转换完成后在父级中移动视图,则可以通过更改view.center坐标来完成此操作。与frame类似,center使用父视图的坐标系。

好的,让我们摆脱旋转并专注于界限。到目前为止,边界起源一直保持在(0,0)。但是,它并不是必须的。如果我们的视图有一个太大的子视图太大而无法一次显示怎么办?我们将其设为UIImageView,图片大。这是我们上面的第二张图片,但这次我们可以看到我们观点子视图的整个内容是什么样的。

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

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

enter image description here

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

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

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

enter image description here

框架未在超视图中移动,但框架内的内容已更改,因为边界矩形的原点从视图的不同部分开始。这是UIScrollView及其子类背后的整个想法(例如,UITableView)。有关详细说明,请参阅Understanding UIScrollView

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

由于frame将视图的位置与其父视图相关联,因此您在进行向外更改时会使用它,例如更改其宽度或查找视图之间的距离和其父视图的顶部。

在进行向内更改时使用bounds,例如在视图中绘制内容或排列子视图。如果您对视图进行了一些转换,也可以使用边界来获取视图的大小。

进一步研究的文章:

Apple文档

相关的StackOverflow问题

其他资源

练习自己

除了阅读上述文章外,制作测试应用程序对我有很大帮助。您可能想尝试做类似的事情。 (我从this video course得到了这个想法,但遗憾的是它并不是免费的。)

enter image description here

以下是供您参考的代码:

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

    }

}

答案 2 :(得分:43)

尝试运行以下代码

- (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}}
显然,你可以看到框架和边界之间的区别

答案 3 :(得分:13)

框架是定义UIView的矩形,与其超级视图相关

bounds rect 是定义 NSView坐标系的值的范围。

即。此矩形中的任何内容实际上都会显示在UIView中。

答案 4 :(得分:9)

框架是超视图坐标系中视图的原点(左上角)和大小,这意味着您可以通过更改框架原点来转换其超视图中的视图, bounds 是其自身坐标系中的大小和原点,因此默认情况下,边界原点为(0,0)。

大多数时候帧和边界是一致的,但是如果你有一个框架((140,65),(200,250))和边界((0,0),(200,250))的视图,例如视图被倾斜以便它位于其右下角,然后边界仍将是((0,0),(200,250)),但框架不是。

enter image description here

框架将是封装/环绕视图的最小矩形,因此框架(如照片中)将是((140,65),(320,320))。

另一个区别是,例如,如果你有一个superView的边界是((0,0),(200,200))并且这个superView有一个subView,其框架是((20,20),(100,100))并且你改变了superView绑定到((20,20),(200,200)),然后subView框架仍然是((20,20),(100,100)),但是(20,20)因为其超视图坐标系被offseted而被offseted (20,20)。

我希望这有助于某人。

答案 5 :(得分:4)

将其相对于其SuperView设置为相对于其NSView相对于边界。

示例:X = 40,Y = 60.还包含3个Views.This图表显示了清晰的想法。

FRAME

BOUNDS

答案 6 :(得分:2)

让我加5美分。

视图的父视图使用

框架将其放置在父视图中。

视图本身使用

Bounds (边界)来放置其自己的内容(就像滚动视图在滚动时一样)。另请参见clipsToBounds。边界也可以用于放大/缩小视图的内容。

答案 7 :(得分:2)

边界-x:0,y:0,宽度:20,高度:40是静态
框架-x:60,y:20,宽度:45,高度:45是基于内部边界的动态效果。

又一幅插图显示了边框和边界之间的差异。 在此示例中:

  • View BView A
  • 的子视图
  • View B已移至x:60, y: 20
  • View B旋转了45 degrees

enter image description here

答案 8 :(得分:1)

上面的答案很好地解释了Bounds和Frames之间的区别。

  

Bounds:根据自己的坐标系统查看大小和位置。

     

框架:相对于SuperView的视图大小和位置。

然后令人困惑的是,在Bounds的情况下,X,Y将永远是" 0"。 这不是真的。这也可以在UIScrollView和UICollectionView中理解。

当界限' x,y不为0.
我们假设我们有一个UIScrollView。我们实施了分页。 UIScrollView有3页,其ContentSize的宽度是屏幕宽度的三倍(假设ScreenWidth为320)。高度是恒定的(假定为200)。

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

添加三个UIImageViews作为子视图,并仔细查看框架的 x

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. 第0页: 当ScrollView处于0 Page时,Bounds将是 (x:0,y:0,宽度:320,身高:200)

  2. 第1页: 滚动并移至第1页 现在边界将是(x:320,y:0,宽度:320,高度:200) 记住我们说过它自己的坐标系。现在,"可见部分"我们的ScrollView有" x"在320.看看imageView1的框架。

  3. 第2页: 滚动并移至第2页 界限:(x:640,y:0,宽度:320,高度:200) 再看一下imageView2的框架
  4. UICollectionView的情况相同。查看collectionView最简单的方法是滚动它并打印/记录它的边界,你就会明白这一点。

答案 9 :(得分:0)

以上所有答案都是正确的,这是我对此的看法:

要区分框架和边界CONCEPTS开发人员应阅读:

  
      
  1. 相对于超级视图(一个父视图),它包含在= FRAME中
  2.   
  3. 相对于其自身的坐标系,确定其子视图位置= BOUNDS
  4.   

“边界”令人困惑,因为它给人的印象是坐标是为其设置视图的位置。但是这些是有关系的,并根据帧常数进行了调整。

iPhone screen

答案 10 :(得分:0)

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

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

视图使用两个矩形(框架矩形和边界矩形)跟踪其大小和位置。框架矩形使用超级视图的坐标系定义视图在超级视图中的位置和大小。边界矩形定义了在绘制视图内容(包括原点和缩放比例)时使用的内部坐标系。图2-1显示了左侧的框架矩形和右侧的边界矩形之间的关系。”

简而言之,框架是超级视图的视图概念,边界是视图的概念。具有多个坐标系(每个视图一个)是视图层次结构的一部分。

答案 11 :(得分:0)

框架与界限

  • 如果您在X:0,Y:0,宽度:400,高度:400及其框架下创建视图 和范围是相同的。
  • 如果您将该视图移至X:400,则其框架将反映该更改,但其边界将不会。请记住,边界是相对于 视图的空间,并且在视图内部没有任何变化。
  • 如果您变换视图,例如旋转或按比例放大,框架将发生变化以反映这一点,但边界仍然不会-因为 就内部而言,它并没有改变。
  • 如果更改边界,则它将更改框架内的内容,因为边界矩形的原点始于 视图的不同部分。