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

Swift使用UIDocumentPickerController上传PDF至Firebase空文件问题求助

问题解决:UIDocumentPicker选PDF后上传Firebase为空文件

你的代码核心问题有两个:

  • 没实现UIDocumentPickerDelegate的代理方法,根本没拿到用户选中的PDF文件路径
  • 上传时直接用了let newData = Data(),传的是空数据,自然下载后也是空文件

按下面的步骤修改:

1. 添加变量存储选中的文件URL

在类里加一个可选的URL变量,用来保存用户选中的PDF路径:

var selectedDocumentURL: URL?

2. 实现文档选择器的代理方法

必须实现UIDocumentPickerDelegate的documentPicker(_:didPickDocumentsAt:)方法,才能获取用户选中的文件。另外iOS 13+需要通过文件协调器(File Coordinator)来读取文件内容,避免权限问题:

func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
    guard let selectedURL = urls.first else {
        alertFunc(titleInput: "错误", messageInput: "未选中任何文件")
        return
    }
    
    // 处理文件权限,iOS 13+需要用file coordination读取
    let fileCoordinator = NSFileCoordinator()
    fileCoordinator.coordinate(readingItemAt: selectedURL, options: .withoutChanges, error: nil) { (readURL) in
        self.selectedDocumentURL = readURL
        self.alertFunc(titleInput: "成功", messageInput: "已选中PDF文件")
    }
}

// 处理用户取消选择的情况
func documentPickerWasCancelled(_ controller: UIDocumentPickerViewController) {
    alertFunc(titleInput: "提示", messageInput: "已取消选择文件")
}

3. 修改上传方法,读取选中的文件数据

原来的uploadClicked方法里,不要直接用空的Data(),而是从selectedDocumentURL读取数据:

@IBAction func uploadClicked(_ sender: Any) {
    guard let documentURL = selectedDocumentURL else {
        alertFunc(titleInput: "错误", messageInput: "请先选择要上传的PDF文件")
        return
    }
    
    // 从URL读取文件数据
    guard let newData = try? Data(contentsOf: documentURL) else {
        alertFunc(titleInput: "错误", messageInput: "无法读取文件内容")
        return
    }
    
    let storage = Storage.storage()
    let storageReference = storage.reference()
    let newUuid = UUID().uuidString
    let mediaFolder = storageReference.child("Documents")
    let newDocumentReference = mediaFolder.child("\(newUuid).pdf")
    
    newDocumentReference.putData(newData, metadata: nil) { metadata, error in
        if let error = error {
            self.alertFunc(titleInput: "错误", messageInput: error.localizedDescription)
            return
        }
        
        newDocumentReference.downloadURL { url, error in
            guard let downloadURL = url else {
                self.alertFunc(titleInput: "错误", messageInput: error?.localizedDescription ?? "获取下载链接失败")
                return
            }
            
            let documentFirestore = Firestore.firestore()
            let firestorePost: [String: Any] = [
                "documentUrl": downloadURL.absoluteString,
                "PostedBy": Auth.auth().currentUser?.email ?? "未知用户",
                "Date": FieldValue.serverTimestamp()
            ]
            
            documentFirestore.collection("Documents").addDocument(data: firestorePost) { error in
                if let error = error {
                    self.alertFunc(titleInput: "错误", messageInput: error.localizedDescription)
                } else {
                    self.tabBarController?.selectedIndex = 0
                }
            }
        }
    }
}

完整修改后的代码

import UIKit
import Firebase
import MobileCoreServices

class DocViewController: UIViewController, UIDocumentPickerDelegate, UINavigationControllerDelegate {
    @IBOutlet weak var docImage: UIImageView!
    
    // 存储选中的文件URL
    var selectedDocumentURL: URL?

    override func viewDidLoad() {
        super.viewDidLoad()
        
        let hideKeyboardGesture = UITapGestureRecognizer(target: self, action: #selector(hideKeyboard))
        view.addGestureRecognizer(hideKeyboardGesture)
        
        docImage.isUserInteractionEnabled = true
        let docGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(selectDocument))
        docImage.addGestureRecognizer(docGestureRecognizer)
    }
    
    @objc func hideKeyboard(){
        view.endEditing(true)
    }
    
    @objc func selectDocument(){
        let documentPicker = UIDocumentPickerViewController(documentTypes: [String(kUTTypePDF)], in: .open)
        documentPicker.delegate = self
        documentPicker.modalPresentationStyle = .fullScreen
        documentPicker.allowsMultipleSelection = false
        present(documentPicker, animated: true, completion: nil)
        // 移除directoryURL设置,打开文档不需要指定该参数
        // documentPicker.directoryURL = .documentsDirectory
    }
    
    func alertFunc(titleInput:String, messageInput: String){
        let alert = UIAlertController(title: titleInput, message: messageInput, preferredStyle: .alert)
        let okButton = UIAlertAction(title: "OK", style: .default, handler: nil)
        alert.addAction(okButton)
        present(alert, animated: true, completion: nil)
    }

    // 文档选择器代理方法:获取选中的文件
    func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
        guard let selectedURL = urls.first else {
            alertFunc(titleInput: "错误", messageInput: "未选中任何文件")
            return
        }
        
        let fileCoordinator = NSFileCoordinator()
        fileCoordinator.coordinate(readingItemAt: selectedURL, options: .withoutChanges, error: nil) { (readURL) in
            self.selectedDocumentURL = readURL
            self.alertFunc(titleInput: "成功", messageInput: "已选中PDF文件")
        }
    }
    
    // 文档选择器取消选择
    func documentPickerWasCancelled(_ controller: UIDocumentPickerViewController) {
        alertFunc(titleInput: "提示", messageInput: "已取消选择文件")
    }
    
    @IBAction func uploadClicked(_ sender: Any) {
        guard let documentURL = selectedDocumentURL else {
            alertFunc(titleInput: "错误", messageInput: "请先选择要上传的PDF文件")
            return
        }
        
        guard let newData = try? Data(contentsOf: documentURL) else {
            alertFunc(titleInput: "错误", messageInput: "无法读取文件内容")
            return
        }
        
        let storage = Storage.storage()
        let storageReference = storage.reference()
        let newUuid = UUID().uuidString
        let mediaFolder = storageReference.child("Documents")
        let newDocumentReference = mediaFolder.child("\(newUuid).pdf")
        
        newDocumentReference.putData(newData, metadata: nil) { metadata, error in
            if let error = error {
                self.alertFunc(titleInput: "错误", messageInput: error.localizedDescription)
                return
            }
            
            newDocumentReference.downloadURL { url, error in
                guard let downloadURL = url else {
                    self.alertFunc(titleInput: "错误", messageInput: error?.localizedDescription ?? "获取下载链接失败")
                    return
                }
                
                let documentFirestore = Firestore.firestore()
                let firestorePost: [String: Any] = [
                    "documentUrl": downloadURL.absoluteString,
                    "PostedBy": Auth.auth().currentUser?.email ?? "未知用户",
                    "Date": FieldValue.serverTimestamp()
                ]
                
                documentFirestore.collection("Documents").addDocument(data: firestorePost) { error in
                    if let error = error {
                        self.alertFunc(titleInput: "错误", messageInput: error.localizedDescription)
                    } else {
                        self.tabBarController?.selectedIndex = 0
                    }
                }
            }
        }
    }
}

额外细节说明:

  • 移除了documentPicker.directoryURL = .documentsDirectory,这个参数用于创建文档时指定保存目录,打开文档时无需设置,反而可能导致选择器异常
  • 上传时增加了多层空值判断,避免用户未选文件就点击上传引发崩溃
  • 将Firestore字段名从imageUrl改为documentUrl,更贴合实际内容
  • 处理了Auth.auth().currentUser?.email的空值情况,避免崩溃

内容的提问来源于stack exchange,提问作者Muhammed Ömer Ünver

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.18 11:36:14