В чем разница между свойствами `frame` и `bounds` у UIView?

Ответ

Свойства frame и bounds объекта UIView описывают его прямоугольную область, но в разных системах координат.

Свойство Система координат Описание Изменение origin
frame Система координат родительского view (superview). Определяет позицию и размер view относительно ее родителя. CGRect.origin — это точка (x, y) в координатах супервью. Перемещает view относительно родителя.
bounds Внутренняя (собственная) система координат view. (0, 0) — это левый верхний угол самой view. Определяет внутренний размер view и видимую область ее содержимого (как «окошко»). CGRect.origin здесь называется bounds.origin. Сдвигает систему координат содержимого внутри view (например, для скроллинга).

Ключевые отличия и правила:

  1. Зависимость: frame является производным свойством. Он вычисляется на основе center, bounds и transform. Прямое изменение frame может быть неточным, если задано вращение или скейлинг (transform).
  2. Трансформации: bounds не зависит от transform. Если вы вращаете view, ее frame (описывающий ограничивающий прямоугольник) изменится, а bounds.size — останется прежним.
  3. Использование bounds.origin: Это мощный инструмент. Изменение bounds.origin сдвигает внутреннюю систему координат, что приводит к смещению всех дочерних view (subviews) и отрисованного содержимого (например, drawRect:). Именно так работает скроллинг в UIScrollView.

Наглядный пример:

// Создаем view размером 100x100, позиционируем ее в (50, 50) у родителя.
let greenView = UIView(frame: CGRect(x: 50, y: 50, width: 100, height: 100))
greenView.backgroundColor = .green

print(greenView.frame)   // (50.0, 50.0, 100.0, 100.0) - относительно родителя
print(greenView.bounds)  // (0.0, 0.0, 100.0, 100.0) - свои внутренние координаты

// Меняем bounds.origin - сдвигаем «окошко», через которое view смотрит на свое содержимое.
greenView.bounds.origin = CGPoint(x: 20, y: 20)
// Теперь внутренняя система координат greenView сдвинута.
// Точка (0,0) в системе greenView теперь соответствует точке (20,20) в его собственной геометрии.
// Это приведет к смещению всех его subviews на 20pt влево и вверх.
print(greenView.frame)   // (50.0, 50.0, 100.0, 100.0) - НЕ ИЗМЕНИЛСЯ (позиция относительно родителя та же)
print(greenView.bounds)  // (20.0, 20.0, 100.0, 100.0) - origin изменился

Когда что использовать:

  • Используйте frame для позиционирования и изменения размера view внутри ее супервью.
  • Используйте bounds для работы с внутренним содержимым, его отрисовкой (drawRect:) и расположением дочерних view. Изменяйте bounds.origin для реализации скроллинга.

Ответ 18+ 🔞

А, ну вот, опять эти ваши frame и bounds! Вечная песня, блядь. Слушай, давай без заумных мантр, а по-простому, как в жизни.

Представь себе картину в рамочке, висит у тебя на стене. Так вот, эта картина — это твой UIView, а стена — это superview, родитель.

  • frame — это где и какого размера эта картина висит на стене. Координаты (x: 50, y: 50) — это от левого верхнего угла стены отмерил 50 точек вправо и 50 вниз, и там её прибили. width: 100, height: 100 — ну, картина квадратная, метровая, блядь. Всё относительно стены, ёпта!
  • bounds — это внутренности самой картины, её холст. Его система координат — это её собственный левый верхний угол (0, 0). Размер bounds — это размер самого холста. Обычно он совпадает с размером рамы, но не всегда, охуенно тонкий лёд!

А теперь смотри, в чём подвох, сука:

  1. frame — он вторичный, производный, как младший брат. Он высчитывается из center, bounds и этой вашей ебучей transform. Если ты картину на стене повернёшь на 45 градусов (transform), то её frame станет описывать новый, больший прямоугольник, в который она вписана. А bounds.size останется прежним — размер холста-то не изменился! Прямо как в том анекдоте: "Я не я, и лошадь не моя".

  2. Волшебство bounds.origin. Вот это, блядь, чистая магия, как у Герасима с Муму. Ты можешь сдвинуть bounds.origin с (0, 0) куда-нибудь. Что произойдёт? Ты сдвигаешь не картину на стене! Ты сдвигаешь систему координат самого холста! Это как если бы ты взял и передвинул "окошко", через которое смотришь на этот холст. Все дочерние виды (subviews), которые ты на этот холст намалевал, ебанаты будут — они все дружно съедут! Именно так, сука, UIScrollView и скроллит — он просто двигает bounds.origin, а всё содержимое плывёт нахуй в обратную сторону. Гениально и просто, как топор!

Вот, смотри на код, не бзди:

// Вешаем зелёную картину на стену
let greenView = UIView(frame: CGRect(x: 50, y: 50, width: 100, height: 100))
greenView.backgroundColor = .green

print(greenView.frame)   // (50.0, 50.0, 100.0, 100.0) - Где висит на стене
print(greenView.bounds)  // (0.0, 0.0, 100.0, 100.0) - Размер её внутреннего холста

// А теперь — внимание, хуй с горы! — двигаем bounds.origin
greenView.bounds.origin = CGPoint(x: 20, y: 20)

print(greenView.frame)   // (50.0, 50.0, 100.0, 100.0) - На стене-то она НЕ ДВИГАЛАСЬ! Родитель в шоке.
print(greenView.bounds)  // (20.0, 20.0, 100.0, 100.0) - А вот её внутренняя система координат съехала!
// Теперь точка (0,0) на её холсте — это на самом деле точка (20,20) от её левого верхнего угла.
// Все её внутренние рисунки (subviews) поехали на 20 точек влево-вверх. Пиздец скроллинг!

Короче, запомни, как "Отче наш":

  • Хочешь подвигать или растянуть вид относительно папаши (superview) — лезь в frame (но помни про transform, а то охуеешь).
  • Хочешь поковыряться во внутренностях вида, в его содержимом, или заставить его скроллиться — работай с bounds. Особенно с этим хитрожопым bounds.origin.

Вот и вся наука, блядь. Не так страшен чёрт, как его bounds.origin.