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


587

UIView和它的子类都具有的属性framebounds。有什么不同?



对我来说,最简洁的解释是在这里:videos.raywenderlich.com/courses/99-scroll-view-school/教训/…
Vlad

1
2:30的视频简明扼要。我可以比2:30快读一两个句子。
dsjoerg

对我来说,如果我这样看,那就更清楚了:框架=边界和位置
Serg

Answers:


902

边界的的UIView的矩形,表示为相对于它自己的坐标系(0,0)位置(x,y)和尺寸(宽度,高度)。

的的UIView的矩形,表示为相对于它包含内的上海华一个位置(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的演示文稿。参见幻灯片1-20,它不仅解释了帧和边界之间的差异,而且还显示了直观的示例。


37
因此,x,y的边界不会始终为0,0,因为其在...本身中的位置。还是可以给出一个可能的情况?
9

5
据我所知,边界的原点将始终为0-0
shek

77
实际上,bounds.origin可以是0,0以外的值。使用setBoundsOrigin:移动/翻译原点。有关更多信息,请参见“ View Cocoa编程指南”中的“ View Geometry”。
Meltemi

127
UIScrollView是一个示例,其中边界可以移动以显示视图中的不同内容区域。
布拉德·拉尔森

92
一个小技巧-使用NSStringFromCGRect可以节省一些时间来记录rect。

645

简短答案

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

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

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

  • 重要提示:将视图的内容或子视图放置在其内部

详细答案

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

为了帮助我记住界限,我想起一个篮球场,有时篮球被打出界限。您正在整个篮球场上运球,但您实际上并不关心篮球场在哪里。它可以在体育馆中,也可以在高中外面,或者在房子前面。没关系 您只想打篮球。以同样的方式,视图边界的坐标系仅关心视图本身。它对该视图在父视图中的位置一无所知。边界的原点(默认为点(0,0))是视图的左上角。与此视图有关的是该视图具有的所有子视图。这就像将篮球带到球场的左前角。

现在,当您尝试比较边框和界限时,就会出现混乱。但是实际上,它并没有开始时看起来的糟糕。让我们用一些图片来帮助我们理解。

框架与界限

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

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坐标会在父视图中移动它。但是视图本身的内容看起来仍然完全相同。界限不知道有什么不同。

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

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

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

在此处输入图片说明

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

现在可以更容易地看到框架和界限之间的区别,不是吗?这篇文章你可能不理解框架和边界定义视图帧

...该视图相对于其父级坐标系的最小边界框,包括应用于该视图的所有变换。

重要的是要注意,如果变换视图,则框架将变得不确定。因此,实际上,我在上图中围绕旋转的绿色边界绘制的黄色框实际上不存在。这意味着,如果您旋转,缩放或进行其他一些转换,则您不应再使用帧值。不过,您仍然可以使用bounds值。苹果文档警告:

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

相当不幸的是自动调整大小。...但是,您可以做一些事情。

苹果文档指出:

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

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

好的,让我们摆脱旋转,专注于边界。到目前为止,边界原点始终保持在(0,0)。但这不是必须的。如果我们的视图有一个大的子视图,太大而无法一次显示,该怎么办?我们将其UIImageView与大图像。这是我们的第二张照片,但是这次我们可以看到视图子视图的全部内容。

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

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

在此处输入图片说明

图像的左上角只能容纳在视图范围内。现在看看如果更改边界的原点坐标会发生什么。

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

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

在此处输入图片说明

框架尚未在超级视图中移动,但框架内的内容已更改,因为边界矩形的原点始于视图的不同部分。这是a UIScrollView及其子类(例如a UITableView)背后的全部思想。有关更多说明,请参见了解UIScrollView

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

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

使用bounds时,你正在向内变化,就像画的东西或视图中安排子视图。如果对视图进行了一些变形,也可以使用边界来获取视图的大小。

有待进一步研究的文章:

苹果文档

相关的StackOverflow问题

其他资源

练习一下

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

在此处输入图片说明

这是供您参考的代码:

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

    }

}

看来bound的width和height是多余的,并且“ origin”属性就足够了(与Flash一样)。
伊恩·沃伯顿

使用边界来“像在视图中绘制事物或安排子视图”。你的意思是,如果我有一个带有两个按钮的viewController,我应该使用viewController的视图“边界”放置按钮吗?我一直都使用视图的框架...
亲爱的

@Honey,自从我去年从事Android以来,我对自己的iOS技能有些不满意。我相信View Controller的根视图会填满整个屏幕,因此其框架和范围应该相同。
Suragch

5
创建github.com/maniramezan/FrameVsBounds.git回购基础上理解这里给予帮助更好的示例应用程序boundsVS frame的视图。
manman

谢谢!很好的解释。图像比1000个单词更有价值
Pedro Trujillo

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

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


2
这不再是真的(iOS 8)
朱利安·克罗(JulianKról


13

是定义与UIView的矩形相对于它的父

边界RECT是定义值的范围的NSView的坐标系。

也就是说,此矩形中的所有内容实际上都会显示在UIView中。


10

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

大多数情况下,框架和边界是一致的,但是例如如果您有框架((140,65),(200,250))和边界((0,0),(200,250))的视图并且该视图是倾斜的使其位于其右下角,则边界仍将是((0,0),(200,250)),但框架不是。

在此处输入图片说明

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

另一个区别是,例如,如果您有一个边界为((0,0),(200,200))的superView,并且该SuperView有一个其框架为((20,20),(100,100))的子视图,并且您更改了SuperView的边界到((20,20),(200,200)),则subView框架将仍然是((20,20),(100,100))但被(20,20)偏移,因为其上级坐标系偏移了(20, 20)。

我希望这对某人有帮助。


关于最后一段: subView frame是相对于它的superView。将superView边界更改为任何值都不会使subView原点与的原点重合superView
staticVoidMan

5

相对于其SuperView构图,而相对于其NSView则定界。

示例:X = 40,Y = 60。还包含3个视图。此图向您展示了清晰的主意。

帧

界线


4

我加5美分。

视图的父视图使用框架将其放置在父视图内。

视图本身使用边界来放置其自己的内容(就像滚动视图在滚动时所做的一样)。也可以看看 clipsToBounds。边界也可以用于放大/缩小视图的内容。

类比:
帧〜电视屏幕
边界〜相机(缩放,移动,旋转)


2

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

边界:根据其自身坐标系的视图大小和位置。

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

然后有一个混淆,在边界的情况下,X,Y始终为“ 0”。这不是真的。在UIScrollView和UICollectionView中也可以理解这一点。

当边界的x,y不为0时
。假定我们有一个UIScrollView。我们已经实现了分页。UIScrollView有3页,其ContentSize的宽度是Screen Width的三倍(假设ScreenWidth为320)。高度是恒定的(假设为200)。

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

添加三个UIImageViews作为子视图,并仔细查看frame 的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页面时,边界将为 (x:0,y:0,宽度:320,高度:200)

  2. 页面1:滚动并移至页面1。
    现在边界将为(x:320,y:0,宽度:320,高度:200) 记住我们所说的是关于它自己的坐标系的。因此,现在ScrollView的“可见部分”在320处具有其“ x”。查看imageView1的帧。

  3. 页面2:滚动并移至页面2 边界:(x:640,y:0,宽度:320,高度:200) 再次查看imageView2的帧

与UICollectionView相同。查看collectionView的最简单方法是对其进行滚动并打印/记录其范围,您将明白这一点。


2

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

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

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

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

iPhone屏幕


1

还有一个插图显示了框架和边界之间的差异。在这个例子中:

  • View B 是的子视图 View A
  • View B 被搬到 x:60, y: 20
  • View B 被旋转 45 degrees

在此处输入图片说明


0

边框与边框

  • 如果在X:0,Y:0,width:400,height:400创建视图,则其框架和边界是相同的。
  • 如果您将该视图移动到X:400,则其框架将反映该更改,但其边界将不会。请记住,边界是相对于视图自身空间的,而内部相对于视图则没有任何变化。
  • 如果您变换视图(例如旋转视图或按比例放大),则框架将更改以反映该视图,但边界仍然不会改变-就视图内部而言,它没有改变。
  • 如果更改边界,则它将更改框架内的内容,因为边界矩形的原点始于视图的不同部分。

-1

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

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

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

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


图2-1在哪里?
亚历山大·梅亚茨基
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.