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

Разработка под ios

Особенности обновлений прошивки мобильных устройств

18.09.2020 14:13:41 | Автор: admin
Обновлять или не обновлять прошивку на личном телефоне каждый решает самостоятельно.
Кто-то ставит CyanogenMod, кто-то не чувствует себя хозяином устройства без TWRP или jailbreak.
В случае с обновлением корпоративных мобильных телефонов процесс должен быть относительно единообразным, иначе IT-шникам даже Рагнарёк покажется забавой.

О том, как это происходит в корпоративном мире, читайте под катом.

Особенности обновлений прошивки мобильных устройств рисунок 1

Краткий ЛикБез


Мобильные устройства на базе iOS получают регулярные обновления аналогично устройствам на Windows, но при этом:

  • обновления выходят реже;
  • обновления получает большинство устройств, но не все.

Apple выпускает обновление iOS сразу для большинства своих устройств, кроме тех, которые снимаются с поддержки. При этом Apple поддерживает свои устройства достаточно долго. Например, обновление iOS 14 получат даже iPhone 6s, вышедшие в 2015 году. Конечно, не обходится без косяков, типа принудительного замедления старых аппаратов, которое, как утверждается, было сделано не с целью вынудить купить новый телефон, а для продления срока службы старого аккумулятора Но в любом случае это лучше, чем ситуация с Android.

Android это по сути своей франшиза. Оригинальный Android от Google встречается только на устройствах линейки Pixel и бюджетных устройствах, которые участвуют в программе Android One. На других устройствах встречаются только производные от Android EMUI, Flyme OS, MIUI, One UI и т.д. Для безопасности мобильных устройств такое разнообразие большая проблема.
Например, сообщество находит очередную уязвимость в Android или системных компонентах, которые лежат в его основе. Далее уязвимости присваивается номер в базе CVE, нашедший получает вознаграждение по одной из баунти-программ от Google, а уж потом Google выпускает заплатку и включает её в очередной релиз Android.

Получит ли её ваш телефон, если он не Pixel или не участник программы Android One?
Если вы купили новое устройство год назад, то, наверное, да, но не сразу. Производителю вашего устройства ещё нужно будет включить патч Google в свою сборку Android и протестировать её на поддерживаемых моделях устройств. Топовые модели поддерживают чуть дольше. Всем остальным остаётся смириться и не читать по утрам базу CVE, чтобы не портить себе аппетит.

Ситуация с мажорными обновлениями Android, как правило, ещё хуже. В среднем новая мажорная версия докатывается до мобильных устройств с кастомными Android не меньше, чем за квартал, а то и больше. Так обновление Android 10 от Google вышло в сентябре 2019, а устройства разных производителей, которым повезло заслужить возможность обновления, получали его вплоть до лета 2020.

Производителей можно понять. Выпуск и тестирование новых прошивок это затраты и не малые. А так как устройства мы уже купили, то дополнительных денег с нас не получить.
Остаётся вынуждать нас покупать новые устройства.

Особенности обновлений прошивки мобильных устройств, Краткий ЛикБез, картинка 2

Дырявость сборок Android отдельных производителей стала причиной того, что Google изменил архитектуру Android, чтобы доставлять критичные обновления самостоятельно. Проект получил название Google Project Zero, около года назад о нём писали на Хабре. Фича относительно новая, но она встроена во все устройства с 2019 года, где есть сервисы Google. Многие знают, что эти сервисы платные для производителей устройств, которые платят за них роялти в Google, но мало кто знает, что дело не ограничивается коммерцией. Чтобы получить разрешение использовать сервисы Google на конкретном устройстве, производитель должен отдать свою прошивку в Google на проверку. При этом Google не принимает на проверку прошивки с древними Android. Это позволяет Google навязывать рынку свой Project Zero, что, надеемся, сделает Android устройства более безопасными.

Рекомендации корпоративным пользователям


В корпоративном мире используются не только публичные приложения, доступные в Google Play и App Store, но и приложения собственной разработки. Иногда жизненный цикл таких приложений прекращается в момент подписания акта приёма-передачи и оплаты услуг разработчика по договору.

В этом случае установка нового мажорного обновления ОС часто приводит к тому, что такие job-is-done приложения перестают работать. Бизнес-процессы останавливаются, а разработчиков нанимают повторно до возникновения следующего косяка. То же самое случается, когда корпоративные разработчики не успевают вовремя адаптировать свои приложения под новую ОС или новая версия приложения уже доступна, но пользователи её ещё не установили. В том числе для решения таких проблем предназначены системы класса UEM.

UEM системы обеспечивают оперативное управление смартфонами и планшетами, своевременно устанавливая и обновляя приложения на устройствах мобильных сотрудников. Кроме того, они могут откатить версию приложения к предыдущей в случае необходимости. Возможность отката версии назад является эксклюзивной фишкой UEM систем. Ни Google Play, ни App Store такой возможности не предоставляют.

UEM системы могут удалённо заблокировать или отложить обновление прошивки мобильных устройств. Поведение зависит от платформы и производителя устройств. На iOS в режиме supervised (о режиме читайте в нашем FAQ) можно отложить обновление до 90 дней. Для этого достаточно настроить соответствующую политику безопасности.

На Android устройствах производства Samsung можно бесплатно запретить обновление прошивки или воспользоваться дополнительным платным сервисом E-FOTA One, с помощью которого можно указать какие обновления ОС устанавливать на устройства. Это даёт администраторам возможность предварительно проверить поведение корпоративных приложений на новых прошивках своих устройств. Понимая трудоёмкость этого процесса, мы предлагаем своим заказчикам сервис на базе Samsung E-FOTA One, включающий услуги проверки работоспособности целевых бизнес-приложений на используемых у заказчика моделях устройств.

На Android устройствах других производителей аналогичной функциональности, увы, нет.
Запретить или отложить их обновление можно, разве что, с помощью страшилок, типа:
Уважаемые пользователи! Не обновляйте свои устройства. Это может привести к неработоспособности приложений. При нарушении этого правила ваши обращения в службу технической поддержки рассматриваться/выслушиваться НЕ БУ-ДУТ!.

Ещё одна рекомендация


Следите за новостями и корпоративными блогами производителей операционных систем, устройств и платформ UEM. Буквально в этом году Google решил отказаться от поддержки одной из возможных мобильных стратегий, а именно fully managed device with work profile.

За этим длинным названием скрывается следующий сценарий:

До Android 10 UEM-системы полноценно управляли устройством И рабочим профилем (контейнером), в котором содержатся корпоративные приложения и данные.
Начиная с Android 11, возможен полноценный функционал управления только ИЛИ устройством ИЛИ рабочим профилем (контейнером).

Google объясняет нововведения заботой о конфиденциальности данных пользователей и своём кошельке. Если есть контейнер, то данные пользователя должны находиться вне зоны видимости и управления со стороны работодателя.

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

Google утверждает, что такой доступ к личному пространству отпугивал 38% процентов пользователей от установки UEM. Теперь UEM-вендорам остаётся кушать что дают.

Особенности обновлений прошивки мобильных устройств, Рекомендации корпоративным пользователям, картинка 3

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

Малоизвестные факты


В завершение еще несколько малоизвестных фактов об обновлении мобильных ОС.

  1. Прошивки на мобильных устройствах иногда можно откатить. Как показывает анализ поисковых фраз, фразу как восстановить Android ищут чаще, чем обновление Android. Казалось бы, фарш нельзя провернуть назад, но иногда всё-таки можно. Технически защита от отката базируется на внутреннем счётчике, который увеличивается не с каждой версией прошивки. В рамках одного значения этого счётчика откат становится возможен. Это то, что касается Android. В iOS ситуация чуть отличается. С сайта производителя (или бесчисленного числа зеркал) можно скачать образ iOS конкретной версии для конкретной модели. Чтобы установить его по проводу с помощью iTunes, Apple должен подписать прошивку. Обычно в первые несколько недель после выхода новой версии iOS Apple подписывает предыдущие версии прошивок, чтобы пользователи, чьи устройства после обновления глючат, могли вернуть себе более стабильный билд.
  2. Во времена, когда jailbreak сообщество ещё не разбежалось по крупным компаниям, можно было изменить версию отображаемую версию iOS в одном из системных plist. Так можно было, например, сделать iOS 6.2 из iOS 6.3 и обратно. Зачем это было нужно, расскажем в одной из следующих статей.
  3. Очевидна всеобщая любовь производителей к программе для прошивки смартфонов Odin. Лучшего инструмента для прошивки ещё не сделали.

Пишите, обсудим, может и поможем.
Подробнее..

Архитектурный шаблон MVI в Kotlin Multiplatform. Часть 3 тестирование

27.08.2020 16:05:58 | Автор: admin


Эта статья является заключительной в серии о применении архитектурного шаблона MVI в Kotlin Multiplatform. В предыдущих двух частях (часть 1 и часть 2) мы вспомнили, что такое MVI, создали общий модуль Kittens для загрузки изображений котиков и интегрировали его в iOS- и Android-приложения.

В этой части мы покроем модуль Kittens модульными и интеграционными тестами. Мы узнаем о текущих ограничениях тестирования в Kotlin Multiplatform, разберёмся, как их преодолеть и даже заставить работать в наших интересах.

Обновлённый пример проекта доступен на нашем GitHub.

Пролог


Нет никаких сомнений в том, что тестирование важный этап в разработке программного обеспечения. Конечно, оно замедляет процесс, но при этом:

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

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

Indeed, the ratio of time spent reading versus writing is well over 10 to 1. We are constantly reading old code as part of the effort to write new code. ...[Therefore,] making it easy to read makes it easier to write. Robert C. Martin, Clean Code: A Handbook of Agile Software Craftsmanship

Kotlin Multiplatform расширяет возможности тестирования. Эта технология добавляет одну важную особенность: каждый тест автоматически выполняется на всех поддерживаемых платформах. Если поддерживаются, например, только Android и iOS, то количество тестов можно умножить на два. И если в какой-то момент добавляется поддержка ещё одной платформы, то она автоматически становится покрытой тестами.

Тестирование на всех поддерживаемых платформах важно, потому что могут быть различия в поведении кода. Например, у Kotlin/Native особенная модель памяти, Kotlin/JS тоже иногда даёт неожиданные результаты.

Прежде чем идти дальше, стоит упомянуть о некоторых ограничениях тестирования в Kotlin Multiplatform. Самое большое из них это отсутствие какой-либо библиотеки моков для Kotlin/Native и Kotlin/JS. Это может показаться большим недостатком, но я лично считаю это преимуществом. Мне довольно трудно давалось тестирование в Kotlin Multiplatform: приходилось создавать интерфейсы для каждой зависимости и писать их тестовые реализации (fakes). На это уходило много времени, но в какой-то момент я понял, что трата времени на абстракции это инвестиция, которая приводит к более чистому коду.

Я также заметил, что последующие модификации такого кода требуют меньше времени. Почему так? Потому что взаимодействие класса с его зависимостями не прибито гвоздями (моками). В большинстве случаев достаточно просто обновить их тестовые реализации. Нет необходимости углубляться в каждый тестовый метод, чтобы обновить моки. В результате я перестал использовать библиотеки моков даже в стандартной Android-разработке. Я рекомендую прочитать следующую статью: "Mocking is not practical Use fakes" (автор Pravin Sonawane).

План


Давайте вспомним, что у нас есть в модуле Kittens и что нам стоит протестировать.

  • KittenStore основной компонент модуля. Его реализация KittenStoreImpl содержит бОльшую часть бизнес-логики. Это первое, что мы собираемся протестировать.
  • KittenComponent фасад модуля и точка интеграции всех внутренних компонентов. Мы покроем этот компонент интеграционными тестами.
  • KittenView публичный интерфейс, представляющий UI, зависимость KittenComponent.
  • KittenDataSource внутренний интерфейс для доступа к Сети, который имеет платформенно-зависимые реализации для iOS и Android.

Для лучшего понимания структуры модуля приведу его UML-диаграмму:



План следующий:

  • Тестирование KittenStore
    • Создание тестовой реализации KittenStore.Parser
    • Создание тестовой реализации KittenStore.Network
    • Написание модульных тестов для KittenStoreImpl

  • Тестирование KittenComponent
    • Создание тестовой реализации KittenDataSource
    • Создание тестовой реализации KittenView
    • Написание интеграционных тестов для KittenComponent

  • Запуск тестов
  • Выводы


Модульное тестирование KittenStore


Интерфейс KittenStore имеет свой класс реализации KittenStoreImpl. Именно его мы и собираемся тестировать. Он имеет две зависимости (внутренние интерфейсы), определённые прямо в самом классе. Начнём с написания тестовых реализаций для них.

Тестовая реализация KittenStore.Parser


Этот компонент отвечает за сетевые запросы. Вот как выглядит его интерфейс:


TestKittenStoreNetwork имеет хранилище строк (как и настоящий сервер) и может их генерировать. По каждому запросу текущий список строк кодируется в одну строку. Если свойство images равно нулю, то Maybe просто завершится, что должно рассматриваться как ошибка.

Мы также использовали TestScheduler. У этого планировщика есть одна важная функция: он замораживает все поступающие задачи. Таким образом, оператор observeOn, используемый вместе с TestScheduler, будет замораживать нисходящий поток, а также все данные, проходящие через него, прямо как в реальной жизни. Но в то же время многопоточность не будет задействована, что упрощает тестирование и делает его надёжнее.

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

Тестовая реализация KittenStore.Parser


Этот компонент отвечает за разбор ответов от сервера. Вот его интерфейс:


Как и в случае с Network, используется TestScheduler для замораживания подписчиков и проверки их совместимости с моделью памяти Kotlin/Native. Ошибки обработки ответов моделируются, если входная строка пуста.

Модульные тесты для KittenStoreImpl


Теперь у нас есть тестовые реализации всех зависимостей. Пришло время модульных тестов. Все модульные тесты можно найти в репозитории, здесь я только приведу инициализацию и несколько самих тестов.

Первый шаг создать экземпляры наших тестовых реализаций:


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


Этапы теста:

  • сгенерировать исходные изображения;
  • создать экземпляр KittenStoreImpl;
  • сгенерировать новые изображения;
  • отправить Intent.Reload;
  • убедиться, что состояние содержит новые изображения.

И наконец давайте проверим следующий сценарий: когда в состоянии установлен флаг isLoading во время загрузки изображений.


Есть две зависимости: KittenDataSource и KittenView. Нам понадобятся тестовые реализации для них, прежде чем мы сможем начать тестирование.

Для полноты картины на этой диаграмме показан поток данных внутри модуля:



Тестовая реализация KittenDataSource


Этот компонент отвечает за сетевые запросы. У него есть отдельные реализации для каждой платформы, и нам нужна ещё одна реализация для тестов. Вот как выглядит интерфейс KittenDataSource:


Как и раньше, мы генерируем разные списки строк, которые кодируются в массив JSON при каждом запросе. Если изображения не сгенерированы или аргументы запроса неверные, Maybe просто завершится без ответа.

Для формирования JSON-массива используется библиотека kotlinx.serialization. Кстати, тестируемый KittenStoreParser использует её же для декодирования.

Тестовая реализация KittenView


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


Нам просто нужно запоминать последнюю принятую модель это позволит проверить правильность отображаемой модели. Мы также можем отправлять события от имени KittenView с помощью метода dispatch(Event), который объявлен в наследуемом классе AbstractMviView.

Интеграционные тесты для KittenComponent


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

Как и раньше, давайте начнём с создания экземпляров зависимостей и инициализации:


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


Этапы:

  • сгенерировать исходные ссылки на изображения;
  • создать и запустить KittenComponent;
  • сгенерировать новые ссылки;
  • отправить Event.RefreshTriggered от имени KittenView;
  • убедиться, что новые ссылки достигли TestKittenView.


Запуск тестов


Чтобы запустить все тесты, нам нужно выполнить следующую Gradle-задачу:

./gradlew :shared:kittens:build

Это скомпилирует модуль и запустит все тесты на всех поддерживаемых платформах: Android и iosx64.

А вот JaCoCo-отчёт о покрытии:



Заключение


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

  • KittenStoreImpl содержит бОльшую часть бизнес-логики;
  • KittenStoreNetwork отвечает за сетевые запросы высокого уровня;
  • KittenStoreParser отвечает за разбор сетевых ответов;
  • все преобразования и связи.

Последний пункт очень важен. Охватить егои возможно благодаря особенности MVI. Единственная ответственность представления отображать данные и отправлять события. Все подписки, преобразования и связи выполняются внутри модуля. Таким образом, мы можем покрыть общими тестами всё, кроме непосредственно отображения.

Такие тесты имеют следующие преимущества:

  • не используют платформенные API;
  • выполняются очень быстро;
  • надёжные (не мигают);
  • выполняются на всех поддерживаемых платформах.

Мы также смогли проверить код на совместимость со сложной моделью памяти Kotlin/Native. Это тоже очень важно из-за отсутствия безопасности во время сборки: код просто падает во время выполнения с исключениями, которые трудно отлаживать.

Надеюсь, это поможет вам в ваших проектах. Спасибо, что читали мои статьи! И не забудьте подписаться на меня в Twitter.



Бонусное упражнение


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

Рефакторинг KittenDataSource


В модуле существуют две реализации интерфейса KittenDataSource: одна для Android и одна для iOS. Я уже упоминал, что они отвечают за доступ к сети. Но на самом деле у них есть ещё одна функция: они генерируют URL-адрес для запроса на основе входных аргументов limit и page. В то же время у нас есть класс KittenStoreNetwork, который ничего не делает, кроме делегирования вызова в KittenDataSource.

Задание: переместить логику генерирования URL-запроса из KittenDataSourceImpl (на Android и iOS) в KittenStoreNetwork. Вам нужно изменить интерфейс KittenDataSource следующим образом:



Как только вы это сделаете, вам нужно будет обновить тесты. Единственный класс, к которому вам потребуется прикоснуться, это TestKittenDataSource.

Добавление постраничной загрузки


TheCatAPI поддерживает разбивку на страницы, поэтому мы можем добавить эту функцию для лучшего взаимодействия с пользователем. Вы можете начать с добавления нового события Event.EndReached для KittenView, после чего код перестанет компилироваться. Затем вам нужно будет добавить соответствующий Intent.LoadMore, преобразовать новый Event в Intent и обработать последний в KittenStoreImpl. Вам также потребуется изменить интерфейс KittenStoreImpl.Network следующим образом:



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

Подробнее..

Перевод Осваиваем Grid в SwiftUI

25.08.2020 18:19:47 | Автор: admin


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

  1. Быстрый старт в iOS-разработку
  2. Делаем многопоточное приложение Kotlin Multiplatform

А теперь перейдем к статье.



На этой неделе я хочу поговорить с вами о сетках элементов (Grids) одном из самых ожидаемых нововведений в SwiftUI. Все с нетерпением ждали альтернативы UICollectionView в SwiftUI, и, наконец, в этом году она появилась. SwiftUI предоставляет нам представления LazyVGrid и LazyHGrid, которые мы можем использовать для создания макетов с сетками элементов.

Основы


LazyVGrid и LazyHGrid это два новых типа представления (View), которые SwiftUI предоставляет нам для создания супер настраиваемого макета (Layout) на основе сетки элементов. Единственное различие между ними ось заполнения. LazyVGrid заполняет доступное пространство в вертикальном направлени. LazyHGrid же размещает свои дочерние элементы в горизонтальном направлении. Ось единственное различие между двумя этими представлениями. По этому все, что вы узнаете о LazyVGrid, применимо к LazyHGrid и наоборот. Давайте посмотрим на первый пример.

struct ContentView: View {    private var columns: [GridItem] = [        GridItem(.fixed(100), spacing: 16),        GridItem(.fixed(100), spacing: 16),        GridItem(.fixed(100), spacing: 16)    ]    var body: some View {        ScrollView {            LazyVGrid(                columns: columns,                alignment: .center,                spacing: 16,                pinnedViews: [.sectionHeaders, .sectionFooters]            ) {                Section(header: Text("Section 1").font(.title)) {                    ForEach(0...10, id: \.self) { index in                        Color.random                    }                }                Section(header: Text("Section 2").font(.title)) {                    ForEach(11...20, id: \.self) { index in                        Color.random                    }                }            }        }    }}




В приведенном выше примере мы создаем сетку из трех столбцов, в которой каждый столбец имеет фиксированный размер в 100pt. Я собираюсь использовать этот пример, чтобы описать все доступные нам варианты конфигурации.

  1. Параметр columns это массив, который определяет столбцы в макете сетки (grid layout). Для описания столбца SwiftUI предоставляет нам тип GridItem. Мы поговорим о нем немного позже.
  2. Параметр alignment позволяет нам выровнять содержимое сетки с помощью перечисления HorizontalAlignment для LazyVGrid и VerticalAlignment для LazyHGrid. Работает так же, как stack alignment.
  3. Параметр spacing указывает расстояние между каждой строкой внутри LazyVGrid или пространство между каждым столбцом внутри LazyHGrid.
  4. Параметр pinnedViews позволяет определять опции для закрепления верхних и нижних колонтитулов секции (headers и footers). По умолчанию он пуст, что означает, что верхние и нижние колонтитулы ведут себя как содержимое и исчезают при прокрутке. Вы можете включить закрепление верхнего и нижнего колонтитулов, в этом случае они будут накладываться на контент и становятся постоянно видимыми.


GridItem


Каждый столбец в сетке должен быть определен с помощью структуры GridItem. Тип GridItem позволяет нам указывать размер (size), выравнивание (alignment) и интервал (spacing) для каждого столбца. Давайте посмотрим на небольшой пример.

private var columns: [GridItem] = [    GridItem(.fixed(50), spacing: 16, alignment: .leading),    GridItem(.fixed(75)),    GridItem(.fixed(100))]




Как видите, каждый столбец может иметь разные параметры размера, интервала и выравнивания. Самое интересное здесь размер. Есть три способа определить размер столбца внутри сетки. Он может быть фиксированным (fixed), гибким (flexible) или адаптивным (adaptive).

Fixed столбец является самым очевидным. Сетка размещает столбец в соответствии с заданным вами размером. В предыдущем примере мы создали макет с тремя столбцами, в котором столбцы имеют фиксированные размеры 50pt, 75pt и 100pt соответственно.

Опция flexible позволяет определить столбец который расширяется или сжимается в зависимости от доступного пространства. Мы также можем предоставить минимальный и максимальный размер гибкого столбца. По умолчанию он использует минимальное значение 10pt и не ограничен по максимуму.

private var columns: [GridItem] = [    GridItem(.flexible(minimum: 250)),    GridItem(.flexible())]




Здесь мы создаем макет, который делит доступное пространство между двумя гибкими столбцами. Первый столбец занимает 250pt своего минимального размера, а второму остается все остальное доступное пространство.

Самый интересная опция adaptive. Адаптивный вариант позволяет нам размещать несколько элементов в пространстве одного гибкого столбца. Давайте попробуем разобраться с этим на примере.

private var columns: [GridItem] = [    GridItem(.adaptive(minimum: 50, maximum: 100)),    GridItem(.adaptive(minimum: 150))]




Как вы можете видеть, у нас есть два адаптивных столбца. В первом столбце разместилось несколько элементов с минимальным размером 50pt и максимальным 100pt. Адаптивные столбцы удобны, когда количество элементов внутри столбца должно зависеть от доступного пространства.
Настоящая мощь сеток проявляется, когда вы начинаете смешивать типы столбцов. Вы можете создать макет из двух столбцов, где первый фиксированный, а второй адаптивный. Давайте посмотрим как это будет выглядеть.

private var columns: [GridItem] = [    GridItem(.fixed(100)),    GridItem(.adaptive(minimum: 50))]




Заключение


Сетка позволяет создавать очень сложные и интересные макеты путем смешивания различных типов GridItem. Следует отметить, что все изменения в сетках можно анимировать. Надеюсь, вам понравилась эта статья. Не стесняйтесь подписываться на меня в Твиттере и задавать свои вопросы, связанные с этой темой. Спасибо за внимание, до встречи!



Узнать подробнее о наших курсах вы можете по ссылкам ниже:



Подробнее..

Российские пасхалки в мобильных приложениях. Какие они?

11.09.2020 14:12:59 | Автор: admin
Привет, Хабр! Уже завтра День программиста (12.09.2020), и специально к нашему профессиональному празднику я написал не хардкорно-технический пост, а лайтовую статью о маленьких, не всегда очевидных фичах, которые хоть и не часто, но встречаются в мобильных приложениях и не только. Как вы уже догадались это пасхалки. И не просто пасхалки, а отечественного производства. Если хотите ненадолго погрузиться в детали, которые мы обычно не замечаем, либо которые сложно найти, добро пожаловать под кат.



Для тех, кто не знаком с понятием пасхальное яйцо небольшая выдержка из википедии:
Пасхальное яйцо (англ. Easter Egg) секрет в компьютерной игре, фильме или программном обеспечении, заложенный создателями. Отличие пасхального яйца в игре от обычного игрового секрета состоит в том, что его содержание, как правило, не вписывается в общую концепцию, выглядит в контексте неправдоподобно, нелепо, и зачастую является внешней ссылкой. Пасхальные яйца играют роль своеобразных шуток для внимательных игроков или пользователей, но могут применяться в целях защиты авторских прав.
Чаще всего для получения пасхального яйца следует произвести сложную или нестандартную совокупность действий, что делает маловероятным случайное обнаружение. Название происходит от популярного в США и бывших Британских колониях семейного мероприятия охота за яйцами (англ. egg hunt), устраиваемого накануне Пасхи, в котором участники должны с помощью подсказок найти как можно больше спрятанных по местности яиц.
Мы опросили наших коллег по цеху из отечественных компаний о наличии пасхалок в мобильных приложениях, и вот какой обзор в результате получился:

Lamoda и OneTwoTrip


В новогодние праздники на страницах с предложениями идёт снег. (к сожалению, сборок приложения с этой пасхалкой найти не удалось).

Joom


В Android-приложении есть змейка. Инструкция для активации: профиль -> настройки -> About -> Провести пальцем по логотипу от верхней части до точки. Обязательно играйте со звуком!


Tinkoff


При выборе диапазона дат в анализе трат по карте 3-го сентября календарь переворачивается.


Сберавто


Если в поиске находится слишком много предложений (число не уточняется), то вместо количества будет написано видимо-невидимо или рука листать устанет. А также в честь дня программиста в приложении появилась игра 2048. Если в поиске ввести число 256 или 2048 она активируется.

VK


В iOS приложении анимация обновления иногда (в случайном порядке) меняется на котика.


Ангстрем


Этот конвертер величин есть только на iOS. При листании экранов вправо последним экраном будет смайлик, и если его вытащить достаточно далеко, он подмигнёт.


2GIS


В День космонавтики маркер геопозиции в навигаторе менялся на ракету и при старте маршрута звучала знаменитая фраза Юрия Гагарина Поехали. Также раньше была доступна карта Вестероса. Сейчас, к сожалению, этого в приложении нет.

iFunny


И, конечно же, флагман нашей компании.
В приложении есть аккаунт c игровым контентом в виде Web Apps, под названием iFunnyArcade_2017. Про их разработку мы писали тут.


Android OS


Даже сама Android OS содержит в себе пасхалки (она, конечно, не отечественного производства, но думаю, вы не будете против почитать про несколько пасхалок разработчика самой популярной операционной системы в мире).

Для пользователей:
Нужно зайти в настройки -> о системе -> версия OS -> 3 раза быстро тапнуть по версии OS (количество может отличаться от версии системы).
Android 10 Android 11

Для разработчиков:
В коде класса Chronometer есть метод isTheFinalCountDown, при вызове которого произойдёт переход по ссылке www.youtube.com/watch?v=9jK-NcRmVcw (клип на песню группы Europe The Final Countdown).
    /**     * @return whether this is the final countdown     */    public boolean isTheFinalCountDown() {        try {            getContext().startActivity(                    new Intent(Intent.ACTION_VIEW, Uri.parse("https://youtu.be/9jK-NcRmVcw"))                            .addCategory(Intent.CATEGORY_BROWSABLE)                            .addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT                                    | Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT));            return true;        } catch (Exception e) {            return false;        }    }

Также у класса SensorManager есть константа-отсылка к Звёздным войнам.
/** Gravity (estimate) on the first Death Star in Empire units (m/s^2) */public static final float GRAVITY_DEATH_STAR_I = 0.000000353036145f;


А где ещё?


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

Формальные грамматики на службе мобильного клиента

17.09.2020 12:16:31 | Автор: admin
В повседневной жизни мы пользуемся готовыми интерпретаторами и компиляторами и редко кому придёт в голову написать их самостоятельно. Во-первых, это же сложно, во-вторых зачем.

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

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



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

В конце концов, заполненную форму мобильное приложение отправляет на сервер. В этот момент важно, чтобы данные были правильно отформатированы. Например, API интернет-банка будет требовать, чтобы номер выглядел вот так: 9161234567 без 8, скобочек и минусов.

Если форма пассивно обрабатывает пользовательские данные, то номер телефона она принимает в любом формате, но возникают проблемы:

  • Мы вынуждаем пользователя разбираться в том, как нужно правильно отформатировать номер телефона, чтобы система его приняла.
  • Мы вынуждаем пользователя набирать на клавиатуре труднодоступные символы: переключать раскладку клавиатуры, чтобы набрать цифры или, например, знак плюса.
  • Нам нужно точно объяснить пользователю, в чем он ошибся.



Конечно, если в форме одно или два поля, особых неудобств пользователь не почувствует. Другое дело, когда нужно заполнить сложную форму, например, для банка с большим количеством разных полей. Как облегчить пользователю жизнь? Одно из решений активно обрабатывать пользовательский ввод с помощью масок.

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

Если продолжать пример с номером телефона, использование маски даст следующие преимущества:

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



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


Почему нельзя просто взять и описать маску


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

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

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

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

  1. понять правила построения исходной грамматики,
  2. понять правила построения целевой грамматики,
  3. написать правила перевода из исходной грамматики в целевую,
  4. реализовать всё это в коде.

Это то, для чего и пишутся компиляторы и трансляторы.

Теперь подробно рассмотрим наше решение на основе формальных грамматик.

Предыстория


В нашем приложении довольно много разнообразных экранов, которые формируются по backend driven принципу: полное описание экрана вместе с данными приходит с сервера.


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



Давайте посмотрим, как устроены маски.

Примеры масок в разных форматах


В качестве первого примера возьмём всё ту же форму ввода номера телефона. Маска для такой формы может выглядеть так.



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

Красным выделена часть, которая называется константа. Это символы, которые появятся автоматически, пользователь их вводить не должен:



Дальше идёт динамическая часть она всегда выделена угловыми скобками:


Далее в тексте я буду называть это выражение динамическим выражением или ДВ сокращённо


Здесь записано выражение, по которому мы будем форматировать наш ввод:



Красным выделены кусочки, отвечающие за содержимое динамической части.

\\d любая цифра.

+ обычный репитер: повторить минимум один раз.

${3} символ метаинформации, который уточняет количество повторений. В данном случае должно быть три символа.

Тогда выражение \\d+${3} означает, что должно быть три цифры.

В данном формате масок внутри динамической части может быть только один репитер:



Это ограничение появилось не просто так сейчас объясню почему.
Допустим, у нас есть ДВ, в котором жёстко указан размер: 4 элемента. И мы задаём ему 2 элемента с репитером: `<!^\\d+\\v+${4}>`. Под такое ДВ попадают следующие сочетания:

  • 1abc
  • 12ab
  • 123a

Получается, что такое ДВ не даёт нам однозначного ответа, чего ожидать на месте второго символа: цифру или букву.

Берём маску, складываем её с пользовательским вводом. Получаем отформатированный номер телефона:



На клиенте формат у масок может выглядеть по-другому. Например, в библиотеке Input Mask от Redmadrobot маска для номера телефона имеет следующий вид:



Выглядит она симпатичнее и понимать её проще.

Получается, что маска для сервера и маска для клиента записываются по-разному, а делают одно и то же.



Переформулируем задачу: как совместить маски разных форматов


Нам нужно эти маски друг с другом совместить или как-то из одной получить вторую.



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

И здесь пришла идея написать очень простой интерпретатор, который позволит из одной грамматики получить вторую грамматику.

Так как мы дошли до интерпретатора, давайте поговорим про грамматики.

Как проводится синтаксический анализ




Сначала у нас есть поток символов наша маска. По сути это строка, которой мы оперируем. Но так как символы не формализованы, нужно формализовать строку: разбить её на элементы, которые будут понятны интерпретатору.

Этот процесс называется токенизация: поток символов превращается в поток токенов. Количество токенов ограничено, они формализованы, следовательно, можно их анализировать.

Далее, исходя из правил грамматики, по потоку токенов строим абстрактное синтаксическое дерево. Из дерева получаем поток символов в нужной нам грамматике.

Есть выражение. Смотрим на него и видим, что у нас есть константа, про которую я говорил выше:



Все константы представим как токен CS, у которого аргумент сама константа:


Следующий вид токенов это начало ДВ:


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



Затем у нас идёт репитер.



Потом несколько символов, которые считаются метаданными. Мы схитрим и представим их одним токеном, потому что так проще.



Конец ДВ. Таким образом, мы разложили всё по токенам.



Пример токенизации маски для номера телефона


Чтобы посмотреть, как в принципе происходит процесс токенизации и как будет работать интерпретатор, возьмём маску для номера телефона и преобразуем её в поток токенов.



Сначала символ +. Преобразуем в константный символ +. Далее то же самое мы делаем для семёрки и для всех остальных символов. Получаем массив из токенов. Это ещё не структура далее будем этот массив анализировать.

Лексер и построение АСД


Теперь более сложная часть это лексер.



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

Правило symbolRule описывает какой-то символ. Если это правило применимо, если оно верно, это значит что мы встретили либо специальный символ, либо константный символ. Можно сказать, что это функция.

Дальше repeaterRule. Это правило описывает ситуацию, когда встречается какой-то символ, а за ним токен репитера.

Дальше всё похожим образом выглядит. Если это ДВ, то это либо symbol, либо repeater. В нашем случае это правило шире. И в конце обязательно должен быть токен с метаданными.
Последнее правило maskRule. Это последовательность из символов и ДВ.

Теперь построим абстрактное синтаксическое дерево (АСД) из массива токенов.

Вот список токенов. Первый узел дерева корневой, с которого начнём построение. Он не несёт никакого смысла, просто дереву нужен корень.



У нас есть первый токен +, значит, мы просто добавляем дочерний узел, и все.



То же самое делаем со всеми остальными константными символами, но дальше сложнее. Мы наткнулись на токен ДВ.



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



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



На самом деле, в данном случае, нет. У нас дочерним узлом будет репитер.



Почему? Потому что так удобнее работать с деревом в будущем. Допустим, мы хотим распарсить это дерево и построить по нему какую-то грамматику. Во время парсинга дерева мы смотрим на типы узлов. Если у нас CS узел, то мы и разбираем его в тот же самый CS узел, но уже для другой грамматики. Условно, мы итерируем по вершинам дерева и запускаем какую-то логику.

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

Особенно это было бы заметно на группах символов: например, [abcde]. В том случае, очевидно, должен быть какой-то родительский узел GROUP, у которого будет список дочерних узлов CS(a)CS(b) и т.д.

Возвращаемся к токену с метаданными. Он не включается в контент, он находится сбоку.



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

Закончилось ДВ, и мы не считаем это каким-то узлом: это был токен, который теперь можно выкинуть. Мы не будем превращать его в узел дерева.



У нас уже есть поддерево, корнем которого является узел SR то есть та самая динамическая часть. Токен конца ДВ очень нам помогает в процессе построения дерева мы можем понять, когда закончено построение поддерева для ДВ. Но никакой ценности для логики этот токен не несёт: глядя на построчное дерево, мы и так понимаем, когда ДВ закончится, потому что оно как бы закрыто узлом SR.

Дальше просто обычные константные символы.



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

Синтаксис библиотеки InputMask от Redmadrobot


Рассмотрим синтаксис библиотеки Redmadrobot.



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

Так выглядит нотация:



Нотация состоит из трёх частей:

  • character символ, который мы будем использовать, чтобы записать маску. То, из чего состоит алфавит маски. Например, d.
  • characterSet какие набранные пользователем символы матчатся этой нотацией. Например, 0, 1, 2, 3, 4 и так далее.
  • isOptional обязательно ли пользователь должен ввести один из символов characterSet или можно ничего не вводить.

Смотрим, у нас сейчас будет такая маска.



  • У символа b специальная нотация цифры и он не опциональный.
  • У символа c другая нотация CharacterSet другой. Он тоже не опциональный.
  • И символ C это то же самое что c, только он опциональный. Это нужно для того, чтобы в маске мы посмотрели на метаданные и увидели, что там не жёсткое ограничение, а слабое.

Если нужно записать правило, когда символов может быть от одного до десяти, то один символ будет не опциональный. А девять символов будут опциональными. То есть в нотации из примера они будут записаны большими буквами. В итоге это правило будет выглядеть так: [cCCCCCCCCC]

Пример: перевод маски номера телефона из формата бекэнда в формат InputMask


Вот дерево, которое мы получили на прошлом этапе. Нам нужно по нему пройтись. Первое, куда мы попадаем, это корень.



Дальше от корня мы попадаем в константный символ + генерируем сразу +. Справа записывается маска в формате InputMask.



Следующий символ понятный просто 7, а за ним открывающая скобка.

Дальше генерируется кусок динамической части, но он пока не заполнен.



Идём внутрь, у нас контент, это технический узел. Ничего не пишем никуда.



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



Доходим, наконец, до какого-то контентного символа.



Контентым символом может быть либо константный символ, либо специальный. В данном случае используется специальный, потому что только он несёт какую-то смысловую нагрузку для ввода.

Вот мы его написали, возвращаемся и идём как раз за метаинформацией.



Смотрим, что у нас там был репитер и здесь у нас 3 жёсткое ограничение. Поэтому повторяем его три раза и получаем такой вот динамический кусок. Дальше мы дописываем наши константные символы.



В результате мы получаем маску, которая похожа на маску в формате роботов.

На практике мы взяли одну грамматику и сгенерировали из неё другую грамматику.

Правила генерации клиентской грамматики из серверной


Теперь немного про правила генерации. Это важно.

Могут быть такие непростые случаи: внутри динамической части несколько разных кусочков ДВ. Внутри фигурные скобки: это то же самое, как в ДВ, один из множества. Давайте разберём, как интерпретатор будет обрабатывать эту ситуацию.



Сначала идёт набор символов, и мы должны конвертировать его в какую-то нотацию с точки зрения InputMask. Почему? Потому что это какой-то ограниченный набор символов, которые нам нужно матчить. Нам нужно совместить пользовательский ввод и символ, и поэтому здесь у нас будет записана какая-то определённая нотация.

Дальше у нас идёт символ \\d.

Дальше ДВ с опциональным размером.



Первый, получается, какой-то символ b. У него будет Character Set, содержащий abcd.
Далее понятно, что будет другой уже символ, потому что не сматчишь иначе, или сматчишь неправильно. И дальше у нас это выражение превращается вот в такое.

В последней части обязательно должен быть хотя бы один символ. Обозначим это требование как d. Но также пользователь может ввести два дополнительных символа, и тогда они обозначаются как DD.

Соберём всё вместе.



Здесь приведён пример Character Set, которые генерируются. Видно, что b соответствует Character Set abcd, для цифр соответствующий предустановленный Character Set. Для d и D соответствующий Character Set содержит 12vf.

Итоги


Мы научились автоматически конвертировать одну грамматику в другую: теперь в нашем приложении работают маски по спецификации сервера.

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



Слева: вверху у поля ввода телефона есть иконка (на самом деле кнопка), которая отправит пользователя в список контактов. Справа: пример клавиатуры для обычного текстового сообщения.

Работающая библиотека по переводу масок


Можете посмотреть на то, как мы реализовали вышеописанный подход. Библиотека лежит на Гитхабе.

Примеры перевода разных масок


Это первая маска, которую мы смотрели в самом начале. Она интерпретируется в такое RedMadRobot представление.



А это вторая маска просто маска ввода чего-то. Она конвертируется в такое представление.

Подробнее..

Виджеты в iOS 14 возможности и ограничения

17.09.2020 14:13:09 | Автор: admin


В этом году для iOS-разработчиков появилось сразу несколько интересных возможностей посадить батарейку айфона улучшить пользовательский опыт, одна из таких новые виджеты. Пока мы все находимся в ожидании выхода релизной версии ОС, хотел бы поделиться опытом написания виджета для приложения Кошелёк и рассказать, с какими возможностями и ограничениями наша команда столкнулась на бета-версиях Xcode.

Начнем с определения виджетами называются представления, показывающие актуальную информацию без запуска основного мобильного приложения и всегда находящиеся под рукой у пользователя. Возможность их использовать уже есть в iOS (Today Extension), начиная с iOS 8, но мой сугубо личный опыт их использования довольно печален под них хоть и выделен специальный рабочий стол с виджетами, но я всё равно редко туда попадаю, привычка не выработалась.

Как итог, в iOS 14 мы видим перерождение виджетов, глубже интегрированных в экосистему, и более удобных для пользователя (в теории).



Работа с картами лояльности одна из основных функций нашего приложения Кошелёк. Периодически в отзывах в App Store появляются предложения от пользователей о возможности добавления виджета в Today. Пользователи, находясь у кассы, хотели бы как можно быстрее показать карту, получить скидку и убежать по своим делам, ведь промедление на любой квант времени вызывает те самые укоризненные взгляды в очереди. В нашем случае виджет может сэкономить несколько пользовательских действий для открытия карты, таким образом сделав операцию оплаты товара на кассе более быстрой. Магазины тоже будут благодарны меньше очередей на кассе.

В этом году Apple неожиданно для всех выпустила релиз iOS практически сразу после презентации, оставив разработчикам сутки на доработки своих приложений на Xcode GM, но мы оказались готовы к релизу, так как свой вариант виджета наша iOS-команда стала делать еще на бета-версиях Xcode. Сейчас виджет находится на ревью в App Store. Обновление устройств до новой iOS по статистике происходит довольно быстро; скорее всего, пользователи пойдут проверять, у каких приложений виджеты уже есть, найдут наш и будут счастливы.

В дальнейшем мы хотели бы добавить еще больше актуальной информации например, баланс, штрихкод, последние непрочитанные сообщения от партнёров и уведомления (например, что пользователям необходимо совершить действие подтвердить или активировать карту). На текущий момент результат выглядит следующим образом:



Добавление виджета в проект


Как и другие подобные дополнительные возможности, виджет добавляется как расширение (extension) к основному проекту. После добавления Xcode любезно генерирует код виджета и других основных классов. Вот тут нас ждала первая интересная особенность для нашего проекта этот код не компилировался, так как в одном из файлов автоматически подставлялся префикс в названиях класса (да-да, те самые Obj-C префиксы!), а в генерируемых файлах нет. Как говорится, не боги горшки обжигают, видимо, разные команды внутри Apple не договорились между собой. Будем надеяться, что к релизной версии исправят. Для того, чтобы настроить префикс своего проекта, в File Inspector основного таргета приложения заполните поле Class Prefix.

Для тех, кто следил за новинками WWDC, не секрет, что реализация виджетов возможна только с использованием SwiftUI. Интересный момент, что таким образом Apple форсит обновление на свои технологии: даже если основное приложение написано с использованием UIKit, то тут, будьте любезны, только SwiftUI. С другой стороны, это хорошая возможность попробовать новый фреймворк для написания фичи, в этом случае он удобно вписывается в процесс никаких изменений состояния, никакой навигации, требуется только задекларировать статичный UI. То есть вместе с новым фреймворком появились и новые ограничения, потому как старые виджеты в Today могут содержать больше логики и анимацию.

Одно из основных нововведений SwiftUI возможность предпросмотра без запуска на симуляторе или девайсе (preview). Классная вещь, но, к сожалению, на больших проектах (в нашем ~400K строк кода) работает крайне медленно даже на топовых макбуках, быстрее запустить на девайсе. Альтернатива такому способу иметь под рукой пустой проект или плейграунд для быстрого прототипирования.

Возможность для дебага также есть с помощью выделенной Xcode схемы. На симуляторе отладка работает нестабильно даже к версии Xcode 12 beta 6, поэтому лучше пожертвовать один из тестовых девайсов, обновить до iOS 14 и тестировать на нем. Будьте готовы, что эта часть и на релизных версиях будет работать не как ожидается.

Интерфейс


На выбор пользователю даются разные типы (WidgetFamily) виджетов трёх размеров small, medium, large.



Для регистрации необходимо явно указать поддерживаемые:
struct CardListWidget: Widget {    public var body: some WidgetConfiguration {        IntentConfiguration(kind: CardListWidgetKind,                            intent: DynamicMultiSelectionIntent.self,                            provider: CardListProvider()) { entry in            CardListEntryView(entry: entry)        }        .configurationDisplayName("Быстрый доступ")        .description("Карты, которые вы используете чаще всего")        .supportedFamilies([.systemSmall, .systemMedium])    }}

Мы с командой решили остановиться на small и medium выводить одну любимую карту для маленького виджета или 4 для medium.

Добавление виджета на рабочий стол происходит из центра управления, там пользователь выбирает необходимый ему вид:



Цвет кнопки Добавить виджет кастомизируем с помощью Assets.xcassets -> AccentColor, имя виджета с описанием тоже (пример кода выше).

Если уперлись в ограничение по количеству поддерживаемых видов, то можно расширить его с помощью WidgetBundle:

@mainstruct WalletBundle: WidgetBundle {    @WidgetBundleBuilder    var body: some Widget {        CardListWidget()        MySecondWidget()    }}

Так как виджет показывает слепок некоторого состояния, то единственная возможность для пользовательского интерактива это переход в основное приложение по нажатию на какой-то элемент или весь виджет. Никакой анимации, навигации и переходов на другие view. Но есть возможность прокинуть диплинку в основное приложение. При этом для small виджета зоной нажатия является вся область, и в этом случае используем widgetURL(_:) метод. Для medium и big доступны нажатия по view, и в этом нам поможет структура Link из SwiftUI.

Link(destination: card.url) {  CardView(card: card)}

Финальный вид виджета двух размеров получился следующим:



При проектировании интерфейса виджета могут помочь следующие правила и требования (согласно гайдлайнам Apple):
  1. Сфокусируйте виджет на одной идее и проблеме, не пытайтесь повторить всю функциональность приложения.
  2. В зависимости от размера выводите больше информации, а не просто масштабируйте контент.
  3. Выводите динамическую информацию, которая может меняться в течение дня. Крайности в виде полностью статической информации и информации, меняющейся ежеминутно, не приветствуются.
  4. Виджет должен давать актуальную информацию пользователям, а не быть еще одним способом открыть приложение.

Внешний вид настроили. Следующий шаг выбрать, какие карты и каким образом показывать пользователю. Карт может быть явно больше четырёх. Рассмотрим несколько вариантов:
  1. Дать возможность пользователю выбирать карты. Кто, как не он, знает, какие карты важнее!
  2. Показывать последние использованные карты.
  3. Сделать более умный алгоритм, ориентируясь, например, на время и день недели и статистику (если пользователь по будням вечером ходит во фруктовую лавку у дома, а на выходных ездит в гипермаркет, то можно помочь пользователю в этом моменте и показывать нужную карту)

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

Пользовательские настройки виджета


Настройки формируются с помощью интентов (привет Андроид-разработчикам) при создании нового виджета файл интента добавляется в проект автоматически. Кодогенератор подготовит класс-наследник от INIntent, который является частью фреймворка SiriKit. В параметрах интента стоит магическая опция Intent is eligible for widgets. Доступны несколько типов параметров, можно настраивать свои подтипы. Так как данные в нашем случае это динамический список, то еще устанавливаем пункт Options are provided dynamically.

Для разных типов виджета настраиваем максимальное количество элементов в списке для small 1, для medium 4.
Этот тип интента используется виджетом как источник данных.



Далее настроенный класс интента необходимо поставить в конфигурацию IntentConfiguration.
struct CardListWidget: Widget {    public var body: some WidgetConfiguration {        IntentConfiguration(kind: WidgetConstants.widgetKind,                            intent: DynamicMultiSelectionIntent.self,                            provider: CardListProvider()) { entry in            CardListEntryView(entry: entry)        }        .configurationDisplayName("Быстрый доступ")        .description("Карты, которые вы используете чаще всего.")        .supportedFamilies([.systemSmall, .systemMedium])    }}

В случае, если пользовательские настройки не требуются, то есть альтернатива в виде класса StaticConfiguration, которая работает без указания интента.

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

iPhone 11 Pro Max28 для настроек21 для меню добавленияiPhone 11 Pro25 для настроек19 для меню добавленияiPhone SE24 для настроек19 для меню добавления

Описание является многострочным. В случае очень длинного текста в настройках контент можно проскролить. А вот на экране добавления сначала сжимается превью у виджета, а потом с версткой происходит что-то страшное.



Также можно изменить цвет фона и значения параметров WidgetBackground и AccentColor по умолчанию они уже лежат в Assets. При необходимости их можно переименовать в конфигурации виджета в Build Settings у группы Asset Catalog Compiler Options в полях Widget Background Color Name и Global Accent Color Name соответственно.



Некоторые параметры могут быть скрыты (или показаны) в зависимости от выбранного значения в другом параметре через настройку Relationship.
Стоит отметить, что UI для редактирования параметра зависит от его типа. К примеру, если укажем Boolean, то мы увидим UISwitch, а если Integer, то тут у нас уже выбор из двух вариантов: ввод через UITextfield или пошаговое изменение через UIStepper.



Взаимодействие с основным приложением.


Связку настроили, осталось определить, откуда сам интент возьмет реальные данные. Мостик с основным приложением в этом случае файл в общей группе (App Groups). Основное приложение пишет, виджет читает.
Для получения URL к общей группе используется следующий метод:
FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: group.ru.yourcompany.yourawesomeapp)

Сохраняем всех кандидатов, так как они будут использоваться пользователем в настройках как словарь для выбора.
Далее операционная система должна узнать, что данные обновились, для этого вызываем:
WidgetCenter.shared.reloadAllTimelines()// Или WidgetCenter.shared.reloadTimelines(ofKind: "kind")

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

Обновление данных


В целях бережного отношения к батарейке пользовательского девайса Apple продумали механизм обновления данных на виджете с использованием timeline механизма генерации слепков (snapshot). Напрямую разработчик не обновляет и не управляет view, но зато предоставляет расписание, руководствуясь которым, операционная система нарежет снапшотов в бэкграунде.
Обновление происходит по следующим событиям:
  1. Вызов используемого ранее WidgetCenter.shared.reloadAllTimelines()
  2. При добавлении виджета пользователем на рабочий стол
  3. При редактировании настроек.

Также в распоряжении разработчика три вида политик по обновлению таймлайнов (TimelineReloadPolicy):
atEnd обновление после показа последнего cнапшота
never обновление только в случае принудительного вызова
after(_:) обновление через определенный промежуток времени.

В нашем случае достаточно попросить систему сделать один снепшот до момента, пока данные карт не обновились в основном приложении:

struct CardListProvider: IntentTimelineProvider {    public typealias Intent = DynamicMultiSelectionIntent    public typealias Entry = CardListEntry    public func placeholder(in context: Context) -> Self.Entry {        return CardListEntry(date: Date(), cards: testData)    }    public func getSnapshot(for configuration: Self.Intent, in context: Self.Context, completion: @escaping (Self.Entry) -> Void) {        let entry = CardListEntry(date: Date(), cards: testData)        completion(entry)    }    public func getTimeline(for configuration: Self.Intent, in context: Self.Context, completion: @escaping (Timeline<Self.Entry>) -> Void) {        let cards: [WidgetCard]? = configuration.cards?.compactMap { card in            let id = card.identifier            let storedCards = SharedStorage.widgetRepository.restore()            return storedCards.first(where: { widgetCard in widgetCard.id == id })        }        let entry = CardListEntry(date: Date(), cards: cards ?? [])        let timeline = Timeline(entries: [entry], policy: .never)        completion(timeline)    }}struct CardListEntry: TimelineEntry {    public let date: Date    public let cards: [WidgetCard]}

Более гибкий вариант пригодился бы при использовании автоматического алгоритма подбора карточек в зависимости от дня недели и времени.

Отдельно стоит отметить показ виджета, если он находится в стэке из виджетов (Smart Stack). В этом случае для управления приоритетами мы можем воспользоваться двумя вариантами: Siri Suggestions или через установку значения relevance у TimelineEntry с типом TimelineEntryRelevance. TimelineEntryRelevance содержит два параметра:
score приоритет текущего снапшота относительно других снапшотов;
duration время, пока виджет остается актуальным и система может поставить его на верхнюю позицию в стэке.

Оба способа, а также возможности конфигурации виджета, были подробно рассмотрены на сессии WWDC.

Также необходимо рассказать о том, как поддерживать актуальное отображение даты и времени. Так как мы не можем регулярно обновлять содержимое виджета, то для компонента Text было добавлено несколько стилей. При использовании стиля система автоматически обновляет содержимое компонента, пока виджет находится на экране. Возможно, в будущем такой же подход распространится и на другие SwiftUI компоненты.

Text поддерживает следующие стили:
relative разница времени между текущей и заданной датами. Тут стоит отметить: если дата указана в будущем, то начинается обратный отсчет, а после показывается дата от момента достижения нуля. Такое же поведение будет и для следующих двух стилей;
offset аналогично предыдущему, но есть индикация в виде префикса с ;
timer аналог таймера;
date отображение даты;
time отображение времени.

Кроме этого, есть возможность отобразить промежуток времени между датами, просто указав интервал.

let components = DateComponents(minute: 10, second: 0) let futureDate = Calendar.current.date(byAdding: components, to: Date())! VStack {   Text(futureDate, style: .relative)      .multilineTextAlignment(.center)   Text(futureDate, style: .offset)      .multilineTextAlignment(.center)   Text(futureDate, style: .timer)      .multilineTextAlignment(.center)   Text(Date(), style: .date)       .multilineTextAlignment(.center)   Text(Date(), style: .time)      .multilineTextAlignment(.center)   Text(Date() ... futureDate)      .multilineTextAlignment(.center)}



Превью виджета


При первом отображении виджет будет открыт в режиме превью, для этого нам необходимо вернуть TimeLineEntry в методе placeholder(in:). В нашем случае это выглядит так:
func placeholder(in context: Context) -> Self.Entry {        return CardListEntry(date: Date(), cards: testData) }

После чего к view применяется модификатор redacted(reason:) с параметром placeholder. При этом элементы на виджете отображаются размытыми.



Мы можем отказаться от этого эффекта у части элементов, использовав unredacted() модификатор.
Также в документации сказано, что вызов метода placeholder(in:) происходит синхронно и результат должен вернуться максимально быстро, в отличие от getSnapshot(in:completion:) и getTimeline(in:completion:)

Скругление элементов


В гайдлайнах рекомендуется согласовать скругление у элементов со скруглением виджета, для этого в iOS 14 была добавлена структура ContainerRelativeShape, которая позволяет применить к view форму контейнера.

.clipShape(ContainerRelativeShape()) 

Поддержка Objective-C


В случае необходимости добавить в виджет код на Objective-C (например, у нас на нем написана генерация изображений штрихкодов) всё происходит стандартным способом через добавление Objective-C bridging header. Единственная проблема, с которой мы столкнулись при сборке Xcode перестал видеть автогенерируемые файлы интентов, поэтому мы также добавили их в bridging header:

#import "DynamicCardSelectionIntent.h"#import "CardSelectionIntent.h"#import "DynamicMultiSelectionIntent.h"

Размер приложения


Тестирование проводилось на Xcode 12 beta 6
Без виджета: 61.6 Мб
С виджетом: 62.2 Мб

Резюмирую основные моменты, которые рассмотрели в статье:
  1. Виджеты отличная возможность пощупать SwiftUI на практике. Добавляйте их в проект, даже если минимальная поддерживаемая версия ниже iOS 14.
  2. WidgetBundle используется для увеличения числа доступных виджетов, вот отличный пример как много различных виджетов имеет приложение ApolloReddit.
  3. Для добавления пользовательских настроек на самом виджете поможет IntentConfiguration или StaticConfiguration, если пользовательские настройки не нужны.
  4. Общая папка на файловой системе в общей группе App Groups поможет синхронизировать данные с основным приложением.
  5. На выбор разработчику предоставляется несколько политик обновления таймлайна (atEnd, never, after(_:)).

На этом тернистый путь разработки виджета на бета-версиях Xcode можно считать завершенным, остался один простой шаг пройти ревью в App Store.

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

Что мы знаем о секретной пасхалке гугла для разработчиков?

29.08.2020 02:07:45 | Автор: admin
Эй, Хаброжитель, приветствую тебя!

На днях произошла очень странная и неожиданная вещь: гугл открыла ворота к своей пасхалке)

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



Что случилось(?), что сделал не так?

Читаю вопрос гугла и не понимаю, что происходит. Какую задачу предлагает решить гугл и вообще почему он это делает? Стало интересно и не думая говорю, что хочу поиграть.



И тут гугл перенаправляет нас в консоль и мы видим следующее:



Так подождите, объясните хоть кто-нибудь, что вообще происходит, почему гугл хочет, чтобы мы решали какие-то задачи?

Как обычно, не долго думая, решаю что сделать(?), верно загуглить)

Копируем ссылку, открываем гугл и спрашиваем у него, что это за ссылка;)
И тут натыкаемся на посты, открываем любой и читаем.

Прочитав, понимаем что это не просто пасхалка, а это приглашение попробовать свои силы и попасть в гугл, а если успешно пройти, то получим ссылку для друга. Оказывается, так они ищут сотрудников к себе.
Так подождите, т.е. любой кто юзает гугл, имеет возможность получить такое приглашение? И если да, то почему этого не было раньше?
Странно, читаем и разбираемся. Прочитав пару постов, узнаем, что действительно, так гугл ищет себе сотрудников.
Но тогда еще один вопрос откуда они узнали обо мне и, опять же, почему сейчас? Оказывается, все просто, гугл анализирует историю наших запросов и, по какому-то своему алгоритму, кидает нам пасхалку.
И тут задаемся вопросом, что вообще мы гуглили за последнюю неделю? Здесь все просто: вопросы по Obj-C, Swift и всякое, связанное с разработкой.
Так подождите везде пишут, что задачи нужно решать либо на Java, либо на Python, но как разработка под iOS с это связано? К сожалению, этот вопрос остался открытым(

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

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

Буду благодарен, если в комментариях поделитесь своим опытом)
Подробнее..

Дайджест интересных материалов для мобильного разработчика 359 (24 30 августа)

30.08.2020 14:14:20 | Автор: admin
В новом дайджесте подготовка к iOS 14, архитектура приложений и многомодульные проекты, детские интерфейсы и вопросы высшего образования разработчиков, продуктивность, новый месседжинг, роботы и много других интересных материалов!


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

Этот дайджест доступен в виде еженедельной рассылки. А ежедневно новости мы рассылаем в Telegram-канале.

iOS

(+8) Make <your iOS app> talk. Доклад Яндекса
(+5) Осваиваем Grid в SwiftUI
Apple заблокировала обновление Facebook из-за предупреждения о 30%
Facebook предупреждает о 50% падении дохода от рекламы после перехода на iOS 14
Как подготовиться к iOS 14
Управление приложением в SwiftUI
Как создать змейку на SwiftUI
Как программно изменить иконку приложения
Новое в iOS 14: App Attest
Постраничное листание на SwiftUI
В чем разница между Leading и Left, Trailing и Right Constraints в Swift
GraphQL в iOS-разработке
Обнаружение утечки памяти во время выполнения на iOS
10 советов по разработке виджетов для iOS 14
Отслеживание рук и определение положения тела с помощью Vision Framework
Протоколы в Swift
Затерянный в стране Xcode
Создание сложных многослойных макетов с Safe Area
NeumorphismKit: неоморфизм в UIKit
Layout Framework Benchmark: сравнительное тестирование Swift-фреймворков

Android

(+8) Архитектурный шаблон MVI в Kotlin Multiplatform. Часть 3: тестирование
(+2) Тестируем плату для 4K Android ТВ-приставок на чипе Realtek RTD1395
Вышла альфа-версия Jetpack Compose
Android Broadcast: Архитектура приложений: Делаем аналитику правильно
Google выпустил приложение AdMob для Android
Android для всех: словарь терминов
Сравнение времени сборки Android на разных процессорах
Создание элегантных DSL на Kotlin
Распознаем нарисованное с ML Kit
Как использовать Ktor в вашем Android-приложении
Использование Firebase In-App-Messaging в Android-приложении
Анимируйте клавиатуру
Связь Фрагмента с другими Фрагментами и Активити
Делаем кастомный Android Sheet, выезжающий сверху экрана
Android Material Component: создаем Navigation Drawer в стиле Gmail
Используем Jetpack CameraX
Как повысить качество приложения с помощью Firebase и Google Play
10 полезных строковых расширений Kotlin
Awesome Dialog: красивые уведомления для Android
Android Additive Animations: анимации для Android

Разработка

(+61) Мои размышления про экранную клавиатуру для Flipper Zero под экранчик 128х64 пикселя
(+27) Как однажды была чуть не сорвана разработка Gardenscapes
(+22) Оригинальный способ генерации мастер-пароля: используй специальный набор костей
(+9) Конфигурация многомодульных проектов
(+5) Интерфейсы для тех, кто еще не читает. Принципы проектирования детского UX-UI
(+3) Марк маркировал, маркировал, да и вымаркировал. Маркировка это ж просто!?
(+2) Использование SVG ресурсов в Xamarin
(+1) Micro-frontends. Асинхронный подход к мультикомандной разработке
(0) 10 вещей, о которых нужно помнить при переходе с React на React Native
(0) Какие бывают метрики. Дизайнер и метрики, 2 часть
Flutter Dev Podcast #19: Яндекс.Про
Хочу в геймдев #12: путь инди
Podlodka #178: Stack Overflow
Повлияла ли работа из дома на продуктивность разработчиков?
Google: ВУЗ больше не нужен
Дизайн приложений: примеры для вдохновения #14
Работа в Mobis Apps, или как потерять 130 тысяч рублей
Тени и размытие. Основы UI дизайна
ActiveLife App: исследование UI/UX
7 устаревших стилей программирования
Как сделать Flutter-приложение с высоким уровнем безопасности?
Каково это быть Senior инженером?
Сколько стоит сделать кроссплатформенное приложение в 2020?
Adobe отказывается от PhoneGap
Как разработать отличное мобильное приложение для фитнеса? (фичи, бюджет и монетизация)
6 потрясающих Github-репозиториев для Flutter
VR-разработка на Unreal: для каких проектов лучше всего использовать?

Аналитика, маркетинг и монетизация

(+1) Арабские страны: новый Клондайк для разработчиков
Игровой рынок России: обзор Wargaming и Superdata
В Канаде самый большой рост расходов на приложения, в России рост ниже прошлогоднего
Muze переосмысливает мобильный месседжинг
Избегайте фичеринга
Житель Петербурга потратил на Великого Султана 763 тыс. рублей и решил вернуть их через суд
Суд запретил Apple удалять аккаунты Epic Games
Microsoft поддержала Epic Games в споре с Apple
Почему все взъелись на Apple с её 30%?
AppMetrica: как правильно собирать и интерпретировать данные отчетов
10 главных метрик для аналитики ecommerce-приложений
ASOindex бенчмарк 20 популярных категорий App Store и Google Play

AI, Устройства, IoT

(+65) Умная детская коляска Максимка
(+31) RPi-няня
(+13) Умный дом в каждую квартиру многоквартирного дома, или наш MVP
(+8) Старый строительный бизнес и новые технологии, или история одного стартапа
(0) Azure IoT Edge и SQL Edge: перенос облачных нагрузок на наземные устройства
Убьет ли Искусственный Интеллект программирование?
Исследователи Intel на основе смартфона сделали робота за $50
Amazon улучшает расстановку мебели в AR

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

Дайджест интересных материалов для мобильного разработчика 360 (31 августа 6 сентября)

06.09.2020 16:13:36 | Автор: admin
В новом выпуске разбираемся со скруглением иконок (два раза!), с UI-тестами и MVI, мультиплатформенными приложениями и CI/CD, проектированием интерфейсов, самыми зарабатывающими приложениями и многим другим.


История с долгими поисками девайсов и/или проводов стала касаться меня ежедневно. Такая пустяковая задача, как найти Xiaomi Mi A1, занимала кучу времени и приносила душевные страдания. Спустя некоторое время, я пришёл к мысли, что меня это бесит и на поиск девайса столько времени уходить не должно. Я нашёл узкое горлышко в рабочих процессах и решил его ликвидировать.

Этот дайджест доступен в виде еженедельной рассылки. А ежедневно новости мы рассылаем в Telegram-канале.

iOS

(+66) Секрет формы иконок iOS: это сквиркл? Разбор
(+3) Формулы переводов: хитрая локализация для iOS и не только
(+3) Swift Best Practices которые не стыдно знать
Apple откладывает защиту конфиденциальности в iOS 14 до следующего года
Особенности реализации календаря с горизонтальным скролом
В App Store запускаются промо-коды для подписок
10 советов по разработке виджетов для iOS 14
Apple не будет задерживать исправление ошибок в App Store
Apple делает свой поисковый сервис?
Мои любимые приложения для iOS-разработки в 2020 году
Как создать интерактивный Bottom Sheet в Swift 5
Встроенные покупки и StoreKit в iOS 14
Привязка к прокрутке элемента
Как создать навигатор для перехода на любую страницу в Swift
Ваш первый UITest на Swift
Что нового в Xcode 12.0?
Обработка видео в SwiftUI в реальном времени с использованием Core Image
Не делайте этого с помощью Swift Optionals
Ассемблер в iOS
Apple Silicon для разработчиков приложений
Swifty Guitar Chords: гитарные аккорды на Swift
MultiplatformApp: мультиплатформенное приложение на SwiftUI

Android

(+10) На чем писать Android UI-тесты
(+4) Быть или не быть: дискуссии о тестировании в мобильной разработке
(+3) Загрузка и сборка AOSP
(+2) Так для чего же нам все таки нужен MVI в мобильной разработке
(+2) Как встроить голосового помощника в любое мобильное приложение. Разбираем на примере Habitica
(+1) Анализ сервисов приема SMS для Android против сайтов-сервисов и опыт разработки нового функционала под Android
Android Broadcast: Dagger Hilt: Deep Dive / No more Koin
Android Broadcast: Kotlin 1.4: быстрее и лучше
Вышла альфа Kotlin Multiplatform Mobile
Thermal в Android
Приватные библиотеки в Android почему вы должны рассмотреть это
Извлекаем взаимодействий из ViewModel
Распознавание касания, двойного касания, панорамирования и щипка в Android
Заставьте ваш (Kotlin) код выражать самого себя
Распространенные ошибки разработчиков при создании RecyclerViews и способы их устранения с помощью класса ViewRepresentation
Отформатируйте код Kotlin с помощью Ktlint
Shape Drawables самый мощный инструмент для Android UI
Исследуем Kotlin Multiplatform
Настраиваем Logcat в Android Studio
20 инструментов Android-разработки о которых вы, вероятно, никогда не слышали
Добавляем ленту на иконку приложения в Android (снова))
Предпочитаемое хранение данных в Jetpack DataStore
Jetpack Release Tracker: отслеживание обновлений AndroidX
TrackerControl: отслеживание сбора данных на Android
Jetpack Compose Samples: примеры работы с Compose

Разработка

(+43) Ваши квадрокруги неправильные
(+18) Создаем разрушаемые объекты в Unreal Engine 4 и Blender
(+17) Ну, покати! или CI/CD мобильных приложений на основе контракта
(+9) Как прошел открытый Demo Day в Райффайзенбанке
(+7) Тестирование Flutter-приложений: инструменты, преимущества, проблемы
(+7) Как работают мобильные кошельки на примере приложения Mir Pay
(+4) Улучшаем работу со сценами с помощью ScriptableObject
(+4) Продвинутое велосипедостроение или клиент-серверное приложение на базе C# .Net framework
(+3) Совет инженерам по тестированию 1: Докеризируйте ваш Selenium Grid
(+2) Победитель Apple Design Awards: статистически усреднённый портрет
Podlodka #179: рациональность
Как создать свой игровой бизнес
Дизайн приложений: примеры для вдохновения #15
Google и Apple разработали упрощенную систему предупреждения о COVID-19
Pokemon GO прекращает поддержку старых смартфонов
Unity Distribution Portal позволит работать сразу со многими магазинами приложений
Проектирование игры в Sketch: интервью с создателем King Rabbit
Не проектируйте для мобильных устройств (Mobile-first)
С чего начать проектирование мобильного приложения. Основы UI дизайна
Что такое Actions Builder? Actions Builder и Actions SDK для Google Assistant
Реверс-инжиниринг: разработка эмулятора сервера для Marvels Avengers
10 лучших и самых популярных пакетов Flutter
5 советов для лучшего дизайна кнопок
Улучшаем Flutter-приложения с помощью автозаполнения
Чистый код для ведущих разработчиков
Автоматизация жизненного цикла Flutter-проекта с помощью GitHub Actions
Да, TDD вас замедляет
Разработка дневника настроения на Flutter за 2 часа
6 шагов в поиске подходящей среды автоматизации тестирования (с примером из практики)

Аналитика, маркетинг и монетизация

Avo: аналитика нового поколения
Самые зарабатывающие приложения в августе 2020
Индия забанила PUBG и еще сотню приложений
Google запустил Growth Academy для Украины и Беларуси
App Annie представляет отчет по геймингу за первое полугодие
myTracker анализирует доходы от рекламы в приложениях
Локализация: как выйти на международный рынок и увеличить количество установок?
Модель монетизации: меняем и дешево тестируем новую
Почему удержание лучший способ роста

AI, Устройства, IoT

(+20) Малиновый киноцентр или как сделать неубиваемый смарт-ТВ
(+18) Как игры стали движущей силой двух школ исследований ИИ
(+1) Микроволновка, знающая о тебе всё: что такое Интернет вещей (IoT)?
Apple iPhone 11 стал самым продаваемым смартфоном в первой половине 2020

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

Дайджест интересных материалов для мобильного разработчика 361 (7 13 сентября)

13.09.2020 18:19:09 | Автор: admin
На этой неделе Google выпустил Android 11, а Huawei представил Harmony 2.0, Apple продолжила биться с Epic в суде, мы продолжили исследование Kotlin в 1.4 и новых веяний неоморфизма, стагнации машинного обучения и правил создания иконок. Все это и многое другое в нашей новой подборке!


Упаковываю и отправляю приложение без троянов для управления своими лампами в F-Droid без каких-либо знаний в разработке для Android. Корпорация Google опубликовала релиз мобильной ОС Android 11. Главный акцент в новой версии операционной системы сделан на упрощении работы с различными мессенджерами, управлении smart-устройствами и улучшенной конфиденциальностью пользователя.

Этот дайджест доступен в виде еженедельной рассылки. А ежедневно новости мы рассылаем в Telegram-канале.

iOS

Apple подала встречный иск к Epic
Микровзаимодействия: анимированная волна
7 вариантов AlertView в SwiftUI
Тестирование производительности Xcode на большом проекте Swift. Сравнение iMac, MacBook, iMac Pro
Преобразование платного iOS-приложения в подписное
Пишем первый виджет для iOS
7 основных инструментов iOS-разработчика
Создание 3D анимации прокрутки карт в SwiftUI
Как сделать iOS-приложение безопасным?
SwiftUI 2.0: будущее декларативно
Swiftagram: клиент для Instagram
Velik: отслеживание поездок на велосипеде

Android

(+11) Navigation Component-дзюцу, vol. 1 BottomNavigationView
(+6) Полируем UI в Android: StateListAnimator
(+4) Превращаем EditText в SearchEditText
Google выпустил Android 11 Go
Huawei представил Harmony 2.0
JetBrains проводит конференцию по Kotlin 1.4
Android Broadcast: новый компилятор Kotlin в 1.4
20 инструментов Android-разработчика, о которых вы, вероятно, никогда не слышали
Google показал зависимость Firebase от GMS
Шесть лет споров: зачем Microsoft сделала Android-смартфон с двумя экранами в мире, где все устройства одинаковые
Подход чистой архитектуры при рассмотрении Модели
Не изобретайте колесо заново, делегируйте его!
Базовая инъекция зависимостей с помощью Hilt
Магические функции Kotlin все, что вам нужно знать
Полируем UI в Android: StateListAnimator
Сборка Android: как уменьшить время с 5 минут до 15 секунд
Разработка сложного пользовательского интерфейса с использованием Android ConstraintLayout
22 расширения Kotlin для более чистого кода
Простая библиотека настроек создаем экран настроек за секунды
TDD в Android
Современная безопасная Android-разработка
Неисправный AndroidX FragmentFactory
Исследуем Jetpack DataStore
Biometric Auth: биометрическая аутентификация в Kotlin
Blue Pair: работа с Bluetooth в Android

Разработка

(+25) Неоморфизм и его проблемы
(+11) Домофоны, СКУД И снова здравствуйте
(+10) Как захватить новую страну за 3 недели
(+6) Flutter.dev: Простое управление состоянием приложения
(+4) Локализуем приложение на React Native
Podlodka #180: PHP
C++ стал самым быстрорастущим языком программирования рейтинга TIOBE
Яндекс запускает новый сезон стажировок
Дизайн приложений: примеры для вдохновения #16
Мотивация разработчиков и других людей творческих профессий руководство для компаний
Илкка Паананен: Игры, как бизнес, не должны управляться процессами
Инструкция: как создать приложение для просмотра погоды на Flutter
Сетки, принципы и правила создания интерфейсных иконок, iOS и Android
Год на воде и хлебе: как делать приложение на свои и не сдаваться
5 советов по улучшению дизайна кнопок. Основы UI дизайна
Действительно ли Firebase так хорош, как кажется?
Создаем веб-приложение Flutter с нуля и размещаем его с помощью Continuous Deployment
4 типа разработчиков, с которыми вы (к сожалению) будете работать
Использование шаблона BLoC для чистых Flutter-приложений: теория и практический пример
Анатомия превосходного дизайна
Советы, как стать более эффективным ревьювером кода
Duofolio: ридер со словарем

Аналитика, маркетинг и монетизация

(+21) Российские пасхалки в мобильных приложениях. Какие они?
(+17) Ошибки в дизайне A/B тестов, которые я думала, что никогда не совершу
(+4) Как понять, что новая фича принесет пользу продукту, а не навредит ему?
(+2) Apple Grace Period и Billing Retry статусы при обработке чеков пользователей
AppsFlyer запускает Xpend платформу для агрегации данных о расходах на рекламу
Руководство по продуктовой аналитике от Mixpanel
Самые скачиваемые приложения в августе 2020
Mustard: скаутинг на основе ИИ
Два типа стратегий роста: стратегии искры (kindle) и стратегии пламени (fire)
Как итерации помогают в поисковой оптимизации приложений

AI, Устройства, IoT

(+31) Стагнация машинного обучения. Многие задачи не будут решены никогда?
(+12) Автоматизируем работу системы отопления в квартире без переделки интерьера умный дом z-wave
(+9) Будни OEMщика (Часть 1)
(+3) Интернет автомобилей: первые шаги к беспилотной езде
Яндекс выпустит ТВ-приставку с Алисой
Представлен новый протокол Z-Wave Long Range
Как стать экспертом в области искусственного интеллекта: пошаговое руководство
Planet-Scale AR Alliance готовит дополненную реальность для 5G
Relativty VR-гарнитура с открытым исходным кодом за 200 долларов

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

Дайджест интересных материалов для мобильного разработчика 362 (14 20 сентября)

20.09.2020 16:05:00 | Автор: admin
В этом дайджесте презентация Apple, инструменты и антипаттерны Android-разработки, ARM против x86 и кроссплатформа против нативной разработки, искусство рассказывания историй, секреты улучшения дизайна и многое другое!


Пока мы все находимся в ожидании выхода релизной версии ОС, хотел бы поделиться опытом написания виджета для приложения Кошелёк и рассказать, с какими возможностями и ограничениями наша команда столкнулась на бета-версиях Xcode.

Этот дайджест доступен в виде еженедельной рассылки. А ежедневно новости мы рассылаем в Telegram-канале.

iOS

(+22) О чем нам рассказали на ежегодной сентябрьской презентации Apple
(+5) Формальные грамматики на службе мобильного клиента
Подготовка к iOS 14
Что означает последнее обновление правил конфиденциальности Apple для вашего приложения
Apple представляет совершенно новый iPad Air с A14 Bionic, iPad 8-го поколения, Apple Watch Series 6 и Apple Watch SE
В App Store разрешили стриминг игр, но очень ограниченно
Социальная сеть на Swift UI
iOS 14 UISplitViewController: 5 проблем, с которыми вы можете столкнуться
Объяснение Диапазонов в Swift на примерах
Декодирование JSON в Swift с помощью Codable: практическое руководство
10 Pod-ов для использования в новом iOS-проекте
Улучшите свой UX с помощью Core Animations
Как обезопасить iOS-приложение от скриншотов и записи экрана?
WidgetKit: продвинутая разработка
10 мощных@Атрибутов в Swift
DTTextField: поле ввода с подсказками
ContainerController: выезжающая панель

Android

(+15) 20 инструментов Android-разработчика, о которых вы могли не знать
(+8) Navigation Component-дзюцу, vol. 2 вложенные графы навигации
(+2) Антипаттерн Репозиторий в Android
(+1) Обзор HMS Core 5.0: ещё больше возможностей для ML на мобильных устройствах и новые инструменты для аудио и видео
(0) Как с помощью возможности распознавания текста HUAWEI ML Kit реализовать функцию автоматического ввода номеров
Microsoft запускает Android-приложения в Windows 10
Android 11 вызвал проблемы в работе с Android Auto
Привет DataStore, пока SharedPreferences
Объяснение жизненного цикла Android Fragment
Изучение Jetpack Compose: модификатор отступа
Управление несколькими приложениями в одном проекте Android (Studio)
Распознавание жестов поворота в Android
Как определить обновление Android-приложения
Просто добавьте MVI с Orbit 2
Адаптируйте свое приложение к последним рекомендациям по обеспечению конфиденциальности
Как корутины формируют новые способы разработки
Автоматизация Code Review
Почему я решил написать свой собственный инструмент для тестирования UI
Понимаем внутреннее устройство Lottie рендеринг файла анимации
JetInstagram: Instagram на Jetpack Compose

Разработка

(+19) ARM против x86: В чем разница между двумя архитектурами процессоров?
(+9) Когда имеет смысл писать кроссплатформенные приложения: появление и исчезновение React Native в Lingualeo
(+8) Вставка реальных объектов в Unity с помощью Meshroom
(+7) UXD Реальность и будущее в дизайне или человек во главе всего
(+7) Crash-crash, baby. Автоматический мониторинг фатальных ошибок мобильных приложений
(+3) Как документ на мобильнике распознается: от простого к сложному
Podlodka #181: хантинг
Искусство рассказывания историй в разработке программного обеспечения
Дизайн приложений: примеры для вдохновения #17
Секрет улучшения дизайна: 4 способа сторителлинга
Исследование. Какую иконку выбрать для обозначения аккордеонов?
Искусство сторителлинга в разработке программного обеспечения
Руководство по минималистическому дизайну
Автоматизация публикации ваших приложений Flutter в Google Play с помощью GitHub Actions
Создаем приложения для чата на Flutter с помощью Firebase
Жизненный цикл разработки программного обеспечения: как мы создали новый Dropbox Plus
Барьеры на пути к разработке игр устранены
12 основных инструментов для разработчика мобильных приложений на Flutter
Начинаем работать с дополненной реальностью с помощью Unity AR Foundation Framework
Действительно ли я знаю программирование?
Mixin: мессенджер, кошелек и клиент для децентрализованной сети

Аналитика, маркетинг и монетизация

(+1) Как представить игру издателям и инвесторам
Почему следующая фаза роста Китая будет определяться потребителями и что это означает для рекламодателей
Bunch получил $20 млн. на социальный слой для игр
Министерство финансов США изучает безопасность игр Riot Games и Epic Games
make sense: О выборе фреймворков приоритизации, подходах к принятию решений и командной осознанности
Зачем бизнесу заказывать разработку приложения?
Отчет О состоянии рынка рекламы приложений для шоппинга в 2020 году
Как студия Donut Lab закрыла раунд инвестиций на $1.6M
Маркетинг приложений в апокалипсис: как работать с тревожными трендами?
Как я получил 200 000 загрузок приложений без платного маркетинга

AI, Устройства, IoT

(+29) Подключем новый Xiaomi Gateway 3 к Home Assistant без паяльника и смс
(+24) Как за два месяца пройти путь от начинающего питониста до сертифицированного TensorFlow-разработчика
(+12) ИК датчик движения на STM32
(+3) Автомобильное ПО: варианты стратегического развития
Facebook анонсировал Oculus Quest 2
Facebook выпустит смарт-очки вместе с Ray-Ban
Gameloft оживляет игрушки Kinder с помощью дополненной реальности
Nvidia покупает ARM
8 лучших No-Code платформ машинного обучения, которые вы должны использовать в 2020 году

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

Дайджест интересных материалов для мобильного разработчика 363 (21 27 сентября)

27.09.2020 20:23:04 | Автор: admin
В новой подборке создание собственной GTA, заработки виджетов, Swift и Flutter для Windows, приложения из таблиц и подписные приложения в Китае, коалиция против Apple, новые устройства Сбера и многое другое.


Возможно ли в здравом уме замахнуться на подобный проект в одного, и надо ли оно вообще?

Этот дайджест доступен в виде еженедельной рассылки. А ежедневно новости мы рассылаем в Telegram-канале.

iOS

(+6) Устройство UI в iOS
Топ-20 iOS-приложений с виджетами получили 5.7 млн установок и $400,000 за 4 дня
Swift заработал с Windows
Улучшение типографики отображения времени в iOS
Как работает SKAdNetwork и фреймворк AppTrackingTransparency в iOS 14
Лучшие ресурсы для изучения iOS-разработки и Swift-программирования
iPad будущее продуктового дизайна?
Создание iOS UI с чистым кодом
Руководство по индикаторам активности в iOS от А до Я
Создание расширяемого списка с помощью UICollectionView
Создаем анимированную кнопку лайка в стиле Twitter на SwiftUI
Используем возможности нескольких ядер в приложении для iOS
Подписки на iOS получение правильной даты независимо от устройства
Выбор минимальной версии iOS для поддержки
Никогда не используйте Pod Install снова
Создание кастомного Navigation View на SwiftUI
Что нового в iOS 14 для разработчиков
TPInAppReceipt: проверка встроенных покупок
SnackBar: уведомления для iOS

Android

(+14) Navigation Component-дзюцу, vol. 3 Corner-кейсы
(+8) Знакомство с App Gallery. Создаем аккаунт разработчика
(+2) Чем опасен postDelayed
(0) Retrofit2 на Android используя Kotlin
Android 11 для телевизоров
Android Broadcast: как выбрать поддерживаемую версию Android: minSdk, targetSdk, compileSdk
Понимание внутреннего устройства библиотеки загрузки изображений Glide анализ исходного кода
Улучшите свою игру с помощью форматов сжатия текстур
Введение в основные концепции Android Navigation
Состояние Java в 2020
Continuous Testing с Android Emulator Containers
Советы по программированию на Kotlin
Три удобных инструмента для Android-разработки
Как RecyclerView работает внутри?
Создаем свой собственный лаунчер для Android на Flutter
Пять полезных расширений Kotlin, которые вы можете использовать
Забавное изучение свойств анимации в Android
Изучение Jetpack Compose с помощью Hilt и ViewModels
Темные секреты быстрой компиляции Kotlin
Smart App Rate: запрос оценки от пользователя
JetQuotes: декларативные цитаты
UTair MVP Sample: Android Clean Architecture + пример MVP

Разработка

(+29) Стартап Glide для создания мобильных приложений из Google-таблиц
(+13) Как мы автоматизировали разработку WL-приложений
(+9) Продуктовый дизайнер: правила эксплуатации
(+6) Используем бесплатные возможности Github Actions для CI/CD на Flutter-проекте
(+6) Flutter.dev: Continuous delivery с Flutter
(+1) 7 способов повысить эффективность автоматизации тестирования в Agile разработке
(0) PWA не для всех
Podlodka #182: Психотерапия
Flutter Dev Podcast #20: Flutter for Web
Вышла альфа-версия Flutter для Windows
Дизайн приложений: примеры для вдохновения #18
Проектирование для Microsoft Surface Duo
ATEC приложение для проверки признаков аутизма. Часть 3
Запуск Go-кода на Android и iOS
Как интегрировать Google Vision API с React Native и Expo
Ленивая загрузка данных из Firestore в реальном времени с использованием Flutter
Чему Сократ научил меня в UX-дизайне
Разработка полноценное новостного приложения на Flutter за день
Как я, Junior, превзошел более опытных разработчиков (и как вы тоже можете)
Учитесь у лучших: принципы мобильного дизайна
Почему Flutter это, по сути, следующая важная вещь в разработке приложений

Аналитика, маркетинг и монетизация

Конкурс Playcore для инди-разработчиков: сделаем из вашей игры кейс!
Почему Китай возможность для роста западных приложений с подпиской
Epic, Spotify и Match Group объединились в коалицию против магазинов приложений
Freeletics: очень умный мобильный фитнес
Финтех приложения в 2020
Приобретение пользователей для мобильных игр в 2020: отчет Liftoff и AppsFlyer
Индийская игровая платформа Mobile Premier League получила $90 млн.
Летом россияне потратили в тревел-приложениях $2,1 млн.
Как фримиум приложения превращают пользователей в платящих: лучшие примеры напоминаний
$4 млн инвестиций и 8 лет разработки: история приложения для дошкольников Сказбука
Как рекламировать мобильные приложения в TikTok гайд по старту работы

AI, Устройства, IoT

(+35) Новый метод биометрии: биоакустическая подпись
(+24) Вернуть пропавший скутер, или история одного IoT мониторинга
(+15) Ultimate Guide по карьере в AI: как выбрать специальность, прокачаться и найти классную работу
(+3) Делаем трекер Bluetooth-устройств с помощью колонок Google
(+2) MQTTv5.0: Обзор новых функций. Часть 2
Сбер запускает собственные устройства и навыки для них
Mozilla выделяет WebThings в отдельный проект
Скоро способ, которым вы пишете код, изменится: присоединяйтесь к революции
12 интересных идей для проектов в области Data Science для начинающих и экспертов

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

Ну, покати! или CICD мобильных приложений на основе контракта

02.09.2020 18:14:51 | Автор: admin

Всем привет! Меня зовут Дмитрий, я релиз-инженер вкоманде CI/CD Speed Авито. Вот уже несколько лет мы сколлегами отвечаем за всё, что связано срелизами наших мобильных приложений и не только. Пронаши релизные поезда и как мы кэтому шли уже очень подробно рассказывал Алексей Шпирко.


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



Немного контекста


Мобильное приложение Авито это:


  • Десятки продуктовых команд.
  • 20+ разработчиков на каждую изплатформ.
  • Тысячи UI-тестов.
  • Десятки тысяч UNIT-тестов.
  • Сотни тысяч строк кода.
  • Еженедельные релизы Android.
  • Релизы iOS раз вдвенедели.

Процесс релиза состоит изследующий частей:


  1. Срез релизной ветки изdevelop и простановка тега вgit.
  2. Прогон всех автоматических проверок кода и прогон всех видов тестов.
  3. Сборка релиз-кандидата.
  4. Загрузка релиз-кандидата вAppStore/GooglePlay и внутреннее хранилище артефактов.
  5. Отправка необходимой информации всистемы мониторинга.
  6. Загрузка данных всистему управления фича-тоглами.
  7. Сборка what's new дляQA и редакторов.
  8. Подготовка Jira-артефактов простановка версии в задачи, создание задач дляредакторов, QA и релиз-инженеров.
  9. Нотификация всех заинтересованных лиц оготовности релиз-кандидата.
  10. Регрессионное тестирование.
  11. Выпуск приложения начасть пользователей и нотификация обэтом.
  12. Выпуск приложения на100% пользователей и снова нотификация.


Так устроен наш релизный поезд


В начале 2019года всё это успешно обслуживалось несколькими десяткам скриптов наразных языках и сложными цепочками TeamCity-билдов. Каждое воскресенье cron запускал стартовую TeamCity-конфигурацию, скрипты и билды выполняли всю работу изпунктов 1-9.


Если всё шло хорошо, продуктовые команды разбирали результаты автотестирования и что-то допроверяли руками. Редакторы писали красивые тексты, а релиз-инженеры следили запервыми и вторыми и ждали, когда можно будет нажать назаветную кнопку релиз вмаркете. Идиллия и победа автоматизации надрутинным трудом.


Но вкажущемся благополучии таился ряд проблем.


Проблема 1. Сложные цепочки билдов вTeamCity


Релизный процесс состоит измногих этапов. Каждый следующий этап зависит отпредыдущего или нескольких предыдущих. И ковсему этому добавляется множество внешних систем.



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


  • Много билдов и сложные связи между ними.
  • Сложность поддержки и внесения изменений.
  • Сложность тестирования всего процесса или его частей.
  • Сложно или нельзя запустить процесс начиная сточки отказа.

Например билд-1 завершает работу, билд-3 ждёт результаты выполнения билдов 1 и 4, а вбилде-7 поменяли параметр, и вся система черезчас работы рухнула. Садишься, разбираешься что куда нужно пробросить и кому что скормить или перезапускаешь весь процесс снуля. В итоге теряешь много сил и времени и получаешь временами автоматизацию наручном приводе.


Это усугублялось другой проблемой или же особенностью организации нашей работы.


Проблема 2. Зоны ответственности


Так сложилось, что наша большая команда состоит издвух независимых команд поменьше. Это собственно мы CI/CD team и наши коллеги Testing team. Мы отвечаем завсю общую часть релиза или же CD как взять нужный срез кода и донести его пользователям. Ребята изTesting team отвечают завсю платформа-специфичную часть как собрать приложение, прогнать нанём нужные тесты и отдать это нам.


И, соответственно, релизная цепочка билдов содержала как наши билды (срез ветки, Jira-задачи, оповещалки, подготовку артефактов дляручных проверок) так и билды коллег. Билды зависели друг отдруга, и это мешало быстро развивать как наши системы и процессы, так и системы и процессы ребят, потому что любое косвенное изменение потенциально ломало весь процесс.


Частично эту проблему мы постарались решить спомощью ночных тестовых релизов. Каждую ночь весь процесс запускался натестовых данных, и утром мы могли видеть состояние системы. Но была ещё одна проблема.


Проблема 3. Люди


У нас есть часть релизного процесса, вкоторой участвуют люди. Это непосредственные участники: тестировщики, редакторы, релиз-инженеры. И косвенные, но заинтересованные втом, чтобы пользователи получили приложение: продакт-менеджеры, разработчики, маркетологи, аналитики. Раньше вся коммуникация осуществлялась через Slack-каналы, а актуальное состояние релиза было разбросано поразным местам (Jira, Slack), его знал только релиз-инженер. Поэтому ему приходилось тратить много времени отвечая навопросы когда поедем на 100%?, релиз стартанул?, так уже можно тестировать или нет?, а следующий релиз когда?.


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


Разграничиваем ответственность


Как уже говорилось, наша большая команда состоит издвух команд поменьше, каждая изкоторых отвечает заCI или CD часть всего процесса.


Давайте посмотрим, что мы вкладываем вэти понятия.


CD:


  • срез релизной ветки вgit;
  • простановка тегов вgit;
  • запуск CI-части;
  • подготовка релизных артефактов (Jira-задачи, Release Notes);
  • подготовка регрессионных артефактов;
  • оповещения остадиях релиза;
  • релиз.

CI:


  • прогон всех тестов;
  • сборка приложения;
  • сборка платформ-специфических артефактов;
  • загрузка приложения вмаркет.

Видно, что есть разграничение зон ответственности напроцессном уровне и есть четкое разграничение наорганизационном уровне. Но вобщем релизном процессе науровне TeamCity всё перемешивалось и доставляло очень много проблем.


При этом, в большинстве своём, CI ничего не знает проCD. CD запускает CI-часть стребуемыми параметрами и ждёт завершения билдов, чтобы получить изних необходимые артефакты. Получается, что жёсткая связность между билдами и сложные зависимости многие-ко-многим были не оправданы. Мы решили явно разграничить CI и CD, сделать одну точку взаимодействия между ними и связать её контрактом.


Контракт по своей сути это пара JSON-файлов, один изкоторых CD передаёт вCI-часть, а второй ожидается как результат работы CI.



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


Пример входного файла контракта config.json:


{"schema_version": 1, "project": "avito", "release_version": "777.5", "output_descriptor": {        "path":"http://artifactory.ru/releases/avito_android/777.5_1/output.json",         "skip_upload": false}, "deployments":  [        {        "type": "google-play",        "artifact_type": "bundle",        "build_variant": "release",         "track": "beta"        }  ]}

Тут мы сообщаем CI-части, что хотим собрать релиз проекта Авито сномером 777.5, ожидаем, что выходной файл врезультате работы будет загружен попути, описанному вoutput_descriptor, а также заказываем, какие артефакты и вкаком виде должны быть собраны и куда загружены после.


Пример выходного файла контракта output.json:


{  "schema_version": 1,  "teamcity_build_url": "https://tmct.ru/viewLog.html?buildId=17317583",  "build_number": "777",  "release_version": "777.5",  "git_branch": {    "name": "release-avito/777.5",    "commit_hash": "2c54c50c220bf91bc1a6ca10b34f53a540c80551"  },  "test_results": {    "report_id": "5f3e94fd23d67bf434e5c1b8",    "report_url": "https://tests.avito.ru/report/AvitoAndroid/FunctionalTests/2c54c50c220bf91",    "report_coordinates": {      "plan_slug": "AvitoAndroid",      "job_slug": "FunctionalTests",      "run_id": "2c54c50c220bf91"    }  },  "artifacts": [    {      "type": "apk",      "name": "avito-777.5-777-release.apk",      "uri": "http://example.com/artifactory/android/avito/777.5-777/avito-777.5-777-release.apk",      "build_variant": "release"    },   ]}

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


Nupokati: сервис релизов мобильных приложений


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


Поэтому мы решили отказаться отTeamCity вCD и реализовывать собственный сервис релизов мобильных приложений.


Что мы хотели получить отнового сервиса?


  1. Отсутствие сложных связей и неявных зависимостей.
  2. Перезапуск релиза, начиная сточки отказа.
  3. Прозрачность процесса релизов длявсех участников.
  4. Простую поддержку, кастомизацию и тестирование.
  5. Возможность использования наразных мобильных проектах компании.

Так появился сервис мобильных релизов Nupokati рабочее название прижилось и осталось снами.



Он состоит изуправляющего CD-сервиса наPython и дашборда мобильных релизов.
Покрону CD-сервис проверяет календарь релизов и стартует нужный релиз, запускает CI, выполняет все необходимые взаимодействия свнешними сервисами и извещает всех заинтересованных участников процесса окаждой стадии.


Основная управляющая сущность вCD-сервисе это Release.



Он, как конструктор, собирается из различных шагов:



Вот пример небольшой части релиза:



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


Дашборд же служит панелью управления всем процессом и показывает актуальное состояние конкретного релиза:




Здесь есть вся необходимая информация порелизу и ссылки наартефакты



Также отсюда осуществляется всё управление релизом



И отображается актуальное положение релизного поезда



Помимо этого надашборде можно всегда найти информацию опрошлых релизах и календарь спланируемыми


Итоги


Такая простая напервый взгляд вещь как контракт между CI и CD оказалась длянас революционной и позволила получить удобную дляразработки, поддержки и использования систему релизов мобильных приложений. Контракт позволил нам вылечить все проблемы и не прибавить впроцессе новых.


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

Подробнее..

Как мы автоматизировали разработку WL-приложений

21.09.2020 12:22:00 | Автор: admin
White Label это мобильные приложения, которые можно кастомизировать под любой бренд: оформить в фирменных цветах, выбрать необходимые блоки и функционал, добавить описание. Мы их выпускаем на основе Рамблер/кассы с 2015 года и в этой статье хотим рассказать, как у нас получилось автоматизировать и ускорить разработку WL.



Рамблер/касса онлайн-сервис и приложение для продажи билетов на концерты, в театр, кино, спортивные и другие мероприятия. Также мы разрабатываем другие B2B-предложения для партнеров: мобильный SDK, встроенные виджеты для сайтов и соцсетей, CRM-систему для аналитики продаж и аудитории, а также ряд технологических решений. Но сегодня мы остановимся только на WL.

Что было раньше


После получения запроса на WL-приложение менеджеру проекта было необходимо заново формировать и уточнять все требования, передавать их менеджеру команды. Он, в свою очередь, еще раз уточнял и запрашивал все необходимые параметры и после распределял работу между дизайнерами, разработчиками и тестировщиками. Также для создания WL-приложения необходимо создать новый таргет в проекте, пройтись по всем конфигурационным файлам, выставить нужные ID для различных сервисов, настроить цвета, шрифты, изображения и т.д.



В идеальных условиях всего на разработку нового WL должно уходить минимум 2-3 дня по одному на разработчиков iOS и Android, плюс тестирование. Если сотрудник первый раз сталкивается с созданием WL-приложения, то выполнение задачи у него может занять до трех дней, что увеличивает общее время работы. Таким образом, для Рамблер/кассы создание WL-приложения это рутинная задача, которая ложилась на плечи разработчиков и отнимала у них ценное время.

Какие были варианты решений


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

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

Как работает решение


Мы запустили сайт с административной панелью в виде микрослужб новый раздел в существующей админке (CMS) по управлению продажами и витринами Рамблер/кассы. Мы разработали скрипты для iOS и Android, которые локально создают в проекте новое приложение и подают на него все нужные параметры.

В качестве брокера сообщений используется RabbitMQ, а все настройки сохраняются в архив и публикуются в рамблеровский Artifactory. После этого используется API GitLab, чтобы запустить процесс сборки в мобильных репозиториях.

На стороне бэкенда формируется архив с файлами в формате JSON, содержащими информацию, которую ввели в административной панели, и графикой. Триггер Gitlab CI вызывает pipeline, в параметрах к которому передает ссылку на архив из Artifactory. Скрипт, настроенный на билд машине и лежащий в корне проекта запускается с входным параметром-ссылкой.

Скрипт для iOS

Ruby-скрипт распаковывает архив, рассылает нужные данные по всему проекту, а после этого передает управление следующей задаче, которая коммитит изменения, обновляет поды и собирает сборку с последующей выкладкой в App Store.

Скрипт для Android

Джоба подтягивает нужные библиотеки jq и unzip. Библиотека unzip распаковывает архив, скрипт парсит json с помощью jq, создает новую папку в app модуле и проверяет наличие .jks файла для данного приложения.

Если приложение новое, то создается данный файл, после собирается релизное APK и скрипт отправляет в его firebase обновляет приложение в Маркете. Далее задача проверяет появился ли новый .jks файл, и, если он есть, то пушит его в GitLab.

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

На практике автоматизация создания WL-приложений выглядит так


  1. Партнёр Рамблер/кассы заполняет и передает заполненный единообразный бриф и брендбук компании, в которых собраны все тексты, изображения, шрифты, иконки, контактные данные и параметры будущего приложения.
  2. Менеджер проекта или сотрудник поддержки формирует и уточняет требования.
  3. Дизайнер на основе брендбука или сайта партнёра предлагает свое решение по цветам и иконкам.
  4. В админке менеджер проекта самостоятельно заполняет все нужные параметры для нового приложения (ID сервисов, цвета, конфигурационные файлы, иконки и т.д.).
  5. После заполнения необходимых параметров менеджер проекта нажимает кнопку Создать приложение, а затем готовая сборка передается на тестирование.
  6. Тестировщик тестирует приложение и публикует его в App Store и Google Play с помощью CI.



Что получилось


Мы максимально автоматизировали создание WL-приложений. Раньше сам процесс разработки занимал 2-3 дня и отнимал ресурсы программистов, а теперь менеджер за 15 минут вбивает все данные и через примерно 20 минут сборка автоматически создается и передается QA на тестирование. Наши партнёры получают все возможности, которые есть в Рамблер/кассе, а мы экономим время, ресурсы и минимизируем ошибки.
Подробнее..

Из песочницы Alt City Online. Как я в одиночку создавал Gta Online для мобильных устройств. Часть 1

23.09.2020 02:13:19 | Автор: admin
Возможно ли в здравом уме замахнуться на подобный проект в одного, и надо ли оно вообще? Спойлер: да (длинный пост с картинками и видео).



Предыстория


Разработкой программных продуктов я занимаюсь уже 6 лет, начинал с разработки приложений для iOS. После выпуска нескольких приложений, которые в общей сумме принесли около $500, решил попробовать себя в разработке сайтов и настройке рекламы. В этой сфере я проработал 3 года, и понял, что создавать сайты не то, чем мне хотелось бы заниматься в жизни.

Так как с детства я очень любил игры, решил рассмотреть геймдев как будущую нишу, где хотел бы себя попробовать. Сделал бесплатную игру на SpriteKit (фреймворк Apple для создания 2D игр), начал знакомиться с инструментами для разработки игр. Решил подробно изучать Unity, так как он мне показался оптимальным вариантом для разработки именно мобильных игр. Выпустил в AppStore и в Google Play простенькую игру на Unity, и естественно поиграли в нее условно 3 с половиной человека. Это меня не особо остановило, так как цель разработки этой игры была в основном в том, чтобы познакомиться с процессом разработки в Unity и запуском игры именно в Google Play. Эти цели были выполнены, можно было двигаться дальше. Я начал уже более тщательно изучать Unity: 3-4 часа в день стабильно проходил Advanced курсы по разработке. Думаю, что мне повезло попался действительно подробный и толковый курс по созданию RPG в Unity, и многое, особенно различные best-practices, я узнал именно из него.

В это время мне подвернулась вакансия в одной крупной компании-разработчике мобильных игр, решил попробовать. Проработал там 3-4 месяца. Это была моя первая работа по найму. До этого вполне успешно работал только на себя. После определенного времени я понял, что это не мое. Надо отдать должное компания предоставляла максимальные удобства для работы. И вообще поразило то, насколько у нас в стране (живу в России, в городе миллионнике) может быть крутое отношение к работникам. Ни в коем случае не хочу сказать, что в других компаниях плохое отношение, просто то что я наблюдал это очень круто.

В общем, после ухода с работы, пришло понимание, что хочу попробовать создать действительно интересную и уникальную игру (наверное термин уникальная игра не совсем вяжется с концепцией игры-аналога GTA Online, но об этом дальше). С детства я обожал серию GTA играл десятками часов напролет в GTA Vice City и GTA San Andreas, ставил моды, крутил параметры машин. Потом после выхода GTA IV все то же самое делал с ней. Потом GTA IV: Episodes from Liberty City, GTA V. И естественно GTA Liberty City Stories, GTA Vice City Stories, GTA Chinatown Wars для PSP. Также было потрачено куча часов в других подобных играх Saints Row, Godfather 2 и т. д. Эх, хорошие были времена

Так вот, потом я познакомился уже с различными Role Play проектами, основанными на GTA. Но знакомство с ними, к сожалению, уже происходило через YouTube, так как работа стала занимать практически все время, а найти 30 минут в день на ролик не проблема. Считаю, что RP проекты создали очень интересную нишу, и вообще переосмыслили GTA.

RP проекты создали очень интересную нишу, и вообще переосмыслили GTA

Как я уже сказал выше, свободного времени становилось все меньше, и я захотел поиграть во что-то по типу GTA Online / GTA RP на телефоне благо сегодня телефоны действительно мощные, и по идее что-то подобное можно реализовать (например тот же PUBG, который отлично работает на практических любых устройствах). Каково было мое удивление, когда в AppStore я нашел буквально 3 игры, которые хоть как-то можно было отнести к аналогам GTA Online, да и те ужасные. Вот так и появилась идея создать первый аналог GTA Online для устройств на базе iOS и Android.

ALT: City Online




Геймплей в ALT: City Online это смесь классической GTA Online и ее Role Play модификаций. В самом начале игры ты появляешься в стартовой точке (предполагается, что это будет либо вокзал, либо аэропорт). Твоя задача найти работу и начать зарабатывать деньги и опыт. По мере получения опыта, тебе будут открываться новые профессии. Список профессий будет широкий, и я сейчас работаю над тем, чтобы сделать геймплей каждой профессии максимально интересным насколько это возможно. Далее ты сможешь купить себе квартиру, мотоцикл, потом дом, машину, машину получше в общем все, кто играли в GTA RP, знакомы с этим. Фишка игры заключается в том, что в сессии, в отличии от классической GTA Online, будут сотни человек, ты сможешь взаимодействовать со всеми разговаривать, наносить урон, кооперировать, обмениваться вещами, продавать вещи.



В игре будет возможность покупать оружие, менять его скины. Также в игре будет доступен тюнинг транспортных средств.

Но главное отличие от RP проектов в ALT: City Online нет классической для таких проектов консоли, нет администраторов, не нужно отыгрывать РП. Ты можешь, но не обязан.

Площадь карты в игре около 225 квадратных километров, но примерно треть площади покрыто водой, так что эффективная площадь равна примерно 150-160 квадратным километрам. Естественно, наполнить такой большой мир контентом в одиночку достаточно сложно, поэтому игра будет наполняться контентом в последующих обновлениях.



Важно то, что игра будет работать не только на последних топовых девайсах. Например, если говорить об iOS, то минимально поддерживаемое устройство iPhone 7. Вообще, основным боттлнеком оптимизации игры стала непрозрачность потребления памяти на iOS платформе (это известный недостаток Unity, с которым на данный момент мало что можно сделать), из-за чего было достаточно сложно оптимизировать огромный открытый мир для работы на маленьком устройстве с 2 гб оперативной памяти.


Какие вопросы предстояло решить


В первую очередь надо было вообще понять, возможно ли реализовать мобильную онлайн игру в большом открытом мире на Unity? Соответственно, был куплен простенький генератор города для Unity, контроллер персонажа, скачаны бесплатные модели оружия и автомобиля и кое-как настроен клиент сервер (вопрос реализации мультиплеера решался достаточно долго, так как официального решения от Unity нет, а фреймворков много, и они очень отличаются, расскажу о сетевой архитектуре подробнее в одной из будущих технических статей). Спустя две недели был готов достаточно играбельный прототип. То есть был запущен тестовый сервер игры на виртуальном сервере, установлены клиенты на телефоны друзей. Мы подключились, поиграли все отлично работает для прототипа, все довольны. Провел стресс тест, получилось, что минимум 100 игроков сервер точно держит. Для игры это достаточный минимум, но вообще, цель 600-1000 игроков на сервере (то есть столько игроков будет одновременно в сессии). В общем, пришло понимание, что Unity очень даже подходит под этот проект.


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

Далее нужно было понять, как быть с 3D моделями? А их нужно было действительно много здания, автомобили, различные пропсы, одежда, оружие. И самое главное как смоделировать сам мир: ландшафт (террейн), дорожную сеть, город? Написал подробный список всех нужных объектов. Потратил неделю на поиск нужных моделей по списку, и понял, что в принципе все что нужно есть в магазинах 3D моделей.

Сделаю отступление надо понимать, что я занимаюсь проектом с декабря на фул-тайме (по 10-12 часов в день, без выходных), а практически все средства, отложенные за прошлые годы, я тратил на покупку различных инструментов и 3D моделей. Думаю, это снимет многие вопросы по поводу того, почему на некоторые этапы потрачено мало времени.

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

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

Текущее состояние проекта


На данный момент я работаю над игрой уже девятый месяц. Проект готов к альфа релизу на 85%. Уже сейчас полностью смоделирована карта, настроена работа открытого мира на телефонах, проведена работа над рендерингом (LODы, различные настройки графики, оптимизация текстур, динамический батчинг и т.д.), настроена архитектура клиент-сервера, оптимизировано потребление памяти.

Для альфа релиза осталось доработать базовые механики, добавить работы, добавить контент (автомобили, одежду), добавить UI и все это хорошенько протестировать.


Скриншоты










Заключение


Спасибо, что дочитали данный пост до конца. Это мой первый опыт написания статьи, так что буду рад фидбеку. Это первая статья из цикла, посвященного скорому релиз игры ALT: City Online. Последняя статья этого цикла будет новостью о релизе игры. Так что, пожалуйста, пишите, про что хотели бы почитать в будущих постах.

Огромная просьба ко всем, кого заинтересовал проект: скоро мы начинаем открытые альфа тесты в онлайне на Android и iOS, поэтому вступайте в группу ВКонтакте, чтобы узнать, когда начнется альфа тестирование. Тем самым вы очень поможете развитию проекта. Всем тестировщикам естественно полагаются жирные игровые призы сразу после релиза проекта.

Также подписывайтесь на Twitter, там будут выкладываться новости, а также актуальные фото и видео о разработке: @AltCityOnline

На сайте ALT: City Online вы также можете оставить свой email. На него придет оповещение, когда игра будет доступна для загрузки. Всем, кто оставил свой email на сайте, так же положен жирный бонус при запуске игры!
Подробнее..

Swift Best Practices которые не стыдно знать

31.08.2020 18:16:08 | Автор: admin

Предисловие


Всем, по традиции, 404! Я собрал коллекцию и частью Swift Best Practices ( которые не только упростят вам жизнь, но и покажут ваш профессионализм на код ревью) поделюсь с вами в этой статье. Хочу чтоб ваш код был чистым, красивым и вы сами ему радовались!

Стартуем от сюда


0. Начнём с UserDefaults, не буду говорить как много я штук видел с ними. Вот небольшая хитрость, которую я использую, чтобы добиться согласованности ключей UserDefault в Swift (#function заменяется именем свойства в геттерах / сеттерах). Просто не забудьте написать хороший набор тестов, которые защитят вас от ошибок при изменении имен свойств.

extension UserDefaults {    var onboardingCompleted: Bool {        get { return bool(forKey: #function) }        set { set(newValue, forKey: #function) }    }}


1. Все знают оператор === с js но почему-то мало кто им пользуется в Swift. (Напомню: Оператор === позволяет проверить, являются ли два объекта одним и тем же экземпляром.) Очень полезно при проверке того, что массив содержит экземпляр в тесте:

protocol InstanceEquatable: class, Equatable {}extension InstanceEquatable {    static func ==(lhs: Self, rhs: Self) -> Bool {        return lhs === rhs    }}extension Enemy: InstanceEquatable {}func testDestroyingEnemy() {    player.attack(enemy)    XCTAssertTrue(player.destroyedEnemies.contains(enemy))}

2. Используя DispatchWorkItem, вы можете легко отменить отложенную асинхронную задачу GCD, если она вам больше не нужна:

let workItem = DispatchWorkItem {    // Ваш асинхронный код здесь}// Вызовем его через 1 секундуDispatchQueue.main.asyncAfter(deadline: .now() + 1, execute: workItem)// А теперь можем прекратить его работу(в любое удобное для нас время)workItem.cancel()

3. С map можно придумать много крутых штук, но эта одна из самых полезных. Используя map, вы можете преобразовать Optional значение в optional Result тип, просто передав его в enum.

enum Result<Value> {    case value(Value)    case error(Error)}class Promise<Value> {    private var result: Result<Value>?        init(value: Value? = nil) {        result = value.map(Result.value)    }}

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

До

public func generate() throws {    let contentFolder = try folder.subfolder(named: "content")    let articleFolder = try contentFolder.subfolder(named: "posts")    let articleProcessor = ContentProcessor(folder: articleFolder)    let articles = try articleProcessor.process()    ...}

После

public func generate() throws {    let contentFolder = try folder.subfolder(named: "content")    let articles = try processArticles(in: contentFolder)    ...}private func processArticles(in folder: Folder) throws -> [ContentItem] {    let folder = try folder.subfolder(named: "posts")    let processor = ContentProcessor(folder: folder)    return try processor.process()}


Ну красиво, удобно, элегантно же, да? Да. Надеюсь был полезен, всем спасибо за прочтение. Красивого UI и чистого кода!
Подробнее..

Apple Grace Period и Billing Retry статусы при обработке чеков пользователей

09.09.2020 16:16:51 | Автор: admin

Привет. В этой статье мы поговорим про такую частую проблему, как ошибка оплаты в мобильных приложениях с подписочной моделью. Если взять средние данные из системы Qonversion, то 15-20% триалов переходят в Billing Issue. Из них около 15% возвращаются в платное состояние. Поддержка Grace Period позволит улучшить пользовательский опыт и повысить процент возврата в платное состояние.


План:


  • Как устроен Billing Retry?
  • Что такое Grace Period?
  • Продуктовые подходы для работы с Billing Retry

Как устроен Billing Retry?


Ошибки с оплатой являются одной из частых причин потери подписчиков.
Для наглядности, рассмотрим следующий пример.



За некоторое время до завершения очередного оплаченного периода, Apple совершает попытку списать средства со счёта пользователя. Если при этом возникает ошибка, пользователь переходит в состояние Billing Retry. В таком состоянии он может находиться до 60 дней. При этом Apple периодически совершает попытку произвести оплату за следующий период подписки. Всё это время доступ к приложению для пользователя закрыт. На примере видно, что через 10 дней после успешного совершения оплаты возобновляется цикл списания средств. Всё это усложняет расчёт LTV, а также приводит к ухудшению пользовательского опыта.


При проверке чека пользователя мы обнаружим поле is_in_billing_retry_period и expiration_intent:


"pending_renewal_info": [    {        "expiration_intent": "2",        "auto_renew_product_id": "product.99.trial.3d",        "original_transaction_id": "10000000306492965",        "is_in_billing_retry_period": "1",        "product_id": "product.99.trial.3d",        "auto_renew_status": "1"    }]

Эти поля сообщают нам о неудачной попытке списать средства.


Что такое Grace Period?


Для улучшения пользовательского опыта в течение периода, когда Apple не может совершить списание средств за следующий автовозобновляемый период, ваше приложение может предоставлять доступ к контенту. Для этого вам необходимо использовать Grace Period. Grace Period включается для авто-возобновляемых подписок Enable Billing Grace Period for Auto-Renewable Subscriptions, в этом случае пользователь сохраняет полный контроль над приложением, пока Apple пытается произвести оплату.


Продолжительность Grace Period зависит от продолжительности вашей подписки:



Давайте рассмотрим два примера:


Пример 1: успешное списание в течение Grace Period



За некоторое время до завершения первого автовозобновляемого периода, Apple совершает неудачную попытку списать средства. Такой результат при отключенном Grace Period означал был моментальный переход в состояние Billing Retry с заблокированным доступом к приложению. Однако при включённом Grace Period, начинается следующий автовозобновляемый период, а пользователь продолжает сохранять доступ к контенту. Через некоторое время Apple совершает успешное списание средств, таким образом существующий цикл оплаты не прерывается.


Пример 2: неудачное списание в течение Grace Period



Во втором примере Apple не удаётся списать средства в течение Grace Period, что приводит к изменению текущего цикла списаний, а пользователь переходит в статус Billing Retry с заблокированным доступом. Через некоторое время Apple удаётся произвести списание денежных средств, при этом запускается новый автовозобновляемый период со дня списания.


После включения Grace Period в настройках приложения, JSON response, который вы получаете при валидации чека будет содержать дополнительное поле grace_period_expires_date_ms, именно это поле и означает новый период завершения доступа к контенту в приложении.


"pending_renewal_info": [    {        "expiration_intent": "2",        "grace_period_expires_date": "2020-09-05 23:41:42 Etc/GMT",        "auto_renew_product_id": "product.99.trial.3d",        "original_transaction_id": "10000000306492965",        "is_in_billing_retry_period": "1",        "grace_period_expires_date_pst": "2020-09-05 16:41:42 America/Los_Angeles",        "product_id": "product.99.trial.3d",        "grace_period_expires_date_ms": "1599349302000",        "auto_renew_status": "1"    }]

Как только платеж будет успешно совершён, поле исчезнет из чека вместе с is_in_billing_retry_period. В этом случае актуальным по истечению срока действия подписки станет поле expires_date_ms
которое нужно брать уже из receipt.in_app


"in_app": [            {                "quantity": "1",                "product_id": "product.99.trial.3d",                "transaction_id": "0000000306492966",                "original_transaction_id": "0000000306492965",                "purchase_date": "2020-08-25 02:53:10 Etc/GMT",                "purchase_date_ms": "1598323990000",                "purchase_date_pst": "2020-08-24 19:53:10 America/Los_Angeles",                "original_purchase_date": "2020-08-25 02:53:12 Etc/GMT",                "original_purchase_date_ms": "1598323992000",                "original_purchase_date_pst": "2020-08-24 19:53:12 America/Los_Angeles",                "expires_date": "2020-09-25 02:53:10 Etc/GMT",                "expires_date_ms": "1601002390000",                "expires_date_pst": "2020-09-24 19:53:10 America/Los_Angeles",                "web_order_line_item_id": "000000003253190",                "is_trial_period": "false",                "is_in_intro_offer_period": "false"            }        ]

Если платёж не будет успешно совершен после истечения 60 дней, поля is_in_billing_retry_period и grace_period_expires_date исчезнут из чека, и можно будет использовать expires_date_ms, однако в этом случае подписка будет истёкшей, а поле auto_renew_status будет равно 0


Продуктовые подходы для работы с Billing Retry и Grace Period


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


Для этого вам необходимо обрабатывать рецепт на серверной стороне исходя из изменений выше или использовать готовые решения.


Qonversion.checkPermissions { (permissions, error) in  if let error = error {    // handle error    return  }  if let premium = permissions["premium"], premium.isActive {    switch premium.renewState {       case .billingIssue:         // Grace period: permission is active, but there was some billing issue.         // Prompt the user to update the payment method.         break       default: break    }  }}
Подробнее..

Кодекс iOS джентльмена

14.09.2020 02:07:47 | Автор: admin

Кодекс iOS джентльмена


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

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

iOS джентльмен всегда вежлив.


Ваше плохое настроение или мания величия это еще не повод портить настроение другим людям. Думаю никому не понравиться если ему нагрубят. Поэтому старайтесь и Сами не грубить.

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

Всегда и при любых обстоятельствах нужно держать себя в руках и стараться быть вежливым.

iOS джентльмен всегда уважает свое время, а значит и уважает время других


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

Стоит вежливо уточнить, может ли нужный вам человек уделить время и помочь Вам. Если он сильно занят, то скорей всего он сможет помочь чуть позже.

iOS джентльмен уважает код и технические решения своих коллег.


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

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

iOS джентльмен не правит код другого разработчика без его ведома


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

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

iOS джентльмен не критикует чужой код без аргументов и без альтернатив


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

Бессмысленная критика приводить только к раздору в команде, что не очень сказывается на результате.

iOS джентльмен также умеет принимать критику достойно


Никто не идеален и никто не пишет идеальный код. Мы все время обучаемся и усовершенствуем наши навыки, в том числе и в разработке ПО. Критика является одним из самых эффективных механизмов обучения. И нужно не только уметь подавать критику но и достойно ее принимать.

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

Если критика действительно полезна, то стоит поблагодарить человека, который указал на Ваши погрешности.

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

iOS джентльмен пишет код в общем стиле


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

То же самое касается проектов. Старайтесь всегда придерживаться общепринятых правил и стандартов, даже если они Вам не нравятся, но команды им следует.

Подробнее..

О чем нам рассказали на ежегодной сентябрьской презентации Apple

15.09.2020 22:15:12 | Автор: admin
image

В этом году событие прошло нетипично хотя бы потому, что ни слова не было сказано о новых iPhone. Презентация сфокусирована на двух продуктах: Apple Watch и iPad.


Apple Watch


Новые часы Apple Watch Series 6 имеют тот же дизайн и выглядят так же, как и Series 5, с теми же вариантами размеров: 40 и 44 нм. Под крышкой Series 6 установлен более производительный процессор S6, что, впрочем, не оказывает особого влияния на время автономной работы. Срок службы батареи Apple Watch обычно не вызывает беспокойства у пользователей, поэтому тот факт, что оно осталось без изменений, позволило Apple направить ресурсы устройства на другие нужды. Новые цвета в линейке: синий, красный, золотой, графитовый.

image

Особенностью новой серии является датчик кислорода в крови, который определяет, какое количество кислорода переносят ваши эритроциты. Подобно функции Apple Watch предупреждать пользователей о низкой/высокой частоте пульса, Series 6 будет присылать уведомления в том случае, если обнаружит, что уровень кислорода в крови ниже нормы.

image

Apple Watch Series 6 оснащены новейшим двухъядерным процессором Apple S6 на базе чипа Apple A13 на 20% более быстрым, чем у предыдущего поколения.

Другие новые функции Apple Watch Series 6: в 2,5 раза более яркий экран при солнечном свете и постоянно работающий высотомер с точностью до 1 фута.

Apple Watch Series 6 включает в себя новые циферблаты: циферблат по Гринвичу с несколькими часовыми поясами, циферблат для подсчета времени на круге во время бега, циферблат на основе мемодзи и другие.
Также у новых часов появится новый ремешок Apple Watch Solo, изготовленный из цельного фрагмента силикона, новый плетеный, а также кожаный ремешки.

Еще из нового: у Apple Watch не будет в коробке адаптера.

image

watchOS, Apple Fitness+, Apple One


watchOS 7 добавит новую функцию для настройки семейного доступа Apple Watch без iPhone, позволяющую членам семьи управлять устройствами Apple Watch своих родственников, по сути, это своеобразный детский режим Apple Watch.

Функция семейного доступа предоставляется бесплатно в Австралии, Канаде, Ирландии, Новой Зеландии, Великобритании и США. Запуск сервиса в которых начнется в конце года.

watchOS 7 станет доступна для всех уже завтра, 16 сентября.

Кроме того, Apple запускает новый сервис Apple Fitness + с полным каталогом тренировок для iPhone, Apple Watch и AppleTV, тренерами мирового класса, интеграцией с Apple Music и новыми тренировками каждую неделю. Apple Fitness+ включает в себя тренировки, для которых практически не требуется оборудования, а также занятия по гребле, на беговой дорожке и на велосипеде. Тренировки встроены в приложение Fitness на iPhone, на iPad и в Apple TV.

image

Apple Fitness+ будет доступен по цене 9,99 долларов в месяц или 79,99 долларов в год. 3 месяца бесплатно предоставляется при покупке новых Apple Watch.

Также этой осенью станет доступна централизованная подписка на сервисы Apple: Apple One. Цены на нее представлены ниже.

image

iPad


Наряду с Apple Watch Series 6 и Watch SE, Apple анонсировала iPad 8-го поколения с процессором A12 Bionic и поддержкой Apple Pencil. Новый iPad обещает быть в 3 раза быстрее, чем самый продаваемый планшет Android. Устройство будет доступно для покупки уже в эту пятницу по цене 329 долларов, для образовательных учреждений 299 долларов.

image

Официально представлен и последний iPad Air с совершенно новым дизайном и безрамочным дисплеем. Новый iPad Air оснащен 10,9-дюймовым дисплеем Liquid Retina с широким цветовым охватом P3, True Tone и ламинированным покрытием. Touch ID находится в кнопке питания. Новые расцветки: Green и Sky Blue.

image

Он также отличается огромным скачком в производительности благодаря новейшему процессору A14 Bionic. ЦП стал на 40% быстрее, производительность графического процессора увеличилась на 30%, производительность машинного обучения также улучшилась благодаря новому нейронному движку.

Новый iPad Air выполнен в стиле iPad Pro с 10,9-дюймовым дисплеем с разрешением 23601640 с полным ламинированием, широкой цветовой гаммой P3, True Tone и антибликовым покрытием. Он также имеет возможность подключения USB-C и обновленную акустическую систему.

Что касается аксессуаров, iPad Air поддерживает Apple Pencil второго поколения и Magic Keyboard. Планшет будет доступен в следующем месяце по цене от 599 долларов.

iOS 14 важное обновление для пользователей iPhone, которое предлагает долгожданную поддержку виджетов на главном экране, а также функцию библиотеки приложений для организации приложений, которые вы не хотите оставлять на главном экране. Кроме того, iOS 14 предлагает новые компактные интерфейсы для Siri и входящих телефонных звонков, а также функции картинка в картинке для воспроизведения видео.

Вот некоторые другие особенности iOS 14:

  • Закрепленные беседы в приложении Сообщения;
  • Упоминания в приложении Сообщения;
  • Встроенные ответы для iMessage;
  • Изменение приложения электронной почты веб-браузера по умолчанию;
  • Поиск эмодзи;
  • Велосипедные маршруты в Apple Maps;
  • Поддержка Apple Maps для маршрутизации электромобилей;
  • Совершенно новое приложение Translate;
  • Улучшения HomeKit: распознавание лиц, зоны активности, адаптивное освещение;
  • Обои для CarPlay;
  • Новые типы приложений CarPlay: парковка, зарядка электромобилей, быстрый заказ еды;
  • Пространственный звук для AirPods Pro;
  • Автоматическое переключение между устройствами для AirPods и AirPods Pro;
  • App Clips для быстрого доступа к функциям приложения без загрузки полного приложения;
  • Новые функции конфиденциальности;

Apple объявила, что выпустит iOS 14 для широкой публики 16 сентября.

Кроме того, компания анансировала Xcode 12 Golden Master для разработчиков.

И не можем не упомянуть, что наша компания поучаствовала в демонстрации технических возможностей нового iPad, показав, как работает на нем обновленная версия нашего мультиплеерного шутера War Robots Remastered!

image
Подробнее..

Устройство 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

Подробнее..

Категории

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

© 2006-2020, personeltest.ru