Тема данной статьи — представление нижнего всплывающего экрана (bottom sheet) с помощью API UISheetPresentationController, предусмотренного в iOS 15. С исходным кодом проекта можно ознакомиться в завершающем разделе руководства. Изучив материал вы узнаете:
- как представить любой
UIViewControllerв виде нижнего всплывающего экрана; - как задать его размеры;
- как настроить макет и поведение такого экрана;
- что следует учитывать, принимая решение об использовании
UISheetPresentationController.
Ниже представлен ожидаемый результат:

Начальный этап
Начнем с UIViewController, который отображает кнопку в центре основного экрана:
import UIKit
class ViewController: UIViewController {
@IBOutlet weak var button: UIButton!
@IBAction func buttonHandler(_ sender: UIButton) {
}
override func viewDidLoad() {
super.viewDidLoad()
}
}
Внутри buttonHandler реализуем нижний всплывающий компонент и отобразим его на основном экране.
Но прежде создадим другой UIViewController, который будет функционировать как требуемый компонент:
import UIKit
class BottomSheetViewController: UIViewController {
override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
super.init(nibName: nil, bundle: nil)
// 1
self.modalPresentationStyle = .pageSheet
// 2
self.isModalInPresentation = false
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
// 3
self.view.backgroundColor = .systemOrange
}
}
- Определяем
modalPresentationStyleкак.pageSheet, таким образом сообщая системе о намерении использовать данный контроллер представления в виде экрана. - Устанавливая для
isModalInPresentationзначениеfalse, позволяем пользователю интерактивно закрывать экран, что не представляется возможным при установке значенияtrue. - Для простоты демонстрируем только лишь оранжевое представление.
Отображаем нижний всплывающий экран в buttonHandler:
import UIKit
class ViewController: UIViewController {
...
@IBAction func buttonHandler(_ sender: UIButton) {
// 1
let vc = BottomSheetViewController()
// 2
if let sheet = vc.sheetPresentationController {
// 3
sheet.detents = [.medium(), .large()]
// 4
sheet.largestUndimmedDetentIdentifier = .medium
// 5
sheet.prefersScrollingExpandsWhenScrolledToEdge = true
// 6
sheet.prefersGrabberVisible = true
}
// 7
self.present(vc, animated: true, completion: nil)
}
...
}
- Прежде всего инициализируем
BottomSheetViewController. - Получаем встроенное свойство
sheetPresentationController. Оно устанавливается для контроллера представления, когдаmodalPresentationStyleявляется.pageSheetили.formSheet. Ранее свойствуBottomSheetViewControllerбыл задан стиль.pageSheet, что подтверждает наличиеsheetPresentationController. - Свойство
detentsпредоставляет конфигурации размеров для нижнего всплывающего экрана. В настоящее время Apple располагает только.medium()и.large(). Поскольку API для создания фиксаторов размеров (detents) разработчикам недоступно, то нет возможности установить пользовательское значение для высоты экрана. Возможно, со временем в API произойдут изменения и в нем появятся такие фиксаторы, как.small()или.custom(size: CGSize). - Свойство
largestUndimmedDetentIdentifierопределяет, когда представление за нижним всплывающим экраном должно затемняться. Устанавливая для него значение.medium, мы инструктируем систему затемнить фон, только когда экран принимает размер.large(). - Свойство
prefersScrollingExpandsWhenScrolledToEdgeобслуживает те ситуации, когда в нижнем всплывающем экране имеетсяUIScrollView. Если данное свойствоtrue, то при максимальном развертывании экрана пользователь может просматривать его содержимое. В противном случае мы утрачиваем возможность прокручиватьUIScrollViewвнутри нижнего всплывающего экрана. Ниже представлен пример работы при значенииtrue:

А вот, что происходит в случае с false:

6. prefersGrabberVisible устанавливается в значение true для показа элемента захвата в верхней части экрана:

7. На завершающей стадии отображаем нижний всплывающий экран с помощью стандартного метода present().
Помимо ранее рассмотренных также применяется свойство радиуса углов preferredCornerRadius. При его равенстве 0 получаем следующий результат:

Изменение размера программным способом
Можно изменить текущее значение Detent и осуществить анимацию этих изменений программным способом:
@IBAction func buttonHandler(_ sender: UIButton) {
let vc = BottomSheetViewController()
if let sheet = vc.sheetPresentationController {
...
// 1
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
// 2
sheet.animateChanges {
sheet.selectedDetentIdentifier = .large
}
}
}
self.present(vc, animated: true, completion: nil)
}
- Через две секунды после представления экрана запускаем данный блок кода.
- С помощью замыкания
animateChangesинструктируем систему выполнить анимацию изменений в свойствах экрана. В данном примере кода дается указание изменить фиксатор высоты наlargeи представить все в виде анимации.
Подведем итоги и перечислим несколько моментов, которые необходимо учитывать, принимая решения о реализации нижнего всплывающего экрана своими силами или при помощи UISheetPresentationController:
- API
UISheetPresentationControllerподдерживает только iOS 15 и более новые версии. - В настоящее время возможности API ограничены, поскольку он предоставляет только конфигурации размеров
.medium()и.large(). - Допустим, нужно устранить такой параллакс-эффект:

Для этого потребуется действовать вопреки инструкциям API, а именно предоставить detents в порядке убывания: sheet.detents = [.large(), .medium()].
Этот шаг приведет к нужному результату:

Однако данное решение отдает “запашком”, поскольку в момент представления экрана он сразу же отображается в размере .large(). Более того, в документации Apple настоятельно рекомендуется использовать порядок возрастания:

Дополнительные ресурсы
Данный проект доступен на GitHub. Надеемся, материал был для вас полезен. Благодарим за внимание!
Читайте также:
- Как я создал свою первую видеоигру
- Как сделать кастомные шорткаты для Siri
- Подписки, чеки и StoreKit в iOS 14
Читайте нас в Telegram, VK и Дзен
Перевод статьи Zafar Ivaev: How to Present Customizable Bottom Sheets in iOS 15





