美文网首页iOS干货
iOS7、8、9相册适配

iOS7、8、9相册适配

作者: wentianen | 来源:发表于2016-07-03 18:01 被阅读811次

    相册适配


    前言

    由于在iOS8及以后苹果将原有的操作相册的ALAssetsLibrary framework替换为Photos framework,所以,如果在应用中使用到的相册需要支持iOS8以下的系统版本的话,就需要了解Photos framework以做不同的版本适配。

    一、iOS8以下

    1. 几个重要的实体概念

    • ALAsset(iOS8及以后使用PHAsset)
      一个ALAsset实例对象代表一个资源实体,比如一张图片、一个视频。共有三种类型:

      ALAssetTypePhoto // 图片
      ALAssetTypeVideo // 视频
      ALAssetTypeUnknown // 未知
      

    通过这个实例,你可以获取到这个资源的创建时间、资源类型、缩略图、二进制数据等信息。

    • ALAssetsGroup(iOS8及以后使用PHAssetCollection)
      一个ALAssetsGroup实例对象代表一组资源的集合,也就是一个相册,可以是系统默认存在的相册(相机胶卷),也可以是开发者给用户创建的自定义相册(QQ)。
      通过它,你可以获取到这个相册的名称、封面缩略图等。

    • ALAssetsLibrary(iOS8及以后使用PHPhotoLibrary)
      一个ALAssetsLibrary实例对象对所有的相册资源进行索引。
      使用它,你可以获取指定类型的相册、单张图片等对象,也可以添加新的自定义相册或者图片到相薄。如果你想要知道有没有获取到系统相薄的访问权限,同样需要用到它。

    2. 实体相关API

    ALAssetsLibrary

    在iOS7及以下系统中,如果你想要获取相册中的资源,或者对相册进行操作,那么你先要创建一个ALAssetsLibrary实例对象。

    • 获取指定类型的相册

       - (void)enumerateGroupsWithTypes:(ALAssetsGroupType)types usingBlock:(ALAssetsLibraryGroupsEnumerationResultsBlock)enumerationBlock failureBlock:(ALAssetsLibraryAccessFailureBlock)failureBlock
      

    注意
    由于这里的遍历都是异步的操作,所以ALAssetsLibrary的实例对象需要被一个静态变量或者成员变量引用来保证不被销毁,回调的Block才能保证被成功调用。下面同此处。

    • 根据一个相册URL获取这个相册

       - (void)groupForURL:(NSURL *)groupURL resultBlock:(ALAssetsLibraryGroupResultBlock)resultBlock failureBlock:(ALAssetsLibraryAccessFailureBlock)failureBlock
      
    • 创建一个相册

       - (void)addAssetsGroupAlbumWithName:(NSString *)name resultBlock:(ALAssetsLibraryGroupResultBlock)resultBlock failureBlock:(ALAssetsLibraryAccessFailureBlock)failureBlock
      
    • 相册授权状态

       + (ALAuthorizationStatus)authorizationStatus
      

    ALAssetsGroup

    iOS7及以下系统的中每个相册都是一个ALAssetsGroup实例,你可以使用这个实例获取这个相册的相关信息。

    • 获取相册信息

       - (id)valueForProperty:(NSString *)property  
      

    参数property包含以下类型:
    ALAssetsGroupPropertyName
    ALAssetsGroupPropertyType
    ALAssetsGroupPropertyPersistentID
    ALAssetsGroupPropertyURL

    • 封面图

       - (CGImageRef)posterImage
      
    • 相册包含实体(照片、视频)的数量

       - (NSInteger)numberOfAssets
      
    • 过滤规则

       - (void)setAssetsFilter:(ALAssetsFilter *)filter
      

    注意
    参数中的filter是一个ALAssetsFilter实例,这个实例只有三种类型:

        + (ALAssetsFilter *)allPhotos; // 所有图片
    
        + (ALAssetsFilter *)allVideos; // 所有视频
    
        + (ALAssetsFilter *)allAssets; // 所有视频及图片
    

    在使用- (NSInteger)numberOfAssets获取相册实体数量时,会依赖该过滤规则。比如一个相薄中存在5个视频和5张图片,如果指定allPhotos,则numberOfAssets返回值为5。同样使用- (void)enumerateAssetsUsingBlock:(ALAssetsGroupEnumerationResultsBlock)enumerationBlock获取相册中的所有资源时,也只能获取到指定过滤规则下的资源。

    • 使用相应遍历规则获取相册中的所有ALAsset资源

       - (void)enumerateAssetsWithOptions:(NSEnumerationOptions)options usingBlock:(ALAssetsGroupEnumerationResultsBlock)enumerationBlock
      

    3. 使用示例

    • 获取所有相册,并过滤相册中的视频

      + (void)getAssetsGroupsForIos8BelowSuccess:(void (^)(NSMutableArray *))success failure:(void (^)(NSError *error))failure {
      NSMutableArray *assetsGroups = [NSMutableArray array];
      [[self library] enumerateGroupsWithTypes:ALAssetsGroupAll usingBlock:^(ALAssetsGroup *group, BOOL *stop) {
          if (group != nil) {
              [group setAssetsFilter:[ALAssetsFilter allPhotos]];
              if (group.numberOfAssets > 0) {
                  [assetsGroups addObject:group];
              }
          } else {
              if (success) {
                  success(assetsGroups);
              }
          }
      } failureBlock:^(NSError *error) {
          if (failure) {
              failure(error);
          }
      }];
      

    }

    • (ALAssetsLibrary *)library {
      static ALAssetsLibrary *library;
      static dispatch_once_t onceToken;
      dispatch_once(&onceToken, ^{
      library = [[ALAssetsLibrary alloc] init];
      });
      return library;
      }
    • 获取一个相册中的所有资源

      + (void)getTimeLineSectionModelsForIos8BelowWithGroup:(MRAlbumGroupModel *)group success:(void (^)(NSMutableArray *))success failure:(void (^)(NSError *))failure {
      NSMutableArray *sectionModels = [NSMutableArray array];
      [group.assetGroup enumerateAssetsWithOptions:NSEnumerationReverse usingBlock:^(ALAsset *result, NSUInteger index, BOOL *stop) {
          if(result) {
              [sectionModels addObject:result];
          }
      }];
      if (success != nil && sectionModels.count > 0) {
          success(sectionModels);
      }
      if (failure != nil && sectionModels.count == 0) {
          failure(nil);
      }
      

    }
    ```

    二、iOS8及以上

    1. 几个重要的实体概念

    与ALAssetsLibrary framework对应的几个实体

    • PHAsset
      一个PHAsset实例对象与ALAsset类似,代表一个资源实体,比如一张图片、一个视频。与ALAsset不同之处在于,多了一种实体类型,共四种类型:

      PHAssetMediaTypeUnknown = 0,
      PHAssetMediaTypeImage   = 1,
      PHAssetMediaTypeVideo   = 2,
      PHAssetMediaTypeAudio   = 3,
      

    同时,还多了更具体的子类型PHAssetMediaSubtype:

    ```
    PHAssetMediaSubtypeNone               = 0,
    
    // Photo subtypes
    PHAssetMediaSubtypePhotoPanorama      = (1UL << 0),
    PHAssetMediaSubtypePhotoHDR           = (1UL << 1),
    PHAssetMediaSubtypePhotoScreenshot PHOTOS_AVAILABLE_IOS_TVOS(9_0, 10_0) = (1UL << 2),
    PHAssetMediaSubtypePhotoLive PHOTOS_AVAILABLE_IOS_TVOS(9_1, 10_0) = (1UL << 3),
    
    // Video subtypes
    PHAssetMediaSubtypeVideoStreamed      = (1UL << 16),
    PHAssetMediaSubtypeVideoHighFrameRate = (1UL << 17),
    PHAssetMediaSubtypeVideoTimelapse     = (1UL << 18),
    ```
    

    通过这个实例,除了可以获取到这个资源的创建时间、资源类型、缩略图、二进制数据等信息,还可以获取到location等位置信息。

    • PHCollection
      PHCollection是个基类,它有两个子类。分别是PHAssetCollectionPHCollectionListPHAssetCollection代表 Photos 中的相册,PHCollectionList代表 Photos 中的文件夹。PHCollectionList里可嵌套PHAssetCollection,也可以嵌套自身类型,同时支持多重嵌套。
      一个PHAssetCollection实例对象与ALAssetsGroup类似,代表一组资源的集合,也就是一个相册。
      通过它,你可以获取到这个相册的名称、封面缩略图。
      以及相册类型PHAssetCollectionType

      PHAssetCollectionTypeAlbum      = 1, // 自定义相册,如QQ
      PHAssetCollectionTypeSmartAlbum = 2, // 相机胶卷、我的照片流、屏幕截图、全景照片等
      PHAssetCollectionTypeMoment     = 3, // 时刻
      

      相册子类型PHAssetCollectionSubtype

      // PHAssetCollectionTypeAlbum regular subtypes
      PHAssetCollectionSubtypeAlbumRegular         = 2,
      PHAssetCollectionSubtypeAlbumSyncedEvent     = 3,
      PHAssetCollectionSubtypeAlbumSyncedFaces     = 4,
      PHAssetCollectionSubtypeAlbumSyncedAlbum     = 5,
      PHAssetCollectionSubtypeAlbumImported        = 6,
          
      // PHAssetCollectionTypeAlbum shared subtypes
      PHAssetCollectionSubtypeAlbumMyPhotoStream   = 100,
      PHAssetCollectionSubtypeAlbumCloudShared     = 101,
          
      // PHAssetCollectionTypeSmartAlbum subtypes
      PHAssetCollectionSubtypeSmartAlbumGeneric    = 200,
      PHAssetCollectionSubtypeSmartAlbumPanoramas  = 201,
      PHAssetCollectionSubtypeSmartAlbumVideos     = 202,
      PHAssetCollectionSubtypeSmartAlbumFavorites  = 203,
      PHAssetCollectionSubtypeSmartAlbumTimelapses = 204,
      PHAssetCollectionSubtypeSmartAlbumAllHidden  = 205,
      PHAssetCollectionSubtypeSmartAlbumRecentlyAdded = 206,
      PHAssetCollectionSubtypeSmartAlbumBursts     = 207,
      PHAssetCollectionSubtypeSmartAlbumSlomoVideos = 208,
      PHAssetCollectionSubtypeSmartAlbumUserLibrary = 209,
      PHAssetCollectionSubtypeSmartAlbumSelfPortraits PHOTOS_AVAILABLE_IOS_TVOS(9_0, 10_0) = 210,
      PHAssetCollectionSubtypeSmartAlbumScreenshots PHOTOS_AVAILABLE_IOS_TVOS(9_0, 10_0) = 211,
          
      // Used for fetching, if you don't care about the exact subtype
      PHAssetCollectionSubtypeAny = NSIntegerMax
      
    • PHPhotoLibrary
      PHPhotoLibrary是个单例对象,与ALAssetsLibrary相比发生了较大变化,只能申请获取PHOtos权限以及授权状态获取等。
      使用这个单例你也可以注册相册改动的监听者,在相册发生变化(比如添加或导入了新图片)时,做一些刷新或其他处理。

    新的实体

    在iOS8及以上的相册资源获取中,都是使用fetch相关API的形式,过程类似Core Data。匹配资源的过程自然需要匹配规则,最终匹配结果也需要一个集合来记录。

    • PHFetchOptions
      PHFetchOptions的实例对象代表获取资源时的匹配规则。
      可以指定资源排序规则(NSArray<NSSortDescriptor *> *sortDescriptors;)、资源类型规则(NSPredicate *predicate)、最大数量(NSUInteger fetchLimit)等。

    • PHFetchResult
      PHFetchResult的实例对象代表相册资源的匹配结果集合。它包含零个或多个符合匹配规则的资源,这些资源可以是PHAssetCollection对象,也可以是PHAsset对象。

    • PHImageManager
      PHImageManager是一个单例对象,不需要你手动创建,这个单例对象可以让你获取到一个PHAsset资源的实际二进制数据——如一张图片数据。

    2. 实体相关API

    PHAssetCollection

    在iOS8及以上系统中,获取相册不需要创建实例,直接使用PHAssetCollection的类方法进行操作即可。

    • 获取指定类型的相册

       // Fetch asset collections of a single type and subtype provided (use PHAssetCollectionSubtypeAny to match all subtypes)
       + (PHFetchResult<PHAssetCollection *> *)fetchAssetCollectionsWithType:(PHAssetCollectionType)type subtype:(PHAssetCollectionSubtype)subtype options:(nullable PHFetchOptions *)options;
      
    • 基本属性

      @property (nonatomic, strong, readonly, nullable) NSString *localizedTitle; // 相册标题
      @property (nonatomic, assign, readonly) PHAssetCollectionType assetCollectionType; // 相册类型
      @property (nonatomic, assign, readonly) PHAssetCollectionSubtype assetCollectionSubtype; // 子类型
      @property (nonatomic, assign, readonly) NSUInteger estimatedAssetCount; // 预估资源数量
      @property (nonatomic, strong, readonly, nullable) NSDate *startDate; // 开始日期
      @property (nonatomic, strong, readonly, nullable) NSDate *endDate; // 结束日期
      @property (nonatomic, strong, readonly, nullable) CLLocation *approximateLocation; // 位置信息
      @property (nonatomic, strong, readonly) NSArray<NSString *> *localizedLocationNames; // 位置名称
      

    注意
    estimatedAssetCount并不是一个准确的值,当PHAssetCollection对象不能马上计算出当前相册中的资源数量时,会返回NSNotFound,如果想要获取资源的具体数量,需要使用PHAsset来fetch所有资源,计算资源数量。

    PHAsset

    • 获取某相册中的PHAsset资源

       + (PHFetchResult<PHAsset *> *)fetchAssetsInAssetCollection:(PHAssetCollection *)assetCollection options:(nullable PHFetchOptions *)options;
      
    • 获取某相册的封面资源

       + (nullable PHFetchResult<PHAsset *> *)fetchKeyAssetsInAssetCollection:(PHAssetCollection *)assetCollection options:(nullable PHFetchOptions *)options;
      

    注意
    这个API可以获取到系统默认的一些封面图资源,这个结果由零个或多个PHAsset组成,在相册是smartAlbum类型的情况下,可能会有零个结果的情况。

    • 获取中Photos中的所有PHAsset资源

       + (PHFetchResult<PHAsset *> *)fetchAssetsWithOptions:(nullable PHFetchOptions *)options;
      

    注意
    这里并不是获取某个相册中的PHAsset资源,而是手机中所有的PHAsset资源。
    比如手机中有10个相册,每个相册中10个PHAsset资源,如果不指定PHFetchOptions,使用该方法会获取到所有的100个PHAsset资源。

    • 获取Photos中指定资源类型的所有PHAsset资源

       + (PHFetchResult<PHAsset *> *)fetchAssetsWithMediaType:(PHAssetMediaType)mediaType options:(nullable PHFetchOptions *)options;
      

    参数中的mediaType可选:

    ```
    PHAssetMediaTypeUnknown = 0,
    PHAssetMediaTypeImage   = 1,
    PHAssetMediaTypeVideo   = 2,
    PHAssetMediaTypeAudio   = 3,
    ```
    

    PHImageManager

    • 获取PHAsset图片资源的图片数据

       - (PHImageRequestID)requestImageForAsset:(PHAsset *)asset targetSize:(CGSize)targetSize contentMode:(PHImageContentMode)contentMode options:(nullable PHImageRequestOptions *)options resultHandler:(void (^)(UIImage *__nullable result, NSDictionary *__nullable info))resultHandler;
      

    注意
    参数中的PHImageRequestOptions对象可以设置目标图片的一些属性,其中synchronous表示是否同步获取,默认是NO,也就是异步获取。

    3. 使用示例

    • 获取指定类型的相册

      + (NSMutableArray *)getCollecionsWithSmartAlbumSubtype:(PHAssetCollectionSubtype)subtype {
      PHFetchOptions *userAlbumsOptions = [PHFetchOptions new];
      PHFetchResult *userAlbumsResult = [PHAssetCollection fetchAssetCollectionsWithType:PHAssetCollectionTypeAlbum
                                                                                 subtype:PHAssetCollectionSubtypeAny
                                                                                 options:userAlbumsOptions];
      PHFetchResult *userSmartAlbumsResult = [PHAssetCollection fetchAssetCollectionsWithType:PHAssetCollectionTypeSmartAlbum
                                                                                      subtype:subtype
                                                                                      options:userAlbumsOptions];
      NSMutableArray *collections = [NSMutableArray array];
      void (^AlbumEnumerateObjectsUsingBlock)(PHAssetCollection *, NSUInteger idx, BOOL *) = ^(PHAssetCollection * _Nonnull collection, NSUInteger idx, BOOL * _Nonnull stop) {
          if (collection.estimatedAssetCount == 0) {
              return ;
          }
          NSUInteger numberOfAssets = 0;
          PHFetchResult *assetsResult = [PHAsset fetchAssetsInAssetCollection:collection options:nil];
          numberOfAssets = [assetsResult countOfAssetsWithMediaType:PHAssetMediaTypeImage];
          if (numberOfAssets == 0) {
              return;
          }
          [collections addObject:collection];
      };
      [userSmartAlbumsResult enumerateObjectsUsingBlock:AlbumEnumerateObjectsUsingBlock];
      [userAlbumsResult enumerateObjectsUsingBlock:AlbumEnumerateObjectsUsingBlock];
      return collections;
      

    }
    ```

    • 获取一个相册中的所有图片资源,按创建时间降序排序

      + (void)getTimeLineSectionModelsForIos8AboveWithGroup:(MRAlbumGroupModel *)group success:(void (^)(NSMutableArray *))success failure:(void (^)(NSError *))failure {
      PHFetchResult *assetsResult = [PHAsset fetchAssetsInAssetCollection:group.collection options:[self fetchOptions]];
      NSMutableArray *sectionModels = [NSMutableArray array];
      [assetsResult enumerateObjectsUsingBlock:^(PHAsset * _Nonnull asset, NSUInteger idx, BOOL * _Nonnull stop) {
          [sectionModels addObject:asset];
      }];
      if (success != nil && sectionModels.count > 0) {
          success(sectionModels);
      }
      if (failure != nil && sectionModels.count == 0) {
          failure(nil);
      }
      

    }

    • (PHFetchOptions *)fetchOptions {
      PHFetchOptions *fetchOptions = [PHFetchOptions new];
      fetchOptions.predicate = [NSPredicate predicateWithFormat:@"mediaType = %@", @(PHAssetMediaTypeImage)];
      fetchOptions.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"creationDate" ascending:NO]];
      return fetchOptions;
      }
      
      

    三、适配参考

    我在不同系统的相册适配中采用适配器的方式进行的适配,仅供参考。

    1. 适配器

    相册适配主要是为了解决在iOS8以上的系统中,使用ALLibrary相关API可能存在一些不兼容的问题,导致资源获取不一致情况的出现。

    在业务展现上并不期望因系统不同而给用户差别较大的体验,所以可以只针对相册资源数据获取的数据测适配即可,而无需改动UI。

    这种场景可以在数据层对ALAssetsGroupPHAssetCollection以及ALAssetPHAsset做抽象,抽取适配器即可。

    2. 简单示例

    • AssetGroupModel

      @interface AlbumGroupModel : NSObject
      
      @property (nonatomic, strong) UIImage *posterImage;
      @property (nonatomic, assign) NSUInteger numberOfAssets;
      @property (nonatomic, copy) NSString *assetsGroupPropertyName;
      
      @property (nonatomic, strong) PHAssetCollection *collection;
      @property (nonatomic, strong) ALAssetsGroup *assetGroup;
      
      @end
      @implementation AlbumGroupModel
      
    • (UIImage *)posterImage {
      if (_posterImage == nil) {
      if ([XSLAppInfoTool systemVersion] < 8.0) {
      _posterImage = [UIImage imageWithCGImage:self.assetGroup.posterImage];
      } else {
      PHFetchResult *groupResult = [PHAsset fetchAssetsInAssetCollection:self.collection options:self.class.fetchOptions];
      PHAsset *asset = groupResult.firstObject;
      PHImageRequestOptions *requestOptions = [[PHImageRequestOptions alloc] init];
      requestOptions.resizeMode = PHImageRequestOptionsResizeModeExact;

            CGFloat scale = [UIScreen mainScreen].scale;
            CGFloat dimension = 80.0f;
            CGSize size = CGSizeMake(dimension * scale, dimension * scale);
            __block UIImage *resultImage = nil;
            [[PHImageManager defaultManager] requestImageForAsset:asset targetSize:size contentMode:PHImageContentModeAspectFill options:requestOptions resultHandler:^(UIImage *result, NSDictionary *info) {
                resultImage = result;
            }];
            _posterImage = resultImage;
        }
      

      }
      return _posterImage;
      }
      @end

      
      
    • AssetModel

      @interface AlbumAssetModel : NSObject
      
      @property (nonatomic, strong, nullable) PHAsset *assetPh;
      @property (nonatomic, strong, nullable) ALAsset *assetAl;
      
      - (void)thumbnail:(nullable void(^)(UIImage * _Nullable thumbnail))resultCallback;
      
      @end
      @implementation AlbumAssetModel
      
      - (void)thumbnail:(void (^)(UIImage *))resultCallback {
          if (self.assetPh) {
              PHImageManager *manager = [PHImageManager defaultManager];
              [manager requestImageForAsset:self.assetPh targetSize:CGSizeMake(100, 100) contentMode:PHImageContentModeDefault options:nil resultHandler:^(UIImage * _Nullable result, NSDictionary * _Nullable info) {
                  if (resultCallback) {
                      resultCallback(result);
                  }
              }];
          } else if (self.assetAl) {
              if (resultCallback) {
                  resultCallback([UIImage imageWithCGImage:[self.assetAl thumbnail]]);
              }
          }
      }
      
      @end
      

    相关文章

      网友评论

        本文标题:iOS7、8、9相册适配

        本文链接:https://www.haomeiwen.com/subject/alkcjttx.html