Тема данной статьи — представление нижнего всплывающего экрана (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