iPad 上具有自定义大小的 SwiftUI sheet() 模态

Joh*_*erg 8 ipad swift swiftui

如何使用 SwiftUI 在 iPad 上控制模态表的首选演示大小?我很惊讶在谷歌上找到答案是多么困难。

此外,通过向下拖动(取消)或实际执行自定义积极操作来了解模态是否被解除的最佳方法是什么?

ccw*_*den 10

以下是我在 iPad 上使用 SwiftUI 显示表单的解决方案:

struct MyView: View {
    @State var show = false

    var body: some View {
        Button("Open Sheet") { self.show = true }
            .formSheet(isPresented: $show) {
                Text("Form Sheet Content")
            }
    }
}
Run Code Online (Sandbox Code Playgroud)

通过此 UIViewControllerRepresentable 启用

class FormSheetWrapper<Content: View>: UIViewController, UIPopoverPresentationControllerDelegate {

    var content: () -> Content
    var onDismiss: (() -> Void)?

    private var hostVC: UIHostingController<Content>?

    required init?(coder: NSCoder) { fatalError("") }

    init(content: @escaping () -> Content) {
        self.content = content
        super.init(nibName: nil, bundle: nil)
    }

    func show() {
        guard hostVC == nil else { return }
        let vc = UIHostingController(rootView: content())

        vc.view.sizeToFit()
        vc.preferredContentSize = vc.view.bounds.size

        vc.modalPresentationStyle = .formSheet
        vc.presentationController?.delegate = self
        hostVC = vc
        self.present(vc, animated: true, completion: nil)
    }

    func hide() {
        guard let vc = self.hostVC, !vc.isBeingDismissed else { return }
        dismiss(animated: true, completion: nil)
        hostVC = nil
    }

    func presentationControllerWillDismiss(_ presentationController: UIPresentationController) {
        hostVC = nil
        self.onDismiss?()
    }
}

struct FormSheet<Content: View> : UIViewControllerRepresentable {

    @Binding var show: Bool

    let content: () -> Content

    func makeUIViewController(context: UIViewControllerRepresentableContext<FormSheet<Content>>) -> FormSheetWrapper<Content> {

        let vc = FormSheetWrapper(content: content)
        vc.onDismiss = { self.show = false }
        return vc
    }

    func updateUIViewController(_ uiViewController: FormSheetWrapper<Content>,
                                context: UIViewControllerRepresentableContext<FormSheet<Content>>) {
        if show {
            uiViewController.show()
        }
        else {
            uiViewController.hide()
        }
    }
}

extension View {
    public func formSheet<Content: View>(isPresented: Binding<Bool>,
                                          @ViewBuilder content: @escaping () -> Content) -> some View {
        self.background(FormSheet(show: isPresented,
                                  content: content))
    }
}

Run Code Online (Sandbox Code Playgroud)

您应该能够func show()根据 UIKit 规范修改代码,以便按照您喜欢的方式调整大小(如果需要,您甚至可以从 SwiftUI 端注入参数)。这就是我如何获取在 iPad 上使用的表单,因为它.sheet对于我的用例来说太大了


pen*_*owe 0

如果它对其他人有帮助,我可以通过依靠此代码来保持视图控制器来完成此工作: https://gist.github.com/timothycosta/a43dfe25f1d8a37c71341a1ebaf82213

struct ViewControllerHolder {
    weak var value: UIViewController?
    init(_ value: UIViewController?) {
        self.value = value
    }
}

struct ViewControllerKey: EnvironmentKey {
    static var defaultValue: ViewControllerHolder? { ViewControllerHolder(UIApplication.shared.windows.first?.rootViewController) }
}

extension EnvironmentValues {
    var viewController: ViewControllerHolder? {
        get { self[ViewControllerKey.self] }
        set { self[ViewControllerKey.self] = newValue }
    }
}

extension UIViewController {
    func present<Content: View>(
        presentationStyle: UIModalPresentationStyle = .automatic,
        transitionStyle _: UIModalTransitionStyle = .coverVertical,
        animated: Bool = true,
        completion: @escaping () -> Void = { /* nothing by default*/ },
        @ViewBuilder builder: () -> Content
    ) {
        let toPresent = UIHostingController(rootView: AnyView(EmptyView()))
        toPresent.modalPresentationStyle = presentationStyle
        toPresent.rootView = AnyView(
            builder()
                .environment(\.viewController, ViewControllerHolder(toPresent))
        )
        if presentationStyle == .overCurrentContext {
            toPresent.view.backgroundColor = .clear
        }
        present(toPresent, animated: animated, completion: completion)
    }
}

Run Code Online (Sandbox Code Playgroud)

结合专门的视图来处理模式中的常见元素:

struct ModalContentView<Content>: View where Content: View {
    // Use this function to provide the content to display and to bring up the modal.
    // Currently only the 'formSheet' style has been tested but it should work with any
    // modal presentation style from UIKit.
    public static func present(_ content: Content, style: UIModalPresentationStyle = .formSheet) {
        let modal = ModalContentView(content: content)

        // Present ourselves
        modal.viewController?.present(presentationStyle: style) {
            modal.body
        }
    }

    // Grab the view controller out of the environment.
    @Environment(\.viewController) private var viewControllerHolder: ViewControllerHolder?
    private var viewController: UIViewController? {
        viewControllerHolder?.value
    }

    // The content to be displayed in the view.
    private var content: Content

    public var body: some View {
        VStack {
            /// Some specialized controls, like X button to close omitted...

            self.content
        }
    }
Run Code Online (Sandbox Code Playgroud)

最后,只需调用: ModalContentView.present( MyAwesomeView() )

MyAwesomeView在 .formSheet 模式内部显示。