Cocoa: Какая разница между фреймом и границами?

UIView, и все его подклассы имеют свойства frame и bounds. Какая разница?

Ответ 1

границы UIView - это выраженный как местоположение (x, y) и размер (ширина, высота) относительно его собственной системы координат (0,0).

фрейм 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, которые не только объясняют разницу между кадрами и границами, но также показывают визуальные примеры.

Ответ 2

Короткий ответ

фрейм= расположение и размер представления с помощью системы координат родительского представления

  • Важно: размещение представления в родительском

границы= расположение и размер представления с помощью собственной системы координат

  • Важно: размещение содержимого представления или субвью внутри себя

Подробный ответ

Чтобы помочь мне запомнить фрейм, я думаю о рамке на стене. Рамка изображения подобна границе представления. Я могу повесить картинку в любом месте на стене. Точно так же я могу поместить представление в любом месте внутри родительского представления (также называемого супервидом). Родительский вид похож на стену. Происхождение системы координат в 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

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 и эти вид и примеры слоев для получения дополнительной информации.)

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

Вы можете видеть, что границы все те же. Они все еще не знают, что что-то случилось! Однако значения фрейма изменились.

Теперь немного легче увидеть разницу между рамкой и границами, не так ли? Статья Вы, вероятно, не понимаете рамки и рамки определяет рамку представления как

... наименьшая ограничивающая рамка этой точки зрения относительно ее родителей системы координат, включая любые преобразования, применяемые к этой точке зрения.

Важно отметить, что если вы преобразуете представление, то рамка станет undefined. Так что, на самом деле, желтый кадр, который я рисовал вокруг повернутых зеленых границ на изображении выше, никогда не существует. Это означает, что если вы вращаете, масштабируете или выполняете другое преобразование, вам больше не следует использовать значения фрейма. Тем не менее, вы все равно можете использовать значения границ. Документы Apple предупреждают:

Важно: Если свойство views transform не содержит преобразование идентичности, кадр этого представления undefined, и также результаты его авторезистивного поведения.

Скорее неудачно об авторезистирующем... Однако есть что-то, что вы можете сделать.

Состояние документов Apple:

При изменении свойства transform вашего представления все преобразования выполняются относительно центральной точки вид.

Итак, если вам нужно переместить представление в родительском объекте после того, как было сделано преобразование, вы можете сделать это, изменив координаты view.center. Как frame, center использует систему координат родительского представления.

Хорошо, давайте избавимся от нашего вращения и сосредоточимся на границах. До сих пор происхождение границ всегда оставалось на (0, 0). Это не обязательно. Что делать, если наш взгляд имеет большое представление, которое слишком велико, чтобы отображать все сразу? Мы сделаем это UIImageView с большим изображением. Вот наша вторая картина сверху, но на этот раз мы увидим, как будет выглядеть весь контент нашего представления subview.

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 и ее подклассы (например, a UITableView). Подробнее см. Общие сведения о UIScrollView.

Когда использовать фрейм и когда использовать границы

Так как frame связывает расположение вида в своем родительском представлении, вы используете его, когда вы делаете внешние изменения, например, изменяете его ширину или находите расстояние между представлением и вершиной его родительского вид.

Используйте bounds, когда вы делаете внутренние изменения, например, рисование вещей или упорядочивание подзонов в представлении. Также используйте границы, чтобы получить размер представления, если вы сделали некоторую трансформирование на нем.

Статьи для дальнейших исследований:

Apple docs

Связанные вопросы StackOverflow

Другие ресурсы

Практикуйте себя

В дополнение к чтению вышеприведенных статей, это помогает мне много сделать тестовое приложение. Возможно, вы захотите сделать что-то подобное. (Я получил идею этого видеокурса, но, к сожалению, он не является бесплатным.)

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

    }

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

    }

}

Ответ 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}}

очевидно, вы можете видеть разницу между рамкой и границами

Ответ 4

фрейм - это прямоугольник, который определяет UIView в отношении его супервизора.

bounds rect - это диапазон значений, определяющих эту систему координат NSView.

то есть. что-либо в этом прямоугольнике будет отображаться в UIView.

Ответ 5

фрейм - это начало (верхний левый угол) и размер представления в его системе координат супер view, это означает, что вы переводите представление в его супер-представление, изменяя начало кадра, bounds, с другой стороны, это размер и начало координат в собственной системе координат, поэтому по умолчанию начало координат равно (0,0).

в большинстве случаев кадр и границы конгруэнтны, но если у вас есть представление о кадре ((140,65), (200,250)) и ограничениях ((0,0), (200, 250)), например, и вид был наклонен так, что он стоит на нижнем правом углу, тогда границы будут по-прежнему ((0,0), (200, 250)), но кадр не является.

enter image description here

кадр будет самым маленьким прямоугольником, который инкапсулирует/окружает вид, поэтому кадр (как на фотографии) будет ((140,65), (320,320)).

другое различие, например, если у вас есть супервизор, чьи границы равны ((0,0), (200,200)), и этот superView имеет подвид, чей кадр ((20,20), (100,100)), и вы изменили superView ограничивается ((20,20), (200,200)), тогда кадр subView будет по-прежнему ((20,20), (100,100)), но компенсируется (20,20), потому что его система координат наблюдения была смещена на (20,20).

Надеюсь, это поможет кому-то.

Ответ 6

Скомпонуйте его относительно своего SuperView, тогда как Bounds относительно его NSView.

Пример: X = 40, Y = 60. Также содержит 3 Представления. Эта схема показывает вам четкую идею.

FRAME

BOUNDS

Ответ 8

Ответы выше очень хорошо объяснили разницу между границами и фреймами.

Границы: Размер и расположение вида согласно его собственной системе координат.

Кадр: размер представления и расположение относительно его SuperView.

Тогда возникает путаница, что в случае границ X, Y всегда будет "0". Это не правда Это также можно понять в UIScrollView и UICollectionView.

Когда границы х, у не 0.
Предположим, у нас есть UIScrollView. Мы осуществили нумерацию страниц. UIScrollView имеет 3 страницы, а его ширина ContentSize в три раза больше ширины экрана (предположим, что ширина экрана равна 320). Высота постоянна (допустим 200).

scrollView.contentSize = CGSize(x:320*3, y : 200)

Добавьте три UIImageViews как subViews и внимательно посмотрите на значение 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, границы будут (x: 0, y: 0, ширина: 320, высота: 200)

  2. Страница 1: Прокрутите страницу и перейдите на страницу 1.
    Теперь границы будут (x: 320, y: 0, ширина: 320, высота: 200). Помните, что мы говорили о своей собственной системе координат. Так что теперь "Видимая часть" нашего ScrollView имеет "х" на 320. Посмотрите на кадр imageView1.

  3. Страница 2: Прокрутите страницу и перейдите на страницу 2 Границы: (x: 640, y: 0, ширина: 320, высота: 200) Снова взгляните на кадр imageView2

То же самое для случая UICollectionView. Самый простой способ взглянуть на collectionView - это прокрутить его и распечатать/записать его границы, и вы получите идею.

Ответ 9

Все ответы выше верны, и это мое мнение об этом:

Чтобы различать рамку и границы, КОНЦЕПЦИИ разработчик должен прочитать:

  1. относительно суперпредставления (одно родительское представление) оно содержится в = FRAME
  2. относительно своей собственной системы координат, определяет свое местоположение подпредставления = BOUNDS

"границы" сбивают с толку, потому что создается впечатление, что координаты - это позиция вида, для которого он установлен. Но они находятся в отношениях и корректируются в соответствии с константами кадра.

iPhone screen

Ответ 10

рамка = расположение и размер вида с использованием системы координат родительского вида

bounds = расположение и размер вида с использованием его собственной системы координат

Представление отслеживает свой размер и местоположение, используя два прямоугольника: прямоугольник рамки и прямоугольник границ. Прямоугольник рамки определяет местоположение и размер вида в суперпредставлении, используя систему координат суперпредставлений. Прямоугольник границ определяет внутреннюю систему координат, которая используется при рисовании содержимого представления, включая начало координат и масштабирование. На рисунке 2-1 показана взаимосвязь между прямоугольником рамки слева и прямоугольником границ справа ".

Короче говоря, фрейм - это идея супервизора представления, а границы - собственная идея представлений. Наличие нескольких систем координат, по одной для каждого вида, является частью иерархии видов.

Ответ 11

Для меня это намного понятнее, если я так думаю

Frame = Bounds & Position

Ответ 12

Позвольте мне добавить мои 5 центов.

Рамка используется родительским представлением представления для помещения его в родительское представление.

Границы используются самим представлением для размещения собственного содержимого (как при прокрутке во время прокрутки). Смотрите также clipsToBounds. Границы также могут быть использованы для увеличения/уменьшения содержимого представления.