Войти

Модальный контроллер как в Apple Music

В приложении Apple Musiс и Почта используется модальный контроллер:

Экран вдохновил. Я захотел добавить его в мои приложения. Готовых классов или библиотек нет, поэтому проект разрабатывал с нуля. Я хочу делиться результатом, поэтому проект доступен для разработчиков бесплатно. Называется он SPStorkController:

В туториале разберем как установить SPStorkController в Xcode-проект и настроить.

Установка

CocoaPods

CocoaPods - менеджер зависимостей для проектов. Изучите инструкцию по установке на их веб-сайте. Чтобы интегрировать SPStorkController в проект Xcode с помощью CocoaPods, добавьте строку в Podfile:

pod 'SPStorkController'

Обновите зависимости проекта.

Carthage

Carthage - это децентрализованный менеджер зависимостей. Он строит связи и предоставляет вам frameworks. Чтобы интегрировать SPStorkController в проект Xcode с помощью Carthage, добавьте строку в файл:

github "IvanVorobei/SPStorkController"

Вручную

Если вы предпочитаете не использовать ни один из вышеупомянутых менеджеров зависимостей, вы можете вручную интегрировать SPStorkController в свой проект. Поместите папку Source / SPStorkController в проект Xcode. Обязательно включите Copy items if needed и Create groups.

Быстрый старт

Чтобы начать достаточно 3 строчки кода, две из который для инициализации и настройки контроллера:

let controller = UIViewController()
controller.view.backgroundColor = .white
self.presentAsStork(controller)

Инициализируем контроллер, устанавливаем для view белый цвет.

Вызываем функцию presentAsStork. Функция доступна из любого контроллера. Результат:

Автоматически добавляется индикатор-стрелка. Его можно убрать, это разберем дальше.

Настройка

Анимацю перехода между контроллерами конфигурирует TransitioningDelegate. Он определяет объекты AnimatedTransitioning для presenting / dismissed и класс UIPresentationController.

Пример, как установить кастомный TransitioningDelegate для контроллера:

let controller = UIViewController()
let transitionDelegate = SPStorkTransitioningDelegate()
controller.transitioningDelegate = transitionDelegate
controller.modalPresentationStyle = .custom
self.present(controller, animated: true, completion: nil)

Нельзя иницилизровать объект SPStorkTransitioningDelegate напрямую. Так как controller.transitioningDelegate это weak проперти, неправильная инициализация приведет к моментальному удалению из памяти объекта.

Чтобы настроить контроллер, будем изменять разные проперти объекта transitionDelegate. Начнем с высоты.

Кастомная высота

Чтобы установить высоту для контроллера, используйте проперти customHeight:

transitionDelegate.customHeight = 500

Как будет выглядеть контроллер:

Изменять высоту после презентации контроллера нельзя. Я планирую добавить этот функционал в следующих версиях.

Белый StatusBar

Чтобы установить для статус бара белый цвет, укажите в модальном контроллере стиль бара:

override var preferredStatusBarStyle: UIStatusBarStyle {
 return .lightContent
}

override func viewDidLoad() {
 super.viewDidLoad()
 self.modalPresentationCapturesStatusBarAppearance = true
}

Индикатор

Это стрелка вверху контроллера. Она меняет состояние в зависимости от контроллера - если вы закрываете контроллер, стрелка станет линией.

Для настройки инидикатора используйте код:

transitionDelegate.showIndicator = true
transitionDelegate.indicatorColor = UIColor.white
transitionDelegate.hideIndicatorWhenScroll = true

Вы можете скрыть стрелку и изменить цвет индикатора. Проперти hideIndicatorWhenScroll скрывает иникатор, если контроллер содержит UIScrollView и контент скролится вверх:

По умолчанию индикатор не прячется. Чтобы эта функция работала корректно, прочитайте о работе с UIScrollView ниже.

Кнопка Закрыть

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

transitionDelegate.showCloseButton = true

В правом верхнем углу появится круглая кнопка:

Dismissing

Действия для закрытия контроллера настраиваются. Контроллер скрывается если нажать на кнопку закрыть, нажать на индикатор, тапнуть по контроллеру-родителю или смахнуть контроллер вниз.

transitionDelegate.swipeToDismissEnabled = true
transitionDelegate.tapAroundToDismissEnabled = true

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

transitionDelegate.translateForDismiss = 100

Закругление углов

Можно настроить закругления презентуемого контроллера и контроллера-родителя. Для этого укажете радиус закругления:

transitionDelegate.cornerRadius = 10

Haptic

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

transitionDelegate.hapticMoments = [.willPresent, .willDismiss]

Navigation Bar

Иногда может потребоваться добавить UINavigationBar в модальный контроллер. Для нативного бара нельзя настроить высоту. Я сделал копию бара. Она не выполняет функции переходов, но отличный варинат если контроллер единственный.

Чтобы добавить бар, вам нужно встроить в проект pod SPFakeBar.

Выберите стиль и добавьте бар на view контроллера:

import UIKit
import SPFakeBar

class ModalController: UIViewController {

 let navBar = SPFakeBarView(style: .stork)

 override func viewDidLoad() {
  super.viewDidLoad()

  self.view.backgroundColor = UIColor.white

  self.navBar.titleLabel.text = "Title"
  self.navBar.leftButton.setTitle("Cancel", for: .normal)
  self.navBar.leftButton.addTarget(self, action: #selector(self.dismissAction), for: .touchUpInside)

  self.view.addSubview(self.navBar)
 }
}

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

Работа c UIScrollView

Если вы используете для контроллера вертикальный UIScrollView, то установите делегат и в методе scrollViewDidScroll вызовите метод:

func scrollViewDidScroll(_ scrollView: UIScrollView) {
 SPStorkController.scrollViewDidScroll(scrollView)
}

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

Delegate

Чтобы отследить события, связанные с SPStorkController, установите делегат:

transitionDelegate.storkDelegate = self

И реализуйте протокол SPStorkControllerDelegate. Доступны методы:

protocol SPStorkControllerDelegate: class {

 optional func didDismissStorkBySwipe()

 optional func didDismissStorkByTap()
}

Методы опциональные и связаны с событиями соответсвенно названиям функций.

Если нужны дополнительные методы или функционал, свяжитесь со мной: hello@ivanvorobei.by