對於 Photos 框架的介紹,推薦觀看 objccn.io 的文章。寫得真好,我寫得的文章水準還差得老遠啊。本文總結了近期使用 Photos 框架編寫一個相冊的經驗,目前還有很大一部分的框架內容沒有涉及到,後續會更新內容。html
照片庫中有兩種資源可供獲取:PHAsset
和PHCollection
,前者表明圖像或視頻對象,後者是前者的集合或自身類型的集合。PHCollection
是個基類,有PHAssetCollection
和PHCollectionList
兩個子類,分別表明 Photos 裏的相冊和文件夾。以往使用 Photos 時,並無注意到能夠創建文件夾,彷佛是從 Photos 框架才支持這個功能,而PHCollectionList
裏可嵌套PHAssetCollection
和自身類型,還支持多重嵌套。獲取PHAsset
以及PHAssetCollection
的過程相似於 Core Data,以下所示,只能經過類方法來返回PHFetchResult
,遍歷返回的結果來獲取須要的資源。ios
PHAsset Fetch Methodswift
PHAssetCollection Fetch Method緩存
注意,PHAsset
、PHAssetCollection
和PHCollectionList
都是輕量級的不可變對象,使用這些類時並無將其表明的圖像或視頻或是集合載入內存中,要使用其表明的圖像或視頻,須要經過PHImageManager
類來請求。app
關於PHImageManager
類,NSHipster 有篇總結文章不錯。框架
- requestImageForAsset:targetSize:contentMode:options:resultHandler:
你不該該生成該類的實例,而應該使用該類的提供的單例對象。該方法提供指定的尺寸的圖像,與ALAssetsLibrary
庫相比,沒有了方便的縮略圖提供。不過要吐槽的是,ALAssetsLibrary
庫提供的縮略圖每每尺寸過小而且質量很低,用在 TableView 上還能夠。異步
須要注意的是,該方法在默認狀況下是異步執行的,並且 Photos 庫可能會屢次執行 resultHandler 塊,由於對於指定的尺寸,Photos 可能會先提供低質量的圖像以供臨時顯示,隨後會將指定尺寸的圖像返回。若是指定尺寸的高質量的圖像有緩存,那麼直接提供高質量的圖像。而這些行爲,能夠經過 options 參數來定製。ide
PHImageRequestOptions
類用於定製請求。這裏有巨坑。上面的方法返回指定尺寸的圖像,若是你僅僅指定必要的參數而沒有對 options 進行配置的話,返回的圖像尺寸將會是原始圖像的尺寸。或者,你指定的尺寸很小,這時候會按照你的要求來返回接近該尺寸的圖像。在個人 iPad mini 一代上,對於自拍的圖像,指定尺寸不超過(257, 257)的話,返回的圖像尺寸和你預期的同樣,其餘狀況下都是原始尺寸。PHImageRequestOptions
有如下幾個重要的屬性:fetch
synchronous:指定請求是否同步執行。 resizeMode:對請求的圖像怎樣縮放。有三種選擇:None,不縮放;Fast,儘快地提供接近或稍微大於要求的尺寸;Exact,精準提供要求的尺寸。 deliveryMode:圖像質量。有三種值:Opportunistic,在速度與質量中均衡;HighQualityFormat,無論花費多長時間,提供高質量圖像;FastFormat,以最快速度提供好的質量。 這個屬性只有在 synchronous 爲 true 時有效。 normalizedCropRect:用於對原始尺寸的圖像進行裁剪,基於比例座標。只在 resizeMode 爲 Exact 時有效。
resizeMode 默認是 None,這也形成了返回圖像尺寸與要求尺寸不符。這點須要注意。要返回一個指定尺寸的圖像須要避免兩層陷阱:必定要指定 options 參數,resizeMode 不能爲 None。ui
除了必有的請求圖像或是視頻的功能外,PHImageManager
添加了兩大功能:
1.緩存圖像,由其子類PHCachingImageManager
實現,緩存效率和空間管理能知足大部分場景的需求;
2.裁剪圖像,這個功能好久之前就有強烈的需求。六年前 StackOverflow 上 Cropping a UIImage 這個問題就被提出來了,方法也五花八門,然而這些方法可能會有各類小問題。官方的方法能讓你避免這些小問題。使用方法能夠參考 NSHipster 的總結文章裏用人臉識別獲取頭像的例子。
Photos 框架推出時,和原來的照片庫 AssetsLibrary 框架之間還有些交互,PHAsset
類的+ fetchAssetsWithALAssetURLs:options:
和PHAssetCollection
類的 + fetchAssetCollectionsWithALAssetGroupURLs:options:
能夠利用原來的 AssetsLibrary 提供的 URL 進行轉化,而在 iOS 9 中,原來的照片框架 AssetsLibrary 已經被廢棄了,現在這兩個方法也沒有用處了。當初我還找過如何從 Photos 框架到 AssetsLibrary 框架的方法,理所固然地白費功夫,官方要淡化照片庫中 URL 的概念,改之使用一個標誌符來惟一表明一個資源。Photos 框架中的根類PHObject
只有一個公開接口localIdentifier
,AssetsLibrary 框架中不管是 Asset 仍是 AssetGroup 的 URL 也是惟一標誌符,並且同時仍是動態變化的,每次啓動應用後獲取的 URL 和上一次是不同的,而 AssetGroup 有一個 PersistentID 與PHObject
的localIdentifier
相似,但獲取比較麻煩。
localIdentifier
屬性帶來的最大好處是PHObject
類實現了 NSCopying 協議,能夠直接使用localIdentifier
屬性對PHObject
及其子類對象進行對比是否同一個對象。
這是最基本的一個用途,可是每次隔了幾天就忘了具體的類型。
經過PHAssetCollection
的如下方法來獲取指定的相冊:
func fetchAssetCollectionsWithType(_ type: PHAssetCollectionType, subtype subtype: PHAssetCollectionSubtype, options options: PHFetchOptions?) -> PHFetchResult
這個方法須要至少指定兩個參數:
enum PHAssetCollectionType : Int { case Album //從 iTunes 同步來的相冊,以及用戶在 Photos 中本身創建的相冊 case SmartAlbum //經由相機得來的相冊 case Moment //Photos 爲咱們自動生成的時間分組的相冊 } enum PHAssetCollectionSubtype : Int { case AlbumRegular //用戶在 Photos 中建立的相冊,也就是我所謂的邏輯相冊 case AlbumSyncedEvent //使用 iTunes 從 Photos 照片庫或者 iPhoto 照片庫同步過來的事件。然而,在iTunes 12 以及iOS 9.0 beta4上,選用該類型無法獲取同步的事件相冊,而必須使用AlbumSyncedAlbum。 case AlbumSyncedFaces //使用 iTunes 從 Photos 照片庫或者 iPhoto 照片庫同步的人物相冊。 case AlbumSyncedAlbum //作了 AlbumSyncedEvent 應該作的事 case AlbumImported //從相機或是外部存儲導入的相冊,徹底沒有這方面的使用經驗,無法驗證。 case AlbumMyPhotoStream //用戶的 iCloud 照片流 case AlbumCloudShared //用戶使用 iCloud 共享的相冊 case SmartAlbumGeneric //文檔解釋爲非特殊類型的相冊,主要包括從 iPhoto 同步過來的相冊。因爲本人的 iPhoto 已被 Photos 替代,沒法驗證。不過,在個人 iPad mini 上是沒法獲取的,而下面類型的相冊,儘管沒有包含照片或視頻,但可以獲取到。 case SmartAlbumPanoramas //相機拍攝的全景照片 case SmartAlbumVideos //相機拍攝的視頻 case SmartAlbumFavorites //收藏文件夾 case SmartAlbumTimelapses //延時視頻文件夾,同時也會出如今視頻文件夾中 case SmartAlbumAllHidden //包含隱藏照片或視頻的文件夾 case SmartAlbumRecentlyAdded //相機近期拍攝的照片或視頻 case SmartAlbumBursts //連拍模式拍攝的照片,在 iPad mini 上按住快門不放就能夠了,可是照片依然沒有存放在這個文件夾下,而是在相機相冊裏。 case SmartAlbumSlomoVideos //Slomo 是 slow motion 的縮寫,高速攝影慢動做解析,在該模式下,iOS 設備以120幀拍攝。不過個人 iPad mini 不支持,無法驗證。 case SmartAlbumUserLibrary //這個命名最神奇了,就是相機相冊,全部相機拍攝的照片或視頻都會出如今該相冊中,並且使用其餘應用保存的照片也會出如今這裏。 case Any //包含全部類型 }
有些參數的命名十分使人困惑,我每次看了都暈菜。新的 Photos Kit 框架是在 iOS 8 中推出的,主類型分爲三種類型:Album,SmartAlbum 以及 Moment。然而,對於前二者的分類我是比較困惑的。Mac 上支持智能文件夾,就是能夠無論文件的物理位置而將一系列文件集合起來創建一個文件夾,能夠說是物理相冊和邏輯相冊。而在 iOS 上,SmartAlbum 卻給了相機衍生的相冊,用戶收集不一樣照片創建的邏輯相冊被歸類到 Album 類型的 AlbumRegular 下,十分反個人直覺。在文檔中,SmartAlbum 是指內容會動態變化的相冊,這樣一來又有一個比較困惑的設計,PHAssetCollection
類有個屬性 estimatedAssetCount
,能夠用來快速獲取該相冊中的照片和視頻的數量,可是在 SmartAlbum 上該屬性永遠爲0,動態相冊沒能實現對數量的監測。
注意,獲取指定類型的相冊時,主類型和子類型要匹配,不要串臺。若是不匹配,系統會按照 Any 子類型來處理。對於 Moment 類型,子類型使用 Any。
1.獲取用戶本身創建的相冊和文件夾(我稱之爲邏輯相冊,非系統相冊和從 iTunes 同步來的相冊)有兩種方法:
PHCollection.fetchTopLevelUserCollectionsWithOptions(nil) PHAssetCollection.fetchAssetCollectionsWithType(.Album, subtype: .AlbumRegular, options: nil)
在沒有提供PHOptions
的狀況下,返回的PHFetchResult
結果是按相冊的創建時間排序的,最新的在前面。
2.獲取相機相冊:
PHAssetCollection.fetchAssetCollectionsWithType(.SmartAlbum, subtype: .SmartAlbumUserLibrary, options: nil)
另外:PHAsset
的獲取方式在 iOS 8.1 後發生了一些變化。如下的兩個方法在 iOS 8.1後再也不包含從 iTunes 同步以及在 iCloud 中的照片和視頻。要獲取 iOS 設備上本地的全部照片和資源只能從 PHAssetCollection 入手了。
+ fetchAssetsWithMediaType:options: + fetchAssetsWithOptions:
對照片庫進行操做,可參見官方文檔 Requesting Changes to the Photo Library,照片庫中的資源都有對應的變動請求類:PHAssetChangeRequest
, PHAssetCollectionChangeRequest
和PHCollectionListChangeRequest
, 而這些操做的請求都要求在PHPhotoLibrary
的performChanges(_ changeBlock: dispatch_block_t!, completionHandler completionHandler: ((Bool, NSError!) -> Void)!)
中的 changeBlock 中執行。注意,這裏只是發出請求並無作出實質的更改,所以想要根據更改結果更新 UI 的話不要在 completionHandler 中進行,而應該在 photoLibraryDidChange(changeInfo: PHChange!)
中進行。三種變動請求中,刪除和編輯操做都比較簡單,而添加操做有須要注意的地方。
添加操做: placeholder 的用處
在相冊中添加照片:
let createAssetRequest = PHAssetChangeRequest.creationRequestForAssetFromImage(image) let assetPlaceholder = createAssetRequest.placeholderForCreatedAsset let albumChangeRequest = PHAssetCollectionChangeRequest(forAssetCollection: album) albumChangeRequest.addAssets([assetPlaceholder])
在文件夾中添加相冊:
let fetchResult = PHCollection.fetchCollectionsInCollectionList(collectionList, options: nil) let createSubAlbumRequest = PHAssetCollectionChangeRequest.creationRequestForAssetCollectionWithTitle(title!) let albumPlaceholder = createSubAlbumRequest.placeholderForCreatedAssetCollection let folderChangeRequest = PHCollectionListChangeRequest.init(forCollectionList: collectionList, childCollections: fetchResult) folderChangeRequest?.addChildCollections([albumPlaceholder])
在文件夾中添加子文件夾:
let fetchResult = PHCollection.fetchCollectionsInCollectionList(collectionList, options: nil) let createSubFolderRequest = PHCollectionListChangeRequest.creationRequestForCollectionListWithTitle(title!) let subfolderPlaceholder = createSubFolderRequest.placeholderForCreatedCollectionList let folderChangeRequest = PHCollectionListChangeRequest.init(forCollectionList: collectionList, childCollections: fetchResult) folderChangeRequest?.addChildCollections([subfolderPlaceholder])
對相冊發出變動請求後,系統會通知用戶是否容許,用戶容許後纔會發生實質上的變化,系統會發布通知。
首先,註冊成爲PHPhotoLibrary
的觀察者來接收變化通知:
PHPhotoLibrary.shareLibrary().registerChangeObserver(self)
而後,實現PHPhotoLibraryChangeObserver
協議的photoLibraryDidChange(changeInfo: PHChange!)
。官方有個很好的例子:Handling Changes: An Example,有如下幾點須要注意:
1.在photoLibraryDidChange(changeInfo: PHChange!)
的實現裏將全部處理放在主線程裏處理;
2.全部PHPhotoLibrary
的觀察者都會收到通知,無論觀察者自己引用的內容是否發生變化,所以要根據觀察者的狀況來對通知進行過濾。從參數PHChange
對象裏能得到全部的變化,經過changeDetailsForObject:
和changeDetailsForFetchResult:
來獲取細節。changeDetailsForObject:
獲取的細節只是PHObject
子類對象自己的信息變化,包括是否有成員被刪除以及是否有圖像或視頻發生變化兩種信息,有用信息實在有限,要處理成員變化須要依靠後者;對一個PHFetchResult
對象使用changeDetailsForFetchResult:
獲取的細節中只包含該PHFetchResult
對象變化的信息,能夠利用這點來對通知進行過濾處理。
3.經過changeDetailsForFetchResult:
獲取的PHFetchResultChangeDetails
對象,包含了 FetchResult 的結果的全部變化狀況以及 FetchResult 的成員變化先後的數據,須要注意的是成員變化的通知。
例如,經過
var rootCollectionsFetchResult = PHCollection.fetchTopLevelUserCollectionsWithOptions(nil)
獲取全部用戶創建的相冊和文件夾,在photoLibraryDidChange(changeInfo: PHChange!)
中經過如下方法得到PHFetchResultChangeDetails
對象。
let fetchChangeDetails = changeInstance.changeDetailsForFetchResult(rootCollectionsFetchResult)
fetchChangeDetails.changedObject
返回一組其內容或元數據發生變化的成員,返回的成員是更新後的成員對象。當用戶對某個文件夾內的相冊或子文件夾進行添加、刪除和編輯操做即文件夾的內容而不是文件夾自己的屬性發生變化時,通知中會該變化的信息嗎?實際上只有在文件夾中添加相冊或子文件夾時纔會在fetchChangeDetails.changedObject
中有所反應,而刪除成員或是修改元數據等操做都不會在通知有所反應,你須要使用其餘手段來跟蹤變化。
文/seedante(簡書做者) 原文連接:http://www.jianshu.com/p/42e5d2f75452/ 著做權歸做者全部,轉載請聯繫做者得到受權,並標註「簡書做者」。