Write UIImage along with metadata (EXIF, GPS, TIFF) in iPhone's Photo library

后端 未结 8 1792
情歌与酒
情歌与酒 2020-11-29 05:12

I am developing a project, where the requirements are: - User will open the camera through the application - Upon capturing an Image, some data will be appended to the captu

8条回答
  •  攒了一身酷
    2020-11-29 05:19

    For anyone who comes here trying to take a photo with the camera in your app and saving the image file to the camera roll with GPS metadata, I have a Swift solution that uses the Photos API since ALAssetsLibrary is deprecated as of iOS 9.0.

    As mentioned by rickster on this answer, the Photos API does not embed location data directly into a JPG image file even if you set the .location property of the new asset.

    Given a CMSampleBuffer sample buffer buffer, some CLLocation location, and using Morty’s suggestion to use CMSetAttachments in order to avoid duplicating the image, we can do the following. The gpsMetadata method extending CLLocation can be found here.

    if let location = location {
        // Get the existing metadata dictionary (if there is one)
        var metaDict = CMCopyDictionaryOfAttachments(nil, buffer, kCMAttachmentMode_ShouldPropagate) as? Dictionary ?? [:]
    
        // Append the GPS metadata to the existing metadata
        metaDict[kCGImagePropertyGPSDictionary as String] = location.gpsMetadata()
    
        // Save the new metadata back to the buffer without duplicating any data
        CMSetAttachments(buffer, metaDict as CFDictionary, kCMAttachmentMode_ShouldPropagate)
    }
    
    // Get JPG image Data from the buffer
    guard let imageData = AVCaptureStillImageOutput.jpegStillImageNSDataRepresentation(buffer) else {
        // There was a problem; handle it here
    }
    
    // Now save this image to the Camera Roll (will save with GPS metadata embedded in the file)
    self.savePhoto(withData: imageData, completion: completion)
    

    The savePhoto method is below. Note that the handy addResource:with:data:options method is available only in iOS 9. If you are supporting an earlier iOS and want to use the Photos API, then you must make a temporary file and then create an asset from the file at that URL if you want to have the GPS metadata properly embedded (PHAssetChangeRequest.creationRequestForAssetFromImage:atFileURL). Only setting PHAsset’s .location will NOT embed your new metadata into the actual file itself.

    func savePhoto(withData data: Data, completion: (() -> Void)? = nil) {
        // Note that using the Photos API .location property on a request does NOT embed GPS metadata into the image file itself
        PHPhotoLibrary.shared().performChanges({
          if #available(iOS 9.0, *) {
            // For iOS 9+ we can skip the temporary file step and write the image data from the buffer directly to an asset
            let request = PHAssetCreationRequest.forAsset()
            request.addResource(with: PHAssetResourceType.photo, data: data, options: nil)
            request.creationDate = Date()
          } else {
            // Fallback on earlier versions; write a temporary file and then add this file to the Camera Roll using the Photos API
            let tmpURL = URL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true).appendingPathComponent("tempPhoto").appendingPathExtension("jpg")
            do {
              try data.write(to: tmpURL)
    
              let request = PHAssetChangeRequest.creationRequestForAssetFromImage(atFileURL: tmpURL)
              request?.creationDate = Date()
            } catch {
              // Error writing the data; photo is not appended to the camera roll
            }
          }
        }, completionHandler: { _ in
          DispatchQueue.main.async {
            completion?()
          }
        })
      }
    

    Aside: If you are just wanting to save the image with GPS metadata to your temporary files or documents (as opposed to the camera roll/photo library), you can skip using the Photos API and directly write the imageData to a URL.

    // Write photo to temporary files with the GPS metadata embedded in the file
    let tmpURL = URL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true).appendingPathComponent("tempPhoto").appendingPathExtension("jpg")
    do {
        try data.write(to: tmpURL)
    
        // Do more work here...
    } catch {
        // Error writing the data; handle it here
    }
    

提交回复
热议问题