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


当前回答

让我加上我的5美分。

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

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

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

其他回答

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)的子view,你把superView的边界改为((20,20),(200,200)),那么子view的框架仍然是((20,20),(100,100)),但是被(20,20)偏移,因为它的父视图坐标系被(20,20)偏移。

我希望这能帮助到一些人。

iOS框架与边界

frame and bounds有CGRect类型,这些属性你可以在UIView和CALayer中找到[关于]

Frame与边界、位置、变换有直接关系:

(x, y)依赖于parenView和 (宽度,高度)被边界占据的完整矩形。

范围:

(x, y) = 0,0 (width, height)是视图的宽度和高度

再举一个例子来说明框架和边界之间的区别。 在这个例子中:

视图B是视图a的子视图 视图B被移动到x:72, y: 22 视图B旋转了45度

let viewB = UIView(frame: CGRect(origin: CGPoint(x: 72, y: 22), size: CGSize(width: 20, height: 40)))
viewB.backgroundColor = .cyan
let radian = 45 * CGFloat.pi / 180
viewB.transform = CGAffineTransform(rotationAngle: radian)
viewA.addSubview(viewB)

[iOS像素、点数、单位]

试着运行下面的代码

- (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 =使用父视图坐标系统的视图的位置和大小

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

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

重要的是:将视图的内容或子视图置于视图自身中


详细的回答

To help me remember frame, I think of a picture frame on a wall. The picture frame is like the border of a view. I can hang the picture anywhere I want on the wall. In the same way, I can put a view anywhere I want inside a parent view (also called a superview). The parent view is like the wall. The origin of the coordinate system in iOS is the top left. We can put our view at the origin of the superview by setting the view frame's x-y coordinates to (0, 0), which is like hanging our picture in the very top left corner of the wall. To move it right, increase x, to move it down increase y.

To help me remember bounds, I think of a basketball court where sometimes the basketball gets knocked out of bounds. You are dribbling the ball all over the basketball court, but you don't really care where the court itself is. It could be in a gym, or outside at a high school, or in front of your house. It doesn't matter. You just want to play basketball. In the same way, the coordinate system for a view's bounds only cares about the view itself. It doesn't know anything about where the view is located in the parent view. The bounds' origin (point (0, 0) by default) is the top left corner of the view. Any subviews that this view has are laid out in relation to this point. It is like taking the basketball to the front left corner of the court.

当你试着比较框架和边界时,困惑就来了。事实上,它并没有一开始看起来那么糟糕。让我们用一些图片来帮助我们理解。

框架与边界

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

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

你可以看到改变坐标系的x-y坐标会把它移到父视图中。但是视图本身的内容看起来仍然完全相同。边界不知道有什么不同。

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

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

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

你可以看到积分限还是一样的。他们还不知道发生了什么!但是帧值都改变了。

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

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

重要的是要注意,如果转换视图,那么框架将变成未定义的。所以实际上,我在上图中围绕旋转的绿色边界所画的黄色框架实际上并不存在。这意味着如果你旋转,缩放或做一些其他变换,那么你不应该再使用帧值。不过,你仍然可以使用边界值。苹果的文件警告说:

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

相当不幸的自动调整大小....不过还是有办法的。

苹果文件中写道:

当修改视图的transform属性时,所有 对象的中心点相对地执行转换 视图。

如果你需要在转换完成后在父视图中移动视图,你可以通过改变视图来实现。中心坐标。像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与视图在父视图中的位置相关,所以当你进行向外更改时,比如更改其宽度或查找视图与父视图顶部之间的距离时,可以使用它。

当你在进行内部更改时,比如在视图中绘制东西或排列子视图时,请使用边界。如果您对视图进行了一些转换,也可以使用边界来获取视图的大小。

进一步研究的文章:

苹果公司的文档

视图几何 的观点 视图和窗口体系结构

StackOverflow相关问题

uiviewframe, bounds和center UIView的框架,边界,中心,原点,什么时候用什么? 在iPhone中重新定位后,边框/窗口大小“不正确”

其他资源

你可能不了解框架和边界 iOS基础:框架,边界和CGGeometry CS193p第5讲-视图,绘图,动画

实践自己

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

以下是代码供您参考:

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

    }

}

框架与边界

If you create a view at X:0, Y:0, width:400, height:400, its frame and bounds are the same. If you move that view to X:400, its frame will reflect that change but its bounds will not. Remember, the bounds is relative to the view’s own space, and internally to the view nothing has changed. If you transform the view, e.g. rotating it or scaling it up, the frame will change to reflect that, but the bounds still won’t – as far as the view is concerned internally, it hasn’t changed. If you change the bounds then it will change the content inside the frame because the origin of the bounds rectangle starts at a different part of the view.