Добро пожаловать на первую лекцию на этой неделе. Она посвящена изучению UIView и ее сабклассов. Для начала мы рассмотрим предков UIView и основные протоколы, которые он поддерживает. На самом верху, как это часто бывает Object C классами расположен NCObject. Сейчас он нас не интересует. От него наследуется UIResponder. Классы являющиеся наследниками UIResponder участвуют в обработке событий UIKit. Например, нажатие на экран или в встряхивание устройства. Они выстраиваются в последовательность называемую Responder chain. Когда происходит какое-то событие UIKit определяет First Responder и дает ему возможность обработать события, если он не может этого сделать, то передает событие вверх по цепочке для определения следующего респондера. UIKit вызывает свойство Next у текущего. По умолчанию события передаются вверх по иерархии View, а дальше к контроллеру UIWindow и UIApplication. Для разных событий First Responder определяется по разному. Если произошло нажатие на экран, то им будет в View на которую нажал пользователь. В других случаях First Responder назначаете вы. Вызвав Bool у сабкласса UIResponder, метод becomeFirstResponder. Вы можете отозвать статус First Responder вызвав resignFirstResponder. Обратите внимание, что последние две операции не гарантируют выполнения в случае отказа из метода будет возвращен falls. Еще одна важная особенность и обязанность UIResponder это обработка нажатий. На слайде вы видите сабкласс кнопки, который переопределяет эти методы. Когда пользователь опустит палец на кнопку, вызовется метод touchesBegan, в него передаются множество экземпляров UITouch. Каждая из них представляет собой одно нажатие и содержит информацию о нём: время, расположения, силу нажатия и другое. UIEvent вспомогательный объект создаваемый при нажатии и других событиях. Когда пользователь перемещает палец по элементам, то у него вызывается touchesMoved, а при подъеме пальца с экрана touchesEnded. TouchesCanselled вызывается, когда нажатие было прервано системой. Вы можете использовать эти методы, например, для изменения внешнего вида вашей View при нажатии на неё. Следующим в иерархии классов идёт сам UIView. Он содержит много протоколов, но подробно мы разберем лишь несколько из них. NSCoding используется для сереализации данных, преобразования объектов в последовательность байтов, которую можно передать или сохранить на диск. Он поддерживается не только UIView, но и многими другими классами. Подробнее эту тему мы рассмотрим, когда будем обсуждать хранения данных. Но есть один важный момент, основной инициализатор UIView это initFrame. Но если вы создаете View в store board или в ksip файле, то UIKit инициализирует её. Для этого он использует другой инициализатор init coder aDecoder. В сабклассах UIView вам не нужно самостоятельно писать какую-то логику для обработки NCcoder. Просто вызовете супер init coder, но если вы в инициализаторе используйте какую-то логику, то её нужно либо продублировать в обоих конструкторах, либо вынести в отдельный метод. Следующий протокол UIAppearence, позволяет централизованно менять внешний вид элементов интерфейса. Есть два способа это сделать, первый для всех экземпляров выбранного класса в примере на слайде для всех кнопок выставляется зелёный цвет фона. Обратите внимание, что установленные вами свойства применяются ко View только в момент добавления её на экран. Если же View уже была на экране, то изменений свойств через UIAppearence на неё не повлияет. Второй вариант, применить свойство только к тем View, которые находятся внутри какого-то определенного UIAppearence contained. Их может являться UIView или её сабклассы, в том числе созданные вами. Во втором примере на слайдах красный цвет фона будет применяться к кнопке только если она находится внутри UIStackView. UIStackView - это контейнер для View. Он располагает свои дочерние элементы в горизонтальном или вертикальном виде. Мы ещё рассмотрим его подробнее. Также вы можете указать иерархию View перечислив их в массиве. В третьем примере кнопка будет синей только если она находится внутри UIView и та в свою очередь внутри ещё одной UIView. Если какие-то правила противоречат друг другу, то будет применяться наиболее детализированное. В примере на слайдах более высокий приоритет будет у третьей строки, наименьший у первый. UIDynamicitem - используется для анимации ее мы рассмотрим в других лекциях. UITraitEnvironmental - необходим для адаптации интерфейса под разные устройства. Эту тему мы тоже обязательно обсудим. UICoordinateSpace - требует наличия методов для конвертации координат. Для начала рассмотрим как они хранятся. Координаты View задаются с помощью структур CGReact. Она состоит из двух групп структур origin и size. Первая имеет тип СGPoint и содержит координаты X и Y в виде CGFloat. Вторая размер, она имеет тип СGSize и хранит два CGFloat, width и height. Положение относительно родительской указывается в свойстве frame. На слайде вы видите пример, точка с координатами 10 10 во внутренней View является точкой с координатой 20 30 во внешней. Для того чтобы получить это значение можно воспользоваться методом convert to. Обратите также внимание, что точка 0 0 находится в левом верхнем углу. Свойство bounds это расположение и размер в координатной системе самой View. Обычно размеры во frame и bounds совпадают, а bounds origin имеют нулевые значение, но это не всегда так, можно установить в bounds origin значения отличные от нуля. На слайде вы видите два представления: размеры frame и bounds синего View совпадают, а положения равны. 20 20 и 0 0. Соответственно, если у родительской View, установить bounds origin равным 20 60, то получится следующий результат. Чтобы понять почему это произошло, рассмотрим все содержимое родительской View в ее системе координат. Представьте, что bounds - это объект или окно через которое вы смотрите на контент. Изначально он расположен в координатах 0,0. Имеет размер 200 на 200. Когда мы изменили значение boundsorigin, мы сдвинули это окно на двадцать point-ов вправо и 60 вниз. Поэтому subView сдвинулась ближе к левому верхнем углу. Такой принцип используется в UIScrollView. Его значение bounds origin. Совпадает с content offset, текущим значением сдвига контента. Когда пользователь прокручивает контент вверх на самом деле вниз опускается окно. При изменении bounds size просто изменяется frame size, так как они связаны между собой. Однако они тоже не всегда равны. С помощью трансформации вы можете изменить размер и положение View. Подробнее о них мы расскажем позже. Сейчас рассмотрим чему будут равны Frame и bounds при повороте синий View на два радиана. После поворота bounds имеет то же значение, что и до поворота 0,0 200 200. А Frame изменил свое значение таким образом, чтобы вместить повёрнутую View целиком. На слайде мы добавили чёрную рамку чтобы было понятнее. Из-за того, что Frame может иметь не то значение, которое вы ожидаете, будьте внимательны при выставления размеров дочерних View. Скорее всего вам нужно будет использовать bounds, а не Frame. Например, если мы хотим увеличить ширину красной View до размеров синей, нам нужно обратиться к свойству bounds. UIFocusitem используется в TV OC. CALayerDeligate является более низкоуровневым представлением View, оно используется в core animation. Обратиться к нему из UIView можно с помощью свойства Layer. Тема слоев заслуживает отдельной лекции, поэтому сейчас на ней останавливаться не будем. Мы рассмотрели верхний уровень схемы, переходим к нижнему.