我在 Swift 中编写了一个图像选择器(NOT UIImagePickerController
),按下“完成”后,我返回所选 PHAssets(图像或视频或两者)的 URL,然后将它们上传到 Firebase 存储。
图像的 URL 如下所示:file:///var/mobile/Media/DCIM/101APPLE/IMG_1239.PNG
视频的 URL 如下所示:file:///var/mobile/Media/DCIM/101APPLE/IMG_1227.MOV
然后我显示 PHAsset。播放视频 Assets 没有问题:
let player = AVPlayer(url: url)
let playerLayer = AVPlayerLayer(player: player)
playerLayer.frame = view.frame
playerLayer.videoGravity = .resizeAspectFill
view.layer.addSublayer(playerLayer)
player.play()
但是当我尝试使用其 URL 显示图像时,出现“没有读取文件的权限”错误:
do {
let data = try Data(contentsOf: url)
imageView.image = UIImage(data: data)
} catch {
print(error)
}
我确实有权访问用户的照片库,这就是图像选择器工作正常的原因。我只是无法使用它们的 URL 读取图像文件。
我在想,也许我应该将图像 Assets 复制到一个临时文件并从那里读取它,但是为什么视频 Assets 可读但图像 Assets 不可读没有任何意义。
------------更新----------------
这就是我获取 PHAsset 的 url 的方式
extension PHAsset {
func getURL(completionHandler : @escaping ((_ responseURL : URL?) -> Void)) {
if self.mediaType == .image {
let options = PHContentEditingInputRequestOptions()
options.canHandleAdjustmentData = { (adjustmeta: PHAdjustmentData) -> Bool in
return true
}
self.requestContentEditingInput(with: options, completionHandler: { (contentEditingInput: PHContentEditingInput?, info: [AnyHashable : Any]) in
completionHandler(contentEditingInput!.fullSizeImageURL as URL?)
})
} else if self.mediaType == .video {
let options = PHVideoRequestOptions()
options.version = .original
PHImageManager.default().requestAVAsset(forVideo: self, options: options, resultHandler: {(asset: AVAsset?, audioMix: AVAudioMix?, info: [AnyHashable : Any]?) in
if let urlAsset = asset as? AVURLAsset {
let localVideoUrl: URL = urlAsset.url as URL
completionHandler(localVideoUrl)
} else {
completionHandler(nil)
}
})
}
}
}
最佳答案
iOS 中的应用程序以沙盒模式运行,这会阻止它访问其自身范围之外的文件。
一个网址,例如:
file:///var/mobile/Media/DCIM/101APPLE/...
不在您应用的范围内,因此无法通过 Data(contentsOf:)
直接访问。
AVPlayer
能够访问外部 url
,因为它具有特殊的权利,这就是为什么您能够使用 url< 加载视频 Assets 的原因
.
要从 url
加载图像,我们会在 PHAssets.fetchAssets(withALAssetURLs:options:)
中使用此 url
来获取返回 PHAsset
以加载它,但该方法已在 iOS 11 中弃用。
作为替代方案,PHAssets.fetchAssets(withLocalIdentifiers:options:)
可用,但要使用它,您需要为其提供 localIdentifier
。
为此,您应该保存 PHAsset
的 localIdentifier
,而不是保存 url
。
总结:
- 获取您的
PHAsset
- 保存
asset.localIdentifier
而不是它们的url
需要时,使用 [1] 取回
PHAsset
:PHAssets.fetchAssets(withLocalIdentifiers:options:)
根据
mediaType
处理PHAsset
一般示例:
警告 fatalError()
已被用于在您的设置出现问题时故意终止应用程序。< br/>
如果它崩溃然后检查你的 Info.plist
NSPhotoLibraryUsageDescription
和/或通过 PHPhotoLibrary.requestAuthorization(_:)
获取最新资源(图片/视频;由您决定)的演示功能
func getLatestAsset(for type: PHAssetMediaType) { let fetchOptions = PHFetchOptions() fetchOptions.sortDescriptors = [NSSortDescriptor(key:"creationDate", ascending: false)] fetchOptions.fetchLimit = 2 let fetchResult: PHFetchResult = PHAsset.fetchAssets(with: type, options: fetchOptions) guard let asset = fetchResult.firstObject else { fatalError("Unable to fetch photos") } //save `localIdentifier` for later use (app relaunch or whatever) let assetIdentifier = asset.localIdentifier print(assetIdentifier) //Just a demo that you can get the same asset based on a local identifier loadAsset(identifier: assetIdentifier) }
根据
加载 Assets 的函数localIdentifier
func loadAsset(identifier: String) { guard let asset = PHAsset.fetchAssets(withLocalIdentifiers: [identifier], options: nil).firstObject else { fatalError("No Asset found with identifier: \(identifier)") } print(asset.localIdentifier) //Do something with the asset now if asset.mediaType == .image { loadImage(asset: asset) } else if asset.mediaType == .video { loadVideo(asset: asset) } }
从
PHAsset
加载图像func loadImage(asset: PHAsset) { PHImageManager().requestImageData(for: asset, options: nil) { (data, string, orientation, userInfo) in guard let data = data else { fatalError("Image Data is nil") } DispatchQueue.main.async { self.imageView.image = UIImage(data: data) } } }
从
PHAsset
加载视频func loadVideo(asset: PHAsset) { PHImageManager().requestAVAsset(forVideo: asset, options: nil) { (avAsset, audioMix, userInfo) in guard let avAsset = avAsset else { fatalError("AVAsset is nil") } let playerItem = AVPlayerItem(asset: avAsset) let player = AVPlayer(playerItem: playerItem) let playerLayer = AVPlayerLayer(player: player) playerLayer.frame = self.view.frame playerLayer.videoGravity = .resizeAspectFill DispatchQueue.main.async { self.view.layer.addSublayer(playerLayer) player.play() } } }
PS:您将不再需要 getURL(completionHandler:)
,因为您的整个逻辑将基于 localIdentifier
而不是 url
我会说那好多了。
Url
只是,有点变幻无常,所以我们至少要避免在这里使用它们。
关于ios - 有权在 Swift 中读取视频文件但不能读取图像文件?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50670260/