iOS 開發之照片框架詳解

来源:http://www.cnblogs.com/zhouxubin/archive/2016/06/11/5575537.html
-Advertisement-
Play Games

一. 概要 在 iOS 設備中,照片和視頻是相當重要的一部分。最近剛好在製作一個自定義的 iOS 圖片選擇器,順便整理一下 iOS 中對照片框架的使用方法。在 iOS 8 出現之前,開發者只能使用 AssetsLibrary 框架來訪問設備的照片庫,這是一個有點跟不上 iOS 應用發展步伐以及代碼設 ...


一. 概要

在 iOS 設備中,照片和視頻是相當重要的一部分。最近剛好在製作一個自定義的 iOS 圖片選擇器,順便整理一下 iOS 中對照片框架的使用方法。在 iOS 8 出現之前,開發者只能使用 AssetsLibrary 框架來訪問設備的照片庫,這是一個有點跟不上 iOS 應用發展步伐以及代碼設計原則但確實強大的框架,考慮到 iOS7 仍占有不少的滲透率,因此 AssetsLibrary 也是本文重點介紹的部分。而在 iOS8 出現之後,蘋果提供了一個名為 PhotoKit 的框架,一個可以讓應用更好地與設備照片庫對接的框架,文末也會介紹一下這個框架。

另外值得強調的是,在 iOS 中,照片庫並不只是照片的集合,同時也包含了視頻。在 AssetsLibrary 中兩者都有相同類型的對象去描述,只是類型不同而已。文中為了方便,大部分時候會使用「資源」代表 iOS 中的「照片和視頻」。

二. AssetsLibrary 組成介紹

AssetsLibrary 的組成比較符合照片庫本身的組成,照片庫中的完整照片庫對象、相冊、相片都能在 AssetsLibrary 中找到一一對應的組成,這使到 AssetsLibrary 的使用變得直觀而方便。

  • AssetsLibrary: 代表整個設備中的資源庫(照片庫),通過 AssetsLibrary 可以獲取和包括設備中的照片和視頻
  • ALAssetsGroup: 映射照片庫中的一個相冊,通過 ALAssetsGroup 可以獲取某個相冊的信息,相冊下的資源,同時也可以對某個相冊添加資源。
  • ALAsset: 映射照片庫中的一個照片或視頻,通過 ALAsset 可以獲取某個照片或視頻的詳細信息,或者保存照片和視頻。
  • ALAssetRepresentation: ALAssetRepresentation 是對 ALAsset 的封裝(但不是其子類),可以更方便地獲取 ALAsset 中的資源信息,每個 ALAsset 都有至少有一個 ALAssetRepresentation 對象,可以通過 defaultRepresentation 獲取。而例如使用系統相機應用拍攝的 RAW + JPEG 照片,則會有兩個 ALAssetRepresentation,一個封裝了照片的 RAW 信息,另一個則封裝了照片的 JPEG 信息。

三. AssetsLibrary 的基本使用

AssetsLibrary 的功能很多,基本可以分為對資源的獲取/保存兩個部分,保存的部分相對簡單,API 也比較少,因此這裡不作詳細介紹。獲取資源的 API 則比較豐富了,一個常見的使用大量 AssetsLibrary API 的例子就是圖片選擇器(ALAsset Picker)。要製作一個圖片選擇器,思路應該是獲取照片庫-列出所有相冊-展示相冊中的所有圖片-預覽圖片大圖。

首先是要檢查 App 是否有照片操作授權:

1 2 3 4 5 6 7 8 9 10 NSString *tipTextWhenNoPhotosAuthorization; // 提示語 // 獲取當前應用對照片的訪問授權狀態 ALAuthorizationStatus authorizationStatus = [ALAssetsLibrary authorizationStatus]; // 如果沒有獲取訪問授權,或者訪問授權狀態已經被明確禁止,則顯示提示語,引導用戶開啟授權 if (authorizationStatus == ALAuthorizationStatusRestricted || authorizationStatus == ALAuthorizationStatusDenied) {     NSDictionary *mainInfoDictionary = [[NSBundle mainBundle] infoDictionary];     NSString *appName = [mainInfoDictionary objectForKey:@"CFBundleDisplayName"];     tipTextWhenNoPhotosAuthorization = [NSString stringWithFormat:@"請在設備的\"設置-隱私-照片\"選項中,允許%@訪問你的手機相冊", appName];     // 展示提示語 }

如果已經獲取授權,則可以獲取相冊列表:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 _assetsLibrary = [[ALAssetsLibrary alloc] init]; _albumsArray = [[NSMutableArray alloc] init]; [_assetsLibrary enumerateGroupsWithTypes:ALAssetsGroupAll usingBlock:^(ALAssetsGroup *group, BOOL *stop) {     if (group) {         [group setAssetsFilter:[ALAssetsFilter allPhotos]];         if (group.numberOfAssets > 0) {             // 把相冊儲存到數組中,方便後面展示相冊時使用             [_albumsArray addObject:group];         }     } else {         if ([_albumsArray count] > 0) {             // 把所有的相冊儲存完畢,可以展示相冊列表         } else {             // 沒有任何有資源的相冊,輸出提示         }     } } failureBlock:^(NSError *error) {     NSLog(@"Asset group not found!\n"); }];

上面的代碼中,遍歷出所有的相冊列表,並把相冊中資源數不為空的相冊 ALAssetGroup 對象的引用儲存到一個數組中。這裡需要強調幾點:

  • iOS 中允許相冊為空,即相冊中沒有任何資源,如果不希望獲取空相冊,則需要像上面的代碼中那樣手動過濾
  • ALAssetsGroup 有一個 setAssetsFilter 的方法,可以傳入一個過濾器,控制只獲取相冊中的照片或只獲取視頻。一旦設置過濾,ALAssetsGroup 中資源列表和資源數量的獲取也會被自動更新。
  • 整個 AssetsLibrary 中對相冊、資源的獲取和保存都是使用非同步處理(Asynchronous),這是考慮到資源文件體積相當比較大(還可能很大)。例如上面的遍歷相冊操作,相冊的結果使用 block 輸出,如果相冊遍歷完畢,則最後一次輸出的 block 中的 group 參數值為 nil。而 stop 參數則是用於手工停止遍歷,只要把 *stop 置 YES,則會停止下一次的遍歷。關於這一點常常會引起誤會,所以需要註意。

現在,已經可以獲取相冊了,接下來是獲取相冊中的資源:

1 2 3 4 5 6 7 8 _imagesAssetArray = [[NSMutableArray alloc] init]; [assetsGroup enumerateAssetsWithOptions:NSEnumerationReverse usingBlock:^(ALAsset *result, NSUInteger index, BOOL *stop) {     if (result) {         [_imagesAssetArray addObject:result];     } else {         // result 為 nil,即遍歷相片或視頻完畢,可以展示資源列表     } }];

跟遍歷相冊的過程類似,遍歷相片也是使用一系列的非同步方法,其中上面的方法所輸出的 block 中,除了 result 參數表示資源信息,stop 用於手工停止遍歷外,還提供了一個 index 參數,這個參數表示資源的索引。一般來說,展示資源列表都會使用縮略圖(result.thumbnail),因此即使資源很多,遍歷資源的速度也會相當快。但如果確實需要載入資源的高清圖或者其他耗時的處理,則可以利用上面的 index 參數和 stop 參數做一個分段拉取資源。例如:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 NSUInteger _targetIndex; // index 目標值,拉取資源直到這個值就手工停止拉取 NSUInteger _currentIndex; // 當前 index,每次拉取資源時從這個值開始   _targetIndex = 50; _currentIndex = 0;   - (void)loadAssetWithAssetsGroup:(assetsGroup *)assetsGroup {     [assetsGroup enumerateAssetsAtIndexes:[NSIndexSet indexSetWithIndex:_currentIndex] options:NSEnumerationReverse usingBlock:^(ALAsset *result, NSUInteger index, BOOL *stop) {         _currentIndex = index;         if (index > _targetIndex) {             // 拉取資源的索引如果比目標值大,則停止拉取             *stop = YES;         } else {             if (result) {                 [_imagesAssetArray addObject:result];             } else {                 // result 為 nil,即遍歷相片或視頻完畢             }         }     }]; }   // 之前拉取的數據已經顯示完畢,需要展示新數據,重新調用 loadAssetWithAssetsGroup 方法,並根據需要更新 _targetIndex 的值

最後一步是獲取圖片詳細信息,例如:

1 2 3 4 // 獲取資源圖片的詳細資源信息,其中 imageAsset 是某個資源的 ALAsset 對象 ALAssetRepresentation *representation = [imageAsset defaultRepresentation]; // 獲取資源圖片的 fullScreenImage UIImage *contentImage = [UIImage imageWithCGImage:[representation fullScreenImage]];

對於一個 ALAssetRepresentation,裡面包含了圖片的多個版本。最常用的是 fullResolutionImage 和 fullScreenImage。fullResolutionImage 是圖片的原圖,通過 fullResolutionImage 獲取的圖片沒有任何處理,包括通過系統相冊中“編輯”功能處理後的信息也沒有被包含其中,因此需要展示“編輯”功能處理後的信息,使用 fullResolutionImage 就比較不方便,另外 fullResolutionImage 的拉取也會比較慢,在多張 fullResolutionImage 中切換時能明顯感覺到圖片的載入過程。因此這裡建議獲取圖片的 fullScreenImage,它是圖片的全屏圖版本,這個版本包含了通過系統相冊中“編輯”功能處理後的信息,同時也是一張縮略圖,但圖片的失真很少,缺點是圖片的尺寸是一個適應屏幕大小的版本,因此展示圖片時需要作出額外處理,但考慮到載入速度非常快的原因(在多張圖片之間切換感受不到圖片載入耗時),仍建議使用 fullScreenImage。

系統相冊的處理過程大概也是如上,可以看出,在整個過程中並沒有使用到圖片的 fullResolutionImage,從相冊列表展示到最終查看資源,都是使用縮略圖,這也是 iOS 相冊載入快的一個重要原因。

三. AssetsLibrary 的坑點

作為一套老框架,AssetsLibrary 不但有坑,而且還不少,除了上面提到的資源非同步拉取時需要註意的事項,下麵幾點也是值得註意的:

1. AssetsLibrary 實例需要強引用

實例一個 AssetsLibrary 後,如上面所示,我們可以通過一系列枚舉方法獲取到需要的相冊和資源,並把其儲存到數組中,方便用於展示。但是,當我們把這些獲取到的相冊和資源儲存到數組時,實際上只是在數組中儲存了這些相冊和資源在 AssetsLibrary 中的引用(指針),因而無論把相冊和資源儲存數組後如何利用這些數據,都首先需要確保 AssetsLibrary 沒有被 ARC 釋放,否則把數據從數組中取出來時,會發現對應的引用數據已經丟失(參見下圖)。這一點較為容易被忽略,因此建議在使用 AssetsLibrary 的 viewController 中,把 AssetsLibrary 作為一個強持有的 property 或私有變數,避免在枚舉出 AssetsLibrary 中所需要的數據後,AssetsLibrary 就被 ARC 釋放了。

如下圖:實例化一個 AssetsLibrary 的局部變數,枚舉所有相冊並儲存在名為 _albumsArray 的數組中,展示相冊時再次查看數組,發現 ALAssetsGroup 中的數據已經丟失。

ALAssetsLibrary_release

2. AssetsLibrary 遵循寫入優先原則

寫入優先也就是說,在利用 AssetsLibrary 讀取資源的過程中,有任何其它的進程(不一定是同一個 App)在保存資源時,就會收到 ALAssetsLibraryChangedNotification,讓用戶自行中斷讀取操作。最常見的就是讀取 fullResolutionImage 時,用進程在寫入,由於讀取 fullResolutionImage 耗時較長,很容易就會 exception。

3. 開啟 Photo Stream 容易導致 exception

本質上,這跟上面的 AssetsLibrary 遵循寫入優先原則是同一個問題。如果用戶開啟了共用照片流(Photo Stream),共用照片流會以 mstreamd 的方式“偷偷”執行,當有人把相片寫入 Camera Roll 時,它就會自動保存到 Photo Stream Album 中,如果用戶剛好在讀取,那就跟上面說的一樣產生 exception 了。由於共用照片流是用戶決定是否要開啟的,所以開發者無法改變,但是可以通過下麵的介面在需要保護的時刻關閉監聽共用照片流產生的頻繁通知信息。

1 [ALAssetsLibrary disableSharedPhotoStreamsSupport];

四. PhotoKit 簡介

PhotoKit 是一套比 AssetsLibrary 更完整也更高效的庫,對資源的處理跟 AssetsLibrary 也有很大的不同。

首先簡單介紹幾個概念:

  • PHAsset: 代表照片庫中的一個資源,跟 ALAsset 類似,通過 PHAsset 可以獲取和保存資源
  • PHFetchOptions: 獲取資源時的參數,可以傳 nil,即使用系統預設值
  • PHFetchResult: 表示一系列的資源集合,也可以是相冊的集合
  • PHAssetCollection: 表示一個相冊或者一個時刻,或者是一個「智能相冊(系統提供的特定的一系列相冊,例如:最近刪除,視頻列表,收藏等等,如下圖所示)
  • PHImageManager: 用於處理資源的載入,載入圖片的過程帶有緩存處理,可以通過傳入一個 PHImageRequestOptions 控制資源的輸出尺寸等規格
  • PHImageRequestOptions: 如上面所說,控制載入圖片時的一系列參數

下圖中 UITableView 的第二個 section 就是 PhotoKit 所列出的所有智能相冊

photokit-album-list

再列出幾個代碼片段,展示如何獲取相冊以及某個相冊下資源的代碼:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 // 列出所有相冊智能相冊 PHFetchResult *smartAlbums = [PHAssetCollection fetchAssetCollectionsWithType:PHAssetCollectionTypeSmartAlbum subtype:PHAssetCollectionSubtypeAlbumRegular options:nil];   // 列出所有用戶創建的相冊 PHFetchResult *topLevelUserCollections = [PHCollectionList fetchTopLevelUserCollectionsWithOptions:nil];   // 獲取所有資源的集合,並按資源的創建時間排序 PHFetchOptions *options = [[PHFetchOptions alloc] init]; options.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"creationDate" ascending:YES]]; PHFetchResult *assetsFetchResults = [PHAsset fetchAssetsWithOptions:options];   // 在資源的集合中獲取第一個集合,並獲取其中的圖片 PHCachingImageManager *imageManager = [[PHCachingImageManager alloc] init]; PHAsset *asset = assetsFetchResults[0]; [imageManager requestImageForAsset:asset                          targetSize:SomeSize                         contentMode:PHImageContentModeAspectFill                             options:nil                       resultHandler:^(UIImage *result, NSDictionary *info) {                                                       // 得到一張 UIImage,展示到界面上                                                   }];

結合上面幾個代碼片段上看,PhotoKit 相對 AssetsLibrary 主要有三點重要的改進:

  • 從 AssetsLibrary 中獲取數據,無論是相冊,還是資源,本質上都是使用枚舉的方式,遍歷照片庫取得相應的數據。而 PhotoKit 則是通過傳入參數,直接獲取相應的數據,因而效率會提高不少。
  • 在 AssetsLibrary 中,相冊和資源是對應不同的對象(ALAssetGroup 和 ALAsset),因此獲取相冊和獲取資源是兩個完全沒有關聯的介面。而 PhotoKit 中則有 PHFetchResult 這個可以統一儲存相冊或資源的對象,因此處理相冊和資源時也會比較方便。
  • PhotoKit 返回資源結果時,同時返回了資源的元數據,獲取元數據在 AssetsLibrary 中是很難辦到的一件事。同時通過 PHAsset,開發者還能直接獲取資源是否被收藏(favorite)和隱藏(hidden),拍攝圖片時是否開啟了 HDR 或全景模式,甚至能通過一張連拍圖片獲取到連拍圖片中的其他圖片。這也是文章開頭說的,PhotoKit 能更好地與設備照片庫接入的一個重要因素。

關於 PhotoKit,建議可以參考 Apple 的 Example app using Photos framework


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 概述 上一篇我們介紹了通過構造函數和原型可以實現JavaScript中的“類”,由於構造函數和函數的原型都是對象,所以JavaScript的“類”本質上也是對象。這一篇我們將介紹JavaScript中的一個重要概念原型鏈,以及如何經原型鏈實現JavaScript中的繼承。 C#的繼承 首先,我們簡單... ...
  • ...
  • 隨著rc(release candidate,候選版本)版本的推出,萬眾矚目的angular2終於離正式發佈不遠啦!五月初舉辦的ng-conf大會已經過去了整整一個月,大多數api都如願保持在了相對穩定的狀態——當然也有router這樣的例外,在rc階段還在大面積返工,讓人頗為不解——不過總得說來, ...
  • 1、css的概念:(CascadingStyleSheet級聯樣式表) 優點:1.內容與表現分離。(用網頁的內容xhtml就可以與表象分開) 2.表象統一 3.豐富的樣式 4.減少網頁代碼 5.運用獨立於網頁的css 2.選擇器 1.標簽選擇器 標簽名{屬性:屬性值;} 2.類選擇器 .類名{屬性: ...
  • 開篇語 最近接手了一個移動端的項目。個人感覺是自己做得比較快而且比較健壯的一個。。。移動端最主要就是頁面要適用不同的手機屏幕,ipad等。下麵就分享一些技巧,讓你不依賴任何框架高效地搭建自己的項目。 一、樣式按組件或板塊分文件寫再合成 ①設置各種變數 採用scss或者less來寫css代碼有很多好處 ...
  • 這次我們來看下js組件的使用,本篇文章會有點長,希望大家可以耐心看,相信收穫會有不少。不少園友加我好友,表示喜歡我寫文字的風格,簡單明瞭,這裡,再次謝謝你們的支持。一方面,博主自身技術有限,寫的東西都比較基礎,另一方面,博主寫的東西,都是根據自己的理解,把複雜的東西用最簡單的語言表達出來。所以,寫的 ...
  • 我是一隻即將大四的大三狗,這是我的第一篇博客,說來慚愧。今年1月份,學校放寒假的時候開始自學的IOS,放假的時候比較起勁,看了一堆Object-C的視頻,然後照著中英文對照的IOS基礎開發教程,做了兩個簡單的手機APP,一個是小游戲,一個是日程提醒的。 但是開學之後,又投入到學校的課程還有我的一些學 ...
  • step 1:定義一個監聽介面 step 2:android application文件中添加如下代碼 //初始化 //註冊監聽 //記憶體空間過低的時候,被系統調用 step 3:那些浪費記憶體的地方,比如圖片緩存,可以實現並註冊這個監聽 推薦:http://www.cnblogs.com/rouch ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...