В одном из моих проектов потребовалось сделать интерфейс наподобие такового в Снепчате. Когда выезжает карточка с информацией поверх изображения с камеры, плавно его заменяя на сплошной цвет, и точно также в обратную сторону. Меня лично особо заворожил переход из окна камеры в боковую карточку, и я с огромным удовольствием отправился ресерчить способы решения данной задачи. Слева — пример Снепчата, справа — пример приложения, созданием которого мы займемся.   Вероятно, первое решение, приходящее в голову – адаптировать UIScrollView, расположить как-то вьюхи на нем, использовать паджинацию, но, откровенно говоря, скролл придуман для решения абсолютно иных задач, подцепить на него дополнительные анимации трудоемко, да и он не обладает необходимой гибкостью настройки. Поэтому использование его для решения данной проблемы – абсолютно не обосновано. Скролл между окном камеры и боковой вкладкой обманчив — это вовсе не скролл, это интерактивный переход между вьюхами, принадлежащими к разным контроллерам. Кнопки в нижней его части — это обыкновенные табы, нажатие на которые перекидывает нас между контроллерами.  Таким образом Снепчат использует свою собственную версию навигационного контроллера типа UITabBarController с кастомным интерактивными переходами. UIKit включает в себя два варианта навигационных контроллера, позволяющих кастомизировать переходы – это UINavigationController и UITabBarController. Оба имеют в своих делегатах методы navigationController(_:interactionControllerFor:) и tabBarController(_:interactionControllerFor:) соответственно, которые нам позволяют использовать свою собственную интерактивную анимацию для перехода. tabBarController(_:interactionControllerFor:) navigationController(_:interactionControllerFor:) Но я бы не хотел быть ограниченными реализацией UITabBarController или UINavigationController, тем более что мы не можем управлять их внутренней логикой. Поэтому я решил написать свой подобный контроллер, и сейчас хочу рассказать и показать что из этого получилось. Сделать свой контроллер-контейнер, в котором можно будет переключаться между дочерними контроллерами, используя для переходов интерактивные анимации, применив стандартный механизм как в UITabBarController и UINavigationController. Этот стандартный механизм нам нужен, чтобы использовать уже написанные готовые анимации переходов типа UIViewControllerAnimatedTransitioning. Обычно я стараюсь выносить модули в отдельные фреймворки, для этого я создаю новый проект приложения, и добавляю туда дополнительный таргет Cocoa Touch Framework, и дальше раскидываю исходники в проекте по соответствующим таргетам. Таки образом у меня получается отдельный фреймворк с тестовым приложением для отладки. Создаем Single View App.  Product Name и будет нашим таргетом.  Нажимаем на +, чтобы добавить таргет.  Выбираем Cocoa Touch Framework.  Называем наш фреймворк соответствующим именем, Xcode автоматом выбирает проект для нашего таргета и предлагает подвязать бинарник сразу в приложение. Соглашаемся.  Нам не нужны будут дефолтные Main.storyboard и ViewController.swift, удаляем их.  Также не забываем удалить значение из Main Interface в таргете приложения на вкладке General.  Теперь идем в AppDelegate.swift и оставляем только метод application следующего содержания: Здесь мы устанавливаем свой контроллер на главное место, чтобы он показывался после лаунчскрина. Теперь создадим этот самый MasterViewController. Он будет относиться к приложению, поэтому важно выбрать правильный таргет при создании файла.  MasterViewController мы унаследуем от SnapchatNavigationController, который чуть позже реализуем во фреймворке. Не забываем указать import нашего фреймворка. Полный код контроллера здесь я не привожу, пропуски показаны многоточием ..., приложение я разместил на GitHub, там можно посмотреть все детали. В этом контроллере нас интересует только метод viewDidLoad(), который инициализирует фоновый контроллер с камерой + один прозрачный контроллер (главное окно) + контроллер, содержащий вылетающую карточку. Что здесь происходит? Мы создаем контроллер с камерой и сеттим его на фон методом setBackground из SnapchatNavigationController. Этот контроллер содержит растянутое на всю вьюху изображение с камеры. Затем создаем пустой прозрачный контроллер и добавляем его в массив, он просто пропускает через себя изображение с камеры, на нем можно будет расположить элементы управления, создаем еще один прозрачный контроллер, добавляем на него скролл, внутрь скролла добавляем вьюху с контентом, добавляем второй контроллер в массив и сеттим этот массив специальным методом setViewControllers из родительского SnapchatNavigationController. Не забываем добавить запрос на использование камеры в Info.plist На этом тестовое приложение считаем готовым, и переходим к самому интересному — к реализации фреймворка. Для начала создадим пустой SnapchatNavigationController, важно выбрать для него правильный таргет. Если все было сделано правильно, то приложение должно собираться. Данное состояние проекта можно выгрузить по ссылке. Теперь добавим внутренние компоненты, из которых будет состоять контроллер. Весь код здесь не привожу, акцентирую внимание только на важных моментах. Задаем переменные для хранения массива дочерних контроллеров. Сейчас мы жестко задаем их требуемое количество — 2 штуки. В будущем можно будет расширить логику контроллера для использования с любым количеством контроллеров. Также мы задаем переменную для хранения текущего отображаемого контроллера. Создаем вьюхи. Нам нужна одна вьюха под фон, одна вьюха с эффектом, который мы хотим наложить на фон при смене контроллера. Также у нас есть вьюха-контейнер для текущего дочернего контроллера и вьюха-индикатор, которая будет подсказывать пользователю как работать с навигацией. В следующем блоке мы задаем две переменных, swipeAnimator отвечает за анимацию, swipeInteractor отвечает за интерактив (возможность контролировать ход анимации), его мы обязательно инициализируем во время загрузки контроллера, поэтому делаем force unwrap. Также мы задаем трансформацию для индикатора. Индикатор мы смещаем на ширину контейнера + двойной сдвиг от края + ширину самого индикатора, чтобы индикатор оказался на противоположенном конце контейнера. Ширина контейнера станет известна в процессе работы приложения, поэтому переменную вычисляем на ходу. Во время загрузки контроллера мы назначаем для анимации self (ниже мы реализуем соответствующий протокол), инициализируем интерактор на основе нашей анимации, ход которой он будет контролировать. Также ему назначаем себя в качестве делегата. Делегат будет реагировать на начало жеста пользователя и либо запускать анимацию, либо отменять в зависимости от состояния контроллера. Затем добавляем все вьюхи на главную и вызываем setupViews(), устанавливающий констрейнты. Дальше мы переходим к логике установки и удаления дочерних контроллеров в контейнер. Здесь все просто как в документации Apple. Мы используем методы предписанные для такого рода операций. addChildViewController(vc) — добавляем дочерний контроллер к текущему. contentViewContainer.addSubview(vc.view) — добавляем вьюху контроллера в иерархию вьюх. vc.view.frame = contentViewContainer.bounds — растягиваем вьюху на весь контейнер. Раз мы используем здесь фреймы вместо авто лейаута, нужно менять их размеры каждый раз при изменении размера контроллера, эту логику мы опустим и будем считать, что во время работы приложения размеры контейнера меняться не будут. vc.didMove(toParentViewController: self) — ставим точку в операции добавления дочернего контроллера. swipeInteractor.wireTo — подвязываем текущий контроллер на жесты пользователя. Позже мы разберем этот метод. Есть еще два метода, код которых я здесь приводить не буду: setViewControllers и setBackground. В методе setViewControllers мы просто устанавливаем массив дочерних контроллеров в соответствующую переменную нашего контроллера и вызываем addChild, чтобы отобразить один из них на вьюхе. В методе setBackground мы делаем тоже самое, что и в addChild, только для фонового контроллера. Итого, основа нашего родительского контроллера – это: Сейчас мы разберем два последних пункта, затем перейдем к реализации AnimatedTransitioning и CustomSwipeInteractor. Делегат состоит всего из одного метода panGestureDidStart(rightToLeftSwipe: Bool) -> Bool, сообщающего контроллеру о начале жеста и его направлении. В ответ он ждет информацию о том, можно ли считать анимацию начавшейся. В делегате мы проверяем текущий порядок контроллеров, чтобы понять, можем ли мы начать анимацию в заданном направлении, и если все ок, мы запускаем метод transition, с параметрами: контроллер из которого мы переходим, контроллер в который мы переходим, направление движения, флаг интерактивности (в случае false запускается фиксированная по времени анимация перехода). Рассмотрим сразу тело метода transition. В первую очередь мы создаем контекст выполнения анимации CustomControllerContext. Этот класс мы также разберем чуть позже, он реализует протокол UIViewControllerContextTransitioning. В случае с UINavigationController и UITabBarController экземпляр реализации этого протокола создается системой автоматически и его логика от нас скрыта, нам нужно создать свою. Затем мы просто вызываем либо фиксированную, либо интерактивную анимацию. Фиксированную можно будет повесить в будущем на кнопки-табы навигации между контроллерами, в данном примере мы этого делать не будем. Протокол анимации TransitionAnimation состоит из 4 методов: addTo — метод, призванный создать правильную структуру дочерних вьюх в контейнере, так чтобы перекрытие предыдущей вьюхи новой происходило по задумке анимации. prepare — метод, вызываемый перед анимацией для подготовки вьюх. animation — сама анимация. finalize — необходимые действия после завершения анимации. Используемую реализацию мы рассматривать не будем, там все достаточно прозрачно, перейдем сразу к трем основным классам, благодаря которым анимация происходит. Контекст выполнения анимации. Для описания его функции обратимся к справке протокола UIViewControllerContextTransitioning: Самое занимательное — это запрет на адаптацию данного протокола: Но нам он очень нужен для запуска стандартного механизма анимаций, поэтому мы все равно его адаптируем. В нем почти нет логики, он лишь хранит состояние. Поэтому даже не буду его здесь приводить. Его можно посмотреть на GitHub. Он отлично работает на фиксированных по времени анимациях. Но при его использовании для интерактивных анимаций возникает одна проблема — UIPercentDrivenInteractiveTransition вызывает у контекста недокументированный метод. Единственным правильным решением в данной ситуации будет адаптировать еще один протокол — UIViewControllerInteractiveTransitioning для использования собственного контекста. Вот оно — сердце проекта, позволяющее существовать интерактивным анимациям в кастомных контроллерах-контейнерах. Разберем его по порядку. Инициализируется класс одним параметром типа UIViewControllerAnimatedTransitioning, это стандартный протокол анимации перехода между контроллерами. Таким образом мы сможем использовать любую из уже написанных анимаций вместе с нашим классом. Public interface у нас достаточно простой, четыре метода, функционал которых должен быть очевиден. Стоит только отметить момент начала анимации, мы берем родительское вью контейнера и устанавливаем скорость слоя равной 0, таким образом мы добиваемся возможности контролировать ход выполнения анимации вручную. Перейдем теперь к private блоку логики нашего класса. setPercentComplete устанавливает временное смещение прогресса анимации для слоя супервью, рассчитывая значение из процентного значения завершенности и продолжительности анимации. completeTransition вызывается в момент, когда пользователь прекратил свой жест. Здесь мы создаем экземпляр класса CADisplayLink, который позволит нам в автоматическом режиме красиво завершить анимацию с точки когда пользователь больше не управляет ее ходом. Мы добавляем наш displayLink в run loop затем, чтобы система вызывала наш селектор всякий раз когда ей нужно отобразить новый кадр на экране девайса. В нашем селекторе мы вычисляем и устанавливаем временное смещение прогресса анимации как это делали раньше во время жеста пользователя, либо завершаем выполнение анимации при достижении ее начальной или финальной точки. Завершая анимацию, мы отключаем наш displayLink, возвращаем скорость слоя, и, если анимация не была отменена, то есть достигла своего финального кадра, вычисляем время с которого анимация слоя должна начаться. Подробнее об этом можно узнать в Core Animation Programming Guide, или вот в этом ответе на stackoverflow. Последний класс, который мы еще не разобрали — реализация протокола UIViewControllerAnimatedTransitioning, в которой мы управляем порядком выполнения методов протокола нашей анимации addTo, prepare, animation, finalize. Здесь все достаточно прозаично, стоит отметить только использование UIViewPropertyAnimator для выполнения анимации вместо более типичного UIView.animate(withDuration:animations:). Это сделано для того, чтобы была возможность дополнительно управлять ходом анимации, и в случае ее отмены, вернуть ее в начальную позицию вызовом finishAnimation(at: .start), что позволяет избежать ненужного моргания финального кадра анимации на экране. Мы создали рабочую демку интерфейса подобного интерфейсу программы Snapchat. В своей версии я сконфигурировал констрейнты таким образом, чтобы справа и слева от карточки оставались поля, кроме того я оставил работать камеру на фоновой вьюхе, чтобы создать эффект позади карточки. Это сделано исключительно для демонстрации возможностей данного подхода, как это будет влиять на производительность девайса и заряд его батареи я не проверял. Эта статья — моя первая проба пера в жанре технической литературы, я мог упустить какие-то важные моменты, поэтому я готов с радостью ответить на вопросы в комментариях. Спасибо всем кто прочитал мою статью, надеюсь вы нашли что-то полезное для себя здесь. Готовый проект можно загрузить из GitHub по ссылке. Еще раз спасибо, всем хорошего дня, интересных задач, продуктивного коддинга!  Для написания этой программы я использовал следующую информацию: Статья Custom Container View Controller Transitions, автор Joachim Bondo. Автор статьи предложил вариант кастомного контекста на Objective C. Я использовал его вариант для написания своего класса на Swift. Ссылка Статья Interactive Custom Container View Controller Transitions, автор Alek Åström Автор продолжил работу из предыдущей статьи и предложил свой подход в создании интерактивного варианта анимации, также на Objective C, также я использовал его код для написания своего класса на Swift. Ссылка SwipeableTabBarController Проект, в котором автор подключил различные интерактивные анимации для переходов в стандартном UITabBarController. Использовал некоторые идеи из кода проекта. Ссылка iOS