UIView和它的子类都有属性 frame和 bounds。有什么区别吗?
UIView
frame
bounds
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,它不仅解释了帧和边界之间的区别,而且还展示了可视化的例子。
框架是用关于它的父视图定义UIView的矩形。
边界矩形是定义NSView的坐标系。的值的范围
也就是说,这个矩形中的任何东西都会显示在UIView中。
试着运行下面的代码
- (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}}
显然,你可以看到框架和边界之间的区别
框架 =视图的位置和大小,使用父视图的坐标系统
界限 =视图的位置和大小,使用它自己的坐标系
为了帮助我记住框架,我想到了墙上的画框。相框就像视图的边框。我可以把这幅画挂在墙上任何我想挂的地方。以同样的方式,我可以把一个视图放在父视图(也称为父视图)中的任何我想要的位置。父视图就像墙一样。iOS中坐标系的原点是左上角。通过设置视图框架的x-y坐标为(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
你可以看到改变坐标系的x-y坐标会把它移到父视图中。但是视图本身的内容看起来仍然完全相同。边界不知道有什么不同。
到目前为止,框架和边界的宽度和高度都是完全相同的。但这并不总是正确的。看看如果我们顺时针旋转视图20度会发生什么。(旋转是使用变换完成的。更多信息请参见文档和视图和层的例子。)
Frame origin = (20, 52) // These are just rough estimates. width = 118 height = 187 Bounds origin = (0, 0) width = 80 height = 130
你可以看到积分限还是一样的。他们还不知道发生了什么!但是帧值都改变了。
现在更容易看出框架和边界的区别了,不是吗?文章你可能不了解框架和边界将视图框架定义为
< p >…视图相对于父视图的最小边界框 坐标系统,包括应用于该视图的任何转换
重要的是要注意,如果转换视图,那么框架将变成未定义的。所以实际上,我在上图中围绕旋转的绿色边界所画的黄色框架实际上并不存在。这意味着如果你旋转,缩放或做一些其他变换,那么你不应该再使用帧值。不过,你仍然可以使用边界值。苹果的文件警告说:
如果一个视图的transform属性不包含恒等变换,则该视图的帧是未定义的
transform
相当不幸的自动调整大小....不过还是有办法的。
苹果文档状态:
当修改视图的transform属性时,所有 对象的中心点相对地执行转换 视图。< / p >
因此,如果您确实需要在转换完成后在父视图中移动视图,您可以通过更改view.center坐标来实现。像frame一样,center使用父视图的坐标系统。
view.center
center
好,我们先不做旋转,把注意力集中在积分限上。到目前为止,边界原点一直保持在(0,0),但也不是必须的。如果我们的视图有一个很大的子视图,太大而不能一次显示呢?我们将它设置为UIImageView和一个大图像。这是我们上面的第二张图片,但是这一次我们可以看到我们视图的子视图的整个内容是什么样子的。
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获得更多解释。
UIScrollView
UITableView
因为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))" } }
框架是视图在它的父视图的坐标系中的原点(左上角)和大小,这意味着你在它的父视图中通过改变帧原点来转换视图,界限另一方面是它自己坐标系中的大小和原点,所以默认情况下边界原点是(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)偏移。
我希望这能帮助到一些人。
Frame相对于SuperView,而Bounds相对于NSView。
例子:40 X = Y = 60。也包含3个视图。这张图向你展示了清楚的想法。
上面的答案已经很好地解释了边界和框架之间的区别。
边界:一个视图的大小和位置按照它自己的坐标系统。 Frame:相对于它的SuperView的视图大小和位置。
边界:一个视图的大小和位置按照它自己的坐标系统。
Frame:相对于它的SuperView的视图大小和位置。
然后有一个混乱的情况下,边界的X,Y将永远是“0”。# EYZ0。这也可以在UIScrollView和UICollectionView中理解。
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)
UICollectionView的情况也一样。查看collectionView最简单的方法是滚动它并打印/记录它的边界,您将了解它的概念。
以上所有答案都是正确的,以下是我的看法:
为了区分框架和边界概念,开发者应该阅读:
相对于父视图(父视图),它包含在= FRAME中 相对于它自己的坐标系,决定了它的子视图location = BOUNDS
"bounds"是令人困惑的,因为它给人的印象是坐标是它所设置的视图的位置。但这些是有关系的,可以根据坐标系常数进行调整。
让我加上我的5美分。
视图的父视图使用框架将其放置在父视图中。
视图本身使用界限来放置它自己的内容(就像滚动视图在滚动时所做的那样)。参见clipsToBounds。边界也可以用来放大/缩小视图的内容。
框架与边界
iOS框架与边界
frame和bounds有CGRect类型,这些属性可以在UIView和CALayer<一口>[对]< /一口>中找到
CGRect
CALayer
框架与bounds、position和transform有直接关系:
position
# EYZ0:
View B
View A
x:72, y: 22
45 degrees
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像素vs点数vs单位]
这里有一个很棒的视频(来自Sean Allen):
Swift - Bounds vs. Frame - iOS面试问题
,边界是相对于它自己的坐标系的位置。 # EYZ0 < / p >
和…如果视图是旋转的,它会变得更加棘手。(看视频!)