I try to retrieve exif data from a picture.
I can load it in a Dictionary, but I am unable to use this Dictionary.
my Current code is :
import Cocoa
import ImageIO
let path = "/Volumes/Olivier/Original/Paysage/affoux/_OPI7684.NEF"
let UrlPath = URL(fileURLWithPath: path)
UrlPath.isFileURL
UrlPath.pathExtension
UrlPath.hasDirectoryPath
let imageSource = CGImageSourceCreateWithURL(UrlPath as CFURL, nil)
let imageProp = CGImageSourceCopyPropertiesAtIndex(imageSource!, 0, nil)
var key = "kCGImagePropertyWidth" as NSString
let h :NSDictionary = CFDictionaryGetValue(imageProp, kCGImagePropertyWidth)
The last line, doesn't work at all.
Any solution ?
Thank's
The problem is that your key name is wrong. You mean kCGImagePropertyPixelWidth. And it's not a string. It's a constant. So it should not be in quotes; just use the constant directly, and don't worry what its value is.
I would suggest also that you convert to a Swift dictionary earlier in the process. Here is actual working code that you can model yourself after:
let src = CGImageSourceCreateWithURL(url as CFURL, nil)!
let result = CGImageSourceCopyPropertiesAtIndex(src, 0, nil)!
let d = result as! [AnyHashable:Any]
let width = d[kCGImagePropertyPixelWidth] as! CGFloat
let height = d[kCGImagePropertyPixelHeight] as! CGFloat
Of course that code is pretty bad because every single line contains an exclamation mark (which means "crash me"), but in real life I don't crash, so I've allowed it to stand.
Related
In the process of implementing init(coder:) for a custom NSView subclass, I came across some strange behavior with NSKeyedArchiver and NSKeyedUnarchiver that I still don't entirely understand. Consider this sample code:
let label = NSTextField(labelWithString: "Test")
// Encode
let data = try NSKeyedArchiver.archivedData(withRootObject: label, requiringSecureCoding: false)
// Decode
try NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data) as? NSTextField
This appears to encode and decode an NSTextField as expected. However, if I try to use decodeTopLevelObject() instead of unarchiveTopLevelObjectWithData(_:), the result is nil:
// Encode
let data = try NSKeyedArchiver.archivedData(withRootObject: label, requiringSecureCoding: false)
// Decode
let decoder = try NSKeyedUnarchiver(forReadingFrom: data)
decoder.decodeTopLevelObject() as? NSTextField // nil
Similarly, if I try to use encodedData instead of archivedData(withRootObject:requiringSecureCoding:), the result is nil:
// Encode
let coder = NSKeyedArchiver(requiringSecureCoding: false)
coder.encodeRootObject(label)
let data = coder.encodedData
// Decode
try NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data) as? NSTextField // nil
The result is even nil if I use encode(_:forKey:) and decodeObject(forKey:):
// Encode
let coder = NSKeyedArchiver(requiringSecureCoding: false)
coder.encode(label, forKey: "label")
let data = coder.encodedData
// Decode
let decoder = try NSKeyedUnarchiver(forReadingFrom: data)
decoder.decodeObject(forKey: "label") as? NSTextField // nil
I'm surprised that the first example above appears to work correctly but none of the others do (especially the last one). Could someone help me understand what's going on here?
If you read the documentation for init(forReadingFrom:) it states:
This initializer enables requiresSecureCoding by default....
This has probably been the main source of your confusion. Setting requiresSecureCoding back to false, then, will make the following work:
/* ENCODING */
let archiver = NSKeyedArchiver(requiringSecureCoding: false)
archiver.encodeRootObject(label) // same as .encode(label)
archiver.encode(label, forKey: "SOME_CUSTOM_KEY")
archiver.finishEncoding() // as per documentation
let data = archiver.encodedData
/* DECODING */
let unarchiver = try! NSKeyedUnarchiver(forReadingFrom: data)
// DON'T FORGET THIS!!
unarchiver.requiresSecureCoding = false
let firstResult = unarchiver.decodeTopLevelObject() as! NSTextField . // same as .decodeObject()
let secondResult = unarchiver.decodeObject(forKey: "SOME_CUSTOM_KEY") as! NSTextField
unarchiver.finishDecoding() // as per documentation
When it comes to encoding and decoding correctly, just make sure you have matching keys. encodeRootObject(_:), which is implemented the same as encode(_:), internally uses the key of nil, so then just call decodeTopLevelObject(), or decodeObject().
On the other hand, NSKeyedArchiver.archivedData(withRootObject:requiringSecureCoding:) uses the key NSKeyedArchiveRootObjectKey, so you could technically decode by performing:
let value = unarchiver.decodeObject(forKey: NSKeyedArchiveRootObjectKey) as! NSTextField
...but you wouldn't want to do this, since it's an internal implementation that theoretically could change. Instead you'd just use NSKeyedArchiver.unarchiveTopLevelObjectWithData(_:), as you did in your working example.
Note: if you are using secure coding, there are other considerations to be made, but I think that's beyond the scope of this question.
So I am currently working on a personal project to help understand API's and how they work..so I am still a little new to iOS Development. I have already connected to the URL and gotten the data however now I am trying to make the results a little bit more clear cut.
Below is the code for the class (when the button is clicked it prints all this information)
First part of code
Second part of code
The error I get is Type 'Any' has no subscript members. Any idea as to why? Or how this can be fixed?
you can set their types like this then you can print values.
if let main = json["main"] as? [String: Any] {
let temp = main["temp"] as? Double
print("temp\(temp!)")
let temp_max = main["temp_max"] as? Double
print("temp\(temp_max!)")
let temp_min = main["temp_min"] as? Double
print("temp\(temp_min!)")
}
let items = json["weather"] as! [AnyObject]
let main = items[0]["main"] as! String
print(main)
let description = items[0]["description"] as! String
print(description)
let icon = items[0]["icon"] as! String
print(icon)
let name = json["name"] as! String
print("name\(name)")
I have some code to get EXIF data from file, but it uses NS-Types. I like to get Swift 3 conform and use standard swift types like Dictionary or String. When deleting "NS", I get the error that ".value()" does not exist. And no hint by the compiler what is the new function call:
let imageSource = CGImageSourceCreateWithURL(url as CFURL, nil)
let imageProperties = CGImageSourceCopyPropertiesAtIndex(imageSource!, 0, nil)! as NSDictionary
let exifDict = imageProperties.value(forKey: "{Exif}") as! NSDictionary
let dateTimeOriginal = exifDict.value(forKey: "DateTimeOriginal") as! NSString
print ("DateTimeOriginal: \(dateTimeOriginal)")
let PixelXDimension = exifDict.value(forKey: "PixelXDimension") as! Double
print ("PixelXDimension: \(PixelXDimension)")
let exifDictTIFF = imageProperties.value(forKey: "{TIFF}") as! NSDictionary
// optional
if let Software = exifDictTIFF.value(forKey: "Software") as? NSString {
print ("Software: \(Software)")
}
Any hint how to change it?
Additionally:
Using this
let imageProperties = CGImageSourceCopyPropertiesAtIndex(imageSource!, 0, nil)! as Dictionary
let exifDict = imageProperties["{Exif}"] as! Dictionary
will deliver an error "Ambiguous reference to member 'subScript'" for the second row!
All of the NSDictionary needs to be something like [String:Any]. And all of the value calls should use normal key access.
let imageSource = CGImageSourceCreateWithURL(url as CFURL, nil)
let imageProperties = CGImageSourceCopyPropertiesAtIndex(imageSource!, 0, nil) as! [String:Any]
let exifDict = imageProperties["{Exif}"] as! [String:Any]
let dateTimeOriginal = exifDict["DateTimeOriginal"] as! String
print ("DateTimeOriginal: \(dateTimeOriginal)")
let PixelXDimension = exifDict["PixelXDimension"] as! Double
print ("PixelXDimension: \(PixelXDimension)")
let exifDictTIFF = imageProperties["{TIFF}"] as! [String:Any]
// optional
if let Software = exifDictTIFF["Software"] as? String {
print ("Software: \(Software)")
}
This code is terrible. All of those uses of ! are a bad idea. Proper, safe unwrapping and casting should be used throughout this code.
I'm try rewrite metadata from JPG file. I want addition one keyword to metadata. Xcode don't give any errors, but file not changed.
Here is my code:
var pathToOpenFile:NSURL?
Next I write path from file to variable "pathToOpenFile".
If user pushed ENTER button into NSTextField, then work action:
#IBAction func endEditKeys(sender: AnyObject) {
if (pathToOpenFile != nil) {
let imageSource = CGImageSourceCreateWithURL(pathToOpenFile!, nil)
let imageProperties = CGImageSourceCopyPropertiesAtIndex(imageSource!, 0, nil)! as NSDictionary;
let exifDict = imageProperties.valueForKey("{IPTC}") as! NSDictionary;
var Keywords:[String] = exifDict.valueForKey("Keywords") as! [String];
Keywords.append("ANY")
exifDict.setValue(Keywords, forKey: "Keywords")
let type = CGImageSourceGetType(imageSource!)
let count = CGImageSourceGetCount(imageSource!)
let mutableData = NSMutableData(contentsOfURL: pathToOpenFile!)
let destination = CGImageDestinationCreateWithData(mutableData!, type!, count, nil)
let removeExifProperties: CFDictionary = exifDict
for i in 0..<count {
CGImageDestinationAddImageFromSource(destination!, imageSource!, i, removeExifProperties)
}
CGImageDestinationFinalize(destination!)
}
}
Can you help me, why it isn't work (not change metadata)? Thank you!
Thanks all!
It is working, just need rewrite .JPG file in end.
if let _ = try? mutableData!.writeToURL(pathToOpenFile!, options: NSDataWritingOptions.AtomicWrite) {
// if you need, do anything. For example "print ("Savig file")"
}
I'm building something and it all worked fine until Swift 1.2 came out. I made some changes but still have one line of code that is playing nice. I don't understand why this is breaking:
let swiftArray = positionDictionary.objectForKey["positions"] as? [AnyObject]
it gives me an error:
'(AnyObject) -> AnyObject?' does not have a member named 'subscript'
I also tried using this:
let swiftArray = positionDictionary.objectForKey?["positions"] as? [AnyObject]
but then I get an error saying:
Operand of postfix '?' should have an optional type; type is '(AnyObject) -> AnyObject?'
I'm really confused...can anyone help?
func addOrbsToForeground() {
let orbPlistPath = NSBundle.mainBundle().pathForResource("orbs", ofType: "plist")
let orbDataDictionary : NSDictionary? = NSDictionary(contentsOfFile: orbPlistPath!)
if let positionDictionary = orbDataDictionary {
let swiftArray = positionDictionary.objectForKey["positions"] as? [AnyObject]
let downcastedArray = swiftArray as? [NSArray]
for position in downcastedArray {
let orbNode = Orb(textureAtlas: textureAtlas)
let x = position.objectForKey("x") as CGFloat
let y = position.objectForKey("y") as CGFloat
orbNode.position = CGPointMake(x,y)
foregroundNode!.addChild(orbNode)
}
}
positionDictionary is an NSDictionary. You can use it just like a Swift dictionary - you don't need to use objectForKey.
You should just use if let and optional casting to get the value you want, which I think is an array of NSDictionary since you're using objectForKey again later:
if let downcastedArray = positionDictionary["positions"] as? [NSDictionary] {
for position in downcastedArray {
let orbNode = Orb(textureAtlas: textureAtlas)
let x = position["x"] as CGFloat
let y = position["y"] as CGFloat
orbNode.position = CGPointMake(x,y)
foregroundNode!.addChild(orbNode)
}
}
As a side note, CGPointMake is not stylistically preferred in Swift. Instead, consider using the CGPoint initializer:
orbNode.position = CGPoint(x: x, y: y)