题
UIView
及其子类都具有以下属性 frame
和 bounds
. 。有什么不同?
解决方案
界限 noreferrer“> UIView 是矩形,表示为相对于其自身坐标系(0,0)的位置(x,y)和大小(宽度,高度)。
框架 noreferrer“> 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,其中不仅解释了帧和边界之间的差异,还显示了可视化示例。
其他提示
简答
框架 = 视图的位置和大小,使用 父视图的坐标系
- 重要的是:将视图放置在父视图中
边界 = 视图的位置和大小使用 自己的坐标系
- 重要的是:将视图的内容或子视图放置在其自身中
详细解答
为了帮助我记住 框架, , 我想 墙上的相框. 。图片框就像视图的边框。我可以把照片挂在墙上任何我想要的地方。以同样的方式,我可以将视图放置在父视图(也称为超级视图)内的任何位置。父视图就像墙。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
您可以看到边界仍然相同。他们还不知道发生了什么事!不过,框架值都发生了变化。
现在更容易看出框架和边界之间的区别了,不是吗?文章 你可能不理解框架和界限 将视图框架定义为
...有关其父母坐标系统的最小边界框,包括应用于该视图的任何转换。
需要注意的是,如果变换视图,则框架将变得不确定。所以实际上,我在上图中围绕旋转的绿色边界绘制的黄色框架实际上并不存在。这意味着如果您旋转、缩放或进行其他一些转换,则不应再使用帧值。不过,您仍然可以使用边界值。苹果文档警告:
重要的: 如果一个视图的
transform
属性不包含身份变换,该视图的框架不确定,其自动行为的结果也是如此。
关于自动调整大小相当不幸......不过,你可以做一些事情。
修改时
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
它的子类(例如,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))"
}
}
尝试运行以下代码
- (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}}
显然,你可以看到框架和边界之间的区别 框架是定义UIView的矩形,与其超级视图相关。
bounds rect 是定义 NSView坐标系的值的范围。
即。此矩形中的任何内容实际上都会显示在UIView中。
框架是超视图坐标系中视图的原点(左上角)和大小,这意味着您可以通过更改框架原点来转换其超视图中的视图, bounds 是其自身坐标系中的大小和原点,因此默认情况下,边界原点为(0,0)。
大多数时候帧和边界是一致的,但是如果你有一个框架((140,65),(200,250))和边界((0,0),(200,250))的视图,例如视图被倾斜以便它位于其右下角,然后边界仍将是((0,0),(200,250)),但框架不是。
框架将是封装/环绕视图的最小矩形,因此框架(如照片中所示)将为((140,65),(320,320))。
另一个区别是,例如,如果你有一个superView的边界是((0,0),(200,200))并且这个superView有一个subView,其框架是((20,20),(100,100))并且你改变了superView绑定到((20,20),(200,200)),然后subView框架仍然是((20,20),(100,100)),但是(20,20)因为其超视图坐标系被offseted而被offseted (20,20)。
我希望这有助于某人。
将其相对于其SuperView设置为相对于其NSView相对于边界。
示例:X = 40,Y = 60.还包含3个Views.This图表显示了清晰的想法。
上面的答案很好地解释了Bounds和Frames之间的区别。
Bounds:根据自己的坐标系统查看大小和位置。
框架:相对于SuperView的视图大小和位置。
然后令人困惑的是,在Bounds的情况下,X,Y将始终为“0”。 这不是真的。这也可以在UIScrollView和UICollectionView中理解。
当界限'x,y不为0时。
我们假设我们有一个UIScrollView。我们实施了分页。 UIScrollView有3页,其ContentSize的宽度是屏幕宽度的三倍(假设ScreenWidth为320)。高度是恒定的(假定为200)。
scrollView.contentSize = CGSize(x:320*3, y : 200)
添加三个UIImageViews作为子视图,并仔细查看框架的 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)
-
第0页: 当ScrollView处于0 Page时,Bounds将是 (x:0,y:0,宽度:320,身高:200)
-
第1页: 滚动并移至第1页 现在边界将是(x:320,y:0,宽度:320,高度:200) 记住我们说过它自己的坐标系。所以现在是“可见部分”。我们的ScrollView有它的“x”在320.看看imageView1的框架。
- 第2页: 滚动并移至第2页 界限:(x:640,y:0,宽度:320,高度:200) 再看一下imageView2的框架 醇>
UICollectionView的情况相同。查看collectionView最简单的方法是滚动它并打印/记录它的边界,你就会明白这一点。
frame =使用父视图坐标系
的视图位置和大小bounds =使用自己的坐标系
的视图位置和大小视图使用两个矩形跟踪其大小和位置:框架矩形和边界矩形。框架矩形使用superview的坐标系在superview中定义视图的位置和大小。边界矩形定义绘制视图内容时使用的内部坐标系,包括原点和缩放。图2-1显示了左边的框架矩形和右边的边界矩形之间的关系。“
简而言之,框架是superview的视图概念,边界是视图自己的想法。拥有多个坐标系统,每个视图一个坐标系统,是视图层次结构的一部分。