PhotoKit框架详细解析(二) —— 图像的获取、修改、保存
版本记录
版本号 | 时间 |
---|---|
V1.0 | 2020.09.30 星期三 |
前言
在我们开发中总有和系统相册进行交互的时候,包含图片和视频的获取,存储,修改等操作。这个模块我们就一起来看下这个相关的框架
PhotoKit
。感兴趣的可以看下面几篇文章。
1. PhotoKit框架详细解析(一) —— 基本概览(一)
开始
首先看下主要内容:
在本教程中,您将学习如何使用
PhotoKit
访问和修改照片,智能相册和用户收藏。 您还将学习如何保存和还原对照片所做的修改。本片内容来自翻译。
下面看一下写作环境
Swift 5, iOS 13, Xcode 11
Photos
应用通过一组称为PhotoKit
的API
在iOS中管理图像资源。 如果您一直想知道如何构建像Photos
之类的应用程序,或者只是访问照片库,PhotoKit
就是答案。 本教程将重点放在iOS
上,但PhotoKit也可用于macOS
,Catalyst
和tvOS
。
您将使用NoirIt
,这是一款可将精美的Noir
滤镜应用于照片的应用程序。 为此,您将:
- 了解
PhotoKit
的权限模型。 - 访问图像资源数据。
- 了解如何访问用户收藏和智能相册数据。
- 显示图像资源。
- 修改资源元数据。
- 编辑资源的图像。
- 保存修改后的图像资源。
- 将修改后的图像资源还原为原始图像。
下载入门项目。
首先打开启动文件夹中的NoirIt.xcodeproj
。 展开资源文件夹,然后打开Main.storyboard
。
![](https://img.haomeiwen.com/i3691932/7a50b9a64f911086.png)
该应用程序的布局非常简单。 有一个相册收集视图控制器,一个照片收集视图控制器和一个照片细节视图控制器。
构建并运行。
![](https://img.haomeiwen.com/i3691932/e9e513a14d4c9ea0.png)
现在看可能不多,但是很快。
1. Prepping the Photos App
在开始之前,请在Photos
中创建一个相册,以便以后至少可以在NoirIt
中查看一个相册。
- 1) 打开
Photos
应用。 在模拟器上运行时,照片中存在一个bug
,可能会导致其崩溃。 如果是这样,请重新打开它。 - 2) 点击
tab bar
上的Albums
。 - 3) 点按屏幕顶部的
+
。 - 4) 选择
New Album
。 - 5) 将其命名为
My Cool Pics
,然后点击Save
。 - 6) 选择几张照片添加到新相册中。
- 7) 导航回到主相册视图并查看您的新相册。
![](https://img.haomeiwen.com/i3691932/70c685dfddb1b5b2.png)
这就是您需要在Photos
中执行的所有操作。
Getting PhotoKit Permissions
与许多iOS API
一样,PhotoKit
使用权限模型。 它向用户显示一个对话框,询问该应用访问其图像的权限。 在深入访问和修改图像之前,必须先获得许可。 您可以使用PHPhotoLibrary
(共享对象来管理对照片库的访问)来执行此操作。
1. Modifying Info.plist
第一步是向Info.plist
添加一个密钥,该密钥描述为什么要获得访问该库的权限。
- 1) 打开Info.plist。
- 2) 右键单击
Information Property List
,然后选择Add Row
。 出现一个新行。 - 3) 输入密钥
NSPhotoLibraryUsageDescription
并按Enter
。 - 4) 在值列中,输入
To add a noir filter
。 当iOS首次请求访问该库的权限时,它将显示此信息。
您的Info.plist
应该如下所示:
![](https://img.haomeiwen.com/i3691932/40a029c9b3ad042a.png)
2. Requesting Authorization
打开AlbumCollectionViewController.swift
。 找到getPermissionIfNecessary(completionHandler :)
并将其实现替换为:
// 1
guard PHPhotoLibrary.authorizationStatus() != .authorized else {
completionHandler(true)
return
}
// 2
PHPhotoLibrary.requestAuthorization { status in
completionHandler(status == .authorized)
}
- 1) 您要做的第一件事是从
PHPhotoLibrary
获取当前的授权状态。 如果已被授权,请使用true
值调用完成处理程序。 - 2) 如果先前未授予许可,则请求它。 请求授权时,iOS会显示一个警告对话框,询问权限。 它将状态作为
PHAuthorizationStatus
对象传递回其完成处理程序中。 调用完成处理程序,如果状态值是.authorized
,则返回true
,否则返回false
。
注意:
PHAuthorizationStatus
是一个枚举,它也可以返回notDefineded
,restricted
,denied
和iOS 14
的新增内容limited
。 您可能需要检查并适当处理它们。 现在,让NoirIt
保持简单。
viewDidLoad()
已经在调用此方法,因此进行构建并运行。 当NoirIt
启动时,iOS会请求访问照片库的权限。 如果您使用的是iOS 13
,请点击OK
,或者在iOS 14
上,点击Allow Access to All Photos
。
![](https://img.haomeiwen.com/i3691932/288a4ae59c08e9ab.png)
Understanding Assets
即使您最终会获得图像,也必须了解您主要使用PhotoKit
中的资源。 考虑一下您如何与Photos
应用进行交互。 当然,您可以查看图像,但其中也包含元数据,例如收藏夹和地理编码的位置数据。 不仅限于图像。 Photos
包含LivePhotos
和视频。 将这些东西塞进UIImage
没有任何意义。 这就是PHAsset
用到的地方。
PHAsset
是描述图像,LivePhoto
或视频的元数据。 它是不可变的,不包含图像本身,但确实提供了获取图像所需的信息。 它还包含大量信息,例如创建和修改日期,位置数据,收藏夹和隐藏状态,突发数据等等。 就像您很快就会看到的那样,PHAsset
是真正的主力军。
有时您需要处理一组资源。 这些通常作为PHAssetCollection
对象返回。
1. Asset Data Models
打开AlbumCollectionViewController.swift
。 在文件顶部附近,在sections
属性的声明下添加以下内容:
private var allPhotos = PHFetchResult<PHAsset>()
private var smartAlbums = PHFetchResult<PHAssetCollection>()
private var userCollections = PHFetchResult<PHAssetCollection>()
您可能会对自己说:“嘿,self
,这些PHFetchResult
是什么? 我以为我正在获取PHAssets
和PHAssetCollections
?” PHFetchResult
的简化思考方式是将其视为一个数组,从本质上讲,它是一个数组。 它包含所有相同的数组方法和约定,例如count()
和index(of :)
。 另外,它可以智能地处理数据的获取,缓存和根据需要重新获取。 如果您将PHFetchResult
视为资源或集合的智能阵列,就可以了。 这些属性是应用程序的数据存储。
2. Fetching Assets and Asset Collections
仍在AlbumCollectionViewController.swift
中,找到fetchAssets()
并添加以下代码:
// 1
let allPhotosOptions = PHFetchOptions()
allPhotosOptions.sortDescriptors = [
NSSortDescriptor(
key: "creationDate",
ascending: false)
]
// 2
allPhotos = PHAsset.fetchAssets(with: allPhotosOptions)
// 3
smartAlbums = PHAssetCollection.fetchAssetCollections(
with: .smartAlbum,
subtype: .albumRegular,
options: nil)
// 4
userCollections = PHAssetCollection.fetchAssetCollections(
with: .album,
subtype: .albumRegular,
options: nil)
- 1) 提取资源时,您可以应用一组选项来指示结果的排序,过滤和管理。 在这里,您将创建一个排序描述符
(sort descriptor)
,该描述符按创建日期从最新到最旧的顺序对资源进行排序。 - 2)
PHAsset
提供了用于获取资源并将结果作为PHFetchResult
返回的功能。 在这里,您将上面创建的选项传递给它,并将结果分配给allPhotos
。 - 3)
Photos
应用程序会自动创建智能相册,例如“收藏夹”和“最新记录”是一组资源,因此属于PHAssetCollection
对象。 在这里,您可以获取智能专辑集。 您将不会对它们进行排序,因此options
为nil
。 - 4) 访问用户创建的相册的方法类似,不同之处在于您获取
.album
类型。
现在填充了数据存储,下一个任务是更新UI。
![](https://img.haomeiwen.com/i3691932/fa5fd1569bae5730.png)
3. Prepping the Collection View
您现在拥有资源,是时候对它们进行一些处理了。 在类末尾添加以下内容:
override func collectionView(
_ collectionView: UICollectionView,
numberOfItemsInSection section: Int
) -> Int {
switch sections[section] {
case .all: return 1
case .smartAlbums: return smartAlbums.count
case .userCollections: return userCollections.count
}
}
在这里,您可以返回每个section
中的items
数,以便收集视图知道每个section
中要显示多少个项目items
。 除了“所有照片”部分,这是如何将PHFetchResult
视为数组的一个很好的示例。
4. Updating the Cell
接下来,用以下代码替换collectionView(_:cellForItemAt :)
中的代码:
// 1
guard let cell = collectionView.dequeueReusableCell(
withReuseIdentifier: AlbumCollectionViewCell.reuseIdentifier,
for: indexPath) as? AlbumCollectionViewCell
else {
fatalError("Unable to dequeue AlbumCollectionViewCell")
}
// 2
var coverAsset: PHAsset?
let sectionType = sections[indexPath.section]
switch sectionType {
// 3
case .all:
coverAsset = allPhotos.firstObject
cell.update(title: sectionType.description, count: allPhotos.count)
// 4
case .smartAlbums, .userCollections:
let collection = sectionType == .smartAlbums ?
smartAlbums[indexPath.item] :
userCollections[indexPath.item]
let fetchedAssets = PHAsset.fetchAssets(in: collection, options: nil)
coverAsset = fetchedAssets.firstObject
cell.update(title: collection.localizedTitle, count: fetchedAssets.count)
}
// 5
guard let asset = coverAsset else { return cell }
cell.photoView.fetchImageAsset(asset, targetSize: cell.bounds.size) { success in
cell.photoView.isHidden = !success
cell.emptyView.isHidden = success
}
return cell
- 1) 首先,使
AlbumCollectionViewCell
出队。 - 2) 创建变量以保存资产(用作专辑封面图像)和分区类型。然后,根据其
section
类型处理单元格。 - 3) 对于“所有照片”
(all photos)
部分,将封面图像设置为allPhotos
的第一个资源。用section
名称和计数更新单元格。 - 4) 因为
smartAlbums
和userCollections
都是集合类型,所以以类似的方式处理它们。首先,从获取结果中获取此单元格和节类型的集合。之后,使用PHAsset
的功能从收藏夹中获取收藏夹的资产。获取收藏集的第一项资源并将其用作封面资源。最后,用相册标题和资源计数更新单元格。 - 5) 如果您没有封面资源,请按原样返回该单元格。否则,请从资产中获取图像。在获取完成块中,使用返回的成功状态在单元格的照片视图和默认的空白视图上设置
hidden
属性。最后,返回单元格。
构建并运行。现在,您将看到“所有照片”(All Photos)
的条目,库中的每个智能相册以及每个用户集合。滚动到底部以查看您的My Cool Pics
相册。
![](https://img.haomeiwen.com/i3691932/bb7967570b393135.png)
还不错,但是封面图像发生了什么? 接下来,您将解决此问题。
Fetching Images from Assets
该默认相册图像有点无聊。 最好能看到相册中的图像。
在上一步中,您调用了fetchImageAsset(_:targetSize:contentMode:options:completionHandler :)
以获取资源的图像。 这是在扩展程序中添加到UIImage
的自定义方法。 目前,它没有任何可提取图像的代码,并且始终返回false
。 要解决此问题,您将使用PHImageManager
。 图像管理器处理从资源中获取图像并缓存结果以便以后快速检索。
打开UIImageView + Extension.swift
并将fetchImageAsset(_:targetSize:contentMode:options:completionHandler :)
中的代码替换为:
// 1
guard let asset = asset else {
completionHandler?(false)
return
}
// 2
let resultHandler: (UIImage?, [AnyHashable: Any]?) -> Void = { image, info in
self.image = image
completionHandler?(true)
}
// 3
PHImageManager.default().requestImage(
for: asset,
targetSize: size,
contentMode: contentMode,
options: options,
resultHandler: resultHandler)
- 1) 如果
asset
为nil
,则返回false
即可完成。 否则,请继续。 - 2) 接下来,创建在图像请求完成时图像管理器将调用的结果处理程序。 将返回的图像分配给
UIImageView
的image
属性。 调用值为true
的完成处理程序,表示请求已完成。 - 3) 最后,从图像管理器请求图像。 提供资产,大小,内容模式,选项和结果处理程序。 所有这些,除了
resultHandler
之外,都是由调用代码提供的。size
是您希望图像返回的尺寸。contentMode
是您希望图像适合尺寸的长宽比的方式。 默认值为aspectFill
。
构建并运行。 您的相册现在有封面图像!
![](https://img.haomeiwen.com/i3691932/e33299fd8787fdf6.png)
如果选择任何相册,则下一个视图为空。 您的下一个任务正在等待。
Displaying Album Assets
显示相册的所有资产仅需要从PHImageManager
请求每个图像即可。 PhotosCollectionViewController
已经设置为使用您刚刚使用的获取图像资产扩展来执行此操作。 要使此工作正常进行,您只需要设置segue
即可传递获取结果。
在AlbumCollectionViewController.swift
中,找到makePhotosCollectionViewController(_ :)
并将其代码替换为:
// 1
guard
let selectedIndexPath = collectionView.indexPathsForSelectedItems?.first
else { return nil }
// 2
let sectionType = sections[selectedIndexPath.section]
let item = selectedIndexPath.item
// 3
let assets: PHFetchResult<PHAsset>
let title: String
switch sectionType {
// 4
case .all:
assets = allPhotos
title = AlbumCollectionSectionType.all.description
// 5
case .smartAlbums, .userCollections:
let album =
sectionType == .smartAlbums ? smartAlbums[item] : userCollections[item]
assets = PHAsset.fetchAssets(in: album, options: nil)
title = album.localizedTitle ?? ""
}
// 6
return PhotosCollectionViewController(assets: assets, title: title, coder: coder)
现在:
- 1) 获取选定的索引路径。
- 2) 获取所选
item
的section
类型和item
。 - 3)
PhotosCollectionViewController
需要资源列表和标题。 - 4) 如果用户选择
all photos
部分,则将allPhotos
用资源assets
并设置标题。 - 5) 如果用户选择了相册或用户集合,则使用
section and item
来获取所选的相册。 从相册中获取资源。 - 6) 创建视图控制器。
构建并运行。 在相册视图中点击所有照片。 现在,您会看到所有照片的集合。
![](https://img.haomeiwen.com/i3691932/a766dd31c0dc6ef6.png)
点击其中一个照片
![](https://img.haomeiwen.com/i3691932/3446e769f1504e66.png)
事情正成为焦点!
Modifying Asset Metadata
1. Change Requests
修改资源的能力是NoirIt
的关键组成部分。 通过允许用户将照片标记为收藏来进行资源修改。 PHAssetChangeRequest
有助于资源的创建,修改和删除。
打开PhotoViewController.swift
并将此代码添加到toggleFavorite()
中:
// 1
let changeHandler: () -> Void = {
let request = PHAssetChangeRequest(for: self.asset)
request.isFavorite = !self.asset.isFavorite
}
// 2
PHPhotoLibrary.shared().performChanges(changeHandler, completionHandler: nil)
- 1) 您创建一个代码块来封装更改。 首先,为资产创建一个更改请求。 接下来,将请求的
isFavorite
属性设置为与当前值相反的属性。 - 2) 通过传递更改请求块来指示照片库执行更改。 您在这里不需要完成处理程序。
接下来,用以下代码替换updateFavoriteButton()
中的代码:
if asset.isFavorite {
favoriteButton.image = UIImage(systemName: "heart.fill")
} else {
favoriteButton.image = UIImage(systemName: "heart")
}
使用isFavorite
属性检查PHAsset
的收藏夹状态,然后将按钮图像设置为空心或实心。
构建并运行。 浏览该应用程序,然后选择您喜欢的照片。 轻按“收藏夹”按钮,然后…什么都没有发生。 那么出了什么问题?
![](https://img.haomeiwen.com/i3691932/7894e83d88550100.png)
2. Photo View Controller Change Observer
PhotoKit
缓存获取请求的结果,以提高性能。 当您点击“收藏夹”按钮时,资源将在库中更新,但是视图控制器的资源副本现在已过期。 控制器需要侦听库的更新,并在必要时更新其资源。 通过使控制器符合PHPhotoLibraryChangeObserver
来执行此操作。
在文件末尾的最后一个花括号之后,添加:
// 1
extension PhotoViewController: PHPhotoLibraryChangeObserver {
func photoLibraryDidChange(_ changeInstance: PHChange) {
// 2
guard
let change = changeInstance.changeDetails(for: asset),
let updatedAsset = change.objectAfterChanges
else { return }
// 3
DispatchQueue.main.sync {
// 4
asset = updatedAsset
imageView.fetchImageAsset(
asset,
targetSize: view.bounds.size
) { [weak self] _ in
guard let self = self else { return }
// 5
self.updateFavoriteButton()
self.updateUndoButton()
}
}
}
}
- 1) 更改观察者只有一种方法:
photoLibraryDidChange(:)
。 每次库更改时,它都会调用此方法。 - 2) 您需要检查更新是否影响您的资源。 通过调用它的
changeDetails(for :)
来使用描述数据库更改的属性changeInstance
并传入您的资源。 如果您的资源不受更改影响,则返回nil
。 否则,您可以通过调用objectAfterChanges
来检索资源的更新版本。 - 3) 由于此方法在后台运行,因此请在主线程上
dispatch
其余逻辑,因为它会更新UI。 - 4) 使用更新后的资源更新控制器的
asset
属性,并获取新图像。 - 5) 刷新UI。
3. Registering the Photo View Controller
仍在PhotoViewController.swift
中,找到viewDidLoad()
并将其添加为最后一行:
PHPhotoLibrary.shared().register(self)
视图控制器必须注册才能接收更新。 在viewDidLoad()
之后,添加:
deinit {
PHPhotoLibrary.shared().unregisterChangeObserver(self)
}
完成后,视图控制器还必须注销。
构建并运行。 导航到您最喜欢的照片之一。 点击“心脏”按钮,心脏就会充满。 再次点击,它会还原。
![](https://img.haomeiwen.com/i3691932/75d576dfe26840ea.png)
但是有一个新问题。 再次点击“收藏夹”按钮,即可充满爱心。 导航回到All Photo
视图,然后再次选择同一张照片。 心脏不再充满,并且如果您选择它,什么也不会发生。 有点不对劲。
4. Photos View Controller Change Observer
PhotosCollectionViewController
也不符合PHPhotoLibraryChangeObserver
。 因此,其资源也已过时。 修复非常简单:您需要使其符合PHPhotoLibraryChangeObserver
。
打开PhotosCollectionViewController.swift
并滚动到文件末尾。 添加以下代码:
extension PhotosCollectionViewController: PHPhotoLibraryChangeObserver {
func photoLibraryDidChange(_ changeInstance: PHChange) {
// 1
guard let change = changeInstance.changeDetails(for: assets) else {
return
}
DispatchQueue.main.sync {
// 2
assets = change.fetchResultAfterChanges
collectionView.reloadData()
}
}
}
这段代码与您在PhotoViewController
中所做的相似,但有一些小区别:
- 1) 由于此视图显示多个资源,因此请求所有资源的详细信息。
- 2) 用更新的获取结果替换
assets
,然后重新加载收集视图。
5. Registering the Photos View Controller
滚动到viewDidLoad()
并将其添加到super.viewDidLoad()
之后:
PHPhotoLibrary.shared().register(self)
与上次一样,视图注册以接收库更新。 在viewDidLoad()
之后添加:
deinit {
PHPhotoLibrary.shared().unregisterChangeObserver(self)
}
该视图还需要注销。
6. Album View Controller Change Observer
在使用它时,应将类似的代码添加到AlbumCollectionViewController.swift
。 如果您不这样做,则一路导航到最后都会遇到类似的问题。 打开AlbumCollectionViewController.swift
并将以下内容添加到文件末尾:
extension AlbumCollectionViewController: PHPhotoLibraryChangeObserver {
func photoLibraryDidChange(_ changeInstance: PHChange) {
DispatchQueue.main.sync {
// 1
if let changeDetails = changeInstance.changeDetails(for: allPhotos) {
allPhotos = changeDetails.fetchResultAfterChanges
}
// 2
if let changeDetails = changeInstance.changeDetails(for: smartAlbums) {
smartAlbums = changeDetails.fetchResultAfterChanges
}
if let changeDetails = changeInstance.changeDetails(for: userCollections) {
userCollections = changeDetails.fetchResultAfterChanges
}
// 4
collectionView.reloadData()
}
}
}
这段代码有些不同,因为您正在检查更改是否影响多个获取结果。
- 1) 如果
allPhotos
中的任何资产发生了更改,请使用新更改来更新属性。 - 2) 如果更改影响智能相册或用户收藏,请也进行更新。
- 3) 最后,重新加载
collection view
。
7. Album View Controller Registration
在AlbumCollectionViewController.swift
中添加代码以注册库更新到viewDidLoad()
的末尾:
PHPhotoLibrary.shared().register(self)
在viewDidLoad()
后,添加:
deinit {
PHPhotoLibrary.shared().unregisterChangeObserver(self)
}
同样,此视图也需要注销。
构建并运行。 点击All Photo
,然后点击照片。 将其标记为收藏,然后一直导航回到主视图。 再次点击All Photo
,然后点击同一张照片。 您会看到它仍被标记为收藏。 导航回到album collection view
。 请注意,“收藏夹”相册计数是最新的,并且已为“收藏夹”设置了封面图像。
![](https://img.haomeiwen.com/i3691932/31cf12b212cbae9e.png)
做得好! 现在,您持久化了对资源的元数据更改,并在每个视图控制器中显示这些更改。
Editing a Photo
打开PhotoViewController.swift
并在声明asset
属性后添加以下内容:
private var editingOutput: PHContentEditingOutput?
PHContentEditingOutput
是一个容器,用于存储对资源的编辑。 稍后您将了解其工作原理。 找到applyFilter()
并将以下代码添加到其中:
// 1
asset.requestContentEditingInput(with: nil) { [weak self] input, _ in
guard let self = self else { return }
// 2
guard let bundleID = Bundle.main.bundleIdentifier else {
fatalError("Error: unable to get bundle identifier")
}
guard let input = input else {
fatalError("Error: cannot get editing input")
}
guard let filterData = Filter.noir.data else {
fatalError("Error: cannot get filter data")
}
// 3
let adjustmentData = PHAdjustmentData(
formatIdentifier: bundleID,
formatVersion: "1.0",
data: filterData)
// 4
self.editingOutput = PHContentEditingOutput(contentEditingInput: input)
guard let editingOutput = self.editingOutput else { return }
editingOutput.adjustmentData = adjustmentData
// 5
let fitleredImage = self.imageView.image?.applyFilter(.noir)
self.imageView.image = fitleredImage
// 6
let jpegData = fitleredImage?.jpegData(compressionQuality: 1.0)
do {
try jpegData?.write(to: editingOutput.renderedContentURL)
} catch {
print(error.localizedDescription)
}
// 7
DispatchQueue.main.async {
self.saveButton.isEnabled = true
}
}
- 1) 编辑是在容器内完成的。输入容器使您可以访问图像。编辑逻辑在完成处理程序内部进行。
- 2) 您需要包标识符,完成处理程序的输入容器和过滤器数据才能继续。
- 3) 调整数据是描述资源变更的一种方式。要创建此数据,请使用唯一的标识符来标识您的更改。
bundle ID
是一个不错的选择。还提供版本号和用于修改图像的数据。 - 4) 您还需要一个输出容器来存储最终的修改图像。为此,您需要传入输入容器。将新的输出容器分配给您在上面创建的
editingOutput
属性。 - 5) 将滤镜应用于图像。描述如何执行此操作不在本文的讨论范围之内,但是您可以在
UIImage + Extensions.swift
中找到代码。 - 6) 为图像创建
JPEG
数据,并将其写入输出容器。 - 7) 最后,启用保存按钮。
构建并运行。选择一张照片。点击Apply Filter
。您的照片现在应该添加了一个漂亮的noir
滤镜。
![](https://img.haomeiwen.com/i3691932/eacfd9d0cb1b5439.png)
点击保存按钮。 没发生什么事。 接下来,您将对其进行修复。
Saving Edits
使用上面创建的编辑输出容器(editing output container)
将更改保存到库。 同样,使用PHAssetChangeRequest
就像您之前更改元数据一样。
仍然在PhotoViewController.swift
中,找到saveImage()
并添加以下内容:
// 1
let changeRequest: () -> Void = { [weak self] in
guard let self = self else { return }
let changeRequest = PHAssetChangeRequest(for: self.asset)
changeRequest.contentEditingOutput = self.editingOutput
}
// 2
let completionHandler: (Bool, Error?) -> Void = { [weak self] success, error in
guard let self = self else { return }
guard success else {
print("Error: cannot edit asset: \(String(describing: error))")
return
}
// 3
self.editingOutput = nil
DispatchQueue.main.async {
self.saveButton.isEnabled = false
}
}
// 4
PHPhotoLibrary.shared().performChanges(
changeRequest,
completionHandler: completionHandler)
- 1) 和以前一样,您可以在代码块中处理更改。 为资源创建
PHAssetChangeRequest
并应用编辑输出容器(editing output container)
。 - 2) 创建完成处理程序以在更改完成后运行。 检查结果是否成功,如果结果不成功,则打印出错误。
- 3) 如果更改成功,则将
nil
分配给容器属性,因为不再需要它。 禁用保存按钮,因为没有其他可保存的内容了。 - 4) 调用库的
performChanges(:completionHandler :)
并传入更改请求和完成处理程序。
构建并运行。 导航到照片,然后点击Apply Filter
按钮。 点击保存按钮。 iOS将显示一个对话框,询问修改照片的权限。 点击Modify
。
![](https://img.haomeiwen.com/i3691932/c88be3d7078f37c7.png)
导航回到All Photos
,然后再次选择照片。 您应该看到修改后的图像已成功保存。
Undoing Edits
照片视图控制器中只剩下一个无法使用的按钮:Undo
。您现在可能已经知道:PHAssetChangeRequest
。
使用存在的资源更改数据来确定Undo
按钮的启用状态。 查找updateUndoButton()
并将其内容替换为:
let adjustmentResources = PHAssetResource.assetResources(for: asset)
.filter { $0.type == .adjustmentData }
undoButton.isEnabled = !adjustmentResources.isEmpty
对资源的每次编辑都会创建一个PHAssetResource
对象。 assetResources(for :)
返回给定资源的资源数组。 通过调整数据的存在来过滤资源。 如果进行编辑,则按钮的isEnabled
属性设置为true
,否则为false
。
现在该添加撤消逻辑了。 找到undo()
并添加以下代码:
// 1
let changeRequest: () -> Void = { [weak self] in
guard let self = self else { return }
let request = PHAssetChangeRequest(for: self.asset)
request.revertAssetContentToOriginal()
}
// 2
let completionHandler: (Bool, Error?) -> Void = { [weak self] success, error in
guard let self = self else { return }
guard success else {
print("Error: can't revert the asset: \(String(describing: error))")
return
}
DispatchQueue.main.async {
self.undoButton.isEnabled = false
}
}
// 3
PHPhotoLibrary.shared().performChanges(
changeRequest,
completionHandler: completionHandler)
现在,这种模式应该已经很熟悉了。
- 1) 创建一个更改请求块以包含更改逻辑。 对于此示例,您将为资源创建一个更改请求并调用
revertAssetContentToOriginal()
。 如您所料,这请求资源变回其原始状态。 这不会影响元数据。 - 2)
completion handler
检查是否有成功的结果,如果结果成功,则禁用撤消按钮。 - 3) 最后,指示库执行更改。
构建并运行。 选择您对其应用滤镜的照片。 点击Undo
。 就像您之前保存资源时一样,iOS会要求用户撤消所有更改的权限。
![](https://img.haomeiwen.com/i3691932/14d8624f8c7b94d2.png)
点击Revert
。 图像变回原始图像。
PhotoKit
提供了更多功能,例如LivePhoto
,视频和照片编辑扩展。 请查看PhotoKit
文档以获取更多信息:
后记
本篇主要讲述了图像的获取、修改、保存、编辑以及撤销等简单示例,感兴趣的给个赞或者关注~~~
![](https://img.haomeiwen.com/i3691932/8c0a9d74118d4f79.png)