從相冊中獲取 GIF 的一些小 Tip
在 iOS 8 的時候,Apple 推出了 PhotoKit 框架,提供了一系列豐富的接口,想了解 PhotoKit 的同學,可以看下 Apple 的示例:Example app using Photos framework。
如何判斷 GIF 資源
1. 獲取 PHAsset
的資源對象 PHAssetResource
,通過其 uniformTypeIdentifier
或 originalFilename
屬性來判斷是否為 GIF:
extension PHAsset { var isGIF: Bool { let resource = PHAssetResource.asssetResources(for: self).first! // 通過統一類型標識符(uniform type identifier) UTI 來判斷 let uti = resource.uniformTypeIdentifier as CFString return UTTypeConformsTo(uti, kUTTypeGIF) // 或者通過文件名后綴來判斷 return assetSource.originalFilename.hasSuffix("GIF") } }
關於PHAssetResource
,每個 PHAsset 對象都會引用一個或多個資源(resource),一個被修改過的圖片的PHAsset
對象會包含圖片編輯之前和之后的 resource,以及關於描述這次編輯的PHAdjustmentData
對象的 resource。我們可以將一個修改過后的 GIF 的 PHAsset
所包含的 PHAssetResource
打印出來看下:
let resources = PHAssetResource.assetResources(for: asset) resources.map { print($0) } /* 輸出: 修改之前: <PHInternalAssetResource: 0x6000002e0f80> type=photo size={636, 400} fileSize=668682 uti=com.compuserve.gif filename=IMG_0006.GIF assetLocalIdentifier=46ACADB2-357B-44B6-8837-4F686D2092BF/L0/001 修改之后: <PHInternalAssetResource: 0x6080000f2d80> type=photo size={636, 400} fileSize=668682 uti=public.jpeg filename=IMG_0006.GIF assetLocalIdentifier=46ACADB2-357B-44B6-8837-4F686D2092BF/L0/001 <PHInternalAssetResource: 0x6080000f3200> type=photo_full size={636, 400} fileSize=15481 uti=public.jpeg filename=FullSizeRender.jpg assetLocalIdentifier=46ACADB2-357B-44B6-8837-4F686D2092BF/L0/001 <PHInternalAssetResource: 0x6000000f2e80> type=adjustment size={0, 0} fileSize=776 uti=com.apple.property-list filename=Adjustments.plist assetLocalIdentifier=46ACADB2-357B-44B6-8837-4F686D2092BF/L0/001 */
從打印出的信息可以看到,在 GIF 被修改之后,UTI 從com.compuserve.gif
變成了 public.jpeg
,所以這里如果還通過 UTI 來判斷的話,就會錯漏,只能通過 fileName 來判斷。
另外, PHAssetResource
類只支持 iOS 9.0+。
2. 通過獲取 PHAsset
的元數據來判斷:
let requestOption = PHImageRequestOptions() requestOption.version = .unadjusted requestOption.isSynchronous = false PHImageManager.default().requestImageData(for: asset, options: requestOption, resultHandler: { (data, uti, orientation, info) in if let UTI = uti, UTTypeConformsTo(UTI as CFString, kUTTypeGIF) { // It's GIF } })
同樣,這里也需要考慮到 GIF 被修改的情況,requestOption.version
默認是 current
,即如果圖片被修改過的話,返回的就是包含所有調整和修改的圖像數據,因此我們需要將其設置為 unadjusted
來獲取原始的圖像數據。
相比上面的方法,這種方法更快速,用時更少。
關於 localIdentifier
localIdentifier
是 PHAsset
的父類 PHObject
的一個屬性,是每個圖片資源獨有的標識符。
我在開發 notGIF 的時候,每次啟動時都需要遍歷相冊中的所有圖片來獲取其中的 GIF,這個操作非常的耗時,十分影響用戶體驗。我嘗試過將其拆分成多個任務,分發到多個線程同時進行,雖然有些效果,但還是不盡如人意,畢竟隨着相冊中照片數量的增加,其所消耗的時間是線性增長的。
這時候,localIdentifier
就有了用武之地。因為 localIdentifier
是識別圖片資源的唯一標識符,所以,我們可以在第一次獲取到相冊中的 GIF 的時候,將其獲取到的所有 GIF 的 localIdentifier
記錄下來。這樣,下次啟動的時候,就可以通過這些 localIdentifier
來直接獲取 GIF 資源:
let gifAssets = PHAsset.fetchAssets(withLocalIdentifiers: gifIDs, options: fetchOptions)
獲取相冊中圖片的 url如果 localIdentifier
所對應的圖片資源被刪除或不存在的話,PHAsset.fetchAssets(withLocalIdentifiers: options:)
會自動過濾掉。同時,我們可以在后台去檢測相冊是否有變化,如果有,則更新 UI 以及 所存儲的 localIdentifier
的信息。
在適配 iMessage Extension 的時候,需要通過圖片的 url 來獲取和發送圖片,PhotoKit 也提供了獲取圖片資源 url 的方法:
asset.requestContentEditingInput(with: requestOptions, completionHandler: { (editingInput, info) in if let input = editingInput, let picURL = input.fullSizeImageURL { // insert attachment } })
#import "ViewController.h"
#import "TZImagePickerController.h"
#import "TZImageCropManager.h"
#import <Photos/Photos.h>
#import <CoreServices/CoreServices.h>
@interface ViewController ()
@property (weak, nonatomic) IBOutlet UIImageView *avatarImageView;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
}
- (IBAction)openBtnClicked:(id)sender {
TZImagePickerController *vc = [[TZImagePickerController alloc] initWithMaxImagesCount:1 delegate:nil];
vc.isSelectOriginalPhoto = YES;
vc.allowTakePicture = YES;
vc.allowTakeVideo = NO;
vc.allowPickingMultipleVideo = YES;
vc.allowPickingVideo = NO;
vc.allowPickingImage = YES;
vc.allowPickingOriginalPhoto = NO;
vc.allowPickingGif = YES;
vc.sortAscendingByModificationDate = NO;
vc.showSelectedIndex = YES;
vc.statusBarStyle = UIStatusBarStyleDefault;
vc.naviBgColor = UIColor.whiteColor;
vc.naviTitleColor = UIColor.blackColor;
vc.naviTitleFont = [UIFont systemFontOfSize:15];
vc.barItemTextColor = UIColor.blackColor;
vc.barItemTextFont = [UIFont systemFontOfSize:15];
vc.navLeftBarButtonSettingBlock = ^(UIButton *leftButton) {
UIImage *image = [UIImage imageNamed:@"nav_back"];
[leftButton setImage:image forState:UIControlStateNormal];
leftButton.imageEdgeInsets = UIEdgeInsetsMake(0, 0, 0, 44 - image.size.width);
};
vc.showPhotoCannotSelectLayer = YES;
vc.cannotSelectLayerColor = UIColor.whiteColor;
vc.oKButtonTitleColorNormal = UIColor.blackColor;
vc.oKButtonTitleColorDisabled = [UIColor colorWithWhite:0 alpha:0.3];
vc.iconThemeColor = [UIColor orangeColor];
vc.takePictureImage = [UIImage imageNamed:@"image_picker_camera"];
vc.photoDefImage = [UIImage imageNamed:@"checkbox3_n"];
vc.photoSelImage = [UIImage imageNamed:@"checkbox3_s2"];
vc.photoPreviewPageUIConfigBlock = ^(UICollectionView *collectionView, UIView *naviBar, UIButton *backButton, UIButton *selectButton, UILabel *indexLabel, UIView *toolBar, UIButton *originalPhotoButton, UILabel *originalPhotoLabel, UIButton *doneButton, UIImageView *numberImageView, UILabel *numberLabel) {
UIImage *image = [UIImage imageNamed:@"nav_back_white"];
[backButton setImage:image forState:UIControlStateNormal];
backButton.imageEdgeInsets = UIEdgeInsetsMake(0, 0, 0, 44 - image.size.width);
[doneButton setTitleColor: [UIColor whiteColor] forState:UIControlStateNormal];
};
vc.didFinishPickingPhotosHandle = ^(NSArray<UIImage *> *photos, NSArray *assets, BOOL isSelectOriginalPhoto) {
PHAsset *asset = assets.firstObject;
if ([self isGif:asset]) {
[self getGIFImage:assets.firstObject];
} else {
self.avatarImageView.image = photos.firstObject;
}
};
[self presentViewController:vc animated:YES completion:nil];
}
- (void)getGIFImage:(PHAsset *)asset {
[[TZImageManager manager] getOriginalPhotoDataWithAsset:asset progressHandler:^(double progress, NSError *error, BOOL *stop, NSDictionary *info) {
} completion:^(NSData *data, NSDictionary *info, BOOL isDegraded) {
if (!isDegraded) {
self.avatarImageView.image = [UIImage sd_tz_animatedGIFWithData:data];
}
}];
}
- (BOOL)isGif:(PHAsset *)asset {
PHAssetResource *resource = [[PHAssetResource assetResourcesForAsset:asset] firstObject];
if (resource != nil) {
return [resource.uniformTypeIdentifier isEqualToString: (__bridge NSString *)kUTTypeGIF];
}
return NO;
}
@end