Универсальные попапы или UIKit против
27 декабря состоялся VK Tech Talks | iOS, на котором Антон Спивак рассказал небольшую историю про проектирование универсального интерфейсного компонента в приложении VK и исследование внутренних особенностей UIKit.
В самом начале работы Антона к нему подошел дизайнер Миша и сказал:
Для начала стоит описать поведение всплывающего экрана, что мы вообще желаем от него получить:
Чем необходимо пользоваться, чтобы реализовать то поведение, которое мы хотим видеть:
Основная идея в следующем: красным обведен большой и главный PopupController, который имеет в себе, например, маленький popup, и мы должны сделать так, чтобы мы могли запихнуть туда любой контент. Что мы будем для этого делать?
- Нам нужно ловить жест UIPanGestureRecognizer, чтобы перемешать экран.
- Возьмём child view controller и просто вставим его вовнутрь.
- Будем менять наш child controller с помощью bounds.
- И анимировать с помощью UIKit Dynamic.
Первая проблема, с которой мы сталкиваемся — это проброс нажатий и Accessibility.
Эта проблема решается, но тут приходит Миша и говорит:
Как придётся это всё встраивать и какие ограничения накладываются на эти вещи?
Первая задача — поиск UiScrollView: есть приватный метод, UIKit позволяет сделать это, на самом деле. Есть такая штука как contentScrollView и они сами этим пользуются, например, для scroll to top. Они пользуются этим методом и пытаются определить у topmost controller'а какой сейчас находится контент: ScrollView или, например, это сделано для того, чтобы в iOS 11 можно было сделать большие заголовки. Придётся бегать по иерархии, искать это всё, запоминать и кэшировать.
Допустим мы это сделали. Теперь нужно реагировать на изменения контента в UIScrollView?
Что делать для перехватывания жеста и как вообще можно было бы это сделать? У UIScrollViewPanGestureRecognizer, который является наследником обычного PanGestureRecognizer, который лежит и управляет действиями на ScrollView, есть такая штука, которая позволяет переместить жест на родительское ScrollView. Чтобы это работало, необходимо сделать так, чтобы у нас родительское ScrollView поддерживало paging, но это опять же приватный API и мы не можем это использовать.
Миша, привет! Миша пришёл и сказал:
Добавим это просто в PopupController. Вроде ничего сложного. Достаточно определить скорость окончания жеста и просто довести до нужной позиции.
Сделали, справились. Миша доволен, почти. Но появилась история, что один попап может появиться чуть выше другого попапа и это, само собой, выглядит не очень.
Миша предложил скрывать нижние и оставить только верхний.
Достаточно просто найти parentPopup, и просто будем вместе с появлением анимации скрывать предыдущий попап.
Но это не совсем так. Он круто работает с UINavigationController, со стеком, когда есть определенный контролер из которого идет транзишн или в который идёт транзишн.
Миша доволен. Миша обрадовался. Но не обрадовались мы.
Началась история, когда приходили ребята из раздела тестирования и приносили баги. У нас был скриншот одного единственного метода, который отвечал за жест, который мы сами обрабатывали для попапа.
Настало время рефакторинга:
- Первоначально хотелось бы избавиться от поиска UIScrollView. Достаточно дорогая операция, учитывая, что часто нужно как-то избавляться от предыдущего найденного ScrollView.
- Также хочется инкапсулировать логику полностью отвечающую, например, за статичный попап, и как-то отделить его от попапа, который может скроллиться у себя внутри.
ScrollView. Как мы будем избавляться от этих поисков и инвалидаций кешей, и прочих вещей? Для начала придумаем протокол, который будет отвечать за изменение поведения ScrollView. Нам нужен такой момент, когда ScrollView может спросить у своего слушателя о том, может ли сейчас он поменять контент. Также понадобятся небольшие обзерверы — протоколы, которые позволяют просто слушать изменение контента. И сделаем условного регулятора, который позволит для определенного ScrollView отдать либо обзервера, либо его регулятора.
В нашем случае сам PopupController будет являться Listener'ом, который отдаст behaviour, чтобы тот определял поведение ScrollView и самого попапа.
Вот такая история, в которой удалось на базе этих попапов сделать обширное количество экранов.
К чему же мы пришли за это всё время?
А на этом доклад Антона заканчивается.
Посмотреть полную версию доклада можно в трансляции мероприятия:
независимое интернет-издание о социальных сетях и современных технологиях
Автор: Сергей Котов
Корректор: Арсений Метелев
