版本記錄
版本號(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)用通過一組稱為PhotoKit
的API
在iOS中管理圖像資源。 如果您一直想知道如何構(gòu)建像Photos
之類的應(yīng)用程序烧给,或者只是訪問照片庫燕偶,PhotoKit
就是答案。 本教程將重點(diǎn)放在iOS
上础嫡,但PhotoKit也可用于macOS
指么,Catalyst
和tvOS
。
您將使用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
睡雇,restricted
,denied
和iOS 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
是什么? 我以為我正在獲取PHAssets
和PHAssetCollections
贾铝?” 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)行排序脏嚷,因此options
為nil
骆撇。 - 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) 如果
asset
為nil
,則返回false
即可完成潦匈。 否則阱高,請(qǐng)繼續(xù)。 - 2) 接下來茬缩,創(chuàng)建在圖像請(qǐng)求完成時(shí)圖像管理器將調(diào)用的結(jié)果處理程序赤惊。 將返回的圖像分配給
UIImageView
的image
屬性。 調(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) 獲取所選
item
的section
類型和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)注~~~