首页 文章

可可:框架和边界之间有什么区别?

提问于
浏览
520

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

7 回答

  • 829

    UIViewboundsrectangle,表示为相对于其自身坐标系(0,0)的位置(x,y)和大小(宽度,高度) .

    UIViewframerectangle,表示为相对于其所包含的超级视图的位置(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的great presentation . 请参阅幻灯片1-20,其中不仅解释了帧和边界之间的差异,还显示了可视化示例 .

  • 1

    简答

    frame =视图的位置和大小使用 parent view's coordinate system

    • 重要信息:将视图放在父视图中

    bounds =使用 its own coordinate system 的视图位置和大小

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

    详细解答

    为了帮助我记住 frame ,我想起了 a picture frame on a wall . 相框就像一个视图的边框 . 我可以将照片挂在墙上的任何地方 . 以同样的方式,我可以在父视图(也称为superview)中的任何地方放置视图 . 父视图就像墙 . iOS中坐标系的原点是左上角 . 我们可以通过将视图框的x-y坐标设置为(0,0)来将视图放在超视图的原点上,这就像将我们的图片悬挂在墙的左上角一样 . 要向右移动,增加x,将其向下移动增加y .

    为了帮助我记住 bounds ,我想到 a basketball court 有时 the basketball gets knocked out of bounds . 你在整个篮球场上运球,但是你不管怎样 . 你只想打篮球 . 以同样的方式,视图的坐标系统知道视图在父视图中的位置 . 边界的原点(默认为点(0,0))是视图的左上角 . 此视图具有的任何子视图都与此相关 . 就像把篮球带到球场的左前角一样 .

    现在,当您尝试比较帧和边界时,会出现混乱 . 不过,它实际上并没有最初看起来那么糟糕 . 让我们用一些图片来帮助我们理解 .

    帧与界限

    在左边的第一张图片中,我们有一个位于其父视图左上角的视图 . The yellow rectangle represents the view's frame. 在右侧,我们再次看到视图,但这次没有显示父视图 . 那个's because the bounds don'知道父视图 . The green rectangle represents the view's bounds. 两个图像中的 red dot 表示帧或边界的 origin .

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

    enter image description here

    因此,该图片中的框架和边界完全相同 . 让我们看一下它们不同的例子 .

    Frame
        origin = (40, 60)  // That is, x=40 and y=60
        width = 80
        height = 130
    
    Bounds 
        origin = (0, 0)
        width = 80
        height = 130
    

    enter image description here

    因此,您可以看到更改框架的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
    

    enter image description here

    您可以看到边界仍然相同 . 他们仍然不知道发生了什么事!但是帧值已经全部改变了 .

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

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

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

    重要说明:如果视图的转换属性不包含标识转换,则该视图的框架是未定义的,其自动调整行为的结果也是如此 .

    相当不幸的是自动化....但是你可以做些什么 .

    The Apple docs state:

    修改视图的transform属性时,将相对于视图的中心点执行所有转换 .

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

    好吧,但是,让我们不得不这样做 . 如果我们的视图有一个太大的子视图太大而无法一次显示怎么办?我们将它变成一个带有大图像的 UIImageView . 这是我们上面的第二张图片,但这次我们可以看到我们视图的子视图的整个内容会是什么样子 .

    Frame
        origin = (40, 60)
        width = 80
        height = 130
    
    Bounds 
        origin = (0, 0)
        width = 80
        height = 130
    

    enter image description here

    只有图像的左上角可以放在视图的边界内 . 现在看看如果我们改变边界的原点坐标会发生什么 .

    Frame
        origin = (40, 60)
        width = 80
        height = 130
    
    Bounds 
        origin = (280, 70)
        width = 80
        height = 130
    

    enter image description here

    框架没有在超视图中移动,但框架内的内容已更改,因为边界矩形的原点从视图的不同部分开始 . 这是 UIScrollView 及其子类背后的整个想法(例如, UITableView ) . 有关更多说明,请参阅Understanding UIScrollView .

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

    由于 frame 将视图的位置与其父视图相关联,因此在创建 outward changes 时使用它,例如更改其宽度或查找视图与其父视图顶部之间的距离 .

    在制作 inward changes 时使用 bounds ,就像在视图中绘制内容或排列子视图一样 . 如果您对视图进行了一些转换,也可以使用边界来获取视图的大小 .

    进一步研究的文章:

    Apple docs

    Related StackOverflow questions

    Other resources

    练习自己

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

    enter image description here

    以下是供您参考的代码:

    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))"
    
        }
    
    }
    
  • 43

    尝试运行下面的代码

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

    显然,你可以看到框架和边界之间的区别

  • 9

    frame 是相对于其超视图定义UIView的矩形 .

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

    即,此矩形中的任何内容实际上都会显示在UIView中 .

  • 530

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

    大多数情况下帧和边界是一致的,但是如果你有一个框架((140,65),(200,250))和边界((0,0),(200,250))的视图,例如,视图是倾斜的所以它站在它的右下角,然后边界仍然是((0,0),(200,250)),但框架不是 .

    enter image description here

    框架将是封装/环绕视图的最小矩形,因此框架(如照片中)将是((140,65),(320,320)) .

    另一个区别是,例如,如果你有一个superView的边界是((0,0),(200,200)),这个superView有一个子视图,其框架是((20,20),(100,100)),你改变了superView边界到((20,20),(200,200)),那么subView框架仍然是((20,20),(100,100)),但是由(20,20)背景,因为它的超视图坐标系被(20, 20) .

    我希望这有助于某人 .

  • 3

    框架相对于其SuperView而Bounds相对于它的NSView .

    示例:X = 40,Y = 60.还包含3个Views.This图表显示了清晰的想法 .

    FRAME

    BOUNDS

  • 13

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

    界限:根据自己的坐标系统查看大小和位置 . 框架:相对于SuperView的视图大小和位置 .

    然后有一个混乱,在Bounds的情况下,X,Y将永远是"0" . This is not true . 这也可以在UIScrollView和UICollectionView中理解 .

    When bounds' x, y are not 0.
    设's assume we have a UIScrollView. We have implemented pagination. The UIScrollView has 3 pages and its 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页时,界限将为 (x:0,y:0, width : 320, height : 200)

    • 第1页:滚动并移至第1页 .
      现在边界将是 (x:320,y:0, width : 320, height : 200) 记得我们说过它自己的坐标系 . 所以现在我们的ScrollView的"Visible Part"的"x"为320.查看imageView1的框架 .

    • 第2页:滚动并移至第2页 Bounds : (x:640,y:0, width : 320, height : 200) 再次查看imageView2的框架

    对于UICollectionView的情况也是如此 . 查看collectionView最简单的方法是滚动它并打印/记录它的边界,你就会明白这个想法 .

相关问题