Русский
Русский
English
Статистика
Реклама

Core animation

Устройство UI в iOS

26.09.2020 20:04:42 | Автор: admin

Всем все еще 404, сегодня мы ныряем в наш всеми любимый U, а если быть точнее в Фреймворк UIKit. Кратко, UIKit - UI фреймворк позволяющий облегчить для разработчиков процесс создания интерфейса для взаимодействия с пользователем.Но несмотря на то, что UIKit содержит в себе огромное кол-во функциональности, его размер исчисляется в десятках килобайт. Причиной тому является факт, что UIKit в современном iOS это по сутиumbrella header, предоставляющий единую точку импорта.

Ввод, как он есть.

UIKit содержит в себе все необходимые компоненты для предоставления доступа к девайсам, через которые пользователь общается с вашим приложением. Это и акселероментры, и хардварные кнопки, внешние клавиатуры, специальные устройства ввода для людей с ограниченными способностями, мыши и карандаши (Apple Pencil).

Не стоит забывать, что помимо перечисленных выше устройстов ввода, UIKit получает и обрабатывает массу информации от системы, начиная с низкоуровневых событий жизненного цикла приложения и memory warnings, заканчивая Push-уведомлениями уровнем повыше.

Для того чтобы эффективно обслуживать такое большое количество входящих источников событий UIKit'у нужен Event Loop, который мы привыкли называтьRunLoop. Тут UIKit вводит понятие главного потока, последовательного обслуживающего входящие источники и наш код. Принято считать, что главный поток это что-то неотъемлемое, что есть у приложения, но на самом деле это абстракция, которую вводит и предоставляет именно UIKit.

Может показаться, что знание RunLoop'а это что-то хардкорное и вовсе не нужное обычным разработчикам знание, но это не так. Понимание того, как UIKit обслуживает входящие события и открисовку UI важно для некоторых оптимизаций. Например, довольно частой задачей может быть добавление таймера для некоторых целей. Опытные разработчики могли встречаться таким эффектом, что таймер работает корректно и отсчитывает время до тех пор, пока пользователь не начинит скролить таблицу. В этот момент таймер просто перестаёт работать. Дело тут вовсе не в нехватке ресурсов девайса, а в том, что все таймеры обслуживаются RunLoop'ом, который в момент скрола переводится UIKit'ом в режимUI Tracking Mode. В этом режиме он отдает приоритет отрисовке UI, оставляя в очереди события из некоторых источников.

Но как там с выводом, то?

Нам доступен экран и Haptic. Следуя определению UI фреймворка можно было бы возразить, что пользователь может взаимодействовать с приложением и с помощью звука, и было бы логично отнести эту часть взаимодействия тоже в UIKit. Но в силу сложности работы с аудио, разработчики Apple выделили это в отдельный фреймворк Core Audio.

Основную часть времени работы над пользовательским интерфейсом разработчик, так или иначе тратит на графический интерфейс. Работая с графикой в iOS, как и на большинстве других платформ, мы имеем дело 2D пространством и прямоугольниками, которые как-то комбинируются и располагаются на экране. Сама абстракция прямоугольных областей очень удобна: с одной стороны это очень понятная схема для разработчиков, с другой стороны, очень понятная хардварной части и GPU. Работая с такими прямоугольниками перед разработчиком всегда стоит две задачи:расположитьэти элементы на экране инарисоватьих.

Сказал А, говори Layout.

Для решения первой задачи UIKit использует тривиальную и очень удобную структуру данных дерево. Ни для кого не секрет, что view'шки можно организовывать в иерархию, бесконечно добавляя subview для subview. В конечном мы будем иметь дело с обычным деревом. Его легко и просто обходить, а использование такой структуры нам дает классную возможность верстать относительно, указывая значения координат родительского элемента, а не абсолютное значение на экране.

Здесь можно возразить и сказать, что мы уже давно не верстаем на фреймах, а используем autolayout или 3rd-party библиотеки для верстки. С этим трудно не согласиться, но все системы верстки для iOS сводятся к одному в конечном итоге они проставляют фреймы, разница только в томкогда они их считаютив какой момент присваиваютэлементам.

Помимо ручного расчета абсолютных величин для фреймов или использования autolayout для версткисуществует еще и третий встроенный в iOS метод верстки. Если спуститься на уровень ниже от UIView касаемо отрисовки элементов, мы попадем на слой Core Animation, который позволяет c помощью свойстваanchorPointспозиционировать элементы используя относительные координаты и указывать позицию элемента в процентах от родительской.

Расположили. Теперь порисуем.

Расположить прямоугольники в нужном порядке и в нужном месте только половина дела. Теперь в них нужно еще и что-то нарисовать. Для решения этой задачи Apple разработала целый фреймворк, первоначально назвав егоLayerKit, а после переименовала вCore Animation.

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

Core Animation предоставляет абстракцию, называемую слои. Почти любая UIView содержит в себе слой CALayer, который используется для отрисовки графического интерфейса. Слои, как и view, организуются в иерархию. Тут следует уточнить: несмотря на то, что принято считать именно UIView строительным кирпичиком UI, она по сути является фасадом для CALayer. При добавлении дочерней view, под капотом происходит добавление дочернего слоя на родительский. Все измененияframe,bounds,center,backgroundColorи многих прочих просто проксируются в CALayer.

Таким образом UIView разделяет отвественности: иерархия UIView ответственна за User Interaction, а иерархия CALayer за графическое представление.

Core Animation используется не только на iOS с UIKit дляUIView, но и на macOS с AppKit с еёNSView. В macOS система коодинат отличается от iOS: начало ее коодинат нижний левый угол, против верхнего левого в iOS. Для кросплатформенной работы Core Animation Apple предоставляет свойствоgeometryFlippedу CALayer. Система коодинат macOS является системой по умолчанию, а UIKit проставляетgeometryFlipped = trueвсем слоям при создании. Но возможны случае, когда созданому слою нужно будет указать значение этого свойства вручную, например, при добавлении слоёв на слой с видеоплеером.

Как уже говорилось ранее, Core Animation вводит понятие слоёв, из которых можно собрать визуальное представление программы. Самый базовый класс, CALayer позволяет только закрасить себя каким-то цветом или отобразить CoreGraphics контент. Для решения более сложных задач существуют специализированные слои, такие какCAShapeLayer,CATextLayer,CAGradientLayerи другие. Эти типы слоёв позволяют решить ту или иную задачу эффективным способом, проводя рисование на GPU.

Тут стоит прояснить разницу между использованием специализированных слоёв и рисованием произвольной графики, используя метод UIViewdraw(in:). Как уже было сказано ранее, специализированные слои позволяют отрисовать контент оптимизированным способом на GPU, в то время как используяdraw(in:)разработчик будет прибегать к рисованию с помощьюCoreGraphics, который работает на CPU. Такой подход может приводить к фризам UI. Конечно, CoreGraphics можно пользоваться не из главного потока (не забывая то, что он не потокобезопасный), но стоит всегда помнить что он загружает CPU.

Осталось самое сладкое - анимации

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

Неявное становится явным

Так происходит, потому что CoreAnimation запускает неявные анимации, делая это автоматически, без каких-либо усилий разработчика. Чтобы понять почему так происходит, нужно сначала рассказать про CATransaction. CATransaction это контейнер, который инкапсулирует группу анимаций, управляет их длительностью и таймингом. UIKit создает корневой CATransaction в начале каждого вращения RunLoop'а, а в конце отправляет его на рендер. Именно по этому, любое изменение свойств слоёв упаковано в анимацию. Довольно часто стандартная анимация может не подходить разработчику, в том случае можно создать свой CATransaction, настроить скорость и указать тайминг функцию.

Описанная логика работы CALayer идет вразрез факту о том, что UIView является всего-лишь прокси для слоя. Ведь при измененииframeу UIView его положение и размер меняются мгновенно, не анимированно, а по логике должно перекинуться на слой и тот должен санимироваться. Тут дело в том, что корневой слой UIView ссылается на этот view как на делегата. И при любом изменении свойства, слой спрашивает нужно ли ему анимировать это свойство, вызывая метод делегатаaction(for:forKey:) View будет отвечать nil'ом на все изменения, выполняемые не в блоке анимацииUIView.animate(...), таким образом блокируя анимации при простановки различных свойств.

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

Мы можем из кода создать дочерней слой, добавить его к основному черезaddSublayer()и после санимировать UIView черезUIView.animate(withDuration:5). При этом будет наблюдаться различие в анимациях: изменения на корневом слое будут длиться 5 секунд, в то время как его дочерний (созданный нами) будет анимироваться куда быстрее. Это необходимо помнить и понимать чтобы сэкономить часы на отладке.

UIView может быть делегатом только у одного слоя. Несмотря на то, что мы можем из кода задать эту view делегатом у дочернего слоя,работать это не будети очень скоро приведет к падению приложения.

Явное - это гибко и удобно

Помимо неявных анимаций, конечно же существуют и явные, которыми мы можем управлять более гибко и удобно. За создание и управление явными анимациями ответственен абстрактный классCAAnimation, который позволяет задать делегата анимации (для отслеживания прогресса), тайминг-функцию, длительность, значение от, значение до и прочий контекст. Как уже было сказано,CAAnimation абстрактный класс, и его использовать нельзя. Для анимаций мы можем реализовать свою анимацию, или воспользоваться его потомками, доступными из коробки:

  • [CABasicAnimation] обычная анимация, интерполирующая значение междуfromPointиtoPoint

  • [CAKeyFrameAnimation] анимация, интерполирующая значения между двумя ключевыми кадрами, заданные с помощью массивовvaluesиkeyTimes

  • [CASpringAnimation] пружинная анимация

Анимация будет изменятьpresentationLayerанимируемого слоя. Это копия этого слоя, которая отражает его состояние в конкретный момент времени. Если вы хоть раз применяли анимации к слою, то вероятно знаете, что пока слой анимируется, значения анимируемых свойств у слоя не меняются. Дело в том, что спрашивая у слоя значения свойств мы спрашиваем значения свойств его модели. Использование жеpresentationLayerпозволяет узнать эти значения в конкретный период, такие, какие они сейчас на экране. Это может быть полезно для нескольких кейсов:

  • Остановка анимации с сохранением текущего состояния (просто удалив анимацию слой вернется к значениям из его модельного представления)

  • Бесшовная смена анимации (для старта новой анимации нужны значенияfromValueиз presentation слоя)

  • Корректная обработка нажатий на анимируемый элемент (во время анимацииhitTest(_:with:) ( точнееpoint(inside:with:)) будет опираться на значения фрейма из модельного представления, и чтобы верно обрабатывать нажатия, необходимо будет переопределитьpoint(inside:with:)для работы с презентационным слоем)

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

Стоит помнить о том, что анимации зависят от жизненного цикла приложения и самого слоя. При уходе приложения в бекграунд или удалении слоя изsuperviewанимацииCAAnimationудаляются, поэтому свернув приложение в середине анимации, вы увидите объект в том состоянии, в котором он был до начала анимации.

А вот и сказочке конец

Надеюсь вы нашли что-то новое или разложили уже имеющиеся знания по полочкам, хотя бы немного. Всем интересных проектов и чистого кода :D

Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru