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

Cocoa: What's the difference between the frame and the bounds?

UIView及其子类均具有framebounds的性质。有什么区别?


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

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

所以,假设一个视图的大小为100x100(宽x高),位于它的超视图的25,25(x,y)处。以下代码打印出此视图的边界和框架:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 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);
}

此代码的输出是:

1
2
3
4
5
6
7
8
9
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)。

还有一个很好的介绍,包括uiview。请看幻灯片1-20,它不仅解释了帧和边界之间的区别,还展示了可视化示例。


简短回答

框架=使用父视图坐标系的视图位置和大小好的。

  • 重要提示:将视图放置在父视图中

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

  • 重要提示:将视图的内容或子视图放在其自身中

详细回答

为了帮助我记住画框,我想到了墙上的画框。图片框就像视图的边框。我可以把这幅画挂在墙上任何我想挂的地方。同样,我可以将视图放在父视图(也称为超级视图)内的任意位置。父视图类似于墙。iOS中坐标系的原点是左上角。我们可以通过将视图框架的X-Y坐标设置为(0,0),将视图置于超视图的原点,这就像将图片挂在墙的左上角一样。要向右移动,增加x,向下移动,增加y。好的。

为了帮助我记住边界,我想到了一个篮球场,有时候篮球会被踢出边界。你在篮球场上带球,但你并不真正关心球场本身在哪里。它可能在健身房,或在高中外面,或在你的房子前面。没关系。你只想打篮球。同样,视图边界的坐标系只关心视图本身。它不知道视图在父视图中的位置。边界的原点(默认为点(0,0))是视图的左上角。此视图所具有的任何子视图都与此点相关。就像把篮球带到球场的左前角。好的。

现在,当您尝试比较帧和边界时,会出现混淆。但事实上,情况并不像一开始看起来那么糟糕。让我们用一些图片来帮助我们理解。好的。框架与边界

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

1
2
3
4
5
6
7
8
9
Frame
    origin = (0, 0)
    width = 80
    height = 130

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

enter image description here好的。

所以这张照片的边框和边界是完全一样的。让我们来看一个不同的例子。好的。

1
2
3
4
5
6
7
8
9
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度会发生什么。(使用变换完成旋转。有关详细信息,请参阅文档以及这些视图和层示例。)好的。

1
2
3
4
5
6
7
8
9
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好的。

你可以看到边界仍然是一样的。他们仍然不知道发生了什么!不过,帧值都已更改。好的。

现在我们更容易看到帧和边界之间的区别,是吗?您可能不理解框架和边界的文章将视图框架定义为好的。

...the smallest bounding box of that view with respect to it’s parents
coordinate system, including any transformations applied to that view.

Ok.

重要的是要注意,如果转换视图,则框架将变为未定义。所以实际上,我在上面的图片中围绕旋转的绿色边界画的黄色框架从来没有实际存在过。这意味着,如果旋转、缩放或进行其他转换,则不应再使用帧值。不过,您仍然可以使用边界值。苹果文档警告:好的。

Important: If a view’s transform property does not contain the identity transform, the frame of that view is undefined and so are the
results of its autoresizing behaviors.

Ok.

很不幸的是自动调整…不过,你可以做些什么。好的。

苹果文档声明:好的。

When modifying the transform property of your view, all
transformations are performed relative to the center point of the
view.

Ok.

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

好吧,让我们摆脱旋转,专注于边界。到目前为止,边界原点始终保持在(0,0)。不过,这不必。如果我们的视图有一个大的子视图,它太大而不能同时显示,怎么办?我们会把它做成一个大图像的UIImageView。这是我们上面的第二张图片,但是这次我们可以看到我们视图的子视图的整个内容是什么样子的。好的。

1
2
3
4
5
6
7
8
9
Frame
    origin = (40, 60)
    width = 80
    height = 130

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

enter image description here好的。

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

1
2
3
4
5
6
7
8
9
Frame
    origin = (40, 60)
    width = 80
    height = 130

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

enter image description here好的。

框架未在超级视图中移动,但框架内的内容已更改,因为边界矩形的原点从视图的其他部分开始。这就是UIScrollView及其子类(例如,UITableView)背后的全部思想。有关更多说明,请参阅了解uiscrollview。好的。何时使用帧以及何时使用边界

由于frame关系到视图在其父视图中的位置,因此在进行外部更改(如更改视图宽度或查找视图与其父视图顶部之间的距离)时,可以使用它。好的。

当您进行向内更改时,如在视图中绘制内容或安排子视图时,请使用bounds。如果您对视图进行了一些转换,也可以使用边界来获取视图的大小。好的。进一步研究的文章:

苹果文档好的。

  • 视图几何
  • 意见
  • 视图和窗口结构

相关堆栈溢出问题好的。

  • ui视图框架、边界和中心
  • uiview的框架、边界、中心、原点,何时使用什么?
  • iPhone中重新定向后的"错误"帧/窗口大小

其他资源好的。

  • 你可能不理解框架和边界
  • iOS基础:框架、边界和CGGeometry
  • CS193P第5讲-视图、绘图、动画

锻炼自己

除了阅读上面的文章外,制作一个测试应用程序也很有帮助。你可能想尝试做类似的事情。(我是从这个视频课程中得到这个主意的,但很不幸它不是免费的。)好的。

enter image description here好的。

以下代码供您参考:好的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
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))"

    }

}

好啊。


尝试运行以下代码

1
2
3
4
5
6
7
8
- (void)viewDidLoad {
    [super viewDidLoad];
    UIWindow *w = [[UIApplication sharedApplication] keyWindow];
    UIView *v = [w.subviews objectAtIndex:0];

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

此代码的输出为:

外壳设备方向为纵向

1
2
{{0, 0}, {768, 1024}}
{{0, 0}, {768, 1024}}

机箱设备方向为横向

1
2
{{0, 0}, {768, 1024}}
{{0, 0}, {1024, 768}}

显然,您可以看到帧和边界之间的区别


框架是定义uiview相对于其超级视图的矩形。

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

即,此矩形中的任何内容都将实际显示在uiview中。


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

大多数时候,帧和边界是一致的,但是如果你有一个帧((140,65),(200250))和边界((0,0),(200250))的视图,例如,视图是倾斜的,所以它位于右下角,那么边界仍然是((0,0),(200250)),但是帧不是。

enter image description here

框架将是封装/包围视图的最小矩形,因此框架(如图所示)将为((140,65),(320320))。

另一个区别是,如果您有一个超视图,其边界为((0,0),(200200)),而这个超视图有一个子视图,其框架为((20,20),(100100)),并且您将超视图边界更改为((20,20),(200200)),那么子视图框架仍将为((20,20),(100100)),但被(20,20)覆盖,因为它的超视图坐标系是F由(20,20)加上。

我希望这对某人有帮助。


将其相对于其超视图设置为框架,而将其相对于其nsview设置为边界。

示例:x=40,y=60。还包含3个视图。此图显示了清晰的概念。

FRAME

BOUNDS


上面的答案很好地解释了边界和帧之间的区别。

Bounds : A view Size and Location as per its own coordinate system.

Frame : A view size and Location relative to its SuperView.

那么在边界为x的情况下,y总是"0",这是一种混淆。这不是真的。这也可以在uiScrollView和uiCollectionView中理解。

当边界'x,y不是0时。假设我们有一个uiscrollview。我们已经实现了分页。uiscrollView有3页,ContentSize的宽度是屏幕宽度的三倍(假定屏幕宽度为320)。高度不变(假设为200)。

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

添加三个uiImageView作为子视图,并密切关注帧的x值

1
2
3
4
5
6
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)
  • 第0页:当滚动视图位于0页时,边界将为(X:0,Y:0,宽320,高200)

  • 第1页:滚动并移动到第1页。现在边界为(x:320,y:0,宽度:320,高度:200)记住我们说过的关于它自己的坐标系。现在,滚动视图的"可见部分"的"x"为320。查看ImageView 1的框架。

  • 第2页:滚动并移至第2页边界:(X:640,Y:0,宽度320,高度200)再次查看ImageView的帧2
  • uiCollectionView的情况也是如此。查看CollectionView最简单的方法是滚动它并打印/记录它的边界,这样您就得到了这个想法。


    以上答案都是正确的,这是我的看法:

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

  • relative to the superview (one parent view) it is contained within = FRAME
  • relative to its own coordinate system, determines its subview location = BOUNDS
  • "边界"令人费解,因为它给人的印象是坐标是视图的位置。但它们是相关的,并根据帧常数进行调整。

    iPhone screen