美文网首页
PhotoKit框架详细解析(二) —— 图像的获取、修改、保存

PhotoKit框架详细解析(二) —— 图像的获取、修改、保存

作者: 刀客传奇 | 来源:发表于2020-09-30 18:39 被阅读0次

版本记录

版本号 时间
V1.0 2020.09.30 星期三

前言

在我们开发中总有和系统相册进行交互的时候,包含图片和视频的获取,存储,修改等操作。这个模块我们就一起来看下这个相关的框架PhotoKit。感兴趣的可以看下面几篇文章。
1. PhotoKit框架详细解析(一) —— 基本概览(一)

开始

首先看下主要内容:

在本教程中,您将学习如何使用PhotoKit访问和修改照片,智能相册和用户收藏。 您还将学习如何保存和还原对照片所做的修改。本片内容来自翻译

下面看一下写作环境

Swift 5, iOS 13, Xcode 11

Photos应用通过一组称为PhotoKitAPI在iOS中管理图像资源。 如果您一直想知道如何构建像Photos之类的应用程序,或者只是访问照片库,PhotoKit就是答案。 本教程将重点放在iOS上,但PhotoKit也可用于macOSCatalysttvOS

您将使用NoirIt,这是一款可将精美的Noir滤镜应用于照片的应用程序。 为此,您将:

  • 了解PhotoKit的权限模型。
  • 访问图像资源数据。
  • 了解如何访问用户收藏和智能相册数据。
  • 显示图像资源。
  • 修改资源元数据。
  • 编辑资源的图像。
  • 保存修改后的图像资源。
  • 将修改后的图像资源还原为原始图像。

下载入门项目。

首先打开启动文件夹中的NoirIt.xcodeproj。 展开资源文件夹,然后打开Main.storyboard

该应用程序的布局非常简单。 有一个相册收集视图控制器,一个照片收集视图控制器和一个照片细节视图控制器。

构建并运行。

现在看可能不多,但是很快。

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) 导航回到主相册视图并查看您的新相册。

这就是您需要在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应该如下所示:

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是一个枚举,它也可以返回notDefinededrestricteddeniediOS 14的新增内容limited。 您可能需要检查并适当处理它们。 现在,让NoirIt保持简单。

viewDidLoad()已经在调用此方法,因此进行构建并运行。 当NoirIt启动时,iOS会请求访问照片库的权限。 如果您使用的是iOS 13,请点击OK,或者在iOS 14上,点击Allow Access to All Photos


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是什么? 我以为我正在获取PHAssetsPHAssetCollections?” 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对象。 在这里,您可以获取智能专辑集。 您将不会对它们进行排序,因此optionsnil
  • 4) 访问用户创建的相册的方法类似,不同之处在于您获取.album类型。

现在填充了数据存储,下一个任务是更新UI。

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) 因为smartAlbumsuserCollections都是集合类型,所以以类似的方式处理它们。首先,从获取结果中获取此单元格和节类型的集合。之后,使用PHAsset的功能从收藏夹中获取收藏夹的资产。获取收藏集的第一项资源并将其用作封面资源。最后,用相册标题和资源计数更新单元格。
  • 5) 如果您没有封面资源,请按原样返回该单元格。否则,请从资产中获取图像。在获取完成块中,使用返回的成功状态在单元格的照片视图和默认的空白视图上设置hidden属性。最后,返回单元格。

构建并运行。现在,您将看到“所有照片”(All Photos)的条目,库中的每个智能相册以及每个用户集合。滚动到底部以查看您的My Cool Pics相册。

还不错,但是封面图像发生了什么? 接下来,您将解决此问题。


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) 如果assetnil,则返回false即可完成。 否则,请继续。
  • 2) 接下来,创建在图像请求完成时图像管理器将调用的结果处理程序。 将返回的图像分配给UIImageViewimage属性。 调用值为true的完成处理程序,表示请求已完成。
  • 3) 最后,从图像管理器请求图像。 提供资产,大小,内容模式,选项和结果处理程序。 所有这些,除了resultHandler之外,都是由调用代码提供的。 size是您希望图像返回的尺寸。 contentMode是您希望图像适合尺寸的长宽比的方式。 默认值为aspectFill

构建并运行。 您的相册现在有封面图像!

如果选择任何相册,则下一个视图为空。 您的下一个任务正在等待。


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) 获取所选itemsection类型和item
  • 3) PhotosCollectionViewController需要资源列表和标题。
  • 4) 如果用户选择all photos部分,则将allPhotos用资源assets并设置标题。
  • 5) 如果用户选择了相册或用户集合,则使用section and item来获取所选的相册。 从相册中获取资源。
  • 6) 创建视图控制器。

构建并运行。 在相册视图中点击所有照片。 现在,您会看到所有照片的集合。

点击其中一个照片

事情正成为焦点!


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的收藏夹状态,然后将按钮图像设置为空心或实心。

构建并运行。 浏览该应用程序,然后选择您喜欢的照片。 轻按“收藏夹”按钮,然后…什么都没有发生。 那么出了什么问题?

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)
}

完成后,视图控制器还必须注销。

构建并运行。 导航到您最喜欢的照片之一。 点击“心脏”按钮,心脏就会充满。 再次点击,它会还原。

但是有一个新问题。 再次点击“收藏夹”按钮,即可充满爱心。 导航回到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。 请注意,“收藏夹”相册计数是最新的,并且已为“收藏夹”设置了封面图像。

做得好! 现在,您持久化了对资源的元数据更改,并在每个视图控制器中显示这些更改。


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滤镜。

点击保存按钮。 没发生什么事。 接下来,您将对其进行修复。


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

导航回到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会要求用户撤消所有更改的权限。

点击Revert。 图像变回原始图像。

PhotoKit提供了更多功能,例如LivePhoto,视频和照片编辑扩展。 请查看PhotoKit文档以获取更多信息:

后记

本篇主要讲述了图像的获取、修改、保存、编辑以及撤销等简单示例,感兴趣的给个赞或者关注~~~

相关文章

网友评论

      本文标题:PhotoKit框架详细解析(二) —— 图像的获取、修改、保存

      本文链接:https://www.haomeiwen.com/subject/qgwuuktx.html