在 SwiftUI 中使用 UISheetPresentationController

Using UISheetPresentationController in SwiftUI

我真的很努力包装新的 iOS 15 UISheetPresentationController 以便在 SwiftUI 中使用(对于半模态)。我明白我应该继承UIViewControllerRepresentable。根据我的自定义 ImagePicker 示例,我无法完成这项工作。

有人可以帮忙吗?特别是我不知道如何处理初始化 UISheetPresentationController 本身所需的 presentedViewController

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

如果你想要图像选择器

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()
    }
}

如果您想要一个采用任何 SwiftUI 的软件 View,只需进行一些更改。

//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)
        
    }
}

API 的工作方式似乎是使用常规 UIViewController,在 viewDidLoad 中,您可以获取 UISheetPresentationController 并进行配置。默认情况下,所有 iOS 13+ 模态都是自动 sheets。

class SheetContentViewController: UIViewController {
     override func viewDidLoad() {
         super.viewDidLoad()
         if let sheetPresentationController = presentationController as? UISheetPresentationController {
            sheetPresentationController.detents = [.medium(), .large()]
            sheetPresentationController.prefersGrabberVisible = true
     }
}

我目前正在做的是使用一个 UIHostingController 作为 sheet.

创建自定义托管控制器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?
    }
}

然后您可以在您的应用流程中展示它

let swiftUIView = SomeSwiftUIView()
let sheetHostingController = SheetHostingController(rootView: swiftUIView)
someViewController.present(sheetHostingController, animated: true)

目前,代表没有触发我来检测 sheet 是否以非编程方式更改,例如拖动手势。不确定这是一个早期的测试版错误。同样令人遗憾的是,他们没有添加一个小的制动设置,使 sheet 不可忽略,并且后面的视图像地图中那样具有交互性。

发现这里给出的选项有点复杂,所以这里有 3 个步骤的替代方案:

1

子类 UIHostingController 和个性化

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

使用您的 UIHostingController 创建一个 UIViewControllerRepresentable,我们在这里使用 ViewBuilder 以获得最大的灵活性。

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

在您的 SwiftUI 上显示为 sheet View

struct Example: View {
    @State private var present = false
    
    var body: some View {
        Button("Present") {
            present = true
        }
        .sheet(isPresented: $present) {
            HalfSheet {
                Text("Hello, World!")
            }
        }
    }
}

11 月 11 日

Lorem Ipsum 现在提供 在 iPad 上工作的解决方案作为 popover

iPad 结果

10 月 10 日

来自 Lorem Ipsum 答案是 在 iPhone 上工作很好,但目前不适用于 iPad

列表

  • 背景颜色清晰
  • 内容大小错误(与内容不符)

iPad 结果

解决方法

guard UIDevice.current.userInterfaceIdiom == .phone else { 
    viewModel.isPresentedPopOver 
    return
}