【问题标题】:Cocoa: What's the difference between the frame and the bounds?Cocoa:frame 和 bounds 有什么区别?
【发布时间】:2010-11-15 15:40:48
【问题描述】:

UIView 及其子类都具有framebounds 属性。有什么区别?

【问题讨论】:

标签: cocoa cocoa-touch uiview


【解决方案1】:

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

UIView框架rectangle,表示为相对于它所在的父视图的位置 (x,y) 和大小 (width,height)。 p>

因此,想象一个大小为 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)。

还有一个great presentation 涵盖了 UIView。请参阅幻灯片 1-20,其中不仅解释了框架和边界之间的区别,还展示了视觉示例。

【讨论】:

  • 所以边界的 x,y 不会总是为 0,0,因为它是对象在...本身的位置。或者你能给出一个不可能的场景吗?
  • 据我所知,边界的原点永远是 0,0
  • 其实bounds.origin可以是0,0以外的值。使用 setBoundsOrigin: 移动/平移原点。有关更多信息,请参阅“查看 Cocoa 编程指南”中的“查看几何”。
  • UIScrollView 是一个示例,其中边界可以移动以显示视图中的不同内容区域。
  • 一个小技巧 - 使用 NSStringFromCGRect 可以节省一些时间来记录 rects。
【解决方案2】:

框架是定义 UIView 相对于其父视图的矩形。

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

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

【讨论】:

    【解决方案3】:

    尝试运行下面的代码

    - (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的区别

    【讨论】:

    【解决方案4】:

    简答

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

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

    bounds = 使用自己的坐标系

    的视图的位置和大小
    • 重要:将视图的内容或子视图放置在其自身中

    详细解答

    为了帮助我记住相框,我想到了墙上的相框。相框就像视图的边框。我可以把照片挂在墙上任何我想挂的地方。同样,我可以将视图放置在父视图(也称为超级视图)中的任何位置。父视图就像墙。 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 度会发生什么。 (旋转是使用变换完成的。有关更多信息,请参阅 documentation 和这些 viewlayer examples。)

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

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

    现在更容易看出 frame 和 bounds 之间的区别,不是吗?文章You Probably Don't Understand frames and bounds 将视图框架定义为

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

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

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

    很遗憾,自动调整大小......不过,您可以做一些事情。

    The Apple docs state:

    修改视图的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)背后的全部想法。更多解释见Understanding UIScrollView

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

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

    在进行向内更改时使用bounds,例如在视图中绘制东西或安排子视图。如果您对其进行了一些变换,也可以使用边界来获取视图的大小。

    进一步研究的文章:

    Apple 文档

    StackOverflow 相关问题

    其他资源

    练习自己

    除了阅读上面的文章之外,对我做一个测试应用有很大的帮助。您可能想尝试做类似的事情。 (我从this video course 得到这个想法,但不幸的是它不是免费的。)

    以下是供您参考的代码:

    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 的宽度和高度是多余的,一个“origin”属性就足够了(就像 Flash 一样)。
    • 使用边界来表示“喜欢在视图中绘制东西或安排子视图”。你的意思是如果我有一个有两个按钮的视图控制器,我应该使用视图控制器的视图“边界”来放置按钮?!我一直使用视图的框架...
    • @Honey,自从我去年一直在 Android 上工作以来,我对自己的 iOS 技能有点生疏了。我相信 View Controller 的根视图会填满屏幕,因此它的框架和边界应该是相同的。
    • 根据此处给出的示例应用程序创建了 github.com/maniramezan/FrameVsBounds.git 存储库,以帮助更好地理解 boundsframe 的视图。
    • @IanWarburton 当视图旋转时,边界的宽度和高度与框架的不匹配,因此不冗余。
    【解决方案5】:

    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)) 的子视图,并且您更改了superView 绑定到 ((20,20),(200,200)) ,那么 subView 框架将仍然是 ((20,20),(100,100)) 但偏移 (20,20) 因为它的 superview 坐标系偏移了(20,20)。

    我希望这对某人有所帮助。

    【讨论】:

    • 关于最后一段: subView 框架相对于它的 superView。将superView 边界更改为任何内容都不会使subView 原点与其superView 一致。
    【解决方案6】:

    Frame 它相对于它的 SuperView 而 Bounds 相对于它的 NSView。

    示例:X=40,Y=60。还包含 3 个视图。此图显示您清楚的想法。

    【讨论】:

      【解决方案7】:

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

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

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

      那么在 Bounds 的情况下,X,Y 将始终为“0”会让人感到困惑。 这不是真的。这在 UIScrollView 和 UICollectionView 中也可以理解。

      当bounds'x,y不为0时。
      假设我们有一个 UIScrollView。我们已经实现了分页。 UIScrollView 有 3 个页面,其 ContentSize 的宽度是屏幕宽度的三倍(假设 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 Page 时,Bounds 将是 (x:0,y:0, 宽度: 320, 高度: 200)

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

      3. 第 2 页: 滚动并移至第 2 页 边界:(x:640,y:0,宽度:320,高度:200) 再看一下imageView2的frame

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

      【讨论】:

        【解决方案8】:

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

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

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

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

        【讨论】:

          【解决方案9】:

          让我加上我的 5 美分。

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

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

          类比:
          框架~电视屏幕
          Bounds ~ Camera(缩放、移动、旋转)

          【讨论】:

            【解决方案10】:

            框架与边界

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

            【讨论】:

              【解决方案11】:

              iOS 框架与边界

              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 BView A 的子视图
              • View B 已移至 x:60, y: 20
              • View B 已旋转 45 degrees

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 2016-08-03
                • 2013-11-17
                • 2010-10-02
                • 2011-12-12
                • 2010-09-16
                • 2012-03-14
                • 2012-02-06
                • 2011-02-25
                相关资源
                最近更新 更多