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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.22 12:50:32