В UIKit, когда у `bounds` стандартного компонента `origin` может быть отличен от нуля?

Ответ

Свойство bounds.origin определяет систему координат для отрисовки внутри view. Оно может быть ненулевым в двух основных случаях:

  1. При скроллинге в UIScrollView (и его подклассах): bounds.origin равен contentOffset. Он смещается, чтобы указать, какая часть внутреннего контента (contentSize) видна во frame.

    let scrollView = UIScrollView(frame: CGRect(x: 0, y: 0, width: 200, height: 200))
    scrollView.contentSize = CGSize(width: 500, height: 500)
    scrollView.contentOffset = CGPoint(x: 150, y: 150)
    // Теперь видимая часть контента начинается с точки (150, 150)
    print(scrollView.bounds.origin) // (150.0, 150.0)
  2. После применения трансформации (transform) к UIView: Система координат view (bounds) смещается, чтобы компенсировать трансформацию и упростить позиционирование subviews. Frame при этом меняется, а bounds.origin становится отрицательным.

    let view = UIView(frame: CGRect(x: 50, y: 50, width: 100, height: 100))
    // Сдвигаем view визуально на 20 точек вправо и вниз
    view.transform = CGAffineTransform(translationX: 20, y: 20)
    // Система координат для рисования внутри view смещается в обратную сторону
    print(view.bounds.origin) // (-20.0, -20.0)

Ключевой момент: bounds.origin редко меняется вручную. Его нестандартное значение — это следствие скроллинга или трансформации, управляемых системой.

Ответ 18+ 🔞

О, слушай, вот эта штука с bounds.origin — это вообще отдельная песня, блядь. Казалось бы, ну origin, ну координаты, хуй с ним. Ан нет, сука, тут такие подлянки бывают, что волосы дыбом встают, ёпта.

Вот смотри, обычно у вьюхи bounds.origin стоит на (0, 0). Как будто так и надо. Но иногда, блядь, открываешь дебаггер, а там какая-то хуйня, типа (-20, -20). И ты такой: «Что за манда? Я же не трогал эту хуйню!»

А оказывается, это система сама, хитрая жопа, его меняет. И в основном по двум поводам, блядь.

Первый случай — это когда вьюха в скроллвью сидит. Тут всё логично, хоть и мозг набекрень ставит. bounds.origin у скроллвью становится равен его contentOffset. По сути, система координат внутри него сдвигается, чтобы показать, какая часть его огромного внутреннего контента сейчас видна в окошке. Это как через иллюминатор подводной лодки смотреть и двигать саму лодку, а не картинку за стеклом.

let scrollView = UIScrollView(frame: CGRect(x: 0, y: 0, width: 200, height: 200))
scrollView.contentSize = CGSize(width: 500, height: 500)
scrollView.contentOffset = CGPoint(x: 150, y: 150)
// Теперь видимая часть контента начинается с точки (150, 150)
print(scrollView.bounds.origin) // (150.0, 150.0)

Видишь? Мы скролльнули на (150, 150), и bounds.origin стал таким же. Всё сходится, мать его.

А второй случай — это вообще ёперный театр. Когда ты применяешь к вьюхе трансформацию, например, сдвигаешь её transform'ом. Система, блядь, чтобы subviews (дочерние вьюхи) внутри неё не ебнулись и оставались на своих местах, — сдвигает саму систему координат, bounds, в обратную сторону! А frame при этом меняется так, как будто вьюха реально сдвинулась. Получается пиздец какой-то разрыв шаблона.

let view = UIView(frame: CGRect(x: 50, y: 50, width: 100, height: 100))
// Сдвигаем view визуально на 20 точек вправо и вниз
view.transform = CGAffineTransform(translationX: 20, y: 20)
// Система координат для рисования внутри view смещается в обратную сторону
print(view.bounds.origin) // (-20.0, -20.0)

Смотри, что выходит: вьюха визуально сдвинулась. Но система координат для рисования внутри неё (bounds) уехала в минуса! Это чтобы, если ты внутри неё что-то рисуешь в точке (0, 0), это что-то оказалось в правильном месте после трансформации. Гениально и ебнуто одновременно, блядь.

Так что запомни главное, чувак: bounds.origin — это не та хуйня, которую ты обычно трогаешь руками. Это следствие, блядь. Следствие того, что где-то скролльнули или трансформ применили. Если видишь в нём не ноль — ищи, где накосячил со скроллом или анимацией. Или просто вспомни, что ты её двигал трансформом, и успокойся, ебать.