SwiftUI上传视频至Firebase报错:文件URL不可访问
问题:Firebase Storage上传视频提示文件URL不可访问
错误提示:
Error uploading video: File at URL: file:///Users/hitesh/Library/Developer/CoreSimulator/Devices/B28A566B-E082-4802-87CA-E2044ECCE57C/data/Containers/Data/Application/25CE276A-BD92-4D0C-9FBE-C594E9C05E90/tmp/.com.apple.Foundation.NSItemProvider.8pCkyb/SampleVideo_1280x720_1mb.mp4 is not reachable. Ensure file URL is not a directory, symbolic link, or invalid url.
实现代码:
import SwiftUI import PhotosUI import FirebaseStorage class ShareVideo: NSObject, ObservableObject { @Published var videoURL: URL? func selectVideo() { PHPhotoLibrary.requestAuthorization { status in switch status { case .authorized: print("Access granted.") case .denied, .restricted: print("Access denied.") case .notDetermined: print("Authorization not determined.") @unknown default: fatalError("New authorization status is available.") } } var configuration = PHPickerConfiguration() configuration.filter = .videos let picker = PHPickerViewController(configuration: configuration) picker.delegate = self UIApplication.shared.windows.first?.rootViewController?.present(picker, animated: true) } func uploadVideo() { guard let videoURL = videoURL else { return } let storage = Storage.storage() let storageRef = storage.reference() let videoRef = storageRef.child("videos/\(UUID().uuidString).mp4") let metadata = StorageMetadata() metadata.contentType = "video/mp4" videoRef.putFile(from: videoURL, metadata: metadata) { metadata, error in if let error = error { print(">>>>>\(videoURL)") print("Error uploading video: \(error.localizedDescription)") } else { print("Video uploaded successfully.") } } } } extension ShareVideo: PHPickerViewControllerDelegate { func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) { picker.dismiss(animated: true) guard let result = results.first else { return } result.itemProvider.loadFileRepresentation(forTypeIdentifier: UTType.movie.identifier) { url, error in if let error = error { print("Error loading video: \(error.localizedDescription)") } else if let url = url { DispatchQueue.main.async { self.videoURL = url } } } } } struct ShareVideoView: View { @StateObject private var shareVideo = ShareVideo() var body: some View { VStack{ Button(action: { shareVideo.selectVideo() }){ Text("Select Video") } Button("Upload Video") { shareVideo.uploadVideo() } } } } struct ShareVideoView_Previews: PreviewProvider { static var previews: some View { ShareVideoView() } }
解决方案
问题原因
PHPicker通过loadFileRepresentation返回的是临时文件URL,这个文件会在PHPickerViewController关闭后被系统自动清理。当点击上传按钮时,原临时文件已被删除,导致Firebase无法访问该路径。
修复步骤
将临时文件复制到App沙盒的持久化目录(比如Documents目录),保存持久化的URL后再进行上传。
修改后的代码
主要修改PHPickerViewControllerDelegate的实现,添加文件复制逻辑:
extension ShareVideo: PHPickerViewControllerDelegate { func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) { picker.dismiss(animated: true) guard let result = results.first else { return } result.itemProvider.loadFileRepresentation(forTypeIdentifier: UTType.movie.identifier) { tempUrl, error in if let error = error { print("Error loading video: \(error.localizedDescription)") return } guard let tempUrl = tempUrl else { return } // 创建App沙盒内的持久化路径 let documentsDir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first! let fileName = UUID().uuidString + ".mp4" let destinationUrl = documentsDir.appendingPathComponent(fileName) do { // 复制临时文件到持久化路径 if FileManager.default.fileExists(atPath: destinationUrl.path) { try FileManager.default.removeItem(at: destinationUrl) } try FileManager.default.copyItem(at: tempUrl, to: destinationUrl) DispatchQueue.main.async { self.videoURL = destinationUrl } } catch { print("Error copying video file: \(error.localizedDescription)") } } } }
额外优化建议
- 上传完成后删除沙盒内的临时文件,避免占用存储空间:
// 在uploadVideo的成功回调中添加 try? FileManager.default.removeItem(at: videoURL) - 优化权限请求逻辑:当前
selectVideo方法中,权限请求未完成就弹出picker,建议在授权成功后再初始化并展示PHPickerViewController。
内容的提问来源于stack exchange,提问作者Hitesh Takhtani
相关产品推荐
相关产品推荐

