iOS 8 Photos framework. Access photo metadata

后端 未结 6 1186
臣服心动
臣服心动 2020-12-07 11:33

I\'m looking at replacing ALAssetsLibrary with Photos framework in my app.

I can retrieve photos, collections, and asset sources just fine (even write t

相关标签:
6条回答
  • 2020-12-07 12:10

    I thought I'd share some code to read the metadata using the ImageIO framework in conjunction with Photos framework. You must request the image data using a PHCachingImageManager.

    @property (strong) PHCachingImageManager *imageManager;
    

    Request the image and use it's data to create a metadata dictionary

    -(void)metadataReader{
        PHFetchResult *result = [PHAsset fetchAssetsInAssetCollection:self.myAssetCollection options:nil];
        [result enumerateObjectsAtIndexes:[NSIndexSet indexSetWithIndex:myIndex] options:NSEnumerationConcurrent usingBlock:^(PHAsset *asset, NSUInteger idx, BOOL *stop) {
            [self.imageManager requestImageDataForAsset:asset options:nil resultHandler:^(NSData *imageData, NSString *dataUTI, UIImageOrientation orientation, NSDictionary *info) {
                NSDictionary *metadata = [self metadataFromImageData:imageData];
                               NSLog(@"Metadata: %@", metadata.description);
                NSDictionary *gpsDictionary = metadata[(NSString*)kCGImagePropertyGPSDictionary];
                if(gpsDictionary){
                    NSLog(@"GPS: %@", gpsDictionary.description);
                }
                NSDictionary *exifDictionary = metadata[(NSString*)kCGImagePropertyExifDictionary];
                if(exifDictionary){
                    NSLog(@"EXIF: %@", exifDictionary.description);
                }
    
                UIImage *image = [UIImage imageWithData:imageData scale:[UIScreen mainScreen].scale];
                // assign image where ever you need...
            }];
    
        }];
    }
    

    Convert NSData to metadata

    -(NSDictionary*)metadataFromImageData:(NSData*)imageData{
        CGImageSourceRef imageSource = CGImageSourceCreateWithData((__bridge CFDataRef)(imageData), NULL);
        if (imageSource) {
            NSDictionary *options = @{(NSString *)kCGImageSourceShouldCache : [NSNumber numberWithBool:NO]};
            CFDictionaryRef imageProperties = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, (__bridge CFDictionaryRef)options);
            if (imageProperties) {
                NSDictionary *metadata = (__bridge NSDictionary *)imageProperties;
                CFRelease(imageProperties);
                CFRelease(imageSource);
                return metadata;
            }
            CFRelease(imageSource);
        }
    
        NSLog(@"Can't read metadata");
        return nil;
    }
    

    This has the overhead of grabbing the image, so it's not nearly as fast as enumerating your assets or collections, but it's something at least.

    0 讨论(0)
  • 2020-12-07 12:17

    PhotoKit limits the access to metadata to the properties of PHAsset (location, creationDate, favorite, hidden, modificatonDate, pixelWidth, pixelHeight...). The reason (I suspect) is that due to the introduction of iCloud PhotoLibrary the images may not be on the device. Therefore the whole metadata is not available. The only way to get full EXIF/IPTC metadata is to first download the original image (if not available) from iCloud and then use ImageIO to extract its metadata.

    0 讨论(0)
  • 2020-12-07 12:21

    I prefer not to CIImage solution, but to ImageIO solution:

    func imageAndMetadataFromImageData(data: NSData)-> (UIImage?,[String: Any]?) {
        let options = [kCGImageSourceShouldCache as String: kCFBooleanFalse]
        if let imgSrc = CGImageSourceCreateWithData(data, options as CFDictionary) {
            let metadata = CGImageSourceCopyPropertiesAtIndex(imgSrc, 0, options as CFDictionary) as! [String: Any]
            //print(metadata)
            // let image = UIImage(cgImage: imgSrc as! CGImage)
            let image = UIImage(data: data as Data)
            return (image, metadata)
        }
        return (nil, nil)
    }
    

    below is code to get data from PHAseet

    func getImageAndMeta(asset: PHAsset){
        let options = PHImageRequestOptions()
        options.isSynchronous = true
        options.resizeMode = .none
        options.isNetworkAccessAllowed = false
        options.version = .current
        var image: UIImage? = nil
        var meta:[String:Any]? = nil
        _ = PHCachingImageManager().requestImageData(for: asset, options: options) { (imageData, dataUTI, orientation, info) in
            if let data = imageData {
                (image, meta) = imageAndMetadataFromImageData(data: data as NSData)
                //image = UIImage(data: data)
            }
        }
        // here to return image and meta
    }
    
    0 讨论(0)
  • 2020-12-07 12:23

    If you request a content editing input, you can get the full image as a CIImage, and CIImage has a property titled properties which is a dictionary containing the image metadata.

    Sample Swift Code:

    let options = PHContentEditingInputRequestOptions()
    options.networkAccessAllowed = true //download asset metadata from iCloud if needed
    
    asset.requestContentEditingInputWithOptions(options) { (contentEditingInput: PHContentEditingInput?, _) -> Void in
        let fullImage = CIImage(contentsOfURL: contentEditingInput!.fullSizeImageURL)
    
        print(fullImage.properties)
    }
    

    Sample Objective-C Code:

    PHContentEditingInputRequestOptions *options = [[PHContentEditingInputRequestOptions alloc] init];
    options.networkAccessAllowed = YES; //download asset metadata from iCloud if needed
    
    [asset requestContentEditingInputWithOptions:options completionHandler:^(PHContentEditingInput *contentEditingInput, NSDictionary *info) {
        CIImage *fullImage = [CIImage imageWithContentsOfURL:contentEditingInput.fullSizeImageURL];
    
        NSLog(@"%@", fullImage.properties.description);
    }];
    

    You'll get the desired {Exif}, {TIFF}, {GPS}, etc dictionaries.

    0 讨论(0)
  • 2020-12-07 12:33

    You can modify the PHAsset (e.g. adding location metadata) using Photos Framework and the UIImagePickerControllerDelegate method. No overhead from third party libraries, no duplicate photos created. Works for iOS 8.0+

    In the didFinishPickingMediaWithInfo delegate method, call UIImageWriteToSavedPhotosAlbum to first save the image. This will also create the PHAsset whose EXIF GPS data we will modify:

    func imagePickerController(picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : AnyObject]) {
    
        if let myImage = info[UIImagePickerControllerOriginalImage] as? UIImage  {
    
            UIImageWriteToSavedPhotosAlbum(myImage, self, Selector("image:didFinishSavingWithError:contextInfo:"), nil)
        }    
    }
    

    The completion selector function will run after the save completes or fails with error. In the callback, fetch the newly created PHAsset. Then, create a PHAssetChangeRequest to modify the location metadata.

    func image(image: UIImage, didFinishSavingWithError: NSErrorPointer, contextInfo:UnsafePointer<Void>)       {
    
        if (didFinishSavingWithError != nil) {
            print("Error saving photo: \(didFinishSavingWithError)")
        } else {
            print("Successfully saved photo, will make request to update asset metadata")
    
            // fetch the most recent image asset:
            let fetchOptions = PHFetchOptions()
            fetchOptions.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: true)]
            let fetchResult = PHAsset.fetchAssetsWithMediaType(PHAssetMediaType.Image, options: fetchOptions)
    
            // get the asset we want to modify from results:
            let lastImageAsset = fetchResult.lastObject as! PHAsset
    
            // create CLLocation from lat/long coords:
            // (could fetch from LocationManager if needed)
            let coordinate = CLLocationCoordinate2DMake(myLatitude, myLongitude)
            let nowDate = NSDate()
            // I add some defaults for time/altitude/accuracies:
            let myLocation = CLLocation(coordinate: coordinate, altitude: 0.0, horizontalAccuracy: 1.0, verticalAccuracy: 1.0, timestamp: nowDate)
    
            // make change request:
            PHPhotoLibrary.sharedPhotoLibrary().performChanges({
    
                // modify existing asset:
                let assetChangeRequest = PHAssetChangeRequest(forAsset: lastImageAsset)
                assetChangeRequest.location = myLocation
    
                }, completionHandler: {
                    (success:Bool, error:NSError?) -> Void in
    
                    if (success) {
                        print("Succesfully saved metadata to asset")
                        print("location metadata = \(myLocation)")
                    } else {
                        print("Failed to save metadata to asset with error: \(error!)")
    }
    
    0 讨论(0)
  • 2020-12-07 12:37

    Better solution i found and worked well for me is:

    [[PHImageManager defaultManager] requestImageDataForAsset:photoAsset
                                                      options:reqOptions
                                                resultHandler:
             ^(NSData *imageData, NSString *dataUTI, UIImageOrientation orientation, NSDictionary *info) {
                 CIImage* ciImage = [CIImage imageWithData:imageData];
                 DLog(@"Metadata : %@", ciImage.properties);
             }];
    
    0 讨论(0)
提交回复
热议问题