如何使用Photos框架仅获取相机胶卷中的图像

25
以下代码加载了也位于iCloud上或者通过流加载的图像。我们怎样才能将搜索限制为仅在相机胶卷中的图像?

以下代码加载了也位于iCloud上或者通过流加载的图像。我们怎样才能将搜索限制为仅在相机胶卷中的图像?

var assets = PHAsset.fetchAssetsWithMediaType(PHAssetMediaType.Image, options: nil)

你在 Stack Overflow 上看到的其他问题/答案有没有对此有所帮助? - admdrew
没有。有什么建议可以看看吗?即使是苹果的文档也不够详细。 - William Falcon
我刚刚开始在这里查看了一些问题,但还没有亲自尝试过任何一个。 - admdrew
8个回答

21
在添加了“相机胶卷”和“照片流”相册后,苹果在iOS 8.1中添加了以下PHAssetCollectionSubtype类型:
  1. PHAssetCollectionSubtypeAlbumMyPhotoStream(与PHAssetCollectionTypeAlbum一起)- 获取照片流相册。

  2. PHAssetCollectionSubtypeSmartAlbumUserLibrary(与PHAssetCollectionTypeSmartAlbum一起)- 获取相机胶卷相册。

尚未测试其是否向后兼容iOS 8.0.x。

2
哦,PHAssetCollectionSubtypeSmartAlbumUserLibrary 在我的 iPad 上列出了 14,850 个项目。那是整个照片集合,而不是在我的 iPad 上的内容... - Grimxn
1
如果您启用了iCloud照片库,它将列出设备上的所有资产以及通过iCloud照片库可用的资产。据我所知,没有(记录在案的)方法可以知道哪些资产存储在设备本地,哪些资产可通过云端访问但尚未同步。 - StatusReport
确实,那样做可以实现,但我认为您不想迭代所有资产并请求它们以确定它们是否本地设备。 - StatusReport
当我使用iCloud照片库时,我得到了相同的结果。它返回“所有照片”集合中的所有图像,无论它们是否实际上在设备上。可以使用requestContentEditingInputWithOptions或requestImageDataForAsset检查图像是否在设备上,但这是异步的,并且似乎为每个列表中的资产执行此操作很疯狂。为什么他们不只是在fetchAssetsInAssetCollection中指定仅本地资产的方法呢? - Tom Kincaid
我不知道有公共API可以揭示这一点 - 他们似乎试图让客户端保持透明。如果您愿意使用未记录的API,则有许多属性可能为nil,如果资产未下载。 - StatusReport

5

如果你和我一样在搜索Objective-C代码,并且由于获取弃用的AssetsLibrary代码未得到新库/照片框架的答案,那么这篇文章可以帮助你:

Swift

全局变量:

  func getAllPhotosFromCameraRoll() -> [UIImage] {
    // TODO: Add `NSPhotoLibraryUsageDescription` to info.plist
    PHPhotoLibrary.requestAuthorization { print($0) } // TODO: Move this line of code to somewhere before attempting to access photos

    var images = [UIImage]()

    let requestOptions: PHImageRequestOptions = PHImageRequestOptions()
    requestOptions.resizeMode = .exact
    requestOptions.deliveryMode = .highQualityFormat
    requestOptions.isSynchronous = true

    let fetchResult: PHFetchResult = PHAsset.fetchAssets(with: .image, options: nil)
    let manager: PHImageManager = PHImageManager.default()

    for i in 0..<fetchResult.count {
      let asset = fetchResult.object(at: i)
      manager.requestImage(
        for: asset,
        targetSize: PHImageManagerMaximumSize,
        contentMode: .default,
        options: requestOptions,
        resultHandler: { (image: UIImage?, info: [AnyHashable: Any]?) -> Void in
          if let image = image {
            images.append(image)
          }
        })
    }

    return images
  }

Objective C

全局变量:

NSArray *imageArray;
NSMutableArray *mutableArray;

以下方法将会帮助您:
-(void)getAllPhotosFromCamera
{
    imageArray=[[NSArray alloc] init];
    mutableArray =[[NSMutableArray alloc]init];

    PHImageRequestOptions *requestOptions = [[PHImageRequestOptions alloc] init];
    requestOptions.resizeMode   = PHImageRequestOptionsResizeModeExact;
    requestOptions.deliveryMode = PHImageRequestOptionsDeliveryModeHighQualityFormat;
    requestOptions.synchronous = true;
    PHFetchResult *result = [PHAsset fetchAssetsWithMediaType:PHAssetMediaTypeImage options:nil];

    NSLog(@"%d",(int)result.count);

    PHImageManager *manager = [PHImageManager defaultManager];
    NSMutableArray *images = [NSMutableArray arrayWithCapacity:[result count]];

    // assets contains PHAsset objects.

    __block UIImage *ima;
    for (PHAsset *asset in result) {
        // Do something with the asset

        [manager requestImageForAsset:asset
                           targetSize:PHImageManagerMaximumSize
                          contentMode:PHImageContentModeDefault
                              options:requestOptions
                        resultHandler:^void(UIImage *image, NSDictionary *info) {
                            ima = image;

                            [images addObject:ima];
                        }];


    }

    imageArray = [images copy];  // You can direct use NSMutuable Array images
}

5

通过一些实验,我们发现了文档中没有列出的隐藏属性(assetSource)。 基本上,您需要执行常规的获取请求,然后使用谓词来过滤来自相机卷中的内容. 此值应为 3。

示例代码:

//fetch all assets, then sub fetch only the range we need
var assets = PHAsset.fetchAssetsWithMediaType(PHAssetMediaType.Image, options: fetchOptions)

assets.enumerateObjectsUsingBlock { (obj, idx, bool) -> Void in
    results.addObject(obj)
}

var cameraRollAssets = results.filteredArrayUsingPredicate(NSPredicate(format: "assetSource == %@", argumentArray: [3]))
results = NSMutableArray(array: cameraRollAssets)

7
请注意,以下内容为标准免责声明,仅供参考。由于使用未记录的API可能存在风险,我们不对其使用后果负责。 - Majd Taby
目前看来,这似乎是最稳定的解决方案。我能想到的唯一其他“解决方案”是调用PHAsset.requestContentEditingInputWithOptions并检查返回的每个PHContentEditingInput对象,以获取URL,然后过滤掉所有不包含“/DCIM/”的URL。但与此相比,那种方法非常缓慢且脆弱。 - cwilper
1
这个问题似乎已经被“修复”,如果您尝试在GM中引用assetSource,它现在会引发异常。 - Rizwan Sattar
@RizwanSattar API已被隐藏,但在当前的8.0.2版本中该属性仍然可用,问题是它现在是私有API且未记录 - 不值得信任。 - Oliver Atkinson
对于任何使用 obj-c 的人来说,assets 将是一个 PHFetchResult 对象,而 objPHAsset 类型的对象。 - CalZone
我收到了“在获取选项中不支持的谓词:assetSource”的错误信息。 - Tom Kincaid

4
这很有帮助。你可以使用自己的数据模型来代替我使用的AlbumModel。
func getCameraRoll() -> AlbumModel {
          var cameraRollAlbum : AlbumModel!

          let cameraRoll = PHAssetCollection.fetchAssetCollections(with: .smartAlbum, subtype: .smartAlbumUserLibrary, options: nil)

          cameraRoll.enumerateObjects({ (object: AnyObject!, count: Int, stop: UnsafeMutablePointer) in
            if object is PHAssetCollection {
              let obj:PHAssetCollection = object as! PHAssetCollection

              let fetchOptions = PHFetchOptions()
              fetchOptions.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)]
              fetchOptions.predicate = NSPredicate(format: "mediaType = %d", PHAssetMediaType.image.rawValue)
              let assets = PHAsset.fetchAssets(in: obj, options: fetchOptions)

              if assets.count > 0 {
                let newAlbum = AlbumModel(name: obj.localizedTitle!, count: assets.count, collection:obj, assets: assets)

                cameraRollAlbum = newAlbum
              }
            }
          })
         return cameraRollAlbum

        }

这应该就是答案了,我之前一直在迭代所有的专辑并按字符串过滤,但它只在英语中起作用。 - Cristi Băluță

4
如果您使用自己的PHCachingImageManager而不是共享的PHImageManager实例,则在调用requestImageForAsset:targetSize:contentMode:options:resultHandler:时,可以在 PHImageRequestOptions 中设置选项以指定图像为本地图像。

networkAccessAllowed 属性

一个布尔值,指定Photos是否可以从iCloud下载所请求的图像。

networkAccessAllowed

讨论

如果为YES,并且所请求的图像未存储在本地设备上,则Photos将从iCloud下载该图像。要通知下载的进度,请使用progressHandler属性提供块,Photos会在下载图像时定期调用该块。如果为NO(默认值),并且图像不在本地设备上,则结果处理程序的信息字典中的 PHImageResultIsInCloudKey 值指示除非启用网络访问,否则无法使用该图像。


0
我也为此苦恼了很久。我发现使用fetchAssetsWithMediaType或fetchAssetsInAssetCollection无法仅过滤设备上的资产。我可以使用requestContentEditingInputWithOptions或requestImageDataForAsset来确定资产是否在设备上,但这是异步的,并且似乎对于列表中的每个资产都要使用太多资源。一定有更好的方法。
PHFetchResult *fetchResult = [PHAsset fetchAssetsWithMediaType:PHAssetMediaTypeImage options:nil];

for (int i=0; i<[fetchResult count]; i++) {

    PHAsset *asset = fetchResult[i];

    [asset requestContentEditingInputWithOptions:nil
                               completionHandler:^(PHContentEditingInput *contentEditingInput, NSDictionary *info) {
                                   if ([[info objectForKey:PHContentEditingInputResultIsInCloudKey] intValue] == 1) {
                                       NSLog(@"asset is in cloud");
                                   } else {
                                       NSLog(@"asset is on device");
                                   }
                               }];

}

0
这是由苹果提供的 Objective-C 版本。
-(NSMutableArray *)getNumberOfPhotoFromCameraRoll:(NSArray *)array{
    PHFetchResult *fetchResult = array[1];
    int index = 0;
    unsigned long pictures = 0;
    for(int i = 0; i < fetchResult.count; i++){
        unsigned long temp = 0;
        temp = [PHAsset fetchAssetsInAssetCollection:fetchResult[i] options:nil].count;
        if(temp > pictures ){
            pictures = temp;
            index = i;
        }
    }
    PHCollection *collection = fetchResult[index];

       if (![collection isKindOfClass:[PHAssetCollection class]]) {
        // return;
    }
    // Configure the AAPLAssetGridViewController with the asset collection.
    PHAssetCollection *assetCollection = (PHAssetCollection *)collection;
    PHFetchResult *assetsFetchResult = [PHAsset fetchAssetsInAssetCollection:assetCollection options:nil];
    self. assetsFetchResults = assetsFetchResult;
    self. assetCollection = assetCollection;
    self.numberOfPhotoArray = [NSMutableArray array];
    for (int i = 0; i<[assetsFetchResult count]; i++) {
        PHAsset *asset = assetsFetchResult[i];
        [self.numberOfPhotoArray addObject:asset];
    }
    NSLog(@"%lu",(unsigned long)[self.numberOfPhotoArray count]);
    return self.numberOfPhotoArray;
}

你可以获取以下细节

PHFetchResult *fetchResult = self.sectionFetchResults[1];
        PHCollection *collection = fetchResult[6];
**value 1,6 used to get camera images**
**value 1,0 used to get screen shots**
**value 1,1 used to get hidden**
**value 1,2 used to get selfies** 
**value 1,3 used to get recently added**
**value 1,4 used to get videos**
**value 1,5 used to get recently deleted**
 **value 1,7 used to get favorites**

苹果演示 链接

声明你的属性

@property (nonatomic, strong) NSArray *sectionFetchResults;
@property (nonatomic, strong) PHFetchResult *assetsFetchResults;
@property (nonatomic, strong) PHAssetCollection *assetCollection;
@property (nonatomic, strong) NSMutableArray *numberOfPhotoArray;

这可能会对Objective-C开发人员有所帮助。谁知道Objective-C开发人员从未进入过这个链接呢? - karthikeyan
演示程序没有像你这样通过索引(例如相机图像的6)明确地引用每个集合;如果它们返回的顺序改变了怎么办? - Dave Nottage
更新后的答案似乎只返回了拥有最多资产的集合的计数。如果有一个集合比相机胶卷更多,那该怎么办? - Dave Nottage

-1

如果您不想依赖未记录的API,请查看[asset canPerformEditOperation:PHAssetEditOperationContent]。仅当设备上有完整的原始内容时,此选项才返回true。

诚然,这也是脆弱的,但测试表明它适用于所有资产来源类型(照片流,iTunes同步等)。


当我使用iCloud照片库时,它对我不起作用。无论图像是否实际在设备上,它都会返回true。 - Tom Kincaid

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接