UIKit - UISheetPresentationController 사용해보기.
UISheetPresentationController
UISheetPresentationController | Apple Developer Documentation
A presentation controller that manages the appearance and behavior of a sheet.
developer.apple.com
이번엔 'UISheetPresentationController' 에 대해서 알아보겠습니다. UISheetPresentationController는 sheet의 모양과 동작을 관리하는presentation controller 입니다.
@MainActor
class UISheetPresentationController : UIPresentationController
UIPresentViewController를 상속받고 있습니다.
기존 모달
UIKit에서 다른 View를 Modal 형식으로 띄울 때 2가지 방법으로 띄웠을 것입니다.
1. 스토리보드
- 뷰 컨트롤러에서 다른 뷰를 연결하여 modalPresentationStyle 에서 'Present Modally' 같은 옵션 선택
2. 코드
let modalViewController = ModalViewController()
self.present(modalViewController, animated: true)
이번에는 기본 모달말고 sheet의 모양과 동작을 조금 더 자세하게 관리할 수 있는 'UISheetPresentationController'에 대해서 알아보겠습니다.
아쉽게도 UISheetPresentationController는 iOS15 버전 이후부터 사용이 가능합니다. 이전 버전 대응에서 비슷한 모양이나 동작을 구현하려면 직접 구현해줘야하겠네요 ㅠ
UISheetPresentationController 모달
원래 하던 방식인 다른 ViewController에 버튼에 Present Modally 방식으로 연결해줬습니다.
이후 연결된 ViewController 파일에 다음과 같은 코드를 작성해보겠습니다.
애플 도큐먼트에 나와있는 기본 예시를 그대로 가져다 사용했습니다.
import UIKit
class UIPresentViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
@IBAction func buttonTapped(_ sender: UIButton) {
let vc = UIViewController()
vc.sheetPresentationController?.delegate = self
showMyViewControllerInACustomizedSheet()
}
}
extension UIPresentViewController: UISheetPresentationControllerDelegate {
func showMyViewControllerInACustomizedSheet() {
let viewControllerToPresent = UIPresentViewController()
if let sheet = viewControllerToPresent.sheetPresentationController {
sheet.detents = [.medium(), .large()]
sheet.largestUndimmedDetentIdentifier = .medium
sheet.prefersScrollingExpandsWhenScrolledToEdge = false
sheet.prefersEdgeAttachedInCompactHeight = true
sheet.widthFollowsPreferredContentSizeWhenEdgeAttached = true
}
present(viewControllerToPresent, animated: true, completion: nil)
}
}
버튼을 클릭하면 'UIPresentViewController'가 실행됩니다.
모달을 띄우면 기존의 모달과는 다른 모달이 나옵니다.
투명한 뷰의 모달이 절반만 뜨고 드래그로 fullscreen으로 채울수도 있습니다.. fullscreen으로 채우게 되면 약간 전체 뷰가 작아지는 애니메이션도 들어가는 것을 볼 수 있습니다..
UISheetPresentController Attributes
UISheetPresentController의 속성들을 한번 살펴보겠습니다.
다음 속성들을 잘 적용하면 half sheet -> full sheet로 나타낼 수도 있고, 다양한 sheet 모양을 나타낼 수 있습니다.
- detents
- largestUndimmedDetentIdentifier
- prefersScrollingExpandsWhenScrolledToEdge
- prefersEdgeAttachedInCompactHeight
- widthFollowsPreferredContentSizeWhenEdgeAttached
detents
sheet의 높이를 배열 형식으로 나타냅니다.
- .medium()
- .large()
medium 속성만 배열에 담으면 sheet가 full modal로 전환할 수 없고 half sheet로만 사용해야합니다. sheet를 드래그해도 움직이지 않는 것을 확인할 수 있습니다.
.large()또 배열에 추가하면 기존의 sheet처럼 1단계에서 half sheet 2단계로 full sheet까지 올릴 수 있습니다.
largestUndimmedDetentIdentifier
dim이 되지 않는 가장 큰 detent 식별자? 라는 의미인데 해석정도만 하고 넘어가도록 하겠습니다.
- .none
- .large
- .medium
우선 위 3가지 속성에 대해 알아보겠습니다. 직접 테스트를 통해 어떤 특성이 있는지 알아봤습니다.
처음으로 .none 설정을 하고 detent에서 medium과 large를 조합해 살펴보았습니다.
테스트해보고 싶은 경우의 수가 좀 많습니다. 그래서 대표적인 변화가 있는 속성부터 알아보겠습니다.
1. .none
sheet.largestUndimmedDetentIdentifier = .none
- detents 배열에 medium, large 둘 다 추가되었을 경우
sheet.detents = [.medium(), .large()]
아래에서 sheet가 올라오는 것이 아닌 처음부터 전체가 sheet로 덮히게 작동하는 것을 볼 수 있습니다.
- detents 배열에 large만 있는 경우
sheet.detents = [.large()]
large만 있는 경우는 둘 다 있는 경우와는 다르게 약간 전체뷰가 줄어드는 애니메이션과 함께 view 전체 sheet로 덮히게 동작합니다.
- detents 배열에 medium만 있는 경우
sheet.detents = [.medium()]
이 경우에는 medium, large 둘 다 있는 경우와 같게 작동하는 것을 확인할 수 있었습니다.
정리하자면 detents의 medium이면 half sheet에서 제한이 걸리는 것 같고 large라면 half sheet에서 full sheet까지 사용할 수 있게 작동하는 것 같습니다.
UISheetPresentationController 는 좀 더 적용해보고 내용을 추가하도록 하겠습니다. 👋
+ 추가
Custom Size Sheet
iOS16.0+ 부터 SheetPresetationController의 Height를 Custom하여서 사용할 수 있습니다. medium, large 2가지 option밖에 없었는데. 조금 자유롭게 bottomSheet를 구현해볼 수 있게 되었습니다.
extension UIViewController {
func makeCustomSheetPresentationController(sheetVC: UIViewController) {
if #available(iOS 16.0, *) {
let _ = UISheetPresentationController.Detent.Identifier("customDetent")
let customDetent = UISheetPresentationController.Detent.custom(identifier: .init("customDetent")) { _ in
return UIScreen.main.bounds.width * 0.45
}
if let sheet = sheetVC.sheetPresentationController {
sheet.prefersGrabberVisible = true
sheet.detents = [customDetent, .medium()]
}
} else {
if let sheet = sheetVC.sheetPresentationController {
// sheet.prefersGrabberVisible = true
sheet.detents = [.medium()]
}
}
self.present(sheetVC, animated: true)
}
}