´UISheetPresentationController´ как в приложении Карты

´UISheetPresentationController´ как в приложении Карты

В iOS 15 появились sheet-контроллеры. Их можно перетаскивать с изменением высоты. Вы встречали эти контроллеры в приложениях «Карты» и «Акции».


Имя Автора
Автор Иван Воробей iOS разработчик. Пишу библиотеки, веду телеграм-канал.

Содержание


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



Выглядит круто, кейсов много. Чтобы показать дефолтный sheet-controller, используйте код:

let controller = UIViewController()
if let sheetController = controller.sheetPresentationController {
    sheetController.detents = [.medium(), .large()]
}
present(controller, animated: true)

Это обычный модальный контроллер, которому добавили сложное поведение. Можно оборачивать в навигационный контроллер, добавлять заголовок и бар-кнопки. Если проект поддерживает предыдущие версии iOS, оберните код с sheetController в if #available(iOS 15.0, *) {}.


Что такое Detents (стопоры)

Стопор — высота, к которой стремится контроллер. Похоже на ситуации с пейджингом скролла или когда электрон не на своём энергетическом уровне.

Доступно два стопора: .medium() с размером на половину экрана и .large(), который повторяет большой модальный контроллер. Если оставить только .medium(), то контроллер откроется на половину экрана и подниматься выше не будет. Установить свою высоту в пикселях нельзя, выбираем только из доступных стопоров. По умолчанию контроллер показывается со стопором .large().

Доступные стопоры указываются так:

sheetController.detents = [.medium(), .large()]

Если укажите только один стопор, то переключиться жестом не получится.

Как переключаться между стопорами

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

sheetController.animateChanges {
    sheetController.selectedDetentIdentifier = .medium
}

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

sheetController.animateChanges {
    sheetController.detents = [.large()]
}

Контроллер переключиться в .large() стопор и не даст переключится жестом в .medium().


Dismiss

Если вы хотите зафиксировать контроллер в одном стопоре без возможности закрыть его, установите isModalInPresentation в true родителю:

navigationController.isModalInPresentation = true
if let sheetController = nav.sheetPresentationController {
    sheetController.detents = [.medium()]
    sheetController.largestUndimmedDetentIdentifier = .medium
}



Scroll контента

Если активен .medium()-стопор и контент контроллера скролится, то при скролле вверх модальный контроллер перейдёт в .large()-стопор, а контент останется на месте.



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

sheetController.prefersScrollingExpandsWhenScrolledToEdge = false


Теперь при скроле вверх будет отрабатываться скрол контента. Чтобы перейти в большой стопор, потяните за navigation-бар.


Альбомная ориентация

По умолчанию sheet-контроллер в альбомной ориентации выглядит как обычный контроллер. Дело в том, что .medium()-стопор недоступен, а .large() — дефолтный режим модального контроллера. Но можно добавить отступы по краям.

sheetController.prefersEdgeAttachedInCompactHeight = true

Вот как это выглядит:

Sheet-контроллер в альбомной ориентации с отступами по краям.

Чтобы контроллер учитывал prefered-размер, установите widthFollowsPreferredContentSizeWhenEdgeAttached в true.


Как затемнять фон

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

sheetController.largestUndimmedDetentIdentifier = .medium


Указано, что .medium затемняться не будет, а всё, что больше, будет. Можно убрать затемнение и для самого большого стопора.


Как добавить индикатор

Чтобы добавить индикатор вверху контроллера, установите .prefersGrabberVisible в true. По умолчанию индикатор спрятан. Индикатор не влияет на safe area и layout margins.

Grabber-индикатора на sheet-контроллере.

Corner Radius

Можно управлять закруглением краёв у контроллера. Установите значение для .preferredCornerRadius. Закругление меняется не только у презентуемого контроллера, но и у родителя.

Corner-радиус у sheet-контроллера.

На скриншоте я установил corner-радиус в 22. Радиус сохраняется и для .medium-стопора.

На этом всё. Напишите в комментариях к посту, будете ли использовать в своих проектах sheet-контроллеры.


Другие туториалы

Как очистить UserDefaults для Mac Catalyst

Как очистить данные для приложения Catalyst включая AppGroup, Realm и UserDefaults.

Индикатор прогресса с ´ProgressView´ в SwiftUI

Как устроен ProgressView. Как настроить внешний вид: спиннер и прогресс-бар.

Действия на сочетания клавиш в SwiftUI

Знакомимся с модификатором keyboardShortcut. Добавим модификаторы для клавиш .command, .option, .shift

Альтернативные иконки для тестов Product Page Optimization

Как добавить альтернативные иконки для A/B тестов на странице приложения.

В telegram-канале приходят уведомления о новых туториалах. В чате для iOS разработчиков ответят на вопросы.

Открыть Telegram-канал