Search code examples
swiftsavejpegexif

Saving EXIF data to JPEG - Swift


I am currently trying to shoot photos within an app with the UIImagePickerController, saving them to the local database and upload them later to a service.

But it seems that the metadata (especially EXIF) is not bundled with this image automatically. I've tried to extract it like this:

func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any])
{ 
    self.imageDelegate?.saveImage( image: info[UIImagePickerControllerOriginalImage] as! UIImage, metadata: (info[UIImagePickerControllerMediaMetadata] as? NSDictionary)! ) 
    // ...
}

Now I should have the image and the metadata, but how can I store it into a JPEG file? I need the GPS data in the image on the server.

I am currently creating the image directly from the UImage from the camera roll:

UIImageJPEGRepresentation(image,CGFloat(Constants.JPEG_QUALITY))

This will be stored and uploaded.

I've read that there might a possibility of using "CGImageDestination", but I have no idea how to use them in my case.


Solution

  • My solution:

    func toJpegWithExif(image: UIImage, metadata: NSDictionary, location: CLLocation?) -> Data? {
        return autoreleasepool(invoking: { () -> Data in
            let data = NSMutableData()
            let options = metadata.mutableCopy() as! NSMutableDictionary
            options[ kCGImageDestinationLossyCompressionQuality ] = CGFloat(Constants.JPEG_QUALITY)
    
            // if location is available, add GPS data, thanks to https://gist.github.com/nitrag/343fe13f01bb0ef3692f2ae2dfe33e86
            if ( nil != location ) {
                let gpsData = NSMutableDictionary()
    
                let altitudeRef = Int(location!.altitude < 0.0 ? 1 : 0)
                let latitudeRef = location!.coordinate.latitude < 0.0 ? "S" : "N"
                let longitudeRef = location!.coordinate.longitude < 0.0 ? "W" : "E"
    
                // GPS metadata
                gpsData[(kCGImagePropertyGPSLatitude as String)] = abs(location!.coordinate.latitude)
                gpsData[(kCGImagePropertyGPSLongitude as String)] = abs(location!.coordinate.longitude)
                gpsData[(kCGImagePropertyGPSLatitudeRef as String)] = latitudeRef
                gpsData[(kCGImagePropertyGPSLongitudeRef as String)] = longitudeRef
                gpsData[(kCGImagePropertyGPSAltitude as String)] = Int(abs(location!.altitude))
                gpsData[(kCGImagePropertyGPSAltitudeRef as String)] = altitudeRef
                gpsData[(kCGImagePropertyGPSTimeStamp as String)] = location!.timestamp.isoTime()
                gpsData[(kCGImagePropertyGPSDateStamp as String)] = location!.timestamp.isoDate()
                gpsData[(kCGImagePropertyGPSVersion as String)] = "2.2.0.0"
    
                options[ kCGImagePropertyGPSDictionary as String ] = gpsData
            }
    
            let imageDestinationRef = CGImageDestinationCreateWithData(data as CFMutableData, kUTTypeJPEG, 1, nil)!
            CGImageDestinationAddImage(imageDestinationRef, image.cgImage!, options)
            CGImageDestinationFinalize(imageDestinationRef)
            return data as Data
        })
    }
    
    extension Date {
    
        func isoDate() -> String {
            let f = DateFormatter()
            f.timeZone = TimeZone(abbreviation: "UTC")
            f.dateFormat = "yyyy:MM:dd"
            return f.string(from: self)
        }
    
        func isoTime() -> String {
            let f = DateFormatter()
            f.timeZone = TimeZone(abbreviation: "UTC")
            f.dateFormat = "HH:mm:ss.SSSSSS"
            return f.string(from: self)
        }
    }
    

    The method converts the UIImage to a Jpeg with EXIF data from the camera plus an optional GPS location coming from CLLocationManager.