如何通过Swift在Firebase中上传PDF、Docx、TXT格式文件?
Hey there! Great job getting your image/video chat app up and running—adding support for PDFs, DOCXs, TXTs, and other documents is totally feasible with Firebase Storage, which handles all those file types without any issues. Let’s walk through how to extend your existing code to make this work smoothly.
First, let’s adjust your sendMedia function to handle document files. We’ll add support for passing document URLs, preserve the original file extension (so Firebase knows the file type), and add a dedicated storage path for documents:
// MARK: - Send media message (including documents) func sendMedia(message: MSMessage, senderRef: FIRDatabaseReference, image: Data?, video: URL?, document: URL?) { var mediaTypeText: String? var storageRef: FIRStorageReference var uniqueID: String if let imageData = image { mediaTypeText = "Sent you a Photo" uniqueID = "\(NSUUID().uuidString).jpg" storageRef = DBManager.manager.imageStorageRef.child(Constants.USERS_IMAGES_STORAGE) .child(message.senderId).child(uniqueID) // Upload image data storageRef.put(imageData, metadata: nil) { (metadata, error) in self.handleUploadCompletion(metadata: metadata, error: error, message: message, senderRef: senderRef, uniqueID: uniqueID) } } else if let videoURL = video { mediaTypeText = "Sent you a Video" uniqueID = "\(NSUUID().uuidString).\(videoURL.pathExtension)" storageRef = DBManager.manager.videoStorageRef.child(Constants.USERS_VIDEOS_STORAGE) .child(message.senderId).child(uniqueID) // Upload video file storageRef.putFile(videoURL, metadata: nil) { (metadata, error) in self.handleUploadCompletion(metadata: metadata, error: error, message: message, senderRef: senderRef, uniqueID: uniqueID) } } else if let docURL = document { mediaTypeText = "Sent you a Document" // Preserve original file extension for proper MIME type detection uniqueID = "\(NSUUID().uuidString).\(docURL.pathExtension)" // Add a new constant for document storage path in your Constants struct storageRef = DBManager.manager.storageRef.child(Constants.USERS_DOCUMENTS_STORAGE) .child(message.senderId).child(uniqueID) // Upload document file storageRef.putFile(docURL, metadata: nil) { (metadata, error) in self.handleUploadCompletion(metadata: metadata, error: error, message: message, senderRef: senderRef, uniqueID: uniqueID) } } } // Helper to reduce code duplication in upload completion private func handleUploadCompletion(metadata: FIRStorageMetadata?, error: Error?, message: MSMessage, senderRef: FIRDatabaseReference, uniqueID: String) { if let err = error { self.delegate?.errorOccurred(description: err.localizedDescription) return } guard let downloadURL = metadata?.downloadURL() else { self.delegate?.errorOccurred(description: "Failed to get download URL") return } self.sendMessage(message: message, senderRef: senderRef, url: downloadURL.absoluteString, fileName: uniqueID) }
Don’t forget to add the new storage constant to your Constants struct:
struct Constants { static let USERS_IMAGES_STORAGE = "user_images" static let USERS_VIDEOS_STORAGE = "user_videos" static let USERS_DOCUMENTS_STORAGE = "user_documents" // Add this line // ... your other constants }
Next, we’ll add helper functions to detect document types and update your observeHandler to handle document messages:
First, add these helper checks:
// Helper to check if URL is an image private func isImage(_ urlString: String) -> Bool { let imageExtensions = ["jpg", "jpeg", "png", "gif"] guard let url = URL(string: urlString) else { return false } return imageExtensions.contains(url.pathExtension.lowercased()) } // Helper to check if URL is a video private func isVideo(_ urlString: String) -> Bool { let videoExtensions = ["mp4", "mov", "avi", "mkv"] guard let url = URL(string: urlString) else { return false } return videoExtensions.contains(url.pathExtension.lowercased()) } // Helper to check if URL is a document private func isDocument(_ urlString: String) -> Bool { let docExtensions = ["pdf", "docx", "doc", "txt", "rtf"] guard let url = URL(string: urlString) else { return false } return docExtensions.contains(url.pathExtension.lowercased()) }
Then update your observeHandler to route document messages:
// MARK : - Observe Handler func observeHandler(snapshot: FIRDataSnapshot) { guard let data = snapshot.value as? NSDictionary else { return } guard let senderID = data[Constants.SENDER_ID] as? String, let senderName = data[Constants.SENDER_NAME] as? String, let sentDate = data[Constants.SENT_DATE] as? Int, let text = data[Constants.SENDER_TEXT] as? String, let receiverID = data[Constants.RECEIVER_ID] as? String, let fileURL = data[Constants.MEDIA_URL] as? String, let fileName = data[Constants.FILE_NAME] as? String, let messageID = data[Constants.MESSAGE_ID] as? String else { debugPrint("\(#function ) error return") return } let currentUserID = AuthManager.User.id.value let receiveID = UserDefaults.standard.string(forKey: "ReceiveID") // Verify message is intended for current user guard currentUserID == receiverID && receiveID == senderID else { return } if !text.isEmpty { // Handle text message (existing logic) let newMessage = MSMessage(senderId: senderID, senderDisplayName: senderName, date: self.millisToDate(sentDate), text: text, receiverID: receiverID, messageID: messageID) self.delegate?.messageReceived(message: newMessage, fromNode: false) } else if !fileURL.isEmpty { guard let mediaURL = URL(string: fileURL) else { return } if self.isImage(fileURL) { self.getImage(mediaURL, senderID, senderName, receiverID, sentDate, messageID, fileName: fileName) } else if self.isVideo(fileURL) { self.getVideo(mediaURL, senderID, senderName, receiverID, sentDate, messageID, fileName: fileName) } else if self.isDocument(fileURL) { self.getDocument(mediaURL, senderID, senderName, receiverID, sentDate, messageID, fileName: fileName) } } }
Create a function to handle document messages, similar to your existing getVideo function. Since documents aren’t displayed directly, we’ll pass the file details to your delegate for user interaction (like opening/saving):
// MARK: - Get Document func getDocument(_ mediaURL: URL, _ senderID: String, _ senderName: String, _ receiverID: String, _ sentDate: Int, _ messageID: String, fileName: String, completion: ((MSMessage)->())? = nil) { DispatchQueue.global().async { // Create your message object with document details let newMessage = MSMessage( senderId: senderID, displayName: senderName, media: nil, // Or create a custom media item for document previews date: self.millisToDate(sentDate), receiverID: receiverID, messageID: messageID, mediaURL: mediaURL, fileName: fileName ) DispatchQueue.main.async { if let completion = completion { completion(newMessage) } else { // Add a new delegate method for document messages, or use existing mediaReceived self.delegate?.documentReceived(message: newMessage) } } } }
Let users select documents from their device using UIDocumentPickerViewController:
func presentDocumentPicker() { // Allow selection of any file type, or restrict to specific types if needed let documentPicker = UIDocumentPickerViewController(documentTypes: ["public.data"], in: .import) documentPicker.delegate = self documentPicker.allowsMultipleSelection = false present(documentPicker, animated: true) } // Implement UIDocumentPickerDelegate extension YourChatViewController: UIDocumentPickerDelegate { func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) { guard let selectedURL = urls.first else { return } // Create your MSMessage object with appropriate details let message = MSMessage( senderId: AuthManager.User.id.value, senderDisplayName: AuthManager.User.name.value, date: Date(), text: "", // Empty text since it's a document message receiverID: UserDefaults.standard.string(forKey: "ReceiveID") ?? "", messageID: NSUUID().uuidString ) // Get your message reference (use existing logic here) let senderRef = DBManager.manager.messagesRef.child(/* your chat node path */) // Send the document sendMedia(message: message, senderRef: senderRef, image: nil, video: nil, document: selectedURL) controller.dismiss(animated: true) } func documentPickerWasCancelled(_ controller: UIDocumentPickerViewController) { controller.dismiss(animated: true) } }
- Firebase Storage Rules: Ensure your storage rules allow authenticated users to read/write to the
user_documentspath (you probably already have similar rules for images/videos). - File Size: Keep an eye on Firebase Storage’s free tier limits—you can add size checks if you want to restrict large uploads.
- Delegate Updates: Add a
documentReceivedmethod to your delegate protocol to handle incoming document messages, or adjust your existingmediaReceivedmethod to detect document types.
内容的提问来源于stack exchange,提问作者Eniya Sri

