繁体   English   中英

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

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

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

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

UIView框架矩形,表示为相对于它所包含的超级视图的位置 (x,y) 和大小 (width,height)。

因此,想象一个大小为 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,它不仅解释了框架和边界之间的区别,而且还展示了视觉示例。

简答

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

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

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

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

详细解答

为了帮助我记住相框,我想到了墙上的相框 相框就像视图的边框。 我可以把这幅画挂在墙上我想要的任何地方。 以同样的方式,我可以在父视图(也称为超级视图)中的任何位置放置视图。 父视图就像墙。 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

在此处输入图片说明

您可以看到边界仍然相同。 他们还不知道发生了什么事! 但是,帧值都已更改。

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

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

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

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

不幸的是,自动调整大小……不过,您可以做一些事情。

Apple 文档指出:

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

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

好的,让我们摆脱旋转并专注于边界。 到目前为止,边界原点一直停留在 (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

在此处输入图片说明

框架在父视图中没有移动,但是框架内的内容已经改变,因为边界矩形的原点从视图的不同部分开始。 这是UIScrollView及其子类(例如, 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))"

    }

}

尝试运行下面的代码

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

显然,您可以看到 frame 和 bounds 之间的区别

框架是定义 UIView相对于其 superview的矩形。

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

即这个矩形中的任何东西都会实际显示在 UIView 中。

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

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

在此处输入图片说明

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

另一个区别是,例如,如果您有一个边界为 ((0,0),(200,200)) 的超级视图,而此超级视图有一个框架为 ((20,20),(100,100)) 的子视图,并且您更改了超级视图的边界到 ((20,20),(200,200)) ,那么 subView 框架将仍然是 ((20,20),(100,100)) 但是偏移了 (20,20) 因为它的父视图坐标系偏移了 (20, 20)。

我希望这有助于某人。

框架相对于其 SuperView 而 Bounds 相对于其 NSView。

Example:X=40,Y=60.Also contains 3 Views.This Diagram显示你清楚的想法。

框架

界限

让我加上我的 5 美分。

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

视图本身使用边界来放置它自己的内容(就像滚动视图在滚动时所做的那样)。 另请参阅clipsToBounds 边界也可用于放大/缩小视图的内容。

比喻:
框架 ~ 电视屏幕
边界 ~ 相机(缩放、移动、旋转)

Frame - x,y 取决于 parenView 并计算由 Bounds 占据的完整矩形点。 x:60, y:20, width: 45, height: 45 是基于内部边界的动态。

界限- x,y 是 0,0。 x:0, y:0, width: 20, height: 40 是静态的

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

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

在此处输入图片说明

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

为了区分框架和边界概念开发人员应该阅读:

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

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

iPhone屏幕

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

Bounds :根据其自己的坐标系的视图大小和位置。

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

然后存在混淆,在 Bounds 的情况下,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作为subViews,仔细观察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, width : 320, height : 200)

  2. 第 1 页:滚动并移至第 1 页。
    现在边界将是(x:320,y:0, width : 320, height : 200)记住我们说的是关于它自己的坐标系。 所以现在我们的 ScrollView 的“可见部分”在 320 处有它的“x”。看看 imageView1 的框架。

  3. 第 2 页:滚动并移动到第 2 页Bounds : (x:640,y:0, width : 320, height : 200)再次看一下 imageView2 的框架

UICollectionView 的情况也是如此。 查看 collectionView 的最简单方法是滚动它并打印/记录其边界,您就会明白这一点。

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

bounds =使用自己的坐标系统查看位置和大小

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

简而言之,框架是superview的视图概念,边界是视图自己的想法。 具有多个坐标系,每个视图一个坐标系,是视图层次结构的一部分。

帧与边界

  • 如果你在 X:0, Y:0, width:400, height:400 创建一个视图,它的框架和边界是一样的。
  • 如果将该视图移动到 X:400,其框架将反映该更改,但其边界不会。 请记住,边界是相对于视图自己的空间而言的,并且在视图内部没有任何变化。
  • 如果您变换视图,例如旋转它或放大它,框架将改变以反映这一点,但边界仍然不会——就视图内部而言,它没有改变。
  • 如果您更改边界,那么它会更改框架内的内容,因为边界矩形的原点从视图的不同部分开始。

您可以在此视频中找到 Allen Sean 的精彩解释:

Swift - 边界与框架 - iOS 面试问题

相对于超级视图坐标,框架是 position: 在此处输入图像描述

并且 Bounds 是 position 相对于它自己的坐标系。 在此处输入图像描述

而且......如果旋转视图,它会变得更加棘手。 (观看视频!)

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM