PhotoKit
是App在使用、管理圖片和視頻的框架,並且還包括了iCloud上面的圖片以及實時照片.html
PhotoKit
支持應用構建照片以及編輯擴展,還能夠直接訪問管理照片和視頻元資源以及元資源集合例如專輯,時刻和共享相冊.Browsing and Modifying Photo Albumsapi
此示例演示如何使用自定義實現相似的佈局.它使用PhotoKit
獲取資源縮略圖,而後將其顯示爲單個照片,視頻或動態圖片。此外示例應用程序PhotoBrowse
還演示瞭如何將用戶的照片整理到相冊和內置集合中,例如最近添加的和收藏夾.它支持專輯的建立,刪除,修改,以及我的資源的編輯和收藏.數組
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
參數相似.緩存
首先,你須要經過共享的
PHPhotoLibrary
對象,用registerChangeObserver(...)
方法註冊一個變化觀察者 (這個觀察者要聽從PHPhotoLibraryChangeObserver
協議).只要另外一個應用或者用戶在照片庫中作的修改影響了你在變化前獲取的任何資源或資源集合的話,變化觀察者的photoLibraryDidChange(...)
方法都會被調用.這個方法只有一個PHChange
類型的參數,你能夠用它來驗證這些變化是否和你所感興趣的獲取對象有關聯.安全
更新獲取的結果
PHChange
提供了幾個方法,讓你能夠經過傳入任何你感興趣的PHObject
對象或PHFetchResult
對象來追蹤它們的變化.這幾個方法是changeDetailsForObject(...)
和changeDetailsForFetchResult(...)
.若是沒有任何變化,這些方法會返回nil
,不然你能夠藉助PHObjectChangeDetails
或PHFetchResultChangeDetails
對象來觀察這些變化。markdown
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)
複製代碼
當圖像即將要展現在屏幕上時,好比當要在一組滾動的
collection
視圖上展現大量的資源圖像的縮略圖時,預先將一些圖像加載到內存中有時是很是有用的.PhotoKit
提供了一個PHImageManager
的子類來處理這種特定的使用場景 ——PHImageCachingManager
.app
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
}
})
複製代碼
用
PhotoKit
在照片庫作改變,說到底實際上是先建立了一個連接到某個資源或者資源集合的變化請求對象,再設置請求對象的相關屬性或調用合適的方法來描述你想要提交的變化.這個必須經過performChanges(...)
方法,在提交到共享的PHPhotoLibrary
的block
內完成.注意:你須要準備好在performChanges
方法的completion block
裏處理失敗的狀況.雖然處理的是能被多個參與者(如你的應用,用戶,其餘應用,照片擴展等)改變的狀態,但這個方式能提供安全性,也相對易用.
想要修改資源,須要建立一個
PHAssetChangeRequest
,而後你就能夠修改建立建立日期,資源位置,以及是否將隱藏資源,是否將資源看作用戶收藏等.此外,你還能夠從用戶的庫裏刪除資源.相似地,若要修改資源集合或集合列表,須要建立一個PHAssetCollectionChangeRequest
或PHCollectionListChangeRequest
對象.而後你就能夠修改集合標題,添加或刪除集合成員,或者徹底刪除集合.
在你的變化提交到用戶照片庫前,系統會向用戶展現一個明確的獲取權限的警告框.
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))") }
})
})
}
複製代碼
PHPhotoLibrary.shared().performChanges({
PHAssetCollectionChangeRequest.creationRequestForAssetCollection(withTitle: title)
}, completionHandler: { success, error in
if !success { print("Error creating album: \(String(describing: error)).") }
})
複製代碼
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))") }
})
複製代碼
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)
}
複製代碼
判斷是不是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))")
}
})
複製代碼
PhotoKit
框架經常使用類簡介圖PhotoKit
框架構成圖原文: iOS照片框架
PHPhotoLibrary
: 表示用戶的照片庫,用於請求、獲取照片庫的權限,監聽照片庫的變化;`
PHObject
: 抽象基類,其餘PhotoKit類都繼承該類,提供了一個localIdentifier屬性;
PHAsset
: 表示照片庫中的一個單獨的資源(能夠是圖片,也能夠是視頻;與ALAsset相似),用於獲取、保存資源的元數據;PHAsset只包含元數據(如圖片大小、建立日期等),具體的圖片、視頻數據須要使用PHImageManager進行加載;
若一個資源的representsBurst屬性爲true,則表示該資源是一系列連拍照片中的表明照片,能夠經過fetchAssetsWithBurstIdentifier()方法,傳入burstIdentifier屬性,獲取連拍照片中的剩餘的其餘照片;
PHCollection
: PHAssetCollection和PHCollectionList的父類;
PHAssetCollection
: 資源集合,表示成組的資源;能夠表示照片庫中的一個相冊、時刻、智能相冊;
智能相冊:系統默認提供的特定相冊,如最近刪除、視頻列表、收藏等;
PHCollectionList
: 表示一組PHCollections的集合;其自己也是PHCollection,故PHCollectionList也能夠包含其餘的PHCollectionList;
PHFetchResult
: 表示一系列的PHAsset的結果集合,也能夠是一系列的PHCollection的結果集合;
PHFetchOptions
: 獲取PHFetchResult時的傳入參數,起過濾、排序等做用,能夠過濾類型、日期、名稱等;
傳入nil則使用系統默認值;
PHImageManager
: 用於加載資源;
PHCachingImageManager
: 繼承於PHImageManager,帶有緩存的加載資源;當使用大量的資源時,能夠先在後臺準備資源圖片,減小在以後的請求單個資源時的延遲;如當想要使用照片、視頻資源的縮略圖填充一個集合視圖時就可使用PHCachingImageManager;先使用startCachingImagesForAssets方法進行資源準備,以後還使用PHImageManager的request方法加載資源;
PHImageRequestOptions
: 加載資源時的傳入參數,控制資源的輸出尺寸等規格;
PHAssetChangeRequest
: 用來建立、刪除和修改PHAsset對象;
PHAssetCollectionChangeRequest
: 用來建立、刪除和修改PHAssetCollection對象;
PHCollectionListChangeRequest
: 用來建立、刪除和修改PHCollectionList對象;
PHAssetCreationRequest
: PHAssetChangeRequest的子類,也能夠用來建立,豐富了添加資源的方式;
注意:獲取指定類型的相冊時,主類型和子類型要匹配,若不匹配則系統會按照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 // 包含全部類型
}
複製代碼
在iOS 8以前,開發者只能使用AssetsLibrary
框架來處理照片,但隨着蘋果手機的不斷更新,設備的不斷進步,照片功能不斷更新,原有的AssetsLibrary
框架已經再也不能跟得上腳步了.但隨着 iOS 8 的到到來,蘋果給咱們提供了一個現代化的框架PhotoKit
,使得開發者們可以更好的適應潮流,更好的開發相關應用,讓應用與設備可以完美無縫的工做.
參考