write GPS metadata to EXIF in Swift - swift

I am trying to write gps coordinates to an image that is taken within my app. So i looked around for code to use, and i wrote a function that takes Data and also returns Data (since i need to send it like this to Firebase.
But no matter how i seem to write this code the metadata won't "stick" to the picture. The picture looks correct when it uploads, but upon download it doesn't contain any more metadata than the size of the photo.
Have I totally missed out on something here?
func setMetaData(imageData: Data) -> Data? {
var source: CGImageSource? = nil
source = CGImageSourceCreateWithData((imageData as CFData?)!, nil)
let metadata = CGImageSourceCopyPropertiesAtIndex(source!, 0, nil) as? [AnyHashable: Any]
var metadataAsMutable = metadata
var EXIFDictionary = (metadataAsMutable?[(kCGImagePropertyExifDictionary as String)]) as? [AnyHashable: Any]
var GPSDictionary = (metadataAsMutable?[(kCGImagePropertyGPSDictionary as String)]) as? [AnyHashable: Any]
if !(EXIFDictionary != nil) {
EXIFDictionary = [AnyHashable: Any]()
}
if !(GPSDictionary != nil) {
GPSDictionary = [AnyHashable: Any]()
}
GPSDictionary![(kCGImagePropertyGPSLatitude as String)] = 30.21313
GPSDictionary![(kCGImagePropertyGPSLongitude as String)] = 76.22346
let dest_data = NSMutableData()
guard let imageDest = CGImageDestinationCreateWithData(dest_data as CFMutableData, kUTTypeJPEG, 1, nil) else { return nil }
CGImageDestinationAddImageFromSource(imageDest, source!, 0, (metadataAsMutable as CFDictionary?))
if CGImageDestinationFinalize(imageDest) {
return dest_data as Data
} else {
print("FAILED")
}
return nil
}

I found a solution that's not super pretty, but it totally does the job
func addMetaData(data: Data) -> NSData? {
guard let source = CGImageSourceCreateWithData(data as CFData, nil) else {return nil}
guard let type = CGImageSourceGetType(source) else {return nil}
let mutableData = NSMutableData(data: data)
guard let destination = CGImageDestinationCreateWithData(mutableData, type, 1, nil) else {
return nil}
guard let path = Bundle.main.url(forResource: "predict_pic", withExtension: "jpg") else {
return nil }
let imageSource = CGImageSourceCreateWithURL(path as CFURL, nil)
let imageProperties = CGImageSourceCopyMetadataAtIndex(imageSource!, 0, nil)
let mutableMetadata = CGImageMetadataCreateMutableCopy(imageProperties!)
CGImageMetadataSetValueMatchingImageProperty(mutableMetadata!, kCGImagePropertyGPSDictionary, kCGImagePropertyGPSLatitudeRef, "N" as CFTypeRef)
CGImageMetadataSetValueMatchingImageProperty(mutableMetadata!, kCGImagePropertyGPSDictionary, kCGImagePropertyGPSLatitude, location.coordinate.latitude as CFTypeRef)
CGImageMetadataSetValueMatchingImageProperty(mutableMetadata!, kCGImagePropertyGPSDictionary, kCGImagePropertyGPSLongitudeRef, "E" as CFTypeRef)
CGImageMetadataSetValueMatchingImageProperty(mutableMetadata!, kCGImagePropertyGPSDictionary, kCGImagePropertyGPSLongitude, location.coordinate.longitude as CFTypeRef)
let finalMetadata:CGImageMetadata = mutableMetadata!
CGImageDestinationAddImageAndMetadata(destination, UIImage(data: data)!.cgImage! , finalMetadata, nil)
guard CGImageDestinationFinalize(destination) else { return nil }
return mutableData;
}

Related

Save (override) my jpg file with editing EXIF data

Im open and edit EXIF (or TIFF...) data in my jpg file.
Please help me save (override) my jpg file with editing data.
Working with this simple code:
let fileURL = URL(fileURLWithPath: "/Users/test/Pictures/IMG_2808.jpg")
if let imageSource = CGImageSourceCreateWithURL(fileURL as CFURL, nil) {
var imageProperties = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, nil) as Dictionary?
let exifDict = imageProperties?[kCGImagePropertyExifDictionary]
if let UserComment = exifDict?[kCGImagePropertyExifUserComment] ?? nil {
print("kCGImagePropertyExifUserComment: \(UserComment)")
}
else {
exifDict!.setValue("My comment...", forKey: kCGImagePropertyExifUserComment as String)
imageProperties![kCGImagePropertyExifDictionary] = exifDict
// What I should do in the next step for update and override my jpg file on disk?
// Maybe this is not nice way for this realisation? Please help
//
}
}
After some test Im found a solution:
let fileURL = URL(fileURLWithPath: "/Users/test/Pictures/IMG_2808.jpg")
if let imageSource = CGImageSourceCreateWithURL(fileURL as CFURL, nil) {
var imageProperties = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, nil) as Dictionary?
let exifDict = imageProperties?[kCGImagePropertyExifDictionary]
if let UserComment = exifDict?[kCGImagePropertyExifUserComment] ?? nil {
print("kCGImagePropertyExifUserComment: \(UserComment)")
}
else {
exifDict!.setValue("My comment...", forKey: kCGImagePropertyExifUserComment as String)
imageProperties![kCGImagePropertyExifDictionary] = exifDict
if let imageDestination = CGImageDestinationCreateWithURL(fileURL as CFURL, kUTTypeJPEG, 1, nil) {
CGImageDestinationAddImageFromSource(imageDestination, imageSource, 0, imageProperties as CFDictionary?)
CGImageDestinationFinalize(imageDestination)
}
}
}
I don't know if this solution is true but it works!
Please comment if you know more perfected way

Google maps API Swift; Nil maneuver response

I am trying to get the maneuver data for the given route from Google Maps' API. Running the code gives me nil values.
Here is the code I am running to get the maneuver data:
func getRouteSteps(source: CLLocationCoordinate2D,destination: CLLocationCoordinate2D) {
let session = URLSession.shared
let url = URL(string: "https://maps.googleapis.com/maps/api/directions/json?origin=\(source.latitude),\(source.longitude)&destination=\(destination.latitude),\(destination.longitude)&sensor=false&mode=driving&key=\(APIKey)")!
let task = session.dataTask(with: url, completionHandler: {
(data, response, error) in
guard error == nil else {
print(error!.localizedDescription)
return
}
guard let jsonResult = try? JSONSerialization.jsonObject(with: data!, options: .allowFragments) as? [String: Any] else {
print("error in JSONSerialization")
return
}
guard let routes = jsonResult["routes"] as? [Any] else { return }
guard let route = routes[0] as? [String: Any] else { return }
guard let legs = route["legs"] as? [Any] else { return }
guard let leg = legs[0] as? [String: Any] else { return }
guard let steps = leg["steps"] as? [Any] else { return }
guard let duration = leg["duration"] as? [String: Any] else { return }
guard let distance = leg["distance"] as? [String: Any] else { return }
RouteData.append(RouteInfo(Time: String(describing: duration["text"]! as Any), Distance: String(describing: distance["text"]! as Any)))
for item in steps {
guard let step = item as? [String: Any] else { return }
guard let stepTurns = step["html_instructions"] as? String else { return }
guard let stepDistance = step["distance"] as? [String: Any] else { return }
guard let stepTime = step["duration"] as? [String: Any] else { return }
guard let polyline = step["polyline"] as? [String: Any] else { return }
guard let polyLineString = polyline["points"] as? String else { return }
guard let maneuver = step["maneuver"] as? Any else { return }
print(maneuver)
Here are the results:
nil
nil
Optional(turn-right)
Optional(turn-left)
Optional(ramp-left)
Optional(ramp-right)
Optional(straight)
Optional(turn-slight-left)
Optional(turn-slight-left)
Optional(turn-left)

store facebook information into Firebase Database using Facebook IOS swift SDK

I'm trying to store facebook user data into firebase database but I keep getting the error "Cannot convert Any? to expected type String"
((FBSDKAccessToken.current()) != nil){
FBSDKGraphRequest(graphPath: "me", parameters: ["fields": "id, name, picture.type(large), email"]).start(completionHandler: { (connection, result, error) -> Void in
if (error == nil && result != nil) {
guard let fbData = result as? [String:Any] else { return }
let fbid = fbData["id"]
let name = fbData["name"]
self.ref.child("users").child(fbid).setValue([
"id": fbid,
"name": name
])
}
})
I also want to store the picture url into the database. How can I do this?
Using Facebook IOS Swift SDK and Firebase
Try my I implement this function. This is from the production app and it works well for us. I also recommend uploading profile image in Firebase storage or other storage, because after a while the profile image url is not valid.
class func getAllFacebookData(success: ((_ result: [String : Any]) -> Void)?, fail: ((_ error: Error) -> Void)?) {
guard !isGetDataFromFacebook else { return }
DispatchQueue.global(qos: .background).async {
guard let tokenString = FBSDKAccessToken.current()?.tokenString else { return }
guard let req = FBSDKGraphRequest(graphPath: "me", parameters: ["fields": "name,age_range,birthday,gender,email,first_name,last_name,picture.width(1000).height(1000),work,education,hometown,location, friends"], tokenString: tokenString, version: nil, httpMethod: "GET") else { return }
req.start { (connection, result, error) in
if error == nil {
guard let _result = result as? [String : Any] else { return }
let _picture = _result["picture"] as? [String : Any]
let _pictureData = _picture?["data"] as? [String : Any]
let _isSilhouette = _pictureData?["is_silhouette"] as? Bool
let userPref = UserDefaults.standard
userPref.set(_isSilhouette, forKey: "UserHasSilhouetteImage")
userPref.synchronize()
debugPrint("facebook result", _result)
isGetDataFromFacebook = true
syncUserInfoInDatabase(_result)
success?(_result)
} else {
debugPrint("request", error!)
fail?(error!)
}
}
}
}
fileprivate class func syncUserInfoInDatabase(_ userInfo: [String : Any]) {
let realmManager = RealmManager()
guard let currentUser = realmManager.getCurrentUser() else { return }
guard let userInfoModel = createUserInfoModel(userInfo) else { return }
do {
let realm = try Realm()
try realm.write {
currentUser.info = userInfoModel
}
} catch {
debugPrint("realm syncUserInfoInDatabase error", error.localizedDescription)
}
savePhoto(userInfo)
let firebaseDatabaseGeneralManager = FirebaseDatabaseGeneralManager()
firebaseDatabaseGeneralManager.updateCurrentUser(success: nil, fail: nil)
// crete a personal settings
let firUserSettingsDatabaseManager = FIRUserSettingsDatabaseManager()
firUserSettingsDatabaseManager.createStartPeopleFilterSettings(success: nil, fail: nil)
let userSearchLocationModel = UserSearchLocationModel()
userSearchLocationModel.userID = currentUser.id
userSearchLocationModel.birthdayTimeStamp = currentUser.birthdayTimeStamp
userSearchLocationModel.gender = currentUser.gender
switch currentUser.gender {
case UserPeopleFilterSettings.FilterGenderMode.female.description:
userSearchLocationModel.genderIndex = UserPeopleFilterSettings.FilterGenderMode.female.index
case UserPeopleFilterSettings.FilterGenderMode.male.description:
userSearchLocationModel.genderIndex = UserPeopleFilterSettings.FilterGenderMode.male.index
default: break
}
let firPeopleSearchDatabaseManager = FIRPeopleSearchDatabaseManager()
firPeopleSearchDatabaseManager.saveUserSearchLocationModel(userSearchLocationModel, success: nil, fail: nil)
}
private class func savePhoto(_ userInfo: [String : Any]) {
if let pictureDict = userInfo["picture"] as? [String : Any], let pictureDataDict = pictureDict["data"] as? [String : Any] {
if let urlPath = pictureDataDict["url"] as? String {
let firImageDatabaseManager = FIRImageDatabaseManager()
firImageDatabaseManager.saveProfileImage(urlPath, fileName: nil, isFacebookPhoto: true, realmSaved: nil)
}
}
}

Image size decreases after modification of metadata i.e (EXIF,GPS,TIFF)

I'm trying to modify the metadata of images but it seems that the file size of original image decreases considerably that annoying me too much. If the size of image is 6MB than it goes down to 3.2MB after modification. I know its strange but not impossible. Is there anyone to help me out in order to solve this issue. Your help will be appreciated.
Here is my code written in swift 2.0 and tool is Xcode 7.3.1 .
func writeProperties(url:NSURL, override: Bool) -> NSURL?{
guard let directory = url.URLByDeletingLastPathComponent else { return nil }
guard let base = url.URLByDeletingPathExtension?.lastPathComponent else { return nil }
guard let ext = url.pathExtension else { return nil }
let newName = override ? url.lastPathComponent! : "\(base)_modified.\(ext)"
let newPath = directory.URLByAppendingPathComponent(newName, isDirectory: false)
print("writeProperties to \(newPath)")
guard let imageSource = CGImageSourceCreateWithURL(url, nil) else { return nil }
let imageType = CGImageSourceGetType(imageSource)!
let data = NSMutableData()
guard let imageDestination = CGImageDestinationCreateWithData(data, imageType, 1, nil) else { return nil }
CGImageDestinationAddImageFromSource(imageDestination, imageSource, 0, saveProperties)
CGImageDestinationFinalize(imageDestination)
if let _ = try? data.writeToURL(newPath, options: NSDataWritingOptions.AtomicWrite) {
let alert = NSAlert()
alert.messageText = "Image Saved"
alert.informativeText = "Image saved to \(newPath.path!)"
alert.runModal()
return newPath
}
return nil
}

EXIF data read and write

I searched for getting the EXIF data from picture files and write them back for Swift. But I only could find predefied libs for different languages.
I also found references to "CFDictionaryGetValue", but which keys do I need to get the data? And how can I write it back?
I'm using this to get EXIF infos from an image file:
import ImageIO
let fileURL = theURLToTheImageFile
if let imageSource = CGImageSourceCreateWithURL(fileURL as CFURL, nil) {
let imageProperties = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, nil)
if let dict = imageProperties as? [String: Any] {
print(dict)
}
}
It gives you a dictionary containing various informations like the color profile - the EXIF info specifically is in dict["{Exif}"].
Swift 4
extension UIImage {
func getExifData() -> CFDictionary? {
var exifData: CFDictionary? = nil
if let data = self.jpegData(compressionQuality: 1.0) {
data.withUnsafeBytes {(bytes: UnsafePointer<UInt8>)->Void in
if let cfData = CFDataCreate(kCFAllocatorDefault, bytes, data.count) {
let source = CGImageSourceCreateWithData(cfData, nil)
exifData = CGImageSourceCopyPropertiesAtIndex(source!, 0, nil)
}
}
}
return exifData
}
}
Swift 5
extension UIImage {
func getExifData() -> CFDictionary? {
var exifData: CFDictionary? = nil
if let data = self.jpegData(compressionQuality: 1.0) {
data.withUnsafeBytes {
let bytes = $0.baseAddress?.assumingMemoryBound(to: UInt8.self)
if let cfData = CFDataCreate(kCFAllocatorDefault, bytes, data.count),
let source = CGImageSourceCreateWithData(cfData, nil) {
exifData = CGImageSourceCopyPropertiesAtIndex(source, 0, nil)
}
}
}
return exifData
}
}
You can use AVAssetExportSession to write metadata.
let asset = AVAsset(url: existingUrl)
let exportSession = AVAssetExportSession(asset: asset, presetName: AVAssetExportPresetHighestQuality)
exportSession?.outputURL = newURL
exportSession?.metadata = [
// whatever [AVMetadataItem] you want to write
]
exportSession?.exportAsynchronously {
// respond to file writing completion
}