

iOS PhotoKit - 浏览照片和视频
source link: http://blog.danthought.com/programming/2020/06/28/ios-photokit-browse-photo-video/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

iOS 中的通过 PhotoKit 提供访问 “照片 App” 中的照片和视频,本文主要讲解使用 PhotoKit 浏览相册中的照片和视频、导出相册中的照片、导出相册中的视频、修改相册中的照片和新增相册中的照片。

完整代码:
浏览相册中的照片和视频
第一步,获取相册使用权限:
PHPhotoLibrary.requestAuthorization { [weak self] status in
guard let self = self else { return }
switch status {
case .authorized:
self.imageManager = PHCachingImageManager()
self.fetchAlbums()
self.album = self.albums.first
DispatchQueue.main.async { [weak self] in
self?.updateAlbum()
self?.tableView.reloadData()
}
default:
DispatchQueue.main.async {
DTMessageBar.error(message: "相册未授权", position: .bottom)
}
}
}
第二步,获取 Albums 和 Assets,先介绍一些核心类:
- PHImageManager 是操作相册的中心类,为了缓存和提升访问性能,这里使用子类 PHCachingImageManager。
- PHAssetCollection 是一组 Assets 的抽象,可以理解为相册。
- PHAsset 是一个 Asset 的抽象,可以是照片、视频或 Live Photo。
获取 PHAssetCollection:
var collections = PHAssetCollection.fetchAssetCollections(with: .smartAlbum,
subtype: .albumRegular,
options: nil)
var index = 0
while index < collections.count {
let collection = collections.object(at: index)
var subtypes: [PHAssetCollectionSubtype] = [.smartAlbumRecentlyAdded]
if #available(iOS 9.0, *) {
if mode.type != .video {
subtypes.append(.smartAlbumScreenshots)
}
}
if mode.type != .photo {
subtypes.append(.smartAlbumVideos)
}
if subtypes.contains(collection.assetCollectionSubtype) {
assets = fetchAssets(in: collection)
album = Album(collection: collection, assets: assets)
albums.append(album)
}
index += 1
}
collections = PHAssetCollection.fetchAssetCollections(with: .album, subtype: .albumRegular, options: nil)
index = 0
while index < collections.count {
let collection = collections.object(at: index)
assets = fetchAssets(in: collection)
album = Album(collection: collection, assets: assets)
albums.append(album)
index += 1
}
获取 PHAsset,通过 NSPredicate 设置一些查询限定条件:
private func fetchAssets(in collection: PHAssetCollection? = nil) -> PHFetchResult<PHAsset> {
if let collection = collection {
let fetchOptions = PHFetchOptions()
switch mode.type {
case .photo:
fetchOptions.predicate = NSPredicate(format: "mediaType = %d", PHAssetMediaType.image.rawValue)
case .video:
fetchOptions.predicate = NSPredicate(format: "mediaType = %d", PHAssetMediaType.video.rawValue)
case .all:
break
}
fetchOptions.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)]
return PHAsset.fetchAssets(in: collection, options: fetchOptions)
} else {
let fetchOptions = PHFetchOptions()
fetchOptions.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)]
switch mode.type {
case .photo:
return PHAsset.fetchAssets(with: .image, options: fetchOptions)
case .video:
return PHAsset.fetchAssets(with: .video, options: fetchOptions)
case .all:
return PHAsset.fetchAssets(with: fetchOptions)
}
}
}
第三步,显示照片和视频的缩略图,PHAsset 包含的只是照片和视频的信息,缩略图要通过 PHImageManager 的 requestImage 方法异步获取:
func requestImage(for asset: PHAsset, targetSize: CGSize, contentMode: PHImageContentMode, options: PHImageRequestOptions?, resultHandler: @escaping (UIImage?, [AnyHashable : Any]?) -> Void) -> PHImageRequestID
PHImageRequestOptions 中的 PHImageRequestOptionsDeliveryMode 决定提供的照片质量和次数。
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
guard let assets = album?.assets else { return UICollectionViewCell() }
let asset = assets.object(at: indexPath.row)
if asset.mediaType == .image {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: photoCellIdentifier, for: indexPath) as! LibraryPhotoCell
cell.photoImageView.image = nil
cell.representedAssetIdentifier = asset.localIdentifier
if let imageManager = imageManager {
let requestOptions = PHImageRequestOptions()
requestOptions.isNetworkAccessAllowed = true
imageManager.requestImage(for: asset,
targetSize: thumbnailSize,
contentMode: .aspectFill,
options: requestOptions,
resultHandler: { photo, _ in
DispatchQueue.main.async {
if cell.representedAssetIdentifier == asset.localIdentifier {
cell.photoImageView.image = photo
}
}
})
}
let index = selectedAssets.firstIndex { $0.localIdentifier == asset.localIdentifier }
if let index = index {
cell.setCheckNumber(index + 1)
cell.toggleMask(isShow: false)
} else {
cell.setCheckNumber(nil)
cell.toggleMask(isShow: selectedAssets.count >= mode.config.limitOfPhotos)
}
return cell
} else {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: videoCellIdentifier, for: indexPath) as! LibraryVideoCell
cell.photoImageView.image = nil
cell.representedAssetIdentifier = asset.localIdentifier
if let imageManager = imageManager {
let requestOptions = PHImageRequestOptions()
requestOptions.isNetworkAccessAllowed = true
imageManager.requestImage(for: asset,
targetSize: thumbnailSize,
contentMode: .aspectFill,
options: requestOptions,
resultHandler: { photo, _ in
DispatchQueue.main.async {
if cell.representedAssetIdentifier == asset.localIdentifier {
cell.photoImageView.image = photo
}
}
})
}
cell.setDuration(asset.duration)
if asset.duration >= TimeInterval(mode.config.minDuration),
asset.duration <= TimeInterval(mode.config.maxDuration) {
cell.toggleMask(isShow: !selectedAssets.isEmpty)
} else {
cell.toggleMask(isShow: true)
}
return cell
}
}
导出相册中的照片
和前面获取照片缩略图的代码一样,只是设置的照片质量不同:
private func fetchPhotos(completionBlock: @escaping (_ photos: [UIImage]?) -> Void) {
guard let imageManager = imageManager else { return }
let total = selectedAssets.count
var current = 0
var success = 0
var photos = [UIImage](repeating: UIImage(), count: selectedAssets.count)
let requestOptions = PHImageRequestOptions()
requestOptions.deliveryMode = .highQualityFormat
requestOptions.isNetworkAccessAllowed = true
for (index, asset) in selectedAssets.enumerated() {
imageManager.requestImage(for: asset,
targetSize: PHImageManagerMaximumSize,
contentMode: .aspectFit,
options: requestOptions,
resultHandler: { photo, _ in
DispatchQueue.main.async {
current += 1
if let photo = photo {
success += 1
photos[index] = photo
}
if current == total {
completionBlock(success == total ? photos : nil)
}
}
})
}
}
导出相册中的视频
PHImageManager 获取相册中的视频资源有如下几个方法:
- requestPlayerItem 获取可以直接播放的 AVPlayerItem。
- requestAVAsset 获取可以直接播放和编辑的 AVAsset。
- requestExportSession 获取可以导出到目录文件的 AVAssetExportSession。
这里使用 requestExportSession 获取 AVAssetExportSession,PHImageRequestOptions 和 PHVideoRequestOptions 都有关于从 iCloud 获取照片和视频的功能,isNetworkAccessAllowed 允许获取从 iCloud 获取照片和视频,progressHandler 获取从 iCloud 下载照片和视频的进度,PHImageManager cancelImageRequest 可以取消整个获取照片和视频的异步任务:
asset.duration >= TimeInterval(mode.config.minDuration),
asset.duration <= TimeInterval(mode.config.maxDuration) {
DTMessageHUD.hud()
guard let videoFile = MediaViewController.getMediaFileURL(name: "video", ext: "mp4"),
let imageManager = imageManager else {
DTMessageHUD.dismiss()
DTMessageBar.error(message: "创建视频文件失败", position: .bottom)
return
}
let requestOptions = PHVideoRequestOptions()
requestOptions.isNetworkAccessAllowed = true
let presets = AVAssetExportSession.allExportPresets()
var preset = presets.first ?? ""
if presets.contains(AVAssetExportPreset1280x720) {
preset = AVAssetExportPreset1280x720
} else if presets.contains(AVAssetExportPresetMediumQuality) {
preset = AVAssetExportPresetMediumQuality
}
imageManager.requestExportSession(forVideo: asset, options: requestOptions,
exportPreset: preset) { [weak self] sess, _ in
self?.exportVideo(videoFile, with: sess)
}
接着,AVAssetExportSession 通过 exportAsynchronously 异步导出相册中视频到沙盒中,cancelExport 取消导出,status 和 progress 获取状态和进度:
private func exportVideo(_ video: URL, with session: AVAssetExportSession?) {
guard let session = session else {
DispatchQueue.main.async {
DTMessageHUD.dismiss()
DTMessageBar.error(message: "获取视频导出会话失败", position: .bottom)
}
return
}
session.outputURL = video
session.outputFileType = .mp4
session.shouldOptimizeForNetworkUse = true
session.exportAsynchronously { [weak self] in
DispatchQueue.main.async { [weak self] in
switch session.status {
case .completed:
DTMessageHUD.dismiss()
self?.previewVideo(video)
default:
DTMessageHUD.dismiss()
DTMessageBar.error(message: "视频文件导出失败", position: .bottom)
}
}
}
}
修改相册中的照片
if asset.canPerform(.content) {
asset.requestContentEditingInput(with: nil) { (contentEditingInput, _) in
do {
guard let contentEditingInput = contentEditingInput else { return }
let contentEditingOutput = PHContentEditingOutput(contentEditingInput: contentEditingInput)
let formatIdentifier = Bundle.main.bundleIdentifier ?? ""
let cropping = "cropping".data(using: .utf8, allowLossyConversion: false)!
contentEditingOutput.adjustmentData = PHAdjustmentData(formatIdentifier: formatIdentifier,
formatVersion: "0.1",
data: cropping)
try croppedPhotoJPEG.write(to: contentEditingOutput.renderedContentURL,
options: .atomicWrite)
PHPhotoLibrary.shared().performChanges({
let request = PHAssetChangeRequest(for: self.asset)
request.contentEditingOutput = contentEditingOutput
}, completionHandler: { (success, error) in
if success {
print("modify success")
} else {
print("modify fail \(error?.localizedDescription ?? "")")
}
})
} catch {
print("modify error \(error)")
}
}
}
新增相册中的照片
PHPhotoLibrary.shared().performChanges({
let request = PHAssetChangeRequest.creationRequestForAsset(from: croppedPhoto)
}) { (success, _) in
if success {
print("save success")
} else {
print("save fail")
}
}
Recommend
-
122
汤不热 - 当你浏览汤不热视频的时候,只需要分享到 app 即可自动下载,请自备梯子 - NEXT
-
39
今天给大家讲一个干货,简单实用,用到了我们今天所说的PhotoKit框架。
-
33
作者:Ole Begemann, 原文链接 ,原文日期:2018-09-28 译者: 张弛 ;校对:
-
32
概述 PhotoKit应该是iOS 8 开始引入为了替代之前ALAssetsLibrary的相册资源访问的标准库,后者在iOS 9开始被弃用。当然相对于ALAsse...
-
18
本文讲解在 iOS 中如何实现图片的剪裁和旋转,重点在于对 UIScrollView、手势触控、UIView 和 CALayer 的理解。 完整代码: UIScrollView
-
3
手机QQ支持浏览短视频_快讯_i黑马 手机QQ支持浏览短视频 2022-03-24 12:20...
-
4
PhotoKit 是一个在线图片编辑器,打开即用,支持电脑、手机、平板电脑下的多种主流浏览器。更有强大的 AI 算法加持。@Appinn
-
3
-
2
V2EX › NAS 用了一圈没有一个好用的 nas 照片备份+浏览的 app,所以考虑自己开发一个
-
9
V2EX › 分享创造 [送码] Pho - 通过 smb,webdav,nfs 同步和浏览照片
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK