You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

iOS与macOS Catalyst文档选择器弹出问题及解决方案咨询

搞定macOS Catalyst下UIDocumentPicker空白&UIActivityViewController位置问题

我之前也踩过这俩Catalyst的坑,给你整理了简洁靠谱的解决方案,不用写那些繁琐的包装类,一套代码就能兼容iOS和macOS:

一、修复文档选择器空白问题

问题根源

Catalyst对UIDocumentPickerViewController的视图层级要求和iOS不一样,直接用SwiftUI的.sheet嵌套它会导致渲染异常,出现空白界面。正确的做法是通过当前活跃的UIViewController手动present,而不是依赖SwiftUI的sheet。

优化后的实现代码

  1. 先改造你的文档选择器包装类,保留核心逻辑:
import SwiftUI
import UIKit

struct DocPickerViewController: UIViewControllerRepresentable {
    private let docTypes: [String] = ["com.adobe.pdf", "public.text", "public.composite-content"]
    var callback: (URL) -> Void
    var onDismiss: () -> Void
    
    func makeCoordinator() -> Coordinator {
        Coordinator(self)
    }
    
    func updateUIViewController(_ uiViewController: UIDocumentPickerViewController, context: Context) {}
    
    func makeUIViewController(context: Context) -> UIDocumentPickerViewController {
        let controller = UIDocumentPickerViewController(documentTypes: docTypes, in: .import)
        controller.allowsMultipleSelection = false
        controller.delegate = context.coordinator
        return controller
    }
    
    class Coordinator: NSObject, UIDocumentPickerDelegate {
        var parent: DocPickerViewController
        
        init(_ picker: DocPickerViewController) {
            self.parent = picker
        }
        
        func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
            parent.callback(urls[0])
            controller.dismiss(animated: true) {
                parent.onDismiss()
            }
        }
        
        func documentPickerWasCancelled(_ controller: UIDocumentPickerViewController) {
            controller.dismiss(animated: true) {
                parent.onDismiss()
            }
        }
    }
}

// 新增工具:获取当前活跃的UIViewController
extension UIViewController {
    static func current() -> UIViewController? {
        let keyWindow = UIApplication.shared.connectedScenes
            .filter({$0.activationState == .foregroundActive})
            .compactMap({$0 as? UIWindowScene})
            .first?.windows
            .first(where: {$0.isKeyWindow})
        
        return keyWindow?.rootViewController?.topMost()
    }
    
    func topMost() -> UIViewController {
        if let presented = presentedViewController {
            return presented.topMost()
        }
        return self
    }
}
  1. 修改ContentView的按钮触发逻辑,直接present选择器:
struct ContentView: View {
    @State var isFilePickerShown = false
    
    var body: some View {
        VStack {
            Button(action: showFilePicker) {
                Image(systemName: "rectangle.and.paperclip")
                    .resizable()
                    .frame(width: 70, height: 70)
            }
        }
    }
    
    func showFilePicker() {
        guard let currentVC = UIViewController.current() else { return }
        let picker = DocPickerViewController(callback: filePicked, onDismiss: {
            self.isFilePickerShown = false
        })
        let pickerVC = picker.makeUIViewController(context: picker.makeContext())
        currentVC.present(pickerVC, animated: true)
    }
    
    func filePicked(_ url: URL) {
        print("\n选中的文件URL: \(url)")
    }
}

为什么这个方案靠谱?

  • 直接用UIKit原生的present方式,符合Catalyst的视图层级规则,避免了SwiftUI和UIKit的兼容冲突。
  • 不需要写平台判断,一套代码直接跑iOS和macOS。

二、修复UIActivityViewController位置固定问题

分享弹窗在Catalyst下默认会弹在(0,0),是因为没指定它的弹出源,只要给它设置sourceView或者sourceRect就行:

实现代码

struct ShareSheet: UIViewControllerRepresentable {
    let activityItems: [Any]
    
    func makeUIViewController(context: Context) -> UIActivityViewController {
        let controller = UIActivityViewController(activityItems: activityItems, applicationActivities: nil)
        // 关键:指定弹窗的来源视图,让它出现在当前页面的中心(或者按钮附近)
        if let currentVC = UIViewController.current() {
            controller.popoverPresentationController?.sourceView = currentVC.view
            // 如果想让弹窗精准定位到按钮位置,可以传按钮的frame:
            // controller.popoverPresentationController?.sourceRect = button.frame
        }
        return controller
    }
    
    func updateUIViewController(_ uiViewController: UIActivityViewController, context: Context) {}
}

使用方式

在ContentView里加个分享按钮试试:

Button(action: showShareSheet) {
    Image(systemName: "square.and.arrow.up")
        .resizable()
        .frame(width: 70, height: 70)
}

func showShareSheet() {
    guard let currentVC = UIViewController.current() else { return }
    let shareSheet = ShareSheet(activityItems: ["这是测试分享的内容"])
    let shareVC = shareSheet.makeUIViewController(context: shareSheet.makeContext())
    currentVC.present(shareVC, animated: true)
}

额外提醒

  • 你已经关闭了沙箱(com.apple.security.app-sandbox设为false),这是文档选择器能正常访问文件的前提,没问题。如果之后要开启沙箱,记得添加对应的文件访问权限(比如com.apple.security.files.user-selected.read-only)。

内容的提问来源于stack exchange,提问作者workingdog support Ukraine

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.14 08:45:14