'Using UISheetPresentationController in SwiftUI
I'm really struggling to wrap the new iOS 15 UISheetPresentationController
for use in SwiftUI (for a half-modal). I understand that I should inherit UIViewControllerRepresentable
. Based upon an example I have for a custom ImagePicker, I've not been able to make this work.
Can anyone help? In particular I don't know to get a handle on the presentedViewController
needed to init the UISheetPresentationController
itself:
func makeUIViewController(context: UIViewControllerRepresentableContext<KitSheet>) -> UISheetPresentationController {
let sheet = UISheetPresentationController(presentedViewController: <#T##UIViewController#>, presenting: <#T##UIViewController?#>)
sheet.delegate = context.coordinator
return sheet
}
https://developer.apple.com/documentation/uikit/uisheetpresentationcontroller
Solution 1:[1]
If you want the Image Picker
import SwiftUI
///Sample usage
@available(iOS 15.0, macCatalyst 15.0,*)
struct ImagePickerParentView: View {
@State var isPresented = false
@State var selectedImage: UIImage? = nil
var body: some View {
print("ImagePickerParentView :: \(#function) :: isPresented == \(isPresented)")
return VStack{
if selectedImage != nil{
Image(uiImage: selectedImage!)
.resizable()
.frame(width: 100, height: 100)
}
Button("present image picker", action: {
isPresented.toggle()
}).imagePicker(isPresented: $isPresented, uiImage: $selectedImage, detents: [.medium()], largestUndimmedDetentIdentifier: .large)
}
}
}
@available(iOS 15.0, macCatalyst 15.0,*)
extension View {
func imagePicker(isPresented: Binding<Bool>, uiImage: Binding<UIImage?>, detents : [UISheetPresentationController.Detent] = [.medium(), .large()], largestUndimmedDetentIdentifier: UISheetPresentationController.Detent.Identifier? = .medium, prefersScrollingExpandsWhenScrolledToEdge: Bool = false, prefersEdgeAttachedInCompactHeight: Bool = true, prefersGrabberVisible: Bool = false, preferredCornerRadius: CGFloat? = nil)-> some View {
print("\(#function) :: isPresented == \(isPresented)")
return modifier(ImagePickerViewModifier(isPresented: isPresented, uiImage: uiImage, detents : detents, largestUndimmedDetentIdentifier: largestUndimmedDetentIdentifier, prefersScrollingExpandsWhenScrolledToEdge: prefersScrollingExpandsWhenScrolledToEdge, prefersEdgeAttachedInCompactHeight: prefersEdgeAttachedInCompactHeight, prefersGrabberVisible: prefersGrabberVisible, preferredCornerRadius: preferredCornerRadius))
}
}
@available(iOS 15.0, macCatalyst 15.0,*)
struct ImagePickerViewModifier: ViewModifier {
@Binding var isPresented: Bool
@Binding var uiImage: UIImage?
let detents : [UISheetPresentationController.Detent]
let largestUndimmedDetentIdentifier: UISheetPresentationController.Detent.Identifier?
let prefersScrollingExpandsWhenScrolledToEdge: Bool
let prefersEdgeAttachedInCompactHeight: Bool
let prefersGrabberVisible: Bool
let preferredCornerRadius: CGFloat?
func body(content: Content) -> some View {
print("ImagePickerViewModifier :: \(#function) :: isPresented == \(isPresented)")
return content.overlay(
AdaptiveImagePicker_UI(isPresented: $isPresented, uiImage: $uiImage, detents: detents, largestUndimmedDetentIdentifier: largestUndimmedDetentIdentifier, prefersScrollingExpandsWhenScrolledToEdge: prefersScrollingExpandsWhenScrolledToEdge, prefersEdgeAttachedInCompactHeight: prefersEdgeAttachedInCompactHeight, prefersGrabberVisible: prefersGrabberVisible, preferredCornerRadius: preferredCornerRadius).frame(width: 0, height: 0)
)
.onChange(of: isPresented, perform: { value in
print("AdaptiveSheet :: onChange :: isPresented == \(value)")
})
//}
}
}
@available(iOS 15.0, macCatalyst 15.0,*)
struct AdaptiveImagePicker_UI: UIViewControllerRepresentable {
@Binding var isPresented: Bool
@Binding var uiImage: UIImage?
var detents : [UISheetPresentationController.Detent] = [.medium(), .large()]
var largestUndimmedDetentIdentifier: UISheetPresentationController.Detent.Identifier? = .medium
var prefersScrollingExpandsWhenScrolledToEdge: Bool = false
var prefersEdgeAttachedInCompactHeight: Bool = true
var prefersGrabberVisible: Bool = false
var preferredCornerRadius: CGFloat?
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
func makeUIViewController(context: Context) -> AdaptiveImagePickerViewController {
print("CustomSheet_UI :: \(#function) :: isPresented == \(isPresented)")
let vc = AdaptiveImagePickerViewController(coordinator: context.coordinator, detents : detents, largestUndimmedDetentIdentifier: largestUndimmedDetentIdentifier, prefersScrollingExpandsWhenScrolledToEdge: prefersScrollingExpandsWhenScrolledToEdge, prefersEdgeAttachedInCompactHeight: prefersEdgeAttachedInCompactHeight, prefersGrabberVisible: prefersGrabberVisible, preferredCornerRadius: preferredCornerRadius)
return vc
}
func updateUIViewController(_ uiViewController: AdaptiveImagePickerViewController, context: Context) {
print("CustomSheet_UI :: \(#function) :: isPresented == \(isPresented)")
print("CustomSheet_UI :: \(#function) :: context.coordinator.parent.isPresented == \(context.coordinator.parent.isPresented)")
if isPresented {
uiViewController.presentImagePicker()
}else{
uiViewController.dismissModalView()
}
}
class Coordinator: NSObject, UIAdaptivePresentationControllerDelegate, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
var parent: AdaptiveImagePicker_UI
var isPresented: Bool = false
init(_ parent: AdaptiveImagePicker_UI) {
print("CustomSheet_UI :: \(#function) :: parent.isPresented == \(parent.isPresented)")
self.parent = parent
}
//Adjust the variable when the user dismisses with a swipe
func presentationControllerDidDismiss(_ presentationController: UIPresentationController) {
print("CustomSheet_UI.Coordinator :: \(#function) :: parent.isPresented == \(parent.isPresented)")
if parent.isPresented{
parent.isPresented = false
}
}
//Adjust the variable when the user cancels
func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
if parent.isPresented{
parent.isPresented = false
}
}
//Get access to the selected image
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]) {
if let image = info[.originalImage] as? UIImage {
parent.uiImage = image
parent.isPresented = false
}
}
}
}
@available(iOS 15.0, macCatalyst 15.0,*)
class AdaptiveImagePickerViewController: UIViewController {
var coordinator: AdaptiveImagePicker_UI.Coordinator
let detents : [UISheetPresentationController.Detent]
let largestUndimmedDetentIdentifier: UISheetPresentationController.Detent.Identifier?
let prefersScrollingExpandsWhenScrolledToEdge: Bool
let prefersEdgeAttachedInCompactHeight: Bool
let prefersGrabberVisible: Bool
let preferredCornerRadius: CGFloat?
private var isLandscape: Bool = UIDevice.current.orientation.isLandscape
init(coordinator: AdaptiveImagePicker_UI.Coordinator, detents : [UISheetPresentationController.Detent] = [.medium(), .large()], largestUndimmedDetentIdentifier: UISheetPresentationController.Detent.Identifier? = .medium, prefersScrollingExpandsWhenScrolledToEdge: Bool = false, prefersEdgeAttachedInCompactHeight: Bool = true, prefersGrabberVisible: Bool, preferredCornerRadius: CGFloat?) {
print("AdaptiveImagePickerViewController :: \(#function) :: isPresented == \(coordinator.parent.isPresented)")
self.coordinator = coordinator
self.detents = detents
self.largestUndimmedDetentIdentifier = largestUndimmedDetentIdentifier
self.prefersEdgeAttachedInCompactHeight = prefersEdgeAttachedInCompactHeight
self.prefersScrollingExpandsWhenScrolledToEdge = prefersScrollingExpandsWhenScrolledToEdge
self.prefersGrabberVisible = prefersGrabberVisible
self.preferredCornerRadius = preferredCornerRadius
super.init(nibName: nil, bundle: .main)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func dismissModalView(){
print("AdaptiveImagePickerViewController :: \(#function) :: isPresented == \(coordinator.parent.isPresented)")
dismiss(animated: true, completion: nil)
}
//This is mostly code from the Apple sample
//https://developer.apple.com/documentation/uikit/uiviewcontroller/customize_and_resize_sheets_in_uikit
func presentImagePicker(){
guard presentedViewController == nil else {
dismiss(animated: true, completion: {
self.presentImagePicker()
})
return
}
let imagePicker = UIImagePickerController()
imagePicker.delegate = coordinator
imagePicker.modalPresentationStyle = .popover
//Added the presentation controller delegate to detect if the user swipes to dismiss
imagePicker.presentationController?.delegate = coordinator as UIAdaptivePresentationControllerDelegate
if let hostPopover = imagePicker.popoverPresentationController {
hostPopover.sourceView = super.view
let sheet = hostPopover.adaptiveSheetPresentationController
//As of 13 Beta 4 if .medium() is the only detent in landscape error occurs
sheet.detents = (isLandscape ? [.large()] : detents)
sheet.largestUndimmedDetentIdentifier =
largestUndimmedDetentIdentifier
sheet.prefersScrollingExpandsWhenScrolledToEdge =
prefersScrollingExpandsWhenScrolledToEdge
sheet.prefersEdgeAttachedInCompactHeight =
prefersEdgeAttachedInCompactHeight
sheet.widthFollowsPreferredContentSizeWhenEdgeAttached = true
sheet.prefersGrabberVisible = prefersGrabberVisible
sheet.preferredCornerRadius = preferredCornerRadius
}
present(imagePicker, animated: true, completion: nil)
}
/// To compensate for l orientation
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
if UIDevice.current.orientation.isLandscape {
isLandscape = true
self.presentedViewController?.popoverPresentationController?.adaptiveSheetPresentationController.detents = [.large()]
} else {
isLandscape = false
self.presentedViewController?.popoverPresentationController?.adaptiveSheetPresentationController.detents = detents
}
super.viewWillTransition(to: size, with: coordinator)
}
}
@available(iOS 15.0, macCatalyst 15.0,*)
struct ImagePickerParentView_Previews: PreviewProvider {
static var previews: some View {
ImagePickerParentView()
}
}
If you want one that takes any SwiftUI View
it only needs a few changes.
//This is the sample usage
@available(iOS 15.0, macCatalyst 15.0,*)
struct CustomSheetParentView: View {
@State var isPresented = false
var body: some View {
print("CustomSheetParentView :: \(#function) :: isPresented == \(isPresented)")
return VStack{
Button("present sheet", action: {
isPresented.toggle()
}).adaptiveSheet(isPresented: $isPresented, detents: [.medium()], largestUndimmedDetentIdentifier: .medium, disableSwipeToDismiss: false){
Rectangle()
.frame(maxWidth: .infinity, maxHeight: 100, alignment: .center)
.foregroundColor(.clear)
.border(Color.blue, width: 3)
.overlay(
LazyVStack{
Text("Hello, World!")
Button("dismiss", action: {
print("dismiss button :: isPresented == \(isPresented)")
isPresented = false
})
CustomSheetParentView()
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.onTapGesture {
print("onTap :: isPresented == \(isPresented)")
isPresented.toggle()
}
)
.background(Color(UIColor.systemBackground))
}
}
}
}
@available(iOS 15.0, macCatalyst 15.0,*)
struct CustomSheetView_Previews: PreviewProvider {
static var previews: some View {
CustomSheetParentView()
}
}
//EVERYTHING from here down is Reusable and can be pasted into a project and then use `.adaptiveSheet` just like `.sheet`
@available(iOS 15.0, macCatalyst 15.0,*)
extension View {
func adaptiveSheet<T: View>(isPresented: Binding<Bool>, detents : [UISheetPresentationController.Detent] = [.medium(), .large()], largestUndimmedDetentIdentifier: UISheetPresentationController.Detent.Identifier? = .medium, prefersScrollingExpandsWhenScrolledToEdge: Bool = false, prefersEdgeAttachedInCompactHeight: Bool = true, prefersGrabberVisible: Bool = false, disableSwipeToDismiss: Bool = false, preferredCornerRadius: CGFloat? = nil, @ViewBuilder content: @escaping () -> T)-> some View {
print("\(#function) :: isPresented == \(isPresented)")
return modifier(AdaptiveSheet<T>(isPresented: isPresented, detents : detents, largestUndimmedDetentIdentifier: largestUndimmedDetentIdentifier, prefersScrollingExpandsWhenScrolledToEdge: prefersScrollingExpandsWhenScrolledToEdge, prefersEdgeAttachedInCompactHeight: prefersEdgeAttachedInCompactHeight, prefersGrabberVisible: prefersGrabberVisible, disableSwipeToDismiss: disableSwipeToDismiss, preferredCornerRadius: preferredCornerRadius, sheetContent: content))
}
}
@available(iOS 15.0, macCatalyst 15.0,*)
struct AdaptiveSheet<T: View>: ViewModifier {
@Binding var isPresented: Bool
let detents : [UISheetPresentationController.Detent]
let largestUndimmedDetentIdentifier: UISheetPresentationController.Detent.Identifier?
let prefersScrollingExpandsWhenScrolledToEdge: Bool
let prefersEdgeAttachedInCompactHeight: Bool
let prefersGrabberVisible: Bool
let disableSwipeToDismiss: Bool
let preferredCornerRadius: CGFloat?
@ViewBuilder let sheetContent: T
func body(content: Content) -> some View {
print("AdaptiveSheet :: \(#function) :: isPresented == \(isPresented)")
return content.overlay(
CustomSheet_UI(isPresented: $isPresented, detents: detents, largestUndimmedDetentIdentifier: largestUndimmedDetentIdentifier, prefersScrollingExpandsWhenScrolledToEdge: prefersScrollingExpandsWhenScrolledToEdge, prefersEdgeAttachedInCompactHeight: prefersEdgeAttachedInCompactHeight, prefersGrabberVisible: prefersGrabberVisible, disableSwipeToDismiss: disableSwipeToDismiss,preferredCornerRadius: preferredCornerRadius, content: {sheetContent}).frame(width: 0, height: 0)
)
.onChange(of: isPresented, perform: { value in
print("AdaptiveSheet :: onChange :: isPresented == \(value)")
})
//}
}
}
@available(iOS 15.0, macCatalyst 15.0,*)
struct CustomSheet_UI<T: View>: UIViewControllerRepresentable {
@Binding var isPresented: Bool
var detents : [UISheetPresentationController.Detent] = [.medium(), .large()]
var largestUndimmedDetentIdentifier: UISheetPresentationController.Detent.Identifier? = .medium
var prefersScrollingExpandsWhenScrolledToEdge: Bool = false
var prefersEdgeAttachedInCompactHeight: Bool = true
var prefersGrabberVisible: Bool = false
var disableSwipeToDismiss: Bool = false
var preferredCornerRadius: CGFloat?
@ViewBuilder let content: T
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
func makeUIViewController(context: Context) -> CustomSheetViewController<T> {
print("CustomSheet_UI :: \(#function) :: isPresented == \(isPresented)")
let vc = CustomSheetViewController(coordinator: context.coordinator, detents : detents, largestUndimmedDetentIdentifier: largestUndimmedDetentIdentifier, prefersScrollingExpandsWhenScrolledToEdge: prefersScrollingExpandsWhenScrolledToEdge, prefersEdgeAttachedInCompactHeight: prefersEdgeAttachedInCompactHeight, prefersGrabberVisible: prefersGrabberVisible, disableSwipeToDismiss: disableSwipeToDismiss, preferredCornerRadius: preferredCornerRadius, content: {content})
return vc
}
func updateUIViewController(_ uiViewController: CustomSheetViewController<T>, context: Context) {
print("CustomSheet_UI :: \(#function) :: isPresented == \(isPresented)")
print("CustomSheet_UI :: \(#function) :: context.coordinator.parent.isPresented == \(context.coordinator.parent.isPresented)")
if isPresented {
uiViewController.presentModalView()
}else{
uiViewController.dismissModalView()
}
}
class Coordinator: NSObject, UIAdaptivePresentationControllerDelegate {
var parent: CustomSheet_UI
var isPresented: Bool = false
init(_ parent: CustomSheet_UI) {
print("CustomSheet_UI :: \(#function) :: parent.isPresented == \(parent.isPresented)")
self.parent = parent
}
//Adjust the variable when the user dismisses with a swipe
func presentationControllerDidDismiss(_ presentationController: UIPresentationController) {
print("CustomSheet_UI.Coordinator :: \(#function) :: parent.isPresented == \(parent.isPresented)")
if parent.isPresented{
parent.isPresented = false
}
}
}
}
@available(iOS 15.0, macCatalyst 15.0,*)
class CustomSheetViewController<Content: View>: UIViewController {
let content: Content
var coordinator: CustomSheet_UI<Content>.Coordinator
let detents : [UISheetPresentationController.Detent]
let largestUndimmedDetentIdentifier: UISheetPresentationController.Detent.Identifier?
let prefersScrollingExpandsWhenScrolledToEdge: Bool
let prefersEdgeAttachedInCompactHeight: Bool
let prefersGrabberVisible: Bool
let disableSwipeToDismiss: Bool
let preferredCornerRadius: CGFloat?
private var isLandscape: Bool = UIDevice.current.orientation.isLandscape
init(coordinator: CustomSheet_UI<Content>.Coordinator, detents : [UISheetPresentationController.Detent] = [.medium(), .large()], largestUndimmedDetentIdentifier: UISheetPresentationController.Detent.Identifier? = .medium, prefersScrollingExpandsWhenScrolledToEdge: Bool = false, prefersEdgeAttachedInCompactHeight: Bool = true, prefersGrabberVisible: Bool, disableSwipeToDismiss: Bool, preferredCornerRadius: CGFloat?, @ViewBuilder content: @escaping () -> Content) {
print("CustomSheetViewController :: \(#function) :: isPresented == \(coordinator.parent.isPresented)")
self.content = content()
self.coordinator = coordinator
self.detents = detents
self.largestUndimmedDetentIdentifier = largestUndimmedDetentIdentifier
self.prefersEdgeAttachedInCompactHeight = prefersEdgeAttachedInCompactHeight
self.prefersScrollingExpandsWhenScrolledToEdge = prefersScrollingExpandsWhenScrolledToEdge
self.prefersGrabberVisible = prefersGrabberVisible
self.disableSwipeToDismiss = disableSwipeToDismiss
self.preferredCornerRadius = preferredCornerRadius
super.init(nibName: nil, bundle: .main)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func dismissModalView(){
print("CustomSheetViewController :: \(#function) :: isPresented == \(coordinator.parent.isPresented)")
dismiss(animated: true, completion: nil)
}
func presentModalView(){
print("CustomSheetViewController :: \(#function) :: isPresented == \(coordinator.parent.isPresented)")
let hostingController = UIHostingController(rootView: content)
//allows background color to be decided by SwiftUI content.
// Incase you want to use a Material that gives transparency
hostingController.view.backgroundColor = nil
hostingController.modalPresentationStyle = .popover
hostingController.presentationController?.delegate = coordinator as UIAdaptivePresentationControllerDelegate
hostingController.modalTransitionStyle = .coverVertical
hostingController.isModalInPresentation = disableSwipeToDismiss
if let hostPopover = hostingController.popoverPresentationController {
hostPopover.sourceView = super.view
let sheet = hostPopover.adaptiveSheetPresentationController
//As of 13 Beta 4 if .medium() is the only detent in landscape error occurs
sheet.detents = (isLandscape ? [.large()] : detents)
sheet.largestUndimmedDetentIdentifier =
largestUndimmedDetentIdentifier
sheet.prefersScrollingExpandsWhenScrolledToEdge =
prefersScrollingExpandsWhenScrolledToEdge
sheet.prefersEdgeAttachedInCompactHeight =
prefersEdgeAttachedInCompactHeight
sheet.widthFollowsPreferredContentSizeWhenEdgeAttached = true
sheet.prefersGrabberVisible = prefersGrabberVisible
sheet.preferredCornerRadius = preferredCornerRadius
}
if presentedViewController == nil{
present(hostingController, animated: true, completion: nil)
}
}
/// To compensate for l orientation
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
if UIDevice.current.orientation.isLandscape {
isLandscape = true
self.presentedViewController?.popoverPresentationController?.adaptiveSheetPresentationController.detents = [.large()]
} else {
isLandscape = false
self.presentedViewController?.popoverPresentationController?.adaptiveSheetPresentationController.detents = detents
}
super.viewWillTransition(to: size, with: coordinator)
}
}
Solution 2:[2]
Found the options given here a wee bit complex, so here is an alternative on 3 steps:
1
Subclass UIHostingController
and personalise
class HalfSheetController<Content>: UIHostingController<Content> where Content : View {
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if let presentation = sheetPresentationController {
// configure at will
presentation.detents = [.medium()]
}
}
}
2
Create an UIViewControllerRepresentable
using your UIHostingController
, we are using a ViewBuilder here for maximum flexibility.
struct HalfSheet<Content>: UIViewControllerRepresentable where Content : View {
private let content: Content
@inlinable init(@ViewBuilder content: () -> Content) {
self.content = content()
}
func makeUIViewController(context: Context) -> HalfSheetController<Content> {
return HalfSheetController(rootView: content)
}
func updateUIViewController(_: HalfSheetController<Content>, context: Context) {
}
}
3
Present as sheet on your SwiftUI View
struct Example: View {
@State private var present = false
var body: some View {
Button("Present") {
present = true
}
.sheet(isPresented: $present) {
HalfSheet {
Text("Hello, World!")
}
}
}
}
Solution 3:[3]
The way this API seems to work is to use a regular UIViewController
and in viewDidLoad
you can grab the UISheetPresentationController
and configure it. By default all iOS 13+ modals are sheets automatically.
class SheetContentViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
if let sheetPresentationController = presentationController as? UISheetPresentationController {
sheetPresentationController.detents = [.medium(), .large()]
sheetPresentationController.prefersGrabberVisible = true
}
}
What I am currently doing is using a UIHostingController that acts as the sheet.
Create a custom hosting controller class.
import UIKit
import SwiftUI
@available(iOS 15.0, *)
final class SheetHostingController<T: View>: UIHostingController<T>, UISheetPresentationControllerDelegate {
// MARK: - Properties
private let detents: [UISheetPresentationController.Detent]
private let prefersEdgeAttachedInCompactHeight: Bool
private let prefersScrollingExpandsWhenScrolledToEdge: Bool
// MARK: - Initialization
init(
rootView: T,
title: String? = nil,
largeTitleDisplayMode: UINavigationItem.LargeTitleDisplayMode = .never,
detents: [UISheetPresentationController.Detent] = [.medium(), .large()],
prefersEdgeAttachedInCompactHeight: Bool = true,
prefersScrollingExpandsWhenScrolledToEdge: Bool = true
) {
self.detents = detents
self.prefersEdgeAttachedInCompactHeight = prefersEdgeAttachedInCompactHeight
self.prefersScrollingExpandsWhenScrolledToEdge = prefersScrollingExpandsWhenScrolledToEdge
super.init(rootView: rootView)
navigationItem.title = title
navigationItem.largeTitleDisplayMode = largeTitleDisplayMode
}
@objc required dynamic init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// MARK: - Life Cycle
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .green
if let sheetPresentationController = presentationController as? UISheetPresentationController {
sheetPresentationController.delegate = self
sheetPresentationController.detents = detents
sheetPresentationController.prefersGrabberVisible = true
sheetPresentationController.prefersEdgeAttachedInCompactHeight = prefersEdgeAttachedInCompactHeight
sheetPresentationController.prefersScrollingExpandsWhenScrolledToEdge = prefersScrollingExpandsWhenScrolledToEdge
}
}
// MARK: - Public Methods
func set(to detentIdentifier: UISheetPresentationController.Detent.Identifier?) {
guard let sheetPresentationController = presentationController as? UISheetPresentationController else { return }
sheetPresentationController.animateChanges {
sheetPresentationController.selectedDetentIdentifier = detentIdentifier
}
}
// MARK: - UISheetPresentationControllerDelegate
func sheetPresentationControllerDidChangeSelectedDetentIdentifier(_ sheetPresentationController: UISheetPresentationController) {
// Currently not working?
}
}
and than you can present it in your app flow
let swiftUIView = SomeSwiftUIView()
let sheetHostingController = SheetHostingController(rootView: swiftUIView)
someViewController.present(sheetHostingController, animated: true)
Currently the delegate is not firing for me to detect if the sheet was changed non-programatically e.g. drag gesture. Not sure this is an early beta bug. Also a bit of a shame they have not added a small detent setting, made the sheet non dismissible and the view behind interactive like in maps.
Solution 4:[4]
Nov, 11
Lorem Ipsum now provides the solution that work on iPad as popover
iPad Result
Nov, 10
From Lorem Ipsum Answer is work great on iPhone but not currently for iPad.
List
- Background Color is clear
- Content Size is wrong (not fit with the content)
iPad Result
Workaround
- Allow
UISheetPresentationController
for iPhone - Use
popover(isPresented:attachmentAnchor:arrowEdge:content:)
for iPad
guard UIDevice.current.userInterfaceIdiom == .phone else {
viewModel.isPresentedPopOver
return
}
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | |
Solution 2 | |
Solution 3 | |
Solution 4 |