【问题标题】:Using UISheetPresentationController in SwiftUIiOS 15:在 SwiftUI 中使用 UISheetPresentationController
【发布时间】:2021-06-15 08:27:46
【问题描述】:

我真的很难包装新的 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

【问题讨论】:

  • 我在此处将其添加到原始答案的底部。如果有效,您介意接受答案并投票吗?
  • 对于搜索此内容的其他人:如果您只需要一个带有大定位器的工作表,则不需要使用 UISheetPresentationController。在这种情况下,在 View 上使用 .sheet() 会简单得多。如果您还需要中度定位,则此 UIKit 方法很有用。

标签: ios swiftui uikit


【解决方案1】:

如果你想要图像选择器

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

【讨论】:

  • 如何在工作表打开时在后面的视图中添加背景颜色?,我尝试将其添加到 SwiftUI 视图中,但关闭后延迟
【解决方案2】:

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

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

我目前正在做的是使用 UIHostingController 作为工作表。

创建一个自定义托管控制器类。

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)

目前,委托并未让我检测工作表是否以非编程方式更改,例如拖动手势。不确定这是早期的 beta 错误。也有点遗憾的是,他们没有添加一个小的定位设置,使工作表不可关闭,并且像地图中那样交互背后的视图。

【讨论】:

  • 我制作了一个通用的,它将任何 SwiftUI 视图作为this question 的参数,它按预期工作
  • @loremipsum 你的例子在那个链接中非常适合我。
【解决方案3】:

发现这里给出的选项有点复杂,所以这里有 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 上显示为工作表 View

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

【讨论】:

  • 我尝试过这样做,但如果您添加presentation.largestUndimmedDetentIdentifier = .medium 以保持背面不暗淡,则后面的View 在这种情况下按钮永远不会恢复正常。有些东西没有被正确地解雇。
【解决方案4】:

11 月 11 日

Lorem Ipsum 现在提供在 iPad 上工作作为弹出框

的解决方案

iPad 结果

11 月 10 日

来自 Lorem Ipsum 的回答是在 iPhone 上很好用 但目前不适用于 iPad

列表

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

iPad 结果

解决方法

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

【讨论】:

  • 我添加了一个 ViewModifer 来解决这个问题。只需将其附加到显示工作表的视图即可。
猜你喜欢
  • 2021-12-21
  • 2021-10-27
  • 2021-11-05
  • 1970-01-01
  • 1970-01-01
  • 2021-12-15
  • 2021-09-29
  • 2021-11-18
  • 1970-01-01
相关资源
最近更新 更多