I use UIImagePickerController to pick images in my iOS App and I know exif info can be got by info[UIImagePickerControllerMediaMetadata]. But when I upload my image to my server by UIImage, most of exif info has been striped. I wonder whether I can add exif info to my image in Http request(image uploaded as jpg after that). If not, how should I solve this problem? I wanna change Make, Model attributes(in other words, what device was used to take this picture)
Below are my code snippets:
func Tapped() {
    let myPickerController = UIImagePickerController()
    myPickerController.delegate = self
    myPickerController.sourceType = UIImagePickerControllerSourceType.Camera
    myPickerController.allowsEditing = false
    self.presentViewController(myPickerController, animated: true, completion: nil)
}
func imagePickerController(picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : AnyObject]) {
    let image = info[UIImagePickerControllerOriginalImage] as? UIImage
    myImageView.image = image
    UIImageWriteToSavedPhotosAlbum(image!, self, #selector(ViewController.image(_:didFinishSavingWithError:contextInfo:)), nil)
    self.dismissViewControllerAnimated(true, completion: nil)
}
func myImageUploadRequest()
{
    let myUrl = NSURL(string: "http://XXXXXX/Uploadfile")
    let request = NSMutableURLRequest(URL:myUrl!)
    request.HTTPMethod = "POST"
    let param = [
        "userId"    : "7"
    ]
    let boundary = generateBoundaryString()
    request.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
    let imageData = UIImageJPEGRepresentation(myImageView.image!, 1)
    if(imageData == nil)  { return; }
    request.HTTPBody = createBodyWithParameters(param, filePathKey: "file", imageDataKey: imageData!, boundary: boundary)
    myActivityIndicator.startAnimating()
    let task = NSURLSession.sharedSession().dataTaskWithRequest(request) {
        data, response, error in
        if error != nil {
            print("error=\(error)")
            return
        }
        // You can print out response object
        print("******* response = \(response)")
        // Print out response body
        let responseString = NSString(data: data!, encoding: NSUTF8StringEncoding)
        print("****** response data = \(responseString!)")
        do{
            let json = try NSJSONSerialization.JSONObjectWithData(data!, options: .MutableContainers) as? NSDictionary
        }catch{
            print(error)
        }
        dispatch_async(dispatch_get_main_queue(),{
            self.myActivityIndicator.stopAnimating()
            self.myImageView.image = nil
        })
    }
    task.resume()
}
func createBodyWithParameters(parameters: [String: String]?, filePathKey: String?, imageDataKey: NSData, boundary: String) -> NSData {
    let body = NSMutableData();
    if parameters != nil {
        for (key, value) in parameters! {
            body.appendString("--\(boundary)\r\n")
            body.appendString("Content-Disposition: form-data; name=\"\(key)\"\r\n\r\n")
            body.appendString("\(value)\r\n")
        }
    }
    let filename = "test.jpg"
    let mimetype = "image/jpg"
    body.appendString("--\(boundary)\r\n")
    body.appendString("Content-Disposition: form-data; name=\"\(filePathKey!)\"; filename=\"\(filename)\"\r\n")
    body.appendString("Content-Type: \(mimetype)\r\n\r\n")
    body.appendData(imageDataKey)
    body.appendString("\r\n")
    body.appendString("--\(boundary)--\r\n")
    return body
}
func generateBoundaryString() -> String {
    return "Boundary-\(NSUUID().UUIDString)"
}
extension NSMutableData {
    func appendString(string: String) {
        let data = string.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: true)
        appendData(data!)
    }
}
Yes! Finally I made a trick to modify the EXIF info. At first, you can get EXIF info from info[UIImagePickerControllerMediaMetadata] and NSData without EXIF from picked UIImage by UIImageJPEGRepresentation. Then, you can create a new NSDictionary with modified EXIF info. After that, call my function in the following, you can get image NSData with modified EXIF!
func saveImageWithImageData(data: NSData, properties: NSDictionary, completion: (data: NSData, path: NSURL) -> Void) {
    let imageRef: CGImageSourceRef = CGImageSourceCreateWithData((data as CFDataRef), nil)!
    let uti: CFString = CGImageSourceGetType(imageRef)!
    let dataWithEXIF: NSMutableData = NSMutableData(data: data)
    let destination: CGImageDestinationRef = CGImageDestinationCreateWithData((dataWithEXIF as CFMutableDataRef), uti, 1, nil)!
    CGImageDestinationAddImageFromSource(destination, imageRef, 0, (properties as CFDictionaryRef))
    CGImageDestinationFinalize(destination)
    var paths: [AnyObject] = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)
    let savePath: String = paths[0].stringByAppendingPathComponent("exif.jpg")
    let manager: NSFileManager = NSFileManager.defaultManager()
    manager.createFileAtPath(savePath, contents: dataWithEXIF, attributes: nil)
    completion(data: dataWithEXIF,path: NSURL(string: savePath)!)
    print("image with EXIF info converting to NSData: Done! Ready to upload! ")
}
using and mergind some info from other posts, I approached problem using Dictionary in Swift. I used it in captureOutput of AVFounfation callback for AVCapturePhoto:
func photoOutput(_ output: AVCapturePhotoOutput,
                 didFinishProcessingPhoto photo: AVCapturePhoto,
                 error: Error?) {
    //retrieve exif information
    var photoFormatDescription: CMFormatDescription?
    CMVideoFormatDescriptionCreateForImageBuffer(kCFAllocatorDefault, photoPixelBuffer, &photoFormatDescription)
    var metadataAttachments: Dictionary = photo.metadata as Dictionary
    if var exifData = metadataAttachments["{Exif}"] as? [String: Any] {
        exifData[kCGImagePropertyExifUserComment as String] = "<whatever you want to write>"
    metadataAttachments[kCGImagePropertyExifDictionary as String] = exifData
    }
}
After that "metadataAttachments" is used to build final image (using CGImageDestinationAddImage in my case)
It seems to work (tried in a project build with Swift 4.0)
Hope it can help!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With