本文首發(fā)于個(gè)人博客,歡迎訪問(wèn)留言把将,轉(zhuǎn)載請(qǐng)注明出處
title: iOS 14 - 使用 PHPicker 選擇照片和視頻
abstract: PHPicker 是 iOS 14 引入的一套全新的相冊(cè) API,通過(guò)它訪問(wèn)相冊(cè)忆矛,可以不需要向用戶獲取相關(guān)權(quán)限察蹲。
date: 2020-09-24 15:18
tags:
- Swift
- iOS
現(xiàn)狀
絕大多數(shù)的 App 都要和相冊(cè)打交道,選擇照片或者視頻,要么用來(lái)發(fā)個(gè)朋友圈递览,要么是放到什么地方做個(gè)背景叼屠。從 AssertLibrary 到 Photos 框架,蘋果已經(jīng)在多年之前就給相冊(cè)相關(guān)的 API 做過(guò)一次大升級(jí)了绞铃。
通過(guò) Photos 框架镜雨,只需要通過(guò)下面幾步就可以從相冊(cè)獲取到內(nèi)容了
- 1、獲取相冊(cè)權(quán)限
- 2儿捧、建立 UI 展示相冊(cè)內(nèi)容
- 3荚坞、響應(yīng)用戶操作,獲取照片或視頻的 PHAsset 對(duì)象
- 4菲盾、通過(guò) PHImageManager 導(dǎo)出照片或者視頻
作為一個(gè)合格的開(kāi)發(fā)者颓影,對(duì)上面這些步驟基本上都能駕輕就熟了。不過(guò)雖然看似簡(jiǎn)單的幾步懒鉴,其實(shí)依然存在著巨大的工作量诡挂,特別是建立 UI 并展示相冊(cè)內(nèi)容這一條,如果要做到細(xì)節(jié)完善临谱、體驗(yàn)順暢璃俗,基本上屬于一個(gè)小型 App 的工作量了。所以很多時(shí)候我們會(huì)選擇使用一些第三方的框架悉默,目前可供選擇的優(yōu)秀作品也很多城豁。看起來(lái)感覺(jué)歲月靜好抄课,可以結(jié)束工作去泡咖啡了 :)
變化
然而唱星,事情總是在變化的,一向在折騰之路上狂奔的蘋果這次依然沒(méi)有讓我們失望跟磨,在相冊(cè)相關(guān)的 API 上帶來(lái)了兩個(gè)重大變動(dòng):
1间聊、相冊(cè)權(quán)限變動(dòng)
Photos 框架提供了查詢相冊(cè)授權(quán)情況的 api,我們可以通過(guò) PHPhotoLibrary
的 authorizationStatus
方法來(lái)查詢當(dāng)前的相冊(cè)授權(quán)情況抵拘。查詢結(jié)果 PHAuthorizationStatus
是個(gè)枚舉哎榴,定義如下:
public enum PHAuthorizationStatus : Int {
@available(iOS 8, *)
case notDetermined = 0
@available(iOS 8, *)
case restricted = 1
@available(iOS 8, *)
case denied = 2
@available(iOS 8, *)
case authorized = 3
}
從 iOS 8 引入 Photos 框架一開(kāi)始,相冊(cè)權(quán)限就存在這幾種狀態(tài):
- notDetermined
狀態(tài)不明確仑濒,用戶還沒(méi)有明確授權(quán)或拒絕訪問(wèn)叹话,這時(shí)候我們一般通過(guò)調(diào)用 requestAuthorization
方法來(lái)顯示權(quán)限詢問(wèn)彈窗,讓用戶授權(quán)訪問(wèn)墩瞳。
- restricted
沒(méi)有訪問(wèn)權(quán)限驼壶,這個(gè)狀態(tài)表示設(shè)備因?yàn)樘厥庠虮唤乖L問(wèn)相冊(cè),可能是基于家長(zhǎng)控制設(shè)置的權(quán)限喉酌,也可能是當(dāng)前設(shè)備隸屬于某個(gè)組織热凹,而組織在權(quán)限描述文件中禁止了這臺(tái)設(shè)備的相冊(cè)權(quán)限泵喘。
這個(gè)狀態(tài)下,用戶也無(wú)法主動(dòng)開(kāi)啟相冊(cè)權(quán)限般妙,所以此時(shí)唯一能做的就是告訴用戶無(wú)法獲取相冊(cè)內(nèi)容纪铺。
- denied
禁止訪問(wèn),這個(gè)狀態(tài)表示用戶在上一次詢問(wèn)相冊(cè)權(quán)限時(shí)明確選擇了禁止碟渺。這時(shí)候我們可以選擇提示用戶無(wú)法訪問(wèn)鲜锚,或者提示用戶主動(dòng)去設(shè)置中開(kāi)啟權(quán)限。
- authorized
允許訪問(wèn)苫拍,這個(gè)狀態(tài)表示用戶明確同意了相冊(cè)的授權(quán)芜繁,此時(shí)我們就可以通過(guò) PHAssetCollection
和 PHAsset
相關(guān)的 api 來(lái)獲取相冊(cè)和其中的照片了。
原本這一切可以很完美的運(yùn)轉(zhuǎn)绒极,但是如果在 Xcode 12 中查看PHAuthorizationStatus 的定義骏令,會(huì)看到從 iOS 14 開(kāi)始,蘋果引入了一個(gè)新的權(quán)限狀態(tài):limited
有限訪問(wèn)權(quán)限
蘋果在每一年的 WWDC 都特別強(qiáng)調(diào)用戶隱私垄提,到了今年榔袋,蘋果終于對(duì)相冊(cè)動(dòng)手了。現(xiàn)有的方案雖然看起來(lái)很完美铡俐,但是存在一個(gè)重大的隱患:用戶往往只需要從相冊(cè)選一張照片上傳凰兑,可此時(shí) App 卻獲取了整個(gè)相冊(cè)所有照片的數(shù)據(jù)!高蜂!
這時(shí)候如果開(kāi)發(fā)者沒(méi)有守住節(jié)操底線聪黎,那么用戶的所有生活照片甚至是私密照片都存在嚴(yán)重的泄漏風(fēng)險(xiǎn)罕容。
新引入的有限訪問(wèn)權(quán)限正是為了解決這個(gè)問(wèn)題备恤。在 iOS 14 下,當(dāng)我們通過(guò) requestAuthorization
向用戶獲取權(quán)限時(shí)锦秒,彈窗中多了一個(gè)選項(xiàng):“選擇部分照片”露泊,此時(shí)系統(tǒng)會(huì)在 App 進(jìn)程之外彈出相冊(cè)讓用戶選擇授權(quán)給當(dāng)前 App 訪問(wèn)的照片。用戶完成選擇后旅择,App 通過(guò)相冊(cè)相關(guān)的 api 就只能獲取到指定的這幾張照片惭笑,這有效的保護(hù)了用戶的隱私。
不過(guò)生真,這個(gè)設(shè)計(jì)雖然保護(hù)了用戶的隱私沉噩,但是從 App 的角度來(lái)看卻引入了新的問(wèn)題:如果我們還是用原先的那一套流程來(lái)獲取照片,那么在用戶選擇了部分權(quán)限的情況下柱蟀,每次彈出相冊(cè)后用戶都無(wú)法選擇其他的照片川蒙,解決辦法是再次彈出權(quán)限詢問(wèn),讓用戶選擇或者更換指定的照片长已,然后再回到我們的相冊(cè) UI畜眨,繼續(xù)之后的流程昼牛。。康聂。
這一套流程明顯變得更加復(fù)雜并且奇怪了贰健。。恬汁。而且作為一個(gè)開(kāi)發(fā)者伶椿,始終不能忘了用戶是不了解技術(shù)細(xì)節(jié)的,在用戶的角度來(lái)看氓侧,他可能會(huì)覺(jué)得很奇怪:“為什么我每次都只能選這兩張照片悬垃?這 App 有什么毛病甘苍?” 而如果你選擇每次都但窗詢問(wèn)權(quán)限尝蠕,那么當(dāng)用戶耐心耗盡的那一天,就是你的 App 被卸載之時(shí)载庭。看彼。。
基于從 Beta 版就開(kāi)始使用 iOS 14 的體驗(yàn)囚聚,升級(jí)之后所有 App 的第一次訪問(wèn)相冊(cè)時(shí)都會(huì)彈窗詢問(wèn)權(quán)限靖榕,目測(cè) iOS 14 應(yīng)該是重置了所有 App 的相冊(cè)授權(quán)狀態(tài)。
2顽铸、新的相冊(cè)組件
好在蘋果還是給開(kāi)發(fā)者留了一扇窗的茁计,那就是這篇文章要講的主題,新成員:PHPicker谓松。
其實(shí)上面在用戶選擇部分照片時(shí)星压,我們就已經(jīng)接觸到這玩意兒了:系統(tǒng)在 App 內(nèi)部額外彈出的相冊(cè),本質(zhì)上就是一個(gè) PHPicker
鬼譬。
蘋果在宣傳中說(shuō)它是基于系統(tǒng)的相冊(cè) App 的娜膘,具有與系統(tǒng)相冊(cè)一致的 UI 和操作方式,可以保證用戶體驗(yàn)的一致性优质。并且和相冊(cè) App 一樣竣贪,支持通過(guò)人物、地點(diǎn)等關(guān)鍵信息來(lái)搜索照片巩螃。并且 PHPicker
是在獨(dú)立進(jìn)程中運(yùn)行的演怎,與宿主 App 無(wú)關(guān),宿主 App 也無(wú)法通過(guò)截屏 api 來(lái)獲取當(dāng)前屏幕上的照片信息避乏。為了保護(hù)用戶隱私爷耀,蘋果真的是在各種細(xì)節(jié)上嚴(yán)防死守。
既然在用戶授權(quán)時(shí)可以在 App 中彈出這個(gè)選擇器淑际,那么我們的 App 是否可以主動(dòng)發(fā)起這個(gè)彈窗呢畏纲?答案是:PHPickerViewController
3扇住、PHPickerViewController
PHPickerViewController
是整個(gè) PHPicker 組件的核心,PHPicker
隸屬于 PhothsUI 庫(kù)盗胀,使用之前需要先導(dǎo)入:
import PhotosUI
先來(lái)查看這個(gè)類的定義:
@available(iOS 14, *)
public class PHPickerViewController : UIViewController {
}
@available(iOS 14, *)
extension PHPickerViewController {
/// The configuration passed in during initialization.
public var configuration: PHPickerConfiguration { get }
/// The delegate to be notified.
weak public var delegate: PHPickerViewControllerDelegate?
/// Initializes new picker with the `configuration` the picker should use.
public convenience init(configuration: PHPickerConfiguration)
}
太簡(jiǎn)單了艘蹋!這個(gè)類的定義中居然沒(méi)有任何聲明,只是表示了一下自己繼承自 UIViewControlelr票灰,以及在擴(kuò)展中暴露了兩個(gè)屬性和一個(gè)構(gòu)造器女阀。。屑迂。
PHPickerConfiguration
查看 PHPickerConfiguration 的定義浸策,發(fā)現(xiàn)這里東西稍微多了一點(diǎn):
- preferredAssetRepresentationMode: PHPickerConfiguration.AssetRepresentationMode
presentationMode 用來(lái)指定導(dǎo)出結(jié)果的表示形式,默認(rèn)值是 automatic
, 此時(shí)系統(tǒng)會(huì)自動(dòng)執(zhí)行一些轉(zhuǎn)碼之類的操作惹盼,并且在注釋中明確說(shuō)了這個(gè)模式的實(shí)際表現(xiàn)會(huì)在之后的發(fā)布中修改庸汗。。手报。
果然這種表述就是坑的表現(xiàn):目前官方論壇上有用戶反饋蚯舱,使用了缺省的 automatic
模式后,從相冊(cè)導(dǎo)出視頻的過(guò)程變得巨慢:
對(duì)此官方的回復(fù)是 automatic
模式可能會(huì)在導(dǎo)出時(shí)對(duì)視頻進(jìn)行轉(zhuǎn)碼處理掩蛤,如果 App 本地能夠處理 HEVC
格式的視頻枉昏,可以指定為 current
模式來(lái)跳過(guò)轉(zhuǎn)碼的過(guò)程。
這一條是我在集成過(guò)程中遇到的最大的坑之一了揍鸟,實(shí)際測(cè)試中兄裂,兩分鐘的原始視頻(大約200到300M),使用
automatic
自動(dòng)轉(zhuǎn)碼模式阳藻,導(dǎo)出過(guò)程耗時(shí)達(dá)到了難以置信的 5分鐘晰奖!而改為current
模式跳過(guò)轉(zhuǎn)碼之后,導(dǎo)出過(guò)程幾乎無(wú)感(5s以內(nèi))稚配。
由于我們的 App 本身會(huì)將導(dǎo)出的視頻壓縮轉(zhuǎn)碼后再上傳畅涂,因此果斷選擇了current
模式
關(guān)于 compatible
模式港华,注釋中用了充滿玄學(xué)的說(shuō)法: 盡量選擇最合適的形式道川。由于 PHPicker 相關(guān)的資料目前還太少,這個(gè)模式的運(yùn)作方式暫時(shí)還摸不清楚立宜,歡迎掉過(guò)坑的同學(xué)來(lái)補(bǔ)充 :)
- selectionLimit
這個(gè)設(shè)置很好理解冒萄,限制用戶最多可以選擇的數(shù)量
- filter: PHPickerFilter
filter 提供了有限的篩選模式設(shè)置,目前可以指定篩選照片橙数、視頻尊流、LivePhoto 幾種類型,或者任何他們的組合灯帮。
這一點(diǎn)上目前還是比較欠缺的崖技,比如說(shuō)發(fā)送朋友圈時(shí)需要限制用戶只能上傳一分鐘以內(nèi)的視頻逻住,原來(lái)我們具有完全相冊(cè)訪問(wèn)權(quán)限的時(shí)候,可以在展示的時(shí)候直接過(guò)濾掉超過(guò)一分鐘的視頻迎献,或者將他們標(biāo)志為不可選瞎访。
目前 PHPicker 并沒(méi)有提供更詳細(xì)的篩選配置,所以應(yīng)對(duì)這種需求折中的方法是將視頻導(dǎo)出之后獲取視頻的時(shí)長(zhǎng)吁恍,如果超過(guò)限制則提示用戶扒秸。
彈出選擇器
彈出窗口其實(shí)沒(méi)什么好說(shuō)的,PHPickerViewController 本身還是一個(gè) ViewController冀瓦,設(shè)置好相應(yīng)的屬性和 delegate
后伴奥,簡(jiǎn)單的調(diào)用 present
就可以了。
- delegate: PHPickerViewControllerDelegate
PHPickerViewController
依然是通過(guò) delegate
屬性來(lái)向宿主 App 返回用戶選擇的結(jié)果翼闽。
看定義同樣很簡(jiǎn)單拾徙,PHPickerViewControllerDelegate
只定義了一個(gè)方法:
public protocol PHPickerViewControllerDelegate : AnyObject {
/// Called when the user completes a selection or dismisses `PHPickerViewController` using the cancel button.
///
/// The picker won't be automatically dismissed when this method is called.
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult])
}
用戶完成選擇后,該方法會(huì)觸發(fā)感局,用戶選擇的結(jié)果會(huì)以 PHPickerResult
數(shù)組的形式傳入锣吼,每一個(gè) PHPickerResult 都對(duì)應(yīng)一個(gè)照片、視頻或者 LivePhoto 的數(shù)據(jù)蓝厌。
需要注意的是玄叠,注釋中明確說(shuō)明了
PHPickerViewController
不會(huì)自動(dòng)關(guān)閉,用戶需要在選擇完畢后拓提,自行調(diào)用dismiss
方法來(lái)關(guān)閉選擇器读恃。
PHPickerResult
的定義一如既往的的“簡(jiǎn)潔”,只有 itemProvider
和 assetIdentifier
兩個(gè)屬性代态,剩下的是繼承的 Equatable 和 Hashable 協(xié)議的實(shí)現(xiàn)寺惫。
- assetIdentifier
這是選中對(duì)象對(duì)應(yīng)的 PHAsset 的 id,可以用這個(gè) id 通過(guò) PHAset 的相關(guān) api 來(lái)進(jìn)行其他操作蹦疑。不過(guò)蘋果在 WWDC 的介紹視頻中明確強(qiáng)調(diào)了如果要訪問(wèn) PHAsset 的額外信息西雀,依然需要獲取到相冊(cè)權(quán)限后才可以執(zhí)行。
- itemProvider
NSItemProvider 是一個(gè) iOS 8.0 就存在了的 api歉摧,上一次使用還是在 iOS 11 引入 Drag & Drop 的時(shí)候了艇肴。不過(guò)用法依然是一致的:故名思議,這個(gè)對(duì)象就是用來(lái)向我們提供另一個(gè)對(duì)象的(好像有點(diǎn)繞叁温?)
通過(guò) itemProvider 的 api再悼,我們可以獲取到最終的結(jié)果,不過(guò)這里有點(diǎn)小麻煩:針對(duì)照片膝但、視頻和 LivePhoto 三種媒體類型冲九,分別要使用不同的 api 來(lái)獲取
獲取照片
獲取照片比較簡(jiǎn)單,通過(guò) NSItemProvider 的 loadObject 方法跟束,并且指定 Class 類型為 UIImage
莺奸,就可以在回調(diào)中得到 UIImage
類型的照片了:
provider.loadObject(ofClass: UIImage.self) { (image, error) in
// do someting with results
}
獲取 LivePhoto
獲取 LivePhoto 與獲取照片類似丑孩,只是需要將 UIImage
替換為 PHLivePhoto
。之后你可以通過(guò) PHLivePhotoView
來(lái)顯示灭贷『垦睿或者通過(guò) PHAssetResourceManager
獲取 LivePhoto 的原始數(shù)據(jù)。
獲取視頻
獲取視頻稍微復(fù)雜一點(diǎn)氧腰,框架開(kāi)發(fā)者在官方論壇中明確指出需要使用 loadFileRepresentation
方法來(lái)加載大文件枫浙,例如視頻:
provider.loadFileRepresentation(forTypeIdentifier: "public.movie") { url, error in
// do something with results
}
loadFileRepresentation
的使用方式與 UIImage
類似,但需要額外傳入一個(gè)參數(shù) forTypeIdentifier
來(lái)指定文件類型古拴,指定為 public.movie
可以覆蓋相冊(cè)中的 .mov
和 .mp4
類型箩帚。
與照片不同的是,這個(gè) api 返回的是一個(gè) URL 類型的臨時(shí)文件路徑黄痪,蘋果在這個(gè) API 的說(shuō)明中指出:系統(tǒng)會(huì)把請(qǐng)求的文件數(shù)據(jù)復(fù)制到這個(gè)路徑對(duì)應(yīng)的地址紧帕,并且在回調(diào)執(zhí)行完畢后刪除臨時(shí)文件。
如果你需要在異步線程中對(duì)這個(gè)文件進(jìn)行處理桅打,那么需要再?gòu)?fù)制一次是嗜,將文件放到不會(huì)被系統(tǒng)自動(dòng)刪除的路徑下,并且在處理完畢后自行刪除挺尾。
關(guān)于 iCloud
iOS 相冊(cè)提供了 iCloud 同步功能鹅搪,如果用戶開(kāi)啟了相冊(cè)同步,那么相冊(cè)中的照片遭铺、視頻或者 LivePhoto 有可能會(huì)被上傳到 iCloud丽柿,而本地只保存有縮略圖,當(dāng)請(qǐng)求某張照片時(shí)魂挂,相冊(cè)會(huì)先從 iCloud 下載甫题,然后再返回?cái)?shù)據(jù)。
原先具有完整訪問(wèn)權(quán)限時(shí)涂召,App 可以獲得資源是否存在 iCloud 的狀態(tài)坠非,并且在下載時(shí)獲得進(jìn)度信息。由于 PHPicker
向 App 隱藏了所有隱私信息果正,因此我們無(wú)法再得知資源的 iCloud 同步狀態(tài)炎码,PHPicker
會(huì)自動(dòng)從 iCloud 下載資源,并且完成之后通過(guò) delegate 回調(diào)將數(shù)據(jù)返回舱卡。
不過(guò)這里有另外一個(gè)坑辅肾,不知道是因?yàn)楣て趩?wèn)題還是蘋果員工偷懶,目前宿主 App 是無(wú)法從 PHPicker
中獲取到 iCloud 的下載進(jìn)度信息的:
這是 PHPicker 的另一個(gè)大坑轮锥,這種情況下,如果用戶選擇了比較大的視頻要尔,或者是網(wǎng)絡(luò)狀態(tài)不好的話舍杜,視頻導(dǎo)出依然需要耗費(fèi)非常長(zhǎng)的時(shí)間新娜,并且沒(méi)有進(jìn)度信息!只能期待 iOS 的后續(xù)版本能夠?qū)⑦@一環(huán)補(bǔ)充完整了既绩。
總結(jié)
PHPicker
的集成本身很簡(jiǎn)單概龄,但是存在一些坑需要注意。簡(jiǎn)單總結(jié)一下優(yōu)缺點(diǎn):
優(yōu)點(diǎn)
保護(hù)用戶隱私(初期可以作為 App 的宣傳點(diǎn)饲握?)
不需要頻繁詢問(wèn)相冊(cè)權(quán)限私杜,對(duì)于用戶體驗(yàn)提升較大
行為邏輯與系統(tǒng)相冊(cè)保持一致,降低了用戶的學(xué)習(xí)成本
集成簡(jiǎn)單救欧,在最低支持版本 iOS 14 之后衰粹,可以拋棄權(quán)限驗(yàn)證相關(guān)代碼和第三方照片庫(kù)(似乎很遙遠(yuǎn))
缺點(diǎn)
太過(guò)簡(jiǎn)單,缺少細(xì)節(jié)的篩選配置比如視頻時(shí)長(zhǎng)等
iCloud 大文件下載缺少進(jìn)度信息(這個(gè)缺陷似乎把優(yōu)點(diǎn)中的用戶體驗(yàn)提升干掉了笆怠。铝耻。。)
iOS 14 正式版剛上線不久蹬刷,目前還沒(méi)有看到適配的大廠 App瓢捉,可能也是在權(quán)衡利弊之中。不過(guò)從長(zhǎng)遠(yuǎn)來(lái)看办成,PHPicker 必然會(huì)將那些第三方的相冊(cè)組件掃進(jìn)歷史的垃圾堆的 :)