Skip to content

Instantly share code, notes, and snippets.

@lukebrandonfarrell
Created June 8, 2022 16:10
Show Gist options
  • Save lukebrandonfarrell/961a6dbc8367f0ac9cabc89b0052d1fe to your computer and use it in GitHub Desktop.
Save lukebrandonfarrell/961a6dbc8367f0ac9cabc89b0052d1fe to your computer and use it in GitHub Desktop.
A Swift class for extracting exif data from URL, UIImage or Data types πŸ”­
//
// ExifData.swift
// Qeepsake
//
// Created by Luke Farrell on 26/05/2022.
//
import Foundation
import ImageIO
class ExifData: NSObject {
var colorModel: String?;
var pixelWidth: Double?;
var pixelHeight: Double?;
var dpiWidth: Int?;
var dpiHeight: Int?;
var depth: Int?;
var orientation: Int?;
var apertureValue: String?;
var brightnessValue: String?;
var dateTimeDigitized: String?;
var dateTimeOriginal: String?;
var offsetTime: String?;
var offsetTimeDigitized: String?;
var offsetTimeOriginal: String?;
var model: String?;
var software: String?;
var tileLength: Double?;
var tileWidth: Double?;
var xResolution: Double?;
var yResolution: Double?;
var altitude: String?;
var destBearing: String?;
var hPositioningError: String?;
var imgDirection: String?;
var latitude: String?;
var longitude: String?;
var speed: Double?;
private var dictionary: [String: Any] {
return [
"colorModel": colorModel as Any,
"pixelWidth": pixelWidth as Any,
"pixelHeight": pixelHeight as Any,
"dpiWidth": dpiWidth as Any,
"dpiHeight": dpiHeight as Any,
"depth": depth as Any,
"orientation": orientation as Any,
"apertureValue": apertureValue as Any,
"brightnessValue": brightnessValue as Any,
"dateTimeDigitized": dateTimeDigitized as Any,
"dateTimeOriginal": dateTimeOriginal as Any,
"offsetTime": offsetTime as Any,
"offsetTimeDigitized": offsetTimeDigitized as Any,
"offsetTimeOriginal": offsetTimeOriginal as Any,
"model": model as Any,
"software": software as Any,
"tileLength": tileLength as Any,
"tileWidth": tileWidth as Any,
"xResolution": xResolution as Any,
"yResolution": yResolution as Any,
"altitude": altitude as Any,
"destBearing": destBearing as Any,
"hPositioningError": hPositioningError as Any,
"imgDirection": imgDirection as Any,
"latitude": latitude as Any,
"longitude": longitude as Any,
"speed": speed as Any
]
}
public var toDictionary: NSDictionary {
return dictionary as NSDictionary;
}
init(data: Data) {
super.init();
self.setExifData(data: data as CFData);
}
init(url: URL) {
super.init();
if let data = NSData(contentsOf: url) {
self.setExifData(data: data);
}
}
init(image: UIImage) {
super.init();
if let data = image.cgImage?.dataProvider?.data {
self.setExifData(data: data);
}
}
func setExifData(data: CFData) {
let options = [kCGImageSourceShouldCache as String: kCFBooleanFalse];
if let imgSrc = CGImageSourceCreateWithData(data, options as CFDictionary) {
if let metadata = CGImageSourceCopyPropertiesAtIndex(imgSrc, 0, options as CFDictionary) as? NSDictionary {
self.colorModel = metadata[kCGImagePropertyColorModel] as? String;
self.pixelWidth = metadata[kCGImagePropertyPixelWidth] as? Double;
self.pixelHeight = metadata[kCGImagePropertyPixelHeight] as? Double;
self.dpiWidth = metadata[kCGImagePropertyDPIWidth] as? Int;
self.dpiHeight = metadata[kCGImagePropertyDPIHeight] as? Int;
self.depth = metadata[kCGImagePropertyDepth] as? Int;
self.orientation = metadata[kCGImagePropertyOrientation] as? Int;
if let tiffData = metadata[kCGImagePropertyTIFFDictionary] as? NSDictionary {
self.model = tiffData[kCGImagePropertyTIFFModel] as? String;
self.software = tiffData[kCGImagePropertyTIFFSoftware] as? String;
self.tileLength = tiffData[kCGImagePropertyTIFFTileLength] as? Double;
self.tileWidth = tiffData[kCGImagePropertyTIFFTileWidth] as? Double;
self.xResolution = tiffData[kCGImagePropertyTIFFXResolution] as? Double;
self.yResolution = tiffData[kCGImagePropertyTIFFYResolution] as? Double;
}
if let exifData = metadata[kCGImagePropertyExifDictionary] as? NSDictionary {
self.apertureValue = exifData[kCGImagePropertyExifApertureValue] as? String;
self.brightnessValue = exifData[kCGImagePropertyExifBrightnessValue] as? String;
self.dateTimeDigitized = exifData[kCGImagePropertyExifDateTimeDigitized] as? String;
self.dateTimeOriginal = exifData[kCGImagePropertyExifDateTimeOriginal] as? String;
if #available(iOS 13.0, *) {
self.offsetTime = exifData[kCGImagePropertyExifOffsetTime] as? String
self.offsetTimeDigitized = exifData[kCGImagePropertyExifOffsetTimeDigitized] as? String;
self.offsetTimeOriginal = exifData[kCGImagePropertyExifOffsetTimeOriginal] as? String;
} else {
// Fallback on earlier versions
};
}
if let gpsData = metadata[kCGImagePropertyGPSDictionary] as? NSDictionary {
self.altitude = gpsData[kCGImagePropertyGPSAltitude] as? String;
self.destBearing = gpsData[kCGImagePropertyGPSDestBearing] as? String;
self.hPositioningError = gpsData[kCGImagePropertyGPSHPositioningError] as? String;
self.imgDirection = gpsData[kCGImagePropertyGPSImgDirection] as? String;
self.latitude = gpsData[kCGImagePropertyGPSLatitude] as? String;
self.longitude = gpsData[kCGImagePropertyGPSLongitude] as? String;
self.speed = gpsData[kCGImagePropertyGPSSpeed] as? Double;
}
}
}
}
}
@nidegen
Copy link

nidegen commented Dec 1, 2022

First, nice writeup of the picker exif issue!

I was just wondering why you opted for an NSObject for ExifData. Wouldn't even a struct be perfect here?

@lukebrandonfarrell
Copy link
Author

@nidegen Thanks! :) my swift is pretty rusty, what are the advantages to using a struct?

@nidegen
Copy link

nidegen commented Dec 5, 2022 via email

@lukebrandonfarrell
Copy link
Author

No specific reason, I think it makes sense to be a struct tbh, you are right. When I get into this code again, which I will do soon as I am working on a library using this Gist. I can update it, thanks for pointing it out! :)

@jmarkstar
Copy link

Thanks for sharing this, it is so helpful. Just wanna let you know that we have to swap the Width and Height when the orientation is Portrait.

@lukebrandonfarrell
Copy link
Author

@jmarkstar Interesting! How does that look in practice? Could you share a code snippet?

@celian-m
Copy link

celian-m commented Mar 7, 2024

I think you forgot the import UIKit. But really handy thank you!

@lukebrandonfarrell
Copy link
Author

@celian-m Very possible yes! You welcome :)

@davidthorn
Copy link

@celian-m Very possible yes! You welcome :)

The importing of Foundation and ImageIO is all that is required. There are not any usage requirements of UIKit in this file.

@jhokit
Copy link

jhokit commented Oct 1, 2024

Thanks, this was super helpful.

I noticed that a little more processing is needed to get the correct latitude and longitude . Those are stored as unsigned numbers, so you must check the kCGImagePropertyGPSLongitudeRef and kCGImagePropertyGPSLatitudeRef to find the location's hemisphere. If kCGImagePropertyGPSLongitudeRef is "W", you must make the longitude negative.

        if let longitudeRef = gpsData[kCGImagePropertyGPSLongitudeRef] as? String{
            if longitudeRef == "W" {
                longitude = -longitude
            }
        }

And the same for latitude, making it negative if kCGImagePropertyGPSLatitudeRef is "S"

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment