有权读取 Swift 中的视频文件但不能读取图像文件?
Have permission to read video files but not image files in Swift?
我在 Swift 中写了一个图像选择器 (NOT UIImagePickerController
),按下 "Done" 后,我 return URL 个选定的 PHAsset(图像或视频或两者),然后将它们上传到 firebase 存储。
URL 的图像看起来像这样:file:///var/mobile/Media/DCIM/101APPLE/IMG_1239.PNG
URL 的视频如下所示:file:///var/mobile/Media/DCIM/101APPLE/IMG_1227.MOV
然后我显示 PHAsset。播放视频资产没有问题:
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 not have permission to read file" 错误:
do {
let data = try Data(contentsOf: url)
imageView.image = UIImage(data: data)
} catch {
print(error)
}
我确实有权访问用户的照片库,这就是图像选择器工作正常的原因。我只是无法使用他们的 URLs.
读取图像文件
我在想,也许我应该将图像资产复制到一个临时文件并从那里读取它,但是为什么视频资产可读但图像资产不可读没有任何意义。
------------更新-------------
这就是我获取 PHAsset 网址的方式
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运行 中的应用程序处于沙盒模式,可防止其访问其自身范围之外的文件。
一个url如:
file:///var/mobile/Media/DCIM/101APPLE/...
不在您的应用范围内,因此无法通过 Data(contentsOf:)
.
直接访问
然而,AVPlayer
能够访问外部 url
,因为它具有特殊权利,这就是为什么您能够使用 url
.[=51 加载视频资产的原因=]
要从 url
加载图像,我们会在 PHAssets.fetchAssets(withALAssetURLs:options:)
中使用这个 url
来取回 PHAsset
以便加载它,但是方法已在 iOS 11.
中弃用
作为替代方案,PHAssets.fetchAssets(withLocalIdentifiers:options:)
可用,但要使用它,您需要提供 localIdentifier
。
为此,您应该保存 PHAsset
s localIdentifier
s.
而不是保存 url
总结:
- 得到你的
PHAsset
s
- 保存
asset.localIdentifier
而不是他们的 url
需要时,使用[1]取回PHAsset
:
PHAssets.fetchAssets(withLocalIdentifiers:options:)
根据mediaType
处理PHAsset
一般示例:
警告: fatalError()
已被用于在您的设置出现问题时故意终止应用程序。
如果它崩溃,请通过 PHPhotoLibrary.requestAuthorization(_:)
检查您的 Info.plist
对照片库的 NSPhotoLibraryUsageDescription
and/or 权限状态
获取最新资产的演示功能(image/video;您决定)
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)
}
基于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
s 而不是 url
s,我会说很多更好。
Url
s 只是,有点,善变,所以让我们至少在这里避免它们。
我在 Swift 中写了一个图像选择器 (NOT UIImagePickerController
),按下 "Done" 后,我 return URL 个选定的 PHAsset(图像或视频或两者),然后将它们上传到 firebase 存储。
URL 的图像看起来像这样:file:///var/mobile/Media/DCIM/101APPLE/IMG_1239.PNG
URL 的视频如下所示:file:///var/mobile/Media/DCIM/101APPLE/IMG_1227.MOV
然后我显示 PHAsset。播放视频资产没有问题:
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 not have permission to read file" 错误:
do {
let data = try Data(contentsOf: url)
imageView.image = UIImage(data: data)
} catch {
print(error)
}
我确实有权访问用户的照片库,这就是图像选择器工作正常的原因。我只是无法使用他们的 URLs.
读取图像文件我在想,也许我应该将图像资产复制到一个临时文件并从那里读取它,但是为什么视频资产可读但图像资产不可读没有任何意义。
------------更新-------------
这就是我获取 PHAsset 网址的方式
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运行 中的应用程序处于沙盒模式,可防止其访问其自身范围之外的文件。
一个url如:
file:///var/mobile/Media/DCIM/101APPLE/...
不在您的应用范围内,因此无法通过 Data(contentsOf:)
.
AVPlayer
能够访问外部 url
,因为它具有特殊权利,这就是为什么您能够使用 url
.[=51 加载视频资产的原因=]
要从 url
加载图像,我们会在 PHAssets.fetchAssets(withALAssetURLs:options:)
中使用这个 url
来取回 PHAsset
以便加载它,但是方法已在 iOS 11.
作为替代方案,PHAssets.fetchAssets(withLocalIdentifiers:options:)
可用,但要使用它,您需要提供 localIdentifier
。
为此,您应该保存 PHAsset
s localIdentifier
s.
url
总结:
- 得到你的
PHAsset
s - 保存
asset.localIdentifier
而不是他们的url
需要时,使用[1]取回
PHAsset
:PHAssets.fetchAssets(withLocalIdentifiers:options:)
根据
处理mediaType
PHAsset
一般示例:
警告: fatalError()
已被用于在您的设置出现问题时故意终止应用程序。
如果它崩溃,请通过 PHPhotoLibrary.requestAuthorization(_:)
Info.plist
对照片库的 NSPhotoLibraryUsageDescription
and/or 权限状态
获取最新资产的演示功能(image/video;您决定)
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) }
基于
加载资产的函数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
s 而不是 url
s,我会说很多更好。
Url
s 只是,有点,善变,所以让我们至少在这里避免它们。