★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
➤微信公眾號:山青詠芝(let_us_code)
➤博客園地址:山青詠芝(https://www.cnblogs.com/strengthen/)
➤GitHub地址:https://github.com/strengthen/LeetCode
➤原文地址:https://www.cnblogs.com/strengthen/p/12604638.html
➤原文已修改更新!強烈建議點擊原文地址閱讀!支持作者!支持原創!
★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
1.介紹
PhotoKit
是App在使用、管理圖片和視頻的框架,而且還包括了iCloud上面的圖片以及實時照片.
2.概要
- 在iOS中,
PhotoKit
支持應用構建照片以及編輯擴展,還可以直接訪問管理照片和視頻元資源以及元資源集合例如專輯,時刻和共享相冊.

3.官方Demo-PhotoBrowse
Browsing and Modifying Photo Albums
此示例演示如何使用自定義實現類似的布局.它使用PhotoKit
獲取資源縮略圖,然后將其顯示為單個照片,視頻或動態圖片。此外示例應用程序PhotoBrowse
還演示了如何將用戶的照片整理到相冊和內置集合中,例如最近添加的和收藏夾.它支持專輯的創建,刪除,修改,以及個人資源的編輯和收藏.
3.1 獲取所有相冊,所有照片請求
let allPhotosOptions = PHFetchOptions() allPhotosOptions.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: true)] // 獲取所有照片 allPhotos = PHAsset.fetchAssets(with: allPhotosOptions) // 獲取智能相冊 smartAlbums = PHAssetCollection.fetchAssetCollections(with: .smartAlbum, subtype: .albumRegular, options: nil) // 獲取用戶創建的所有相冊 userCollections = PHCollectionList.fetchTopLevelUserCollections(with: nil) 復制代碼
獲取操作是由上面描述的實體的類方法實現的.要使用哪個類/方法,取決於問題所在范圍和你展示與遍歷照片庫的方式.所有獲取方法的命名都是相似的:class func fetchXXX(..., options: PHFetchOptions) -> PHFetchResult
.options
參數給了我們一個對結果進行過濾和排序的途徑,這和 NSFetchRequest
的 predicate
與 sortDescriptors
參數類似.


3.2 觀察變化
首先,你需要通過共享的
PHPhotoLibrary
對象,用registerChangeObserver(...)
方法注冊一個變化觀察者 (這個觀察者要遵從PHPhotoLibraryChangeObserver
協議).只要另一個應用或者用戶在照片庫中做的修改影響了你在變化前獲取的任何資源或資源集合的話,變化觀察者的photoLibraryDidChange(...)
方法都會被調用.這個方法只有一個PHChange
類型的參數,你可以用它來驗證這些變化是否和你所感興趣的獲取對象有關聯.
更新獲取的結果
PHChange
提供了幾個方法,讓你可以通過傳入任何你感興趣的PHObject
對象或PHFetchResult
對象來追蹤它們的變化.這幾個方法是changeDetailsForObject(...)
和changeDetailsForFetchResult(...)
.如果沒有任何變化,這些方法會返回nil
,否則你可以借助PHObjectChangeDetails
或PHFetchResultChangeDetails
對象來觀察這些變化。
PHObjectChangeDetails
提供了一個對最新的照片實體對象的引用,以及告訴你對象的圖像數據是否曾變化過、對象是否曾被刪除過的布爾值.PHFetchResultChangeDetails
封裝了施加在你之前通過獲取所得到的PHFetchResult
上的變化的信息.PHFetchResultChangeDetails
是為了盡可能簡化CollectionView
或TableView
的更新操作而設計的.它的屬性恰好映射到你在使用一個典型的CollectionView
的update handler
時所需要的信息.注意,若要正確的更新UITableView/UICollectionView
,你必須以正確順序來處理變化,那就是:RICE —— removedIndexes,insertedIndexes,changedIndexes,enumerateMovesWithBlock
(如果hasMoves
為true
的話).另外,PHFetchResultChangeDetails
的hasIncrementalChanges
屬性可以被設置成false
,這意味着舊的獲取結果應該全部被新的值代替.這種情況下,你應該調用1UITableView/UICollectionView1的reloadData
.
注意:沒有必要以集中的方式處理變化.如果你應用中的多個組件需要處理照片實體,那么它們每個都要有自己的
PHPhotoLibraryChangeObserver
.接着組件就能靠自己查詢PHChange
對象,檢測是否需要 (以及如何)更新它們自己的狀態。
// 注冊監聽,獲取相冊數據源變化,系統提供回調方法-photoLibraryDidChange
PHPhotoLibrary.shared().register(self)
func photoLibraryDidChange(_ changeInstance: PHChange) {
// 接收通知可能會在后台線程,所以此處的UI更新需放於主線程調用
DispatchQueue.main.sync {
// Check each of the three top-level fetches for changes. if let changeDetails = changeInstance.changeDetails(for: allPhotos) { // 更新緩存的數據源 allPhotos = changeDetails.fetchResultAfterChanges } // 更新緩存的數據源並更新UI if let changeDetails = changeInstance.changeDetails(for: smartAlbums) { smartAlbums = changeDetails.fetchResultAfterChanges tableView.reloadSections(IndexSet(integer: Section.smartAlbums.rawValue), with: .automatic) } if let changeDetails = changeInstance.changeDetails(for: userCollections) { userCollections = changeDetails.fetchResultAfterChanges tableView.reloadSections(IndexSet(integer: Section.userCollections.rawValue), with: .automatic) } } } // 取消監聽 PHPhotoLibrary.shared().unregisterChangeObserver(self) 復制代碼
3.3 緩存以及展示列表縮略圖
當圖像即將要展示在屏幕上時,比如當要在一組滾動的
collection
視圖上展示大量的資源圖像的縮略圖時,預先將一些圖像加載到內存中有時是非常有用的.PhotoKit
提供了一個PHImageManager
的子類來處理這種特定的使用場景 ——PHImageCachingManager
.
PHImageCachingManager
提供了一個關鍵方法startCachingImagesForAssets(...)
你傳入一個PHAssets
類型的數組,一些請求參數,以及一些請求單個圖像時即將用到的可選項.此外,還有一些方法可以讓你通知緩存管理器來停止緩存特定資源列表,以及停止緩存所有圖像.
默認情況下,如果圖像管理器決定要用最優策略,那么它會在將圖像的高質量版本遞送給你之前,先傳遞一個較低質量的版本.你可以通過
deliveryMode
屬性來控制這個行為;上面所描述的默認行為的值為.Opportunistic
.如果你只想要高質量的圖像,並且可以接受更長的加載時間,那么將屬性設置為.HighQualityFormat
.如果你想要更快的加載速度,且可以犧牲一點圖像質量,那么將屬性設置為.FastFormat
.
你可以使用
PHImageRequestOptions
的synchronous
屬性,讓requestImage...
系列的方法變成同步操作。注意:當synchronous
設為true
時,deliveryMode
屬性就會被忽略,並被當作.HighQualityFormat
來處理.在設置這些參數時,一定要考慮到你的一些用戶有可能開啟了iCloud
照片庫,這點非常重要.PhotoKit
的API不一定會對設備的照片和iCloud
上照片進行區分 —— 它們都用同一個requestImage
方法來加載.這意味着任意一個圖像請求都有可能是一個通過蜂窩網絡來進行的非常緩慢的網絡請求.當你要用.HighQualityFormat
或者做一個同步請求的時候,要牢記這個.注意:如果你想要確保請求不經過網絡,可以將 networkAccessAllowed設為false
.另一個和iCloud
相關的屬性是progressHandler
.你可以將它設為一個PHAssetImageProgressHandler
的block
,當從iCloud
下載照片時,它就會被圖像管理器自動調用。
這里等頁面展示時預先緩存數據后再取數據后展示.
let (addedRects, removedRects) = differencesBetweenRects(previousPreheatRect, preheatRect) let addedAssets = addedRects .flatMap { rect in collectionView!.indexPathsForElements(in: rect) } .map { indexPath in fetchResult.object(at: indexPath.item) } let removedAssets = removedRects .flatMap { rect in collectionView!.indexPathsForElements(in: rect) } .map { indexPath in fetchResult.object(at: indexPath.item) } // 更新緩存數據 imageManager.startCachingImages(for: addedAssets, targetSize: thumbnailSize, contentMode: .aspectFill, options: nil) imageManager.stopCachingImages(for: removedAssets, targetSize: thumbnailSize, contentMode: .aspectFill, options: nil) 復制代碼
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "GridViewCell", for: indexPath) as? GridViewCell else { fatalError("Unexpected cell in collection view") } // 給動態照片Cell添加一個badge if asset.mediaSubtypes.contains(.photoLive) { cell.livePhotoBadgeImage = PHLivePhotoView.livePhotoBadgeImage(options: .overContent) } // 通過PHCachingImageManager請求照片 cell.representedAssetIdentifier = asset.localIdentifier imageManager.requestImage(for: asset, targetSize: thumbnailSize, contentMode: .aspectFill, options: nil, resultHandler: { image, _ in // UIKit may have recycled this cell by the handler's activation time. // 只有請求到通用類型標識符一致的資源時才配置圖片 if cell.representedAssetIdentifier == asset.localIdentifier { cell.thumbnailImage = image } }) 復制代碼

3.4 編輯修改資源文件
用
PhotoKit
在照片庫做改變,說到底其實是先創建了一個鏈接到某個資源或者資源集合的變化請求對象,再設置請求對象的相關屬性或調用合適的方法來描述你想要提交的變化.這個必須通過performChanges(...)
方法,在提交到共享的PHPhotoLibrary
的block
內完成.注意:你需要准備好在performChanges
方法的completion block
里處理失敗的情況.雖然處理的是能被多個參與者(如你的應用,用戶,其他應用,照片擴展等)改變的狀態,但這個方式能提供安全性,也相對易用.
想要修改資源,需要創建一個
PHAssetChangeRequest
,然后你就可以修改創建創建日期,資源位置,以及是否將隱藏資源,是否將資源看做用戶收藏等.此外,你還可以從用戶的庫里刪除資源.類似地,若要修改資源集合或集合列表,需要創建一個PHAssetCollectionChangeRequest
或PHCollectionListChangeRequest
對象.然后你就可以修改集合標題,添加或刪除集合成員,或者完全刪除集合.
在你的變化提交到用戶照片庫前,系統會向用戶展示一個明確的獲取權限的警告框.
3.4.1 修改資源
DispatchQueue.global(qos: .userInitiated).async {
// 創建調整的數據源.
// 如果你對一張照片進行了濾鏡處理,你應該會創建一個adjustmentData對象,它記錄了用戶選擇了什么濾鏡、相關配置的參數、以及使用這(款/些)濾鏡的命令.接下去,用戶可以用你的app或者別的可以獲取你的adjustmentData對象的app來恢復這張照片到初始狀態
let adjustmentData = PHAdjustmentData(formatIdentifier: self.formatIdentifier, formatVersion: self.formatVersion, data: filterName.data(using: .utf8)!) // 創建輸出數據源. let output = PHContentEditingOutput(contentEditingInput: input) output.adjustmentData = adjustmentData // 設置濾鏡類型. let applyFunc: (String, PHContentEditingInput, PHContentEditingOutput, @escaping () -> Void) -> Void if self.asset.mediaSubtypes.contains(.photoLive) { applyFunc = self.applyLivePhotoFilter } else if self.asset.mediaType == .image { applyFunc = self.applyPhotoFilter } else { applyFunc = self.applyVideoFilter } // 使用濾鏡. applyFunc(filterName, input, output, { // 當濾鏡提交使用后,再更新到資源庫. PHPhotoLibrary.shared().performChanges({ let request = PHAssetChangeRequest(for: self.asset) request.contentEditingOutput = output }, completionHandler: { success, error in if !success { print("Can't edit the asset: \(String(describing: error))") } }) }) } 復制代碼
3.4.2 修改資源集合或集合列表
PHPhotoLibrary.shared().performChanges({
PHAssetCollectionChangeRequest.creationRequestForAssetCollection(withTitle: title)
}, completionHandler: { success, error in if !success { print("Error creating album: \(String(describing: error)).") } }) 復制代碼
3.4.3 添加新資源
PHPhotoLibrary.shared().performChanges({
let creationRequest = PHAssetChangeRequest.creationRequestForAsset(from: image) if let assetCollection = self.assetCollection { let addAssetRequest = PHAssetCollectionChangeRequest(for: assetCollection) addAssetRequest?.addAssets([creationRequest.placeholderForCreatedAsset!] as NSArray) } }, completionHandler: {success, error in if !success { print("Error creating the asset: \(String(describing: error))") } }) 復制代碼
3.4.4 刪除資源
if assetCollection != nil { // 從當前選中的相冊|集合中刪除資源 PHPhotoLibrary.shared().performChanges({ let request = PHAssetCollectionChangeRequest(for: self.assetCollection)! request.removeAssets([self.asset] as NSArray) }, completionHandler: completion) } else { // 直接從資源庫中刪除. PHPhotoLibrary.shared().performChanges({ PHAssetChangeRequest.deleteAssets([self.asset] as NSArray) }, completionHandler: completion) } 復制代碼
3.5 其他
判斷是否是Favorite的相冊,以及添加Favorite
PHPhotoLibrary.shared().performChanges({
let request = PHAssetChangeRequest(for: self.asset) request.isFavorite = !self.asset.isFavorite }, completionHandler: { success, error in if success { DispatchQueue.main.sync { sender.title = self.asset.isFavorite ? "♥︎" : "♡" } } else { print("Can't mark the asset as a Favorite: \(String(describing: error))") } }) 復制代碼
4.API介紹
4.1 PhotoKit
框架常用類簡介圖

4.2 PhotoKit
框架構成圖

4.3 常用類
原文: iOS照片框架
4.3.1 基本
PHPhotoLibrary
: 表示用戶的照片庫,用於請求、獲取照片庫的權限,監聽照片庫的變化;`
4.3.2 資源
PHObject
: 抽象基類,其他PhotoKit類都繼承該類,提供了一個localIdentifier屬性;
PHAsset
: 表示照片庫中的一個單獨的資源(可以是圖片,也可以是視頻;與ALAsset類似),用於獲取、保存資源的元數據;PHAsset只包含元數據(如圖片大小、創建日期等),具體的圖片、視頻數據需要使用PHImageManager進行加載; 若一個資源的representsBurst屬性為true,則表示該資源是一系列連拍照片中的代表照片,可以通過fetchAssetsWithBurstIdentifier()方法,傳入burstIdentifier屬性,獲取連拍照片中的剩余的其他照片;
PHCollection
: PHAssetCollection和PHCollectionList的父類;
PHAssetCollection
: 資源集合,表示成組的資源;可以表示照片庫中的一個相冊、時刻、智能相冊; 智能相冊:系統默認提供的特定相冊,如最近刪除、視頻列表、收藏等;
PHCollectionList
: 表示一組PHCollections的集合;其本身也是PHCollection,故PHCollectionList也可以包含其他的PHCollectionList;
4.3.3 獲取
PHFetchResult
: 表示一系列的PHAsset的結果集合,也可以是一系列的PHCollection的結果集合;
PHFetchOptions
: 獲取PHFetchResult時的傳入參數,起過濾、排序等作用,可以過濾類型、日期、名稱等; 傳入nil則使用系統默認值;
PHImageManager
: 用於加載資源;
PHCachingImageManager
: 繼承於PHImageManager,帶有緩存的加載資源;當使用大量的資源時,可以先在后台准備資源圖片,減少在之后的請求單個資源時的延遲;如當想要使用照片、視頻資源的縮略圖填充一個集合視圖時就可以使用PHCachingImageManager;先使用startCachingImagesForAssets方法進行資源准備,之后還使用PHImageManager的request方法加載資源;
PHImageRequestOptions
: 加載資源時的傳入參數,控制資源的輸出尺寸等規格;
4.3.4 更新
PHAssetChangeRequest
: 用來創建、刪除和修改PHAsset對象;
PHAssetCollectionChangeRequest
: 用來創建、刪除和修改PHAssetCollection對象;
PHCollectionListChangeRequest
: 用來創建、刪除和修改PHCollectionList對象;
PHAssetCreationRequest
: PHAssetChangeRequest的子類,也可以用來創建,豐富了添加資源的方式;
4.3.5 相關枚舉值
注意:獲取指定類型的相冊時,主類型和子類型要匹配,若不匹配則系統會按照any
子類型處理; 對於moment
類型,子類型使用any
; 對於smartAlbum
類型,子類型使用albumRegular
比使用any
多一個Recently Deleted
(最近刪除)的相冊;
enum PHAssetCollectionType : Int {
case album // 用戶自己在Photos app中建立的相冊、從iTunes同步來的相冊 case smartAlbum // Photos app內置的相冊(內容動態更新) case moment // Photos app自動生成的時間、地點分組的相冊 } 復制代碼
enum PHAssetCollectionSubtype : Int {
// PHAssetCollectionTypeAlbum regular subtypes
case albumRegular // 用戶自己在Photos app中建立的相冊 case albumSyncedEvent // 已廢棄;從iPhoto同步來的事件相冊 case albumSyncedFaces // 從iPhoto同步來的人物相冊 case albumSyncedAlbum // 從iPhoto同步來的相冊 case albumImported // 從相機或外部存儲導入的相冊 // PHAssetCollectionTypeAlbum shared subtypes case albumMyPhotoStream // 用戶的iCloud照片流 case albumCloudShared // //用戶使用iCloud共享的相冊 // PHAssetCollectionTypeSmartAlbum subtypes case smartAlbumGeneric // 非特殊類型的相冊,從macOS Photos app同步過來的相冊 case smartAlbumPanoramas // 相機拍攝的全景照片的相冊 case smartAlbumVideos // 相機拍攝的視頻的相冊 case smartAlbumFavorites // 收藏的照片、視頻的相冊 case smartAlbumTimelapses // 延時視頻的相冊 case smartAlbumAllHidden // 包含隱藏照片、視頻的相冊 case smartAlbumRecentlyAdded // 相機近期拍攝的照片、視頻的相冊 case smartAlbumBursts // 連拍模式拍攝的照片的相冊 case smartAlbumSlomoVideos // Slomo是slow motion的縮寫,高速攝影慢動作解析(iOS設備以120幀拍攝)的相冊 case smartAlbumUserLibrary // 相機相冊,包含相機拍攝的所有照片、視頻,使用其他應用保存的照片、視頻 @available(iOS 9.0, *) case smartAlbumSelfPortraits @available(iOS 9.0, *) case smartAlbumScreenshots @available(iOS 10.2, *) case smartAlbumDepthEffect @available(iOS 10.3, *) case smartAlbumLivePhotos @available(iOS 11.0, *) case smartAlbumAnimated @available(iOS 11.0, *) case smartAlbumLongExposures // Used for fetching, if you don't care about the exact subtype case any // 包含所有類型 } 復制代碼
5.總結
在iOS 8之前,開發者只能使用AssetsLibrary
框架來處理照片,但隨着蘋果手機的不斷更新,設備的不斷進步,照片功能不斷更新,原有的AssetsLibrary
框架已經不再能跟得上腳步了.但隨着 iOS 8 的到到來,蘋果給我們提供了一個現代化的框架PhotoKit
,使得開發者們能夠更好的適應潮流,更好的開發相關應用,讓應用與設備能夠完美無縫的工作.