Узнайте разницу между рамкой и границами в программировании iOS с помощью визуального и практического способа

Существует популярная и распространенная тема для интервью среди iOS и не только разработчиков; разница между рамкой и границами представления! Несмотря на то, что все разработчики рано или поздно узнают об этом, это может звучать так же для ушей настоящих новичков. Если не смотреть глубже на эту разницу, почти всегда гарантировано, что в какой-то момент проблемы и неразбериха впереди. Поэтому в интересах каждого разработчика избавиться от этого как можно скорее.

Моя цель в этом посте — поговорить об этой разнице. Однако вместо того, чтобы просто сказать пару слов, объясняющих, что такое рамка и границы, я также перейду к дополнительному шагу; шаг за шагом реализовать небольшое приложение для iOS, которое наглядно продемонстрирует эти две концепции. Таким образом, будет абсолютно ясно, о чем они, и исчезнет любая путаница.

Знание основ

Обо всем по порядку, поэтому давайте начнем с пары пояснений. Рамка и границы описывают две вещи в представлении:

  • исходная точка вида как значение CGPoint (значения x и y на горизонтальной и вертикальной осях),
  • размер представления как значение CGSize (ширина и высота).

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

Ну, разница заключается в исходной системе координат. Конкретно:

  • Фрейм относится к системе координат контейнера вида (родительского вида).
  • Границы относятся к собственной системе координат представления.

Например, предположим, что контроллер представления содержит только одно представление (подпредставление), помимо собственного представления. Фрейм этого подпредставления описывает свою исходную точку и размер в координатах представления контроллера представления, поскольку последнее в данном случае является представлением-контейнером (родителем). Нулевая исходная точка (x=0 и y=0) контейнера находится в верхней левой части, а исходная точка подпредставления — это расстояние от этой точки по обеим осям. Что касается размера подпредставления, то это ширина и высота виртуального прямоугольника, окружающего подпредставление в любой момент времени.

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

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

👉 Примечание. Исходной точкой является верхний левый угол в iOS, но это не так в macOS; исходной точкой является нижний левый угол.

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

По умолчанию размер представления одинаков как в кадре, так и в границах. Однако это утверждение не останется в силе, если мы каким-то образом изменим представление; вращение, перевод (перемещение) и масштабирование — все они влияют на кадр; как точка происхождения, так и размер!

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

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

Реализация демо

В Xcode и в совершенно новом приложении iOS на основе UIKit мы реализуем следующее:

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

Вся реализация пользовательского интерфейса будет выполняться программно; без использования раскадровки. Итак, давайте начнем с контроллера представления по умолчанию, который содержит каждый новый проект UIKit, где мы объявим несколько свойств, которые нам понадобятся:

В дополнение к этому нам понадобится еще одно свойство, чтобы сохранить текущий поворот (в градусах) демо-представления:

Далее мы собираемся определить несколько методов в классе ViewController. Большинство из них будут настраивать элементы управления, которые мы только что объявили выше.

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

После этого мы собираемся инициализировать и настроить frameView, который будет окружать демо-представление. Здесь будет два отличия; представление фрейма будет иметь только цветную рамку, и, что наиболее важно, мы не будем устанавливать для него никаких ограничений макета! Вместо этого мы будем устанавливать его кадр «на лету» каждый раз при изменении кадра демо-представления:

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

Сразу после всего вышеперечисленного мы добавим кнопку. Цель его существования проста; каждый раз, когда мы будем нажимать на него, мы будем изменять преобразование демонстрационного вида, поворачивая его на несколько градусов.

Ничего особо сложного в этом способе нет, поэтому вот его реализация:

Вскоре мы определим метод transform(), который вызывается при закрытии действия кнопки; не беспокойтесь об этом в данный момент.

👉 Примечание. Если вы хотите узнать больше о кнопках с замыканием действий, посмотрите этот пост.

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

Первый реализует общие свойства между двумя метками, а другой реализует общие ограничения автоматического макета. Это поможет избежать повторения одного и того же кода дважды:

Последние штрихи

Закончив на этом этапе настройку всех представлений в этом небольшом демонстрационном приложении, пришло время добавить еще три недостающих метода. Мы начнем с двух похожих методов, которые будут отвечать за установку значений рамки и границ демонстрационного представления в виде текста двух меток. Однако для вашего же удобства мы также заставим их выводить эти значения и на консоль:

Далее мы реализуем метод transform(), с которым мы впервые познакомились ранее, и он будет вызываться каждый раз при нажатии кнопки поворота. В нем происходят три важные вещи:

  • демонстрационный вид каждый раз поворачивается на 15 градусов,
  • frameView получает новый кадр; рамка демо-представления,
  • boundsView также получает новый кадр; границы демо-представления.

Кроме того, мы также вызовем методы printFrame() и printBounds(), чтобы обновить содержимое двух меток.

Вот все это:

Наконец, давайте разместим все на экране и отобразим первое содержимое двух меток; все это мы сделаем в методе viewWillAppear(_:):

Наблюдение за различиями в рамках и границах

Небольшое приложение этого руководства готово, так что пришло время попробовать его. Запустите его либо на реальном устройстве, либо в симуляторе, а когда будете готовы, начните нажимать кнопку поворота, чтобы повернуть демонстрационный вид.

Вот что вы увидите:

Обратите внимание, что во время вращения демонстрационного вида:

  • граница frameView "нарисована" вокруг виртуального прямоугольника демонстрационного представления. То, что вы видите, является реальным кадром демо-представления после поворота!
  • boundsView появляется там, где диктует его рамка, что соответствует границам демо-представления. Мы ясно понимаем, что при вращении демо-представления представление границ все время остается постоянным.

Две метки в нижней части экрана также указывают на это. При каждом повороте исходная точка, а также размер кадра демонстрационного вида получают новые значения, отражающие положение и размеры вида. Однако соответствующие значения границ остаются постоянно неизменными, как мы можем видеть на второй метке справа!

Не удивляйтесь, увидев borderView с зеленой рамкой в ​​верхнем левом углу экрана. Это то, чего мы должны ожидать, поскольку исходное значение его кадра совпадает с исходной точкой границ demoView, которая равна (0, 0).

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

Заключение

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

Спасибо за чтение, наслаждайтесь кодированием! 🧑🏻‍💻

Первоначально опубликовано на https://serialcoder.dev