nocoded 0 Newbie Poster

I have a function that returns an array of photos and videos from an iOS device's camera roll. The info is returned correctly, however, I'm also needing to return the path to the photo and video which I've been unable to do so far.

Here is the code I'm using. The photo and video path code is about 2/3 down this snippet starting with: "if (asset.mediaType == PHAssetMediaTypeImage)":

- (void) photos:(CDVInvokedUrlCommand*)command {
    if (![self isNull:self.photosCommand]) {
        [self failure:command withMessage:E_PHOTO_BUSY];
        return;
    }
    self.photosCommand = command;
    CDVPhotos* __weak weakSelf = self;
    [self checkPermissionsOf:command andRun:^{
        NSArray* collectionIds = [weakSelf argOf:command atIndex:0 withDefault:nil];
        NSLog(@"photos: collectionIds=%@", collectionIds);

        NSDictionary* options = [weakSelf argOf:command atIndex:1 withDefault:@{}];
        int offset = [[weakSelf valueFrom:options
                                    byKey:P_LIST_OFFSET
                              withDefault:@"0"] intValue];
        int limit = [[weakSelf valueFrom:options
                                   byKey:P_LIST_LIMIT
                             withDefault:@"0"] intValue];
        NSTimeInterval interval = [[weakSelf valueFrom:options
                                                 byKey:P_LIST_INTERVAL
                                           withDefault:@"30"] intValue];
        interval = interval < 0 ? .03f : interval / 1000.0f;

        PHFetchResult<PHAssetCollection*>* fetchResultAssetCollections
        = collectionIds == nil || collectionIds.count == 0
        ? [weakSelf fetchCollections:@{}]
        : [PHAssetCollection fetchAssetCollectionsWithLocalIdentifiers:collectionIds
                                                               options:nil];
        if (fetchResultAssetCollections == nil) {
            weakSelf.photosCommand = nil;
            [weakSelf failure:command withMessage:E_COLLECTION_MODE];
            return;
        }

        int __block fetched = 0;
        NSMutableArray<PHAsset*>* __block skippedAssets = [NSMutableArray array];
        NSMutableArray<NSDictionary*>* __block result = [NSMutableArray array];
        [fetchResultAssetCollections enumerateObjectsUsingBlock:
         ^(PHAssetCollection* _Nonnull assetCollection, NSUInteger idx, BOOL* _Nonnull stop) {
             if ([weakSelf isNull:weakSelf.photosCommand]) {
                 *stop = YES;
                 return;
             }
             PHFetchOptions* fetchOptions = [[PHFetchOptions alloc] init];
             fetchOptions.sortDescriptors = @[[NSSortDescriptor
                                               sortDescriptorWithKey:@"creationDate"
                                               ascending:YES]];
             fetchOptions.predicate
             = [NSPredicate predicateWithFormat:@"mediaType = %d OR mediaType = %d", PHAssetMediaTypeImage, PHAssetMediaTypeVideo];

             PHFetchResult<PHAsset*>* fetchResultAssets =
             [PHAsset fetchAssetsInAssetCollection:assetCollection options:fetchOptions];

             [fetchResultAssets enumerateObjectsUsingBlock:
              ^(PHAsset* _Nonnull asset, NSUInteger idx, BOOL* _Nonnull stop) {
                  if ([weakSelf isNull:weakSelf.photosCommand]) {
                      *stop = YES;
                      return;
                  }

                  NSArray *resources = [PHAssetResource assetResourcesForAsset:asset];
                  NSString *filename = ((PHAssetResource*)resources[0]).originalFilename;

                      NSTextCheckingResult* match
                      = [weakSelf.extRegex
                         firstMatchInString:filename
                         options:0
                         range:NSMakeRange(0, filename.length)];

                          NSString* name = [filename substringWithRange:[match rangeAtIndex:1]];
                          NSString* ext = [[filename substringWithRange:[match rangeAtIndex:2]] uppercaseString];
                          NSString* type = weakSelf.extType[ext];

                              if (offset <= fetched) {
    if (asset.mediaType == PHAssetMediaTypeImage)
        {
            [[PHImageManager defaultManager] requestImageDataForAsset:asset options:nil resultHandler:^(NSData *imageData, NSString *dataUTI, UIImageOrientation orientation, NSDictionary *info) {
                NSURL *url = [info objectForKey:@"PHImageFileURLKey"];
                // do what you want with it
            }];
        }
        else if (asset.mediaType == PHAssetMediaTypeVideo)
        {
            [[PHImageManager defaultManager] requestAVAssetForVideo:asset options:nil resultHandler:^(AVAsset *asset, AVAudioMix *audioMix, NSDictionary *info)
            {
                if ([asset isKindOfClass:[AVURLAsset class]])
                {
                    NSURL *url = [(AVURLAsset*)asset URL];
                     // do what you want with it
                }
            }];
        }

                                  NSMutableDictionary<NSString*, NSObject*>* assetItem
                                  = [NSMutableDictionary dictionaryWithObjectsAndKeys:
                                     asset.localIdentifier, P_ID,
                                     name, P_NAME,
                                     type, P_TYPE,
                                     [weakSelf.dateFormat stringFromDate:asset.creationDate], P_DATE,
                                     @((long) (asset.creationDate.timeIntervalSince1970 * 1000)), P_TS,
                                     @(asset.pixelWidth), P_WIDTH,
                                     @(asset.pixelHeight), P_HEIGHT,
                                     nil];
                                  if (![weakSelf isNull:asset.location]) {
                                      CLLocationCoordinate2D coord = asset.location.coordinate;
                                      [assetItem setValue:@(coord.latitude) forKey:P_LAT];
                                      [assetItem setValue:@(coord.longitude) forKey:P_LON];
                                  }
                                  [result addObject:assetItem];
                                  if (limit > 0 && result.count >= limit) {
                                      [weakSelf partial:command withArray:result];
                                      [result removeAllObjects];
                                      [NSThread sleepForTimeInterval:interval];
                                  }
                              }
                              ++fetched;
              }];
         }];
        [skippedAssets enumerateObjectsUsingBlock:^(PHAsset* _Nonnull asset, NSUInteger idx, BOOL* _Nonnull stop) {
            NSLog(@"skipped asset %lu: id=%@; name=%@, type=%ld-%ld; size=%lux%lu;",
                  idx, asset.localIdentifier, [weakSelf getFilenameForAsset:asset],
                  (long)asset.mediaType, (long)asset.mediaSubtypes,
                  (unsigned long)asset.pixelWidth, asset.pixelHeight);
        }];
        weakSelf.photosCommand = nil;
        [weakSelf success:command withArray:result];
    }];
}

Is there an easier way to get the path of these photos and videos? Is there a single path that can be used to the Camera Roll that would be supported across different iPhone's?

Dani AI

Generated

Short answer: there is no single, public “Camera Roll” filesystem path you can safely rely on. The Photos/PhotoKit APIs are the supported way to get asset data; if you need a file path you should export the asset into your app’s container (temporary or Documents) using PhotoKit (PHAssetResourceManager or PHImageManager) and then use that file URL. (developer.apple.com)

In your snippet the async requests are started but you build and add the asset dictionary immediately after — that is why the path never appears. PHImageFileURLKey in the info dictionary is not guaranteed (iOS 13+ removed/changed those keys), and requestImageData(for:...) was renamed/changed in recent SDKs, so relying on those keys is fragile. For images prefer requesting a PHContentEditingInput and reading fullSizeImageURL (or fall back to exporting the asset resource). Also set the request option that allows network access for iCloud-hosted items when needed. (stackoverflow.com)

For video: requestAVAssetForVideo sometimes returns an AVURLAsset you can read, but that URL can be temporary or internal. The robust approach is to export the asset into a file you control. Use PHAssetResourceManager.writeData(for:toFile:options:completionHandler:) or requestExportSession to produce an exported file (MP4/hevc/etc.) and then use that file path for upload or other processing. (developer.apple.com)

Pattern to apply: write each asset to a temp file and only add it to your results when the write completes. Use a dispatch_group (or add inside each completion handler) so you only signal success once all async exports finish. Example pattern (Objective-C, trimmed):

dispatch_group_t g = dispatch_group_create();
NSMutableArray *out = [NSMutableArray array];

for (PHAsset *a in assets) {
  PHAssetResource *r = [PHAssetResource assetResourcesForAsset:a].lastObject;
  NSURL *tmp = [NSURL fileURLWithPath:[NSTemporaryDirectory() stringByAppendingPathComponent:r.originalFilename]];
  dispatch_group_enter(g);
  [[PHAssetResourceManager defaultManager] writeDataForAssetResource:r
                                                              toFile:tmp
                                                             options:nil
                                                   completionHandler:^(NSError *err) {
    if (!err) [out addObject:@{@"id":a.localIdentifier, @"path": tmp.path}];
    dispatch_group_leave(g);
  }];
}

dispatch_group_notify(g, dispatch_get_main_queue(), ^{
  // all paths ready -> return/send 'out'
});

Troubleshooting: watch for iCloud assets (use networkAccessAllowed), handle write errors (asset not locally available), and copy any temp file into your app’s persistent sandbox if you need long-term storage. (developer.apple.com)

Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.