iOS 11 Drag and Drop File Path - ios

I am currently adding the ability for users to drag and drop in my app in iOS 11. The app supports dropping images into a UIImageView.
I would like to find the GPS data from the image that is being dropped in from the files app. I am using the code below to find the other data but cannot access the GPS data.
func dropInteraction(_ interaction: UIDropInteraction, performDrop session: UIDropSession) {
session.loadObjects(ofClass: UIImage.self) { imageItems in
let images = imageItems as! [UIImage]
self.imageView.image = images.first
let data = UIImageJPEGRepresentation(images.first!, 1.0)
let imageData: NSData = data! as NSData
let imageDataOptional: NSData? = data as? NSData
if let imageSource = CGImageSourceCreateWithData(imageData, nil) {
let imageProperties = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, nil)! as NSDictionary
print("Metadata: \(imageProperties)")
print("DPI: \(imageProperties["DPIHeight"])")
print("Width: \(imageProperties["PixelWidth"])")
print("Height: \(imageProperties["PixelHeight"])")
}
}
I am able to find the data by creating a CIImage from the file URL when choosing from the Document picker (see below).
let ciimg = CIImage(contentsOf: url)
if ciimg != nil {
let metadata = ciimg!.properties
print("Meta: \(metadata)")
}
Is it possible to access the GPS Data from the UIImage or find out the URL to do it the CIImage way and if so, how?

Related

How to enrich UIImage data with metadata

In my app I'm trying to have image data have metadata (location, timestamp etc.). I'm using UIImagePickerController to do image capture, and the delegate function of which has:
info[UIImagePickerController.InfoKey.originalImage]
info[UIImagePickerController.InfoKey.phAsset]
info[UIImagePickerController.InfoKey.mediaMetadata]
So for the images picked from the library .phAsset has everything I need. I just use .getDataFromPHAsset function to get the enriched data. However, for the images that were just taken .phAsset is nil. I thought about trying to somehow combine .originalImage and .mediaMetadata together into single Data object, but can't get the result desired. I tried to use this approach: https://gist.github.com/kwylez/a4b6ec261e52970e1fa5dd4ccfe8898f
I know I can also make custom imageCapture controller, using AVCaptureSession, and use AVCapturePhoto function .fileDataRepresentation() on didFinishProcessingPhoto delegate call, but that's not my first choice.
Any kind of help is highly appreciated.
I'm pretty sure mediaMetadata will not have location info. Use this CLLocation extension.
Then use the function below to add the metadata to the image data:
func addImageProperties(imageData: Data, properties: NSMutableDictionary) -> Data? {
let dict = NSMutableDictionary()
dict[(kCGImagePropertyGPSDictionary as String)] = properties
if let source = CGImageSourceCreateWithData(imageData as CFData, nil) {
if let uti = CGImageSourceGetType(source) {
let destinationData = NSMutableData()
if let destination = CGImageDestinationCreateWithData(destinationData, uti, 1, nil) {
CGImageDestinationAddImageFromSource(destination, source, 0, dict as CFDictionary)
if CGImageDestinationFinalize(destination) == false {
return nil
}
return destinationData as Data
}
}
}
return nil
}
Usage:
if let imageData = image.jpegData(compressionQuality: 1.0), let metadata = locationManager.location?.exifMetadata() {
if let newImageData = addImageProperties(imageData: imageData, properties: metadata) {
// newImageData now contains exif metadata
}
}

Saving Data as UIImage while preserving the original image characteristics

I am removing exif and location metadata from images using Photos and image I/O frameworks:
First I get Data from PHAssets:
let manager = PHImageManager()
manager.requestImageData(for: currentAsset, options: options) { (data, dataUTI, orientation, info) in
if let data = data {
dataArray.append(data)
}
}
Then I use this function to remove metadata:
fileprivate func removeMetadataFromData(data: Data) -> NSMutableData? {
guard let source = CGImageSourceCreateWithData(data as CFData, nil) else {return nil}
guard let type = CGImageSourceGetType(source) else {return nil}
let count = CGImageSourceGetCount(source)
let mutableData = NSMutableData(data: data)
guard let destination = CGImageDestinationCreateWithData(mutableData, type, count, nil) else {return nil}
let removeExifProperties: CFDictionary = [String(kCGImagePropertyExifDictionary) : kCFNull, String(kCGImagePropertyGPSDictionary): kCFNull] as CFDictionary
for i in 0..<count {
CGImageDestinationAddImageFromSource(destination, source, i, removeExifProperties)
}
guard CGImageDestinationFinalize(destination) else {return nil}
return mutableData
}
Then I use this to create UIImage from NSMutableData objects that I get from previous function:
let image = UIImage(data: mutableData as Data)
and I save the image to user's library like so:
PHPhotoLibrary.shared().performChanges({
let request = PHAssetChangeRequest.creationRequestForAsset(from: image)
let placeholder = request.placeholderForCreatedAsset
let albumChangeRequest = PHAssetCollectionChangeRequest(for: collection)
if let placeholder = placeholder, let albumChangeRequest = albumChangeRequest {
albumChangeRequest.addAssets([placeholder] as NSArray)
}
return mutableData
}
The problem I have is that using this method, the output file is compressed, and also the name and DPI of the resulting image is different from the original image. I want to keep everything the same as the original image and just remove the metadata. Is there a way to do that?
The problem is the round-trip through UIImage. Just save the Data obtained from requestImageDataAndOrientation.
func saveCopyWithoutLocation(for asset: PHAsset) {
let options = PHImageRequestOptions()
manager.requestImageDataAndOrientation(for: asset, options: options) { data, dataUTI, orientation, info in
guard let data = data else { return }
self.library.performChanges {
let request = PHAssetCreationRequest.forAsset()
request.addResource(with: .photo, data: data, options: nil)
request.location = nil
} completionHandler: { success, error in
if success {
print("successful")
} else {
print(error?.localizedDescription ?? "no error?")
}
}
}
}
Now, that only removes location. If you really want to remove more EXIF data obtained through CGImageSourceCreateWithData, you can do that. But just avoid an unnecessary round-trip through a UIImage. It is the whole purpose to use CGImageSource functions, namely that you can change metadata without changing the underlying image payload. (Round-tripping through UIImage is another way to strip meta data, but as you have discovered, it changes the image payload, too, though often not observable to the naked eye.)
So, if you want, just take the data from CGImageDestination functions directly, and pass that to PHAssetCreationRequest. But I might advise being a little more discriminating about which EXIF metadata you choose to remove, because some of it is important, non-confidential image data (e.g., likely the DPI is in there).
Regarding the filename, I'm not entirely sure you can control that. E.g., I've had images using the above location-stripping routine, and some preserve the file name in the copy, and others do not (and the logic of which applies is not immediately obvious to me; could be the sourceType). Obviously, you can use PHAssetChangeRequest rather than PHAssetCreationRequest, and you can just update the original PHAsset, and that would preserve the file name, but you might not have intended to edit the original asset and may have preferred to make a new copy.

Adding EXIF data to huge image on iOS

I am adding exif data to images on iPhone device using the following code:
func testPhoto(identifier: String) {
let result =
PHAsset.fetchAssets(withLocalIdentifiers: [identifier], options: nil)
PHCachingImageManager
.default()
.requestImageData(for: result.firstObject!, options: nil) {
(data, uti, orientation, info) in
_ = self.addExif(data: data!)
}
}
func addExif(data: Data) -> NSData? {
let selectedImageSourceRef =
CGImageSourceCreateWithData(data as CFData, nil)!
let imagePropertiesDictionaryRef =
CGImageSourceCopyPropertiesAtIndex(selectedImageSourceRef, 0, nil)
var imagePropsDictionary: [String: Any] =
imagePropertiesDictionaryRef as! [String : Any]
var exifData = [String: Any]()
let newImageData = NSMutableData()
let imageDestination =
CGImageDestinationCreateWithData(newImageData, kUTTypeJPEG, 1, nil)!
exifData[kCGImagePropertyExifDateTimeOriginal as String] = NSDate()
imagePropsDictionary[kCGImagePropertyExifDictionary as String] = exifData
CGImageDestinationAddImageFromSource(
imageDestination,
selectedImageSourceRef,
0,
imagePropsDictionary as CFDictionary)
if !CGImageDestinationFinalize(imageDestination) {
NSLog("Could not finalize")
}
return newImageData
}
The method crashes when trying to finalize the destination data source (CGImageDestinationFinalize).
The app terminates due to memory pressure. This happens for huge images (20,000x20,000 pixels). Regular images pass through this method just fine.
Is there any way to add EXIF info to image without causing memory pressure?
Thanks!
CGImageSource is designed to serve as a data source without having to load all the data into memory at once. Rather than using CGImageSourceCreateWithData, use CGImageSourceCreateWithURL(_:_:) to create an image source from a file on disk. The system will then manage streaming the data as needed without having to load it all into memory at once.

Importing an image using Action Extension - URL to a local Image works but not with actual image data

My iOS app (Swift 3) needs to important images from other apps using an Action Extension. I'm using the standard Action Extension template code which works just fine for apps like iOS Mail and Photos where the image shared is a URL to a local file. But for certain apps where the image being shared is the actual image data itself, my action extension code isn't getting the image.
for item: Any in self.extensionContext!.inputItems {
let inputItem = item as! NSExtensionItem
for provider: Any in inputItem.attachments! {
let itemProvider = provider as! NSItemProvider
if itemProvider.hasItemConformingToTypeIdentifier(kUTTypeImage as String) { //we'll take any image type: gif, png, jpg, etc
// This is an image. We'll load it, then place it in our image view.
weak var weakImageView = self.imageView
itemProvider.loadItem(forTypeIdentifier: kUTTypeImage as String, options: nil, completionHandler: { (imageURL,
error) in
OperationQueue.main.addOperation {
if let strongImageView = weakImageView {
if let imageURL = imageURL as? NSURL {
strongImageView.image = UIImage(data: NSData(contentsOf: imageURL as URL)! as Data)
let imageData = NSData(contentsOf: imageURL as URL)! as Data
self.gifImageView.image = UIImage.gif(data: imageData)
let width = strongImageView.image?.size.width
let height = strongImageView.image?.size.height
.... my custom logic
}
}
For reference, I reached out to the developer for one of the apps where things aren't working and he shared this code on how he is sharing the image to the Action Extension.
//Here is the relevant code. At this point the scaledImage variable holds a UIImage.
var activityItems = Array<Any?>()
if let pngData = UIImagePNGRepresentation(scaledImage) {
activityItems.append(pngData)
} else {
activityItems.append(scaledImage)
}
//Then a little later it presents the share sheet:
let activityVC = UIActivityViewController(activityItems: activityItems,applicationActivities: [])
self.present(activityVC, animated: true, completion: nil)
Figured it out thanks to this post which explains the challenge quite well https://pspdfkit.com/blog/2017/action-extension/ . In summary, we don't know if the sharing app is giving us a URL to an existing image or just raw image data so we need to modify the out of the box action extension template code to handle both cases.
for item: Any in self.extensionContext!.inputItems {
let inputItem = item as! NSExtensionItem
for provider: Any in inputItem.attachments! {
let itemProvider = provider as! NSItemProvider
if itemProvider.hasItemConformingToTypeIdentifier(kUTTypeImage as String) { //we'll take any image type: gif, png, jpg, etc
// This is an image. We'll load it, then place it in our image view.
weak var weakImageView = self.imageView
itemProvider.loadItem(forTypeIdentifier: kUTTypeImage as String, options: nil, completionHandler: { (imageURL,
error) in
OperationQueue.main.addOperation {
if let strongImageView = weakImageView {
if let imageURL = imageURL as? NSURL {
strongImageView.image = UIImage(data: NSData(contentsOf: imageURL as URL)! as Data)
let imageData = NSData(contentsOf: imageURL as URL)! as Data
self.gifImageView.image = UIImage.gif(data: imageData)
let width = strongImageView.image?.size.width
let height = strongImageView.image?.size.height
.... my custom logic
}
else
guard let imageData = imageURL as? Data else { return } //can we cast to image data?
strongImageView_.image = UIImage(data: imageData_)
//custom logic
}

How to get image metadata in iOS 9 using Photos Framework

I want to display image metadata using UIImagePickerController in Swift.
where the image is been selected from gallery and would be displayed in the imageView along with the meta data such as PixelHeight,PixelWidth,PixelXDimension,PixelYDimension,Coordinates,Size,Date Created and Imagename.
At first, you can get info[UIImagePickerControllerReferenceURL] from UIImagePickerController. Then you can do things like the following:
let assetURL = info[UIImagePickerControllerReferenceURL] as! NSURL
let asset = PHAsset.fetchAssetsWithALAssetURLs([assetURL], options: nil)
guard let result = asset.firstObject where result is PHAsset else {
return
}
let imageManager = PHImageManager.defaultManager()
imageManager.requestImageDataForAsset(result as! PHAsset, options: nil, resultHandler:{
(data, responseString, imageOriet, info) -> Void in
let imageData: NSData = data!
if let imageSource = CGImageSourceCreateWithData(imageData, nil) {
let imageProperties = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, nil)! as NSDictionary
//now you have got meta data in imageProperties, you can display PixelHeight, PixelWidth, etc.
}
})

Resources