Check given PHAsset is iCloud asset?
Asked Answered
L

8

19

I'm trying to get PhAsset object. I want to segregate iCloud assets. Here is my code,

PHFetchResult *cloudAlbums = [PHAssetCollection fetchAssetCollectionsWithType:PHAssetCollectionTypeAlbum subtype:PHAssetCollectionSubtypeAny options:nil];

[cloudAlbums enumerateObjectsUsingBlock:^(PHAssetCollection *collection, NSUInteger idx, BOOL *stop){
    if(collection != nil){

        PHFetchResult *result = [PHAsset fetchAssetsInAssetCollection:collection options:fetchOptions];
        [result enumerateObjectsUsingBlock:^(PHAsset *asset, NSUInteger idx, BOOL *stop)
         {
            // check asset is iCloud asset 

         }];
    }

}];

Please tell me how to find the PHAsset is iCloud asset?

Leptorrhine answered 12/8, 2015 at 13:25 Comment(0)
M
26

It's a bit kind of hack, where I had to dig out the resource array and debug to find out my required information. But it works. Although this is an undocumented code and I'm not sure whether apple will reject the app because of this or not. Give it a try and see what happens!

// asset is a PHAsset object for which you want to get the information    
NSArray *resourceArray = [PHAssetResource assetResourcesForAsset:asset];
BOOL bIsLocallayAvailable = [[resourceArray.firstObject valueForKey:@"locallyAvailable"] boolValue]; // If this returns NO, then the asset is in iCloud and not saved locally yet

You can also get some other useful information from asset resource, such as - original filename, file size, file url, etc.

Misrule answered 24/9, 2017 at 12:17 Comment(6)
Were you guys able to publish your Apps with this code to the AppStore?Groundhog
Yes, without any issue!!Misrule
@Misrule love you dood! :-) :-) :-)Ameliaamelie
@PanMluvčí Glad it helped you!! :-)Misrule
This is really simple way to figure out if asset is not in local. But for me, sometimes it gives wrong value for some assets. I got "false" value for key "locallyAvailable" even this asset have PHImageFileSandboxExtensionTokenKey and PHImageFileURLKey.Padegs
This works in iOS 16.Marzi
P
12

There are actually 2 kinds of situations: 1. The photo is captured by this device, and is uploaded to iCloud. Then, you can use the progressHandler to check whether it needs iCloud download.

__block BOOL isPhotoInICloud = NO;
PHImageRequestOptions *options = [[PHImageRequestOptions alloc] init];
options.networkAccessAllowed = YES;
options.deliveryMode = PHImageRequestOptionsDeliveryModeHighQualityFormat;
options.progressHandler = ^(double progress, NSError *error, BOOL *stop, NSDictionary *info){

        isPhotoInICloud = YES;

});

[[PHImageManager defaultManager] requestImageForAsset:asset targetSize:PHImageManagerMaximumSize contentMode:PHImageContentModeAspectFit options:options resultHandler:^(UIImage * _Nullable result, NSDictionary * _Nullable info) {

        if (isPhotoInICloud) {
            // Photo is in iCloud.
        }
});
  1. The photo is in iCloud but uploaded from other device. And you did not save it to your local photo library. So the progressHandler block will never ever be invoked. I don't know why but it's true, and I think it's kind of a bug of PhotoKit framework. For this situation, if you use the PHImageResultIsInCloudKey, that is also difficult. Because you can know the PHImageResultIsInCloudKey value just in the requestImageForAsset's resultHandler block. But that's the time after the photo request is initiated.

So, at least, in my opinion, there is no way to check whether photo is stored in iCloud. Maybe there is other better way, please let me know. Thanks very much!

Pl answered 1/11, 2016 at 3:22 Comment(0)
T
2

When you request for an image you get a key in info dictionary which tells you if the asset is present in iCloud.

[cloudAlbums enumerateObjectsUsingBlock:^(PHAssetCollection *collection, NSUInteger idx, BOOL *stop)
{
    PHFetchResult *result = [PHAsset fetchAssetsInAssetCollection:collection options:fetchOptions];
    [result enumerateObjectsUsingBlock:^(PHAsset *asset, NSUInteger idx, BOOL *stop)
    {  
        PHImageRequestOptions *options = [[PHImageRequestOptions alloc] init];
        options.resizeMode = PHImageRequestOptionsResizeModeFast;
        options.synchronous = YES;
        __block BOOL isICloudAsset = NO;
        [[PHImageManager defaultManager] requestImageForAsset:asset targetSize:imageSize contentMode:PHImageContentModeAspectFit options:options resultHandler:^(UIImage *result, NSDictionary *info) 
        {
            if ([info objectForKey: PHImageResultIsInCloudKey].boolValue) 
            {
                isICloudAsset = YES;
            }
        }]; 
    }];
}];
Tristram answered 13/8, 2015 at 2:32 Comment(4)
I want to add PHAssets object to my datasource only if its is in iCloud. This is something which I have to request for image and then I can make out wether it is in iCloudLeptorrhine
There is no other way to know about if the asset is in cloud or not. If you have performance concerns then you can ask for a really small size asset or prefetch the assets.Tristram
In my testing with videos requestImageForAsset: does not return PHImageResultIsInCloudKey in the info directory. However requestImageDataForAsset: always does, so I'd use that function instead.Epideictic
I get an info dictionary every time I call requestImageForAsset; however, for the sake of experimentation, I used the requestImageDataForAsset method just to see what happens. You can substitute it for the requestImageForAsset method if it doesn't work for you.Prune
P
1

Here is the Swift 3 version

func checkVideoType(){
    if selectedAsset != nil {
        guard (selectedAsset.mediaType == .video) else {
            print("Not a valid video media type")
            return
        }
        requestID = checkIsiCloud(assetVideo:selectedAsset, cachingImageManager: catchManager)
    }
}

 func checkIsiCloud(assetVideo:PHAsset,cachingImageManager:PHCachingImageManager) -> PHImageRequestID{

        let opt=PHVideoRequestOptions()
        opt.deliveryMode = .mediumQualityFormat
        opt.isNetworkAccessAllowed=true //iCloud video can play
        return cachingImageManager.requestAVAsset(forVideo:assetVideo, options: opt) { (asset, audioMix, info) in

            DispatchQueue.main.async {
                if (info!["PHImageFileSandboxExtensionTokenKey"] != nil) {
                    self.iCloudStatus=false
                    self.playVideo(videoAsset:asset!)
                }else if((info![PHImageResultIsInCloudKey]) != nil) {
                    self.iCloudStatus=true

                }else{
                   self.iCloudStatus=false
                   self.playVideo(videoAsset:asset!)
                }
            }
    }

}
Punt answered 13/10, 2018 at 8:4 Comment(0)
P
0

Following is a method you can implement to acquire all videos in the Videos folder of the Photos app, which uses a predicate with the PHFetchRequest to filter only videos stored on the iPhone itself, and not in iCloud:

// Collect all videos in the Videos folder of the Photos app
- (PHFetchResult *)assetsFetchResults {
    __block PHFetchResult *i = self->_assetsFetchResults;
    if (!i) {
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            PHFetchOptions *fetchOptions = [PHFetchOptions new];
            fetchOptions.predicate = [NSPredicate predicateWithFormat:@"(sourceType & %d) != 0", PHAssetSourceTypeUserLibrary];
            PHFetchResult *smartAlbums = [PHAssetCollection fetchAssetCollectionsWithType:PHAssetCollectionTypeSmartAlbum subtype:PHAssetCollectionSubtypeSmartAlbumVideos options:fetchOptions];
            PHAssetCollection *collection = smartAlbums.firstObject;
            if (![collection isKindOfClass:[PHAssetCollection class]]) collection = nil;
            PHFetchOptions *allPhotosOptions = [[PHFetchOptions alloc] init];
            allPhotosOptions.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"creationDate" ascending:NO]];
            i = [PHAsset fetchAssetsInAssetCollection:collection options:allPhotosOptions];
            self->_assetsFetchResults = i;
        });
    }

    return i;
}

Apple's documentation on PHFetchResult states that only a subset of attributes can be used with a predicate; so, if the above code does not work for you, remove the PHFetchOptions predicate, and replace the corresponding reference in the PHFetchRequest to nil:

// Collect all videos in the Videos folder of the Photos app
- (PHFetchResult *)assetsFetchResults {
    __block PHFetchResult *i = self->_assetsFetchResults;
    if (!i) {
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            PHFetchResult *smartAlbums = [PHAssetCollection fetchAssetCollectionsWithType:PHAssetCollectionTypeSmartAlbum subtype:PHAssetCollectionSubtypeSmartAlbumVideos options:nil];
            PHAssetCollection *collection = smartAlbums.firstObject;
            if (![collection isKindOfClass:[PHAssetCollection class]]) collection = nil;
            PHFetchOptions *allPhotosOptions = [[PHFetchOptions alloc] init];
            allPhotosOptions.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"creationDate" ascending:NO]];
            i = [PHAsset fetchAssetsInAssetCollection:collection options:allPhotosOptions];
            self->_assetsFetchResults = i;
        });
    }

    return i;
}

Then, add this line:

// Filter videos that are stored in iCloud
- (NSArray *)phAssets {
    NSMutableArray *assets = [NSMutableArray arrayWithCapacity:self.assetsFetchResults.count];
    [[self assetsFetchResults] enumerateObjectsUsingBlock:^(PHAsset *asset, NSUInteger idx, BOOL *stop) {
        if (asset.sourceType == PHAssetSourceTypeUserLibrary)
                 [assets addObject:asset];
     }];

    return [NSArray arrayWithArray:(NSArray *)assets];
}
Prune answered 14/7, 2016 at 20:19 Comment(2)
this is incorrect I think. "PHAssetSourceTypeUserLibrary" means another thing, and does NOT tell you if the asset resides on the device or not. It only tells you that the origin of the asset is either on the device, or from iCloud sync. Physically - the asset can be either local (on device) or not.Witness
@MottiShneor You’d have to refer to outdated docs to be sure, seeing as this is an outdated question. The Photos framework is replaced by PhotoKit, which provides an extended means for managing asset identifiers for local and iCloud storage via the PHPhotoLibrary class. I would assume now you can test a PHAsset for either or both...Prune
R
0

this code should be work. If call this code very frequently, make sure cancel useless request by PHImageRequestID.

- (PHImageRequestID)checkIsCloud:(PHAsset *)asset cachingImageManager:(PHCachingImageManager *)cachingImageManager {
    if (asset.mediaType == PHAssetMediaTypeVideo) {
        PHVideoRequestOptions *options = [PHVideoRequestOptions new];
        options.deliveryMode = PHVideoRequestOptionsDeliveryModeMediumQualityFormat;
        return [cachingImageManager requestAVAssetForVideo:asset options:options resultHandler:^(AVAsset * _Nullable avAsset, AVAudioMix * _Nullable audioMix, NSDictionary * _Nullable info) {
            if (asset != self.asset) return;
            dispatch_async(dispatch_get_main_queue(), ^{
                if (info[@"PHImageFileSandboxExtensionTokenKey"]) {
                    self.iCloudStatus = KICloudStatusNone;
                } else if ([info[PHImageResultIsInCloudKey] boolValue]) {
                    self.iCloudStatus = KICloudStatusNormal;
                } else {
                    self.iCloudStatus = KICloudStatusNone;
                }
            });
        }];
    } else {
        return [cachingImageManager requestImageDataForAsset:asset options:nil resultHandler:^(NSData * _Nullable imageData, NSString * _Nullable dataUTI, UIImageOrientation orientation, NSDictionary * _Nullable info) {
            if (asset != self.asset) return;
            dispatch_async(dispatch_get_main_queue(), ^{
                if ([info[PHImageResultIsInCloudKey] boolValue]) {
                    self.iCloudStatus = KICloudStatusNormal;
                } else {
                    self.iCloudStatus = KICloudStatusNone;
                }
            });
        }];
    }
}
Rustie answered 21/5, 2018 at 12:19 Comment(0)
I
0

Here is Swift 5 version

extension PHAsset {
   var isLocallyAvailable: Bool? {
      let resourceArray = PHAssetResource.assetResources(for: self)
      return resourceArray.first?.value(forKey: "locallyAvailable") as? Bool
   }
}
Inge answered 7/3, 2023 at 11:29 Comment(0)
P
0

The PHAsset class has a property called sourceType of type PHAssetSourceType, which can have three possible values: .typeUserLibrary, .typeCloudShared, and .typeiTunesSynced. You can perform a comparison as follows to check if a PHAsset represents an iCloud photo:

if asset.sourceType == .typeUserLibrary {
    // The PHAsset is a local photo
} else {
    // The PHAsset is from iCloud
}

In the code, asset is type PHAsset.

Putup answered 17/1 at 18:25 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.