PhotoKit框架詳細(xì)解析(二) —— 圖像的獲取肺然、修改蔫缸、保存、編輯以及撤銷等簡單示例(一)

版本記錄

版本號(hào) 時(shí)間
V1.0 2020.09.30 星期三

前言

在我們開發(fā)中總有和系統(tǒng)相冊(cè)進(jìn)行交互的時(shí)候际起,包含圖片和視頻的獲取拾碌,存儲(chǔ),修改等操作街望。這個(gè)模塊我們就一起來看下這個(gè)相關(guān)的框架PhotoKit校翔。感興趣的可以看下面幾篇文章。
1. PhotoKit框架詳細(xì)解析(一) —— 基本概覽(一)

開始

首先看下主要內(nèi)容:

在本教程中灾前,您將學(xué)習(xí)如何使用PhotoKit訪問和修改照片防症,智能相冊(cè)和用戶收藏。 您還將學(xué)習(xí)如何保存和還原對(duì)照片所做的修改豫柬。本片內(nèi)容來自翻譯告希。

下面看一下寫作環(huán)境

Swift 5, iOS 13, Xcode 11

Photos應(yīng)用通過一組稱為PhotoKitAPI在iOS中管理圖像資源。 如果您一直想知道如何構(gòu)建像Photos之類的應(yīng)用程序烧给,或者只是訪問照片庫燕偶,PhotoKit就是答案。 本教程將重點(diǎn)放在iOS上础嫡,但PhotoKit也可用于macOS指么,CatalysttvOS

您將使用NoirIt榴鼎,這是一款可將精美的Noir濾鏡應(yīng)用于照片的應(yīng)用程序伯诬。 為此,您將:

  • 了解PhotoKit的權(quán)限模型巫财。
  • 訪問圖像資源數(shù)據(jù)盗似。
  • 了解如何訪問用戶收藏和智能相冊(cè)數(shù)據(jù)。
  • 顯示圖像資源平项。
  • 修改資源元數(shù)據(jù)赫舒。
  • 編輯資源的圖像。
  • 保存修改后的圖像資源闽瓢。
  • 將修改后的圖像資源還原為原始圖像接癌。

下載入門項(xiàng)目。

首先打開啟動(dòng)文件夾中的NoirIt.xcodeproj扣讼。 展開資源文件夾缺猛,然后打開Main.storyboard

該應(yīng)用程序的布局非常簡單。 有一個(gè)相冊(cè)收集視圖控制器荔燎,一個(gè)照片收集視圖控制器和一個(gè)照片細(xì)節(jié)視圖控制器耻姥。

構(gòu)建并運(yùn)行。

現(xiàn)在看可能不多有咨,但是很快咏闪。

1. Prepping the Photos App

在開始之前,請(qǐng)?jiān)?code>Photos中創(chuàng)建一個(gè)相冊(cè)摔吏,以便以后至少可以在NoirIt中查看一個(gè)相冊(cè)。

  • 1) 打開Photos應(yīng)用纵装。 在模擬器上運(yùn)行時(shí)征讲,照片中存在一個(gè)bug,可能會(huì)導(dǎo)致其崩潰橡娄。 如果是這樣诗箍,請(qǐng)重新打開它。
  • 2) 點(diǎn)擊tab bar上的Albums挽唉。
  • 3) 點(diǎn)按屏幕頂部的+滤祖。
  • 4) 選擇New Album
  • 5) 將其命名為My Cool Pics瓶籽,然后點(diǎn)擊Save匠童。
  • 6) 選擇幾張照片添加到新相冊(cè)中。
  • 7) 導(dǎo)航回到主相冊(cè)視圖并查看您的新相冊(cè)塑顺。

這就是您需要在Photos中執(zhí)行的所有操作汤求。


Getting PhotoKit Permissions

與許多iOS API一樣,PhotoKit使用權(quán)限模型严拒。 它向用戶顯示一個(gè)對(duì)話框扬绪,詢問該應(yīng)用訪問其圖像的權(quán)限。 在深入訪問和修改圖像之前裤唠,必須先獲得許可挤牛。 您可以使用PHPhotoLibrary(共享對(duì)象來管理對(duì)照片庫的訪問)來執(zhí)行此操作。

1. Modifying Info.plist

第一步是向Info.plist添加一個(gè)密鑰种蘸,該密鑰描述為什么要獲得訪問該庫的權(quán)限墓赴。

  • 1) 打開Info.plist。
  • 2) 右鍵單擊Information Property List劈彪,然后選擇Add Row竣蹦。 出現(xiàn)一個(gè)新行。
  • 3) 輸入密鑰NSPhotoLibraryUsageDescription并按Enter沧奴。
  • 4) 在值列中痘括,輸入To add a noir filter。 當(dāng)iOS首次請(qǐng)求訪問該庫的權(quán)限時(shí),它將顯示此信息纲菌。

您的Info.plist應(yīng)該如下所示:

2. Requesting Authorization

打開AlbumCollectionViewController.swift挠日。 找到getPermissionIfNecessary(completionHandler :)并將其實(shí)現(xiàn)替換為:

// 1
guard PHPhotoLibrary.authorizationStatus() != .authorized else {
  completionHandler(true)
  return
}
// 2
PHPhotoLibrary.requestAuthorization { status in
  completionHandler(status == .authorized)
}
  • 1) 您要做的第一件事是從PHPhotoLibrary獲取當(dāng)前的授權(quán)狀態(tài)。 如果已被授權(quán)翰舌,請(qǐng)使用true值調(diào)用完成處理程序嚣潜。
  • 2) 如果先前未授予許可,則請(qǐng)求它椅贱。 請(qǐng)求授權(quán)時(shí)懂算,iOS會(huì)顯示一個(gè)警告對(duì)話框,詢問權(quán)限庇麦。 它將狀態(tài)作為PHAuthorizationStatus對(duì)象傳遞回其完成處理程序中计技。 調(diào)用完成處理程序,如果狀態(tài)值是.authorized山橄,則返回true垮媒,否則返回false

注意:PHAuthorizationStatus是一個(gè)枚舉航棱,它也可以返回notDefineded睡雇,restricteddeniediOS 14的新增內(nèi)容limited饮醇。 您可能需要檢查并適當(dāng)處理它們它抱。 現(xiàn)在,讓NoirIt保持簡單朴艰。

viewDidLoad()已經(jīng)在調(diào)用此方法抗愁,因此進(jìn)行構(gòu)建并運(yùn)行。 當(dāng)NoirIt啟動(dòng)時(shí)呵晚,iOS會(huì)請(qǐng)求訪問照片庫的權(quán)限蜘腌。 如果您使用的是iOS 13,請(qǐng)點(diǎn)擊OK饵隙,或者在iOS 14上撮珠,點(diǎn)擊Allow Access to All Photos


Understanding Assets

即使您最終會(huì)獲得圖像金矛,也必須了解您主要使用PhotoKit中的資源芯急。 考慮一下您如何與Photos應(yīng)用進(jìn)行交互。 當(dāng)然驶俊,您可以查看圖像娶耍,但其中也包含元數(shù)據(jù),例如收藏夾和地理編碼的位置數(shù)據(jù)饼酿。 不僅限于圖像榕酒。 Photos包含LivePhotos和視頻胚膊。 將這些東西塞進(jìn)UIImage沒有任何意義。 這就是PHAsset用到的地方想鹰。

PHAsset是描述圖像紊婉,LivePhoto或視頻的元數(shù)據(jù)。 它是不可變的辑舷,不包含圖像本身喻犁,但確實(shí)提供了獲取圖像所需的信息。 它還包含大量信息何缓,例如創(chuàng)建和修改日期肢础,位置數(shù)據(jù),收藏夾和隱藏狀態(tài)碌廓,突發(fā)數(shù)據(jù)等等乔妈。 就像您很快就會(huì)看到的那樣,PHAsset是真正的主力軍氓皱。

有時(shí)您需要處理一組資源。 這些通常作為PHAssetCollection對(duì)象返回勃刨。

1. Asset Data Models

打開AlbumCollectionViewController.swift波材。 在文件頂部附近,在sections屬性的聲明下添加以下內(nèi)容:

private var allPhotos = PHFetchResult<PHAsset>()
private var smartAlbums = PHFetchResult<PHAssetCollection>()
private var userCollections = PHFetchResult<PHAssetCollection>()

您可能會(huì)對(duì)自己說:“嘿身隐,self廷区,這些PHFetchResult是什么? 我以為我正在獲取PHAssetsPHAssetCollections贾铝?” PHFetchResult的簡化思考方式是將其視為一個(gè)數(shù)組隙轻,從本質(zhì)上講,它是一個(gè)數(shù)組垢揩。 它包含所有相同的數(shù)組方法和約定玖绿,例如count()index(of :)。 另外叁巨,它可以智能地處理數(shù)據(jù)的獲取斑匪,緩存和根據(jù)需要重新獲取。 如果您將PHFetchResult視為資源或集合的智能陣列锋勺,就可以了。 這些屬性是應(yīng)用程序的數(shù)據(jù)存儲(chǔ)。

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) 提取資源時(shí)童擎,您可以應(yīng)用一組選項(xiàng)來指示結(jié)果的排序,過濾和管理苏章。 在這里寂嘉,您將創(chuàng)建一個(gè)排序描述符(sort descriptor)奏瞬,該描述符按創(chuàng)建日期從最新到最舊的順序?qū)Y源進(jìn)行排序。
  • 2) PHAsset提供了用于獲取資源并將結(jié)果作為PHFetchResult返回的功能垫释。 在這里丝格,您將上面創(chuàng)建的選項(xiàng)傳遞給它,并將結(jié)果分配給allPhotos棵譬。
  • 3) Photos應(yīng)用程序會(huì)自動(dòng)創(chuàng)建智能相冊(cè)显蝌,例如“收藏夾”和“最新記錄”是一組資源,因此屬于PHAssetCollection對(duì)象订咸。 在這里曼尊,您可以獲取智能專輯集。 您將不會(huì)對(duì)它們進(jìn)行排序脏嚷,因此optionsnil骆撇。
  • 4) 訪問用戶創(chuàng)建的相冊(cè)的方法類似,不同之處在于您獲取.album類型父叙。

現(xiàn)在填充了數(shù)據(jù)存儲(chǔ)神郊,下一個(gè)任務(wù)是更新UI。

3. Prepping the Collection View

您現(xiàn)在擁有資源趾唱,是時(shí)候?qū)λ鼈冞M(jìn)行一些處理了涌乳。 在類末尾添加以下內(nèi)容:

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

在這里,您可以返回每個(gè)section中的items數(shù)甜癞,以便收集視圖知道每個(gè)section中要顯示多少個(gè)項(xiàng)目items夕晓。 除了“所有照片”部分,這是如何將PHFetchResult視為數(shù)組的一個(gè)很好的示例悠咱。

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出隊(duì)析既。
  • 2) 創(chuàng)建變量以保存資產(chǎn)(用作專輯封面圖像)和分區(qū)類型躬贡。然后,根據(jù)其section類型處理單元格眼坏。
  • 3) 對(duì)于“所有照片”(all photos)部分逗宜,將封面圖像設(shè)置為allPhotos的第一個(gè)資源。用section名稱和計(jì)數(shù)更新單元格空骚。
  • 4) 因?yàn)?code>smartAlbums和userCollections都是集合類型纺讲,所以以類似的方式處理它們。首先囤屹,從獲取結(jié)果中獲取此單元格和節(jié)類型的集合熬甚。之后,使用PHAsset的功能從收藏夾中獲取收藏夾的資產(chǎn)肋坚。獲取收藏集的第一項(xiàng)資源并將其用作封面資源乡括。最后肃廓,用相冊(cè)標(biāo)題和資源計(jì)數(shù)更新單元格。
  • 5) 如果您沒有封面資源诲泌,請(qǐng)按原樣返回該單元格盲赊。否則,請(qǐng)從資產(chǎn)中獲取圖像敷扫。在獲取完成塊中哀蘑,使用返回的成功狀態(tài)在單元格的照片視圖和默認(rèn)的空白視圖上設(shè)置hidden屬性。最后葵第,返回單元格绘迁。

構(gòu)建并運(yùn)行。現(xiàn)在卒密,您將看到“所有照片”(All Photos)的條目缀台,庫中的每個(gè)智能相冊(cè)以及每個(gè)用戶集合。滾動(dòng)到底部以查看您的My Cool Pics相冊(cè)哮奇。

還不錯(cuò)膛腐,但是封面圖像發(fā)生了什么? 接下來鼎俘,您將解決此問題哲身。


Fetching Images from Assets

該默認(rèn)相冊(cè)圖像有點(diǎn)無聊。 最好能看到相冊(cè)中的圖像而芥。

在上一步中,您調(diào)用了fetchImageAsset(_:targetSize:contentMode:options:completionHandler :)以獲取資源的圖像膀值。 這是在擴(kuò)展程序中添加到UIImage的自定義方法棍丐。 目前,它沒有任何可提取圖像的代碼沧踏,并且始終返回false歌逢。 要解決此問題,您將使用PHImageManager翘狱。 圖像管理器處理從資源中獲取圖像并緩存結(jié)果以便以后快速檢索秘案。

打開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即可完成潦匈。 否則阱高,請(qǐng)繼續(xù)。
  • 2) 接下來茬缩,創(chuàng)建在圖像請(qǐng)求完成時(shí)圖像管理器將調(diào)用的結(jié)果處理程序赤惊。 將返回的圖像分配給UIImageViewimage屬性。 調(diào)用值為true的完成處理程序凰锡,表示請(qǐng)求已完成未舟。
  • 3) 最后圈暗,從圖像管理器請(qǐng)求圖像。 提供資產(chǎn)裕膀,大小员串,內(nèi)容模式,選項(xiàng)和結(jié)果處理程序昼扛。 所有這些寸齐,除了resultHandler之外,都是由調(diào)用代碼提供的野揪。 size是您希望圖像返回的尺寸访忿。 contentMode是您希望圖像適合尺寸的長寬比的方式。 默認(rèn)值為aspectFill斯稳。

構(gòu)建并運(yùn)行海铆。 您的相冊(cè)現(xiàn)在有封面圖像!

如果選擇任何相冊(cè)挣惰,則下一個(gè)視圖為空卧斟。 您的下一個(gè)任務(wù)正在等待。


Displaying Album Assets

顯示相冊(cè)的所有資產(chǎn)僅需要從PHImageManager請(qǐng)求每個(gè)圖像即可憎茂。 PhotosCollectionViewController已經(jīng)設(shè)置為使用您剛剛使用的獲取圖像資產(chǎn)擴(kuò)展來執(zhí)行此操作珍语。 要使此工作正常進(jìn)行,您只需要設(shè)置segue即可傳遞獲取結(jié)果竖幔。

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)

現(xiàn)在:

  • 1) 獲取選定的索引路徑。
  • 2) 獲取所選itemsection類型和item拳氢。
  • 3) PhotosCollectionViewController需要資源列表和標(biāo)題募逞。
  • 4) 如果用戶選擇all photos部分,則將allPhotos用資源assets并設(shè)置標(biāo)題馋评。
  • 5) 如果用戶選擇了相冊(cè)或用戶集合放接,則使用section and item來獲取所選的相冊(cè)。 從相冊(cè)中獲取資源留特。
  • 6) 創(chuàng)建視圖控制器纠脾。

構(gòu)建并運(yùn)行。 在相冊(cè)視圖中點(diǎn)擊所有照片蜕青。 現(xiàn)在苟蹈,您會(huì)看到所有照片的集合。

點(diǎn)擊其中一個(gè)照片

事情正成為焦點(diǎn)右核!


Modifying Asset Metadata

1. Change Requests

修改資源的能力是NoirIt的關(guān)鍵組成部分汉操。 通過允許用戶將照片標(biāo)記為收藏來進(jìn)行資源修改。 PHAssetChangeRequest有助于資源的創(chuàng)建蒙兰,修改和刪除磷瘤。

打開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) 您創(chuàng)建一個(gè)代碼塊來封裝更改芒篷。 首先,為資產(chǎn)創(chuàng)建一個(gè)更改請(qǐng)求采缚。 接下來针炉,將請(qǐng)求的isFavorite屬性設(shè)置為與當(dāng)前值相反的屬性。
  • 2) 通過傳遞更改請(qǐng)求塊來指示照片庫執(zhí)行更改扳抽。 您在這里不需要完成處理程序篡帕。

接下來,用以下代碼替換updateFavoriteButton()中的代碼:

if asset.isFavorite {
  favoriteButton.image = UIImage(systemName: "heart.fill")
} else {
  favoriteButton.image = UIImage(systemName: "heart")
}

使用isFavorite屬性檢查PHAsset的收藏夾狀態(tài)贸呢,然后將按鈕圖像設(shè)置為空心或?qū)嵭摹?/p>

構(gòu)建并運(yùn)行镰烧。 瀏覽該應(yīng)用程序,然后選擇您喜歡的照片楞陷。 輕按“收藏夾”按鈕怔鳖,然后…什么都沒有發(fā)生。 那么出了什么問題固蛾?

2. Photo View Controller Change Observer

PhotoKit緩存獲取請(qǐng)求的結(jié)果结执,以提高性能。 當(dāng)您點(diǎn)擊“收藏夾”按鈕時(shí)艾凯,資源將在庫中更新献幔,但是視圖控制器的資源副本現(xiàn)在已過期。 控制器需要偵聽庫的更新趾诗,并在必要時(shí)更新其資源蜡感。 通過使控制器符合PHPhotoLibraryChangeObserver來執(zhí)行此操作。

在文件末尾的最后一個(gè)花括號(hào)之后恃泪,添加:

// 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(:)郑兴。 每次庫更改時(shí),它都會(huì)調(diào)用此方法悟泵。
  • 2) 您需要檢查更新是否影響您的資源杈笔。 通過調(diào)用它的changeDetails(for :)來使用描述數(shù)據(jù)庫更改的屬性changeInstance并傳入您的資源闪水。 如果您的資源不受更改影響糕非,則返回nil。 否則球榆,您可以通過調(diào)用objectAfterChanges來檢索資源的更新版本朽肥。
  • 3) 由于此方法在后臺(tái)運(yùn)行,因此請(qǐng)?jiān)谥骶€程上dispatch其余邏輯持钉,因?yàn)樗鼤?huì)更新UI衡招。
  • 4) 使用更新后的資源更新控制器的asset屬性,并獲取新圖像每强。
  • 5) 刷新UI始腾。

3. Registering the Photo View Controller

仍在PhotoViewController.swift中州刽,找到viewDidLoad()并將其添加為最后一行:

PHPhotoLibrary.shared().register(self)

視圖控制器必須注冊(cè)才能接收更新。 在viewDidLoad()之后浪箭,添加:

deinit {
  PHPhotoLibrary.shared().unregisterChangeObserver(self)
}

完成后穗椅,視圖控制器還必須注銷。

構(gòu)建并運(yùn)行奶栖。 導(dǎo)航到您最喜歡的照片之一匹表。 點(diǎn)擊“心臟”按鈕,心臟就會(huì)充滿宣鄙。 再次點(diǎn)擊袍镀,它會(huì)還原。

但是有一個(gè)新問題冻晤。 再次點(diǎn)擊“收藏夾”按鈕苇羡,即可充滿愛心。 導(dǎo)航回到All Photo視圖明也,然后再次選擇同一張照片宣虾。 心臟不再充滿,并且如果您選擇它温数,什么也不會(huì)發(fā)生绣硝。 有點(diǎn)不對(duì)勁。

4. Photos View Controller Change Observer

PhotosCollectionViewController也不符合PHPhotoLibraryChangeObserver撑刺。 因此鹉胖,其資源也已過時(shí)。 修復(fù)非常簡單:您需要使其符合PHPhotoLibraryChangeObserver够傍。

打開PhotosCollectionViewController.swift并滾動(dòng)到文件末尾甫菠。 添加以下代碼:

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中所做的相似,但有一些小區(qū)別:

  • 1) 由于此視圖顯示多個(gè)資源冕屯,因此請(qǐng)求所有資源的詳細(xì)信息寂诱。
  • 2) 用更新的獲取結(jié)果替換assets,然后重新加載收集視圖安聘。

5. Registering the Photos View Controller

滾動(dòng)到viewDidLoad()并將其添加到super.viewDidLoad()之后:

PHPhotoLibrary.shared().register(self)

與上次一樣痰洒,視圖注冊(cè)以接收庫更新。 在viewDidLoad()之后添加:

deinit {
  PHPhotoLibrary.shared().unregisterChangeObserver(self)
}

該視圖還需要注銷浴韭。

6. Album View Controller Change Observer

在使用它時(shí)丘喻,應(yīng)將類似的代碼添加到AlbumCollectionViewController.swift。 如果您不這樣做念颈,則一路導(dǎo)航到最后都會(huì)遇到類似的問題泉粉。 打開AlbumCollectionViewController.swift并將以下內(nèi)容添加到文件末尾:

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

這段代碼有些不同,因?yàn)槟跈z查更改是否影響多個(gè)獲取結(jié)果。

  • 1) 如果allPhotos中的任何資產(chǎn)發(fā)生了更改嗡靡,請(qǐng)使用新更改來更新屬性跺撼。
  • 2) 如果更改影響智能相冊(cè)或用戶收藏,請(qǐng)也進(jìn)行更新讨彼。
  • 3) 最后财边,重新加載collection view

7. Album View Controller Registration

AlbumCollectionViewController.swift中添加代碼以注冊(cè)庫更新到viewDidLoad()的末尾:

PHPhotoLibrary.shared().register(self)

viewDidLoad()后点骑,添加:

deinit {
  PHPhotoLibrary.shared().unregisterChangeObserver(self)
}

同樣酣难,此視圖也需要注銷。

構(gòu)建并運(yùn)行黑滴。 點(diǎn)擊All Photo憨募,然后點(diǎn)擊照片。 將其標(biāo)記為收藏袁辈,然后一直導(dǎo)航回到主視圖菜谣。 再次點(diǎn)擊All Photo,然后點(diǎn)擊同一張照片晚缩。 您會(huì)看到它仍被標(biāo)記為收藏尾膊。 導(dǎo)航回到album collection view。 請(qǐng)注意荞彼,“收藏夾”相冊(cè)計(jì)數(shù)是最新的冈敛,并且已為“收藏夾”設(shè)置了封面圖像。

做得好鸣皂! 現(xiàn)在抓谴,您持久化了對(duì)資源的元數(shù)據(jù)更改,并在每個(gè)視圖控制器中顯示這些更改寞缝。


Editing a Photo

打開PhotoViewController.swift并在聲明asset屬性后添加以下內(nèi)容:

private var editingOutput: PHContentEditingOutput?

PHContentEditingOutput是一個(gè)容器癌压,用于存儲(chǔ)對(duì)資源的編輯。 稍后您將了解其工作原理荆陆。 找到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) 編輯是在容器內(nèi)完成的滩届。輸入容器使您可以訪問圖像。編輯邏輯在完成處理程序內(nèi)部進(jìn)行被啼。
  • 2) 您需要包標(biāo)識(shí)符帜消,完成處理程序的輸入容器和過濾器數(shù)據(jù)才能繼續(xù)汹碱。
  • 3) 調(diào)整數(shù)據(jù)是描述資源變更的一種方式褂删。要?jiǎng)?chuàng)建此數(shù)據(jù)难衰,請(qǐng)使用唯一的標(biāo)識(shí)符來標(biāo)識(shí)您的更改鳄虱。bundle ID是一個(gè)不錯(cuò)的選擇。還提供版本號(hào)和用于修改圖像的數(shù)據(jù)楼镐。
  • 4) 您還需要一個(gè)輸出容器來存儲(chǔ)最終的修改圖像。為此,您需要傳入輸入容器隔崎。將新的輸出容器分配給您在上面創(chuàng)建的editingOutput屬性今艺。
  • 5) 將濾鏡應(yīng)用于圖像。描述如何執(zhí)行此操作不在本文的討論范圍之內(nèi)爵卒,但是您可以在UIImage + Extensions.swift中找到代碼虚缎。
  • 6) 為圖像創(chuàng)建JPEG數(shù)據(jù),并將其寫入輸出容器钓株。
  • 7) 最后实牡,啟用保存按鈕。

構(gòu)建并運(yùn)行轴合。選擇一張照片铲掐。點(diǎn)擊Apply Filter。您的照片現(xiàn)在應(yīng)該添加了一個(gè)漂亮的noir濾鏡值桩。

點(diǎn)擊保存按鈕摆霉。 沒發(fā)生什么事。 接下來奔坟,您將對(duì)其進(jìn)行修復(fù)携栋。


Saving Edits

使用上面創(chuàng)建的編輯輸出容器(editing output container)將更改保存到庫。 同樣咳秉,使用PHAssetChangeRequest就像您之前更改元數(shù)據(jù)一樣婉支。

仍然在PhotoViewController.swift中,找到saveImage()并添加以下內(nèi)容:

// 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) 和以前一樣澜建,您可以在代碼塊中處理更改向挖。 為資源創(chuàng)建PHAssetChangeRequest并應(yīng)用編輯輸出容器(editing output container)
  • 2) 創(chuàng)建完成處理程序以在更改完成后運(yùn)行炕舵。 檢查結(jié)果是否成功何之,如果結(jié)果不成功,則打印出錯(cuò)誤咽筋。
  • 3) 如果更改成功溶推,則將nil分配給容器屬性,因?yàn)椴辉傩枰?禁用保存按鈕奸攻,因?yàn)闆]有其他可保存的內(nèi)容了蒜危。
  • 4) 調(diào)用庫的performChanges(:completionHandler :)并傳入更改請(qǐng)求和完成處理程序。

構(gòu)建并運(yùn)行睹耐。 導(dǎo)航到照片辐赞,然后點(diǎn)擊Apply Filter按鈕。 點(diǎn)擊保存按鈕硝训。 iOS將顯示一個(gè)對(duì)話框响委,詢問修改照片的權(quán)限新思。 點(diǎn)擊Modify

導(dǎo)航回到All Photos晃酒,然后再次選擇照片。 您應(yīng)該看到修改后的圖像已成功保存窄绒。


Undoing Edits

照片視圖控制器中只剩下一個(gè)無法使用的按鈕:Undo贝次。您現(xiàn)在可能已經(jīng)知道:PHAssetChangeRequest

使用存在的資源更改數(shù)據(jù)來確定Undo按鈕的啟用狀態(tài)彰导。 查找updateUndoButton()并將其內(nèi)容替換為:

let adjustmentResources = PHAssetResource.assetResources(for: asset)
  .filter { $0.type == .adjustmentData }
undoButton.isEnabled = !adjustmentResources.isEmpty

對(duì)資源的每次編輯都會(huì)創(chuàng)建一個(gè)PHAssetResource對(duì)象蛔翅。 assetResources(for :)返回給定資源的資源數(shù)組。 通過調(diào)整數(shù)據(jù)的存在來過濾資源位谋。 如果進(jìn)行編輯山析,則按鈕的isEnabled屬性設(shè)置為true,否則為false掏父。

現(xiàn)在該添加撤消邏輯了笋轨。 找到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)

現(xiàn)在,這種模式應(yīng)該已經(jīng)很熟悉了赊淑。

  • 1) 創(chuàng)建一個(gè)更改請(qǐng)求塊以包含更改邏輯爵政。 對(duì)于此示例,您將為資源創(chuàng)建一個(gè)更改請(qǐng)求并調(diào)用revertAssetContentToOriginal()陶缺。 如您所料钾挟,這請(qǐng)求資源變回其原始狀態(tài)。 這不會(huì)影響元數(shù)據(jù)饱岸。
  • 2) completion handler檢查是否有成功的結(jié)果掺出,如果結(jié)果成功,則禁用撤消按鈕苫费。
  • 3) 最后汤锨,指示庫執(zhí)行更改。

構(gòu)建并運(yùn)行百框。 選擇您對(duì)其應(yīng)用濾鏡的照片泥畅。 點(diǎn)擊Undo。 就像您之前保存資源時(shí)一樣琅翻,iOS會(huì)要求用戶撤消所有更改的權(quán)限位仁。

點(diǎn)擊Revert。 圖像變回原始圖像方椎。

PhotoKit提供了更多功能聂抢,例如LivePhoto,視頻和照片編輯擴(kuò)展棠众。 請(qǐng)查看PhotoKit文檔以獲取更多信息:

后記

本篇主要講述了圖像的獲取琳疏、修改有决、保存、編輯以及撤銷等簡單示例空盼,感興趣的給個(gè)贊或者關(guān)注~~~

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末书幕,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子揽趾,更是在濱河造成了極大的恐慌台汇,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,884評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件篱瞎,死亡現(xiàn)場離奇詭異苟呐,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)俐筋,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,755評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門牵素,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人澄者,你說我怎么就攤上這事笆呆。” “怎么了粱挡?”我有些...
    開封第一講書人閱讀 158,369評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵腰奋,是天一觀的道長。 經(jīng)常有香客問我抱怔,道長劣坊,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,799評(píng)論 1 285
  • 正文 為了忘掉前任屈留,我火速辦了婚禮局冰,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘灌危。我一直安慰自己康二,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,910評(píng)論 6 386
  • 文/花漫 我一把揭開白布勇蝙。 她就那樣靜靜地躺著沫勿,像睡著了一般。 火紅的嫁衣襯著肌膚如雪味混。 梳的紋絲不亂的頭發(fā)上产雹,一...
    開封第一講書人閱讀 50,096評(píng)論 1 291
  • 那天,我揣著相機(jī)與錄音翁锡,去河邊找鬼蔓挖。 笑死,一個(gè)胖子當(dāng)著我的面吹牛馆衔,可吹牛的內(nèi)容都是我干的瘟判。 我是一名探鬼主播怨绣,決...
    沈念sama閱讀 39,159評(píng)論 3 411
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼拷获!你這毒婦竟也來了篮撑?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,917評(píng)論 0 268
  • 序言:老撾萬榮一對(duì)情侶失蹤匆瓜,失蹤者是張志新(化名)和其女友劉穎赢笨,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體陕壹,經(jīng)...
    沈念sama閱讀 44,360評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡质欲,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,673評(píng)論 2 327
  • 正文 我和宋清朗相戀三年树埠,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了糠馆。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,814評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡怎憋,死狀恐怖又碌,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情绊袋,我是刑警寧澤毕匀,帶...
    沈念sama閱讀 34,509評(píng)論 4 334
  • 正文 年R本政府宣布,位于F島的核電站癌别,受9級(jí)特大地震影響皂岔,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜展姐,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,156評(píng)論 3 317
  • 文/蒙蒙 一躁垛、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧圾笨,春花似錦教馆、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,882評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至板鬓,卻和暖如春悲敷,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背俭令。 一陣腳步聲響...
    開封第一講書人閱讀 32,123評(píng)論 1 267
  • 我被黑心中介騙來泰國打工镀迂, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人唤蔗。 一個(gè)月前我還...
    沈念sama閱讀 46,641評(píng)論 2 362
  • 正文 我出身青樓探遵,卻偏偏與公主長得像窟赏,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子箱季,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,728評(píng)論 2 351