NSData from CGImageRef in Swift - swift

I am having difficulty figuring out how to get an NSData representation of an image from a CGImageRef. All of the answers I've found make use of UIImage or NSImage, but my application is cross-platform, so I want to use only Core Graphics. Objective-C answers state simply that CFData is toll-free bridged to NSData and simply cast it, but Swift will not allow this. The closest I've got is:
var image: CGImageRef? = nil
//...
if let dataProvider: CGDataProviderRef = CGDataProviderCreateWithURL(url) {
image = CGImageCreateWithPNGDataProvider(dataProvider, nil, false, CGColorRenderingIntent.RenderingIntentDefault)
// works fine
//...
if let data = CGDataProviderCopyData(CGImageGetDataProvider(image)) as? NSData {
// Do something with data, if only it ever got here!
}
}
but the cast doesn't ever succeed...

CGDataProviderCopyData() returns the optional CFData?, and that cannot be cast to the non-optional NSData. But you can convert/bridge
it to NSData? and use that in the optional binding:
if let data = CGDataProviderCopyData(CGImageGetDataProvider(image)) as NSData? {
// Do something with data ...
}
Here is a simpler example demonstrating the same issue with
CFString and NSString:
let cfstr : CFString? = "Hello world"
if let nsstr = cfstr as? NSString {
print("foo") // not printed
}
if let nsstr = cfstr as NSString? {
print("bar") // printed
}
But I admit that my explanation is not fully satisfying, because
a similar optional cast works in other cases:
class MyClass { }
class MySubclass : MyClass { }
let mc : MyClass? = MySubclass()
if let msc = mc as? MySubclass {
print("yes") // printed
}
So this must be related to the toll-free bridging between CoreFoundation
and Foundation types.

Related

Init has been renamed to init(describing) error in Swift 3

This code works fine in Swift 2:
guard let userData = responseData["UserProfile"] as? [String : AnyObject] else { return }
var userProfileFieldsDict = [String: String]()
if let profileUsername = userData["Username"] as? NSString {
userProfileFieldsDict["username"] = String(profileUsername)
}
if let profileReputationpoints = userData["ReputationPoints"] as? NSNumber {
userProfileFieldsDict["reputation"] = String(profileReputationpoints)
}
But, in Swift 3 it throws an error on userProfileFieldsDict["reputation"] saying
init has been renamed to init(describing:)
My question is why does it trigger on that line and not on the userProfileFieldsDict["username"] assignment line, and how to go about fixing it? I'm assuming it's because I'm casting a NSNumber to a String, but I can't really understand why that matters.
NSNumber is a very generic class. It can be anything from a bool to a long to even a char. So the compiler is really not sure of the exact data type hence it's not able to call the right String constructor.
Instead use the String(describing: ) constructor as shown below
userProfileFieldsDict["reputation"] = String(describing: profileReputationpoints)
Here's more info about it.
You need to drop your use of Objective-C types. This was always a bad habit, and now the chickens have come home to roost. Don't cast to NSString and NSNumber. Cast to String and to the actual numeric type. Example:
if let profileUsername = userData["Username"] as? String {
userProfileFieldsDict["username"] = profileUsername
}
if let profileReputationpoints = userData["ReputationPoints"] as? Int { // or whatever
userProfileFieldsDict["reputation"] = String(profileReputationpoints)
}

CFDictionary won't bridge to NSDictionary (Swift 2.0 / iOS9)

OK, this is a case I came across when working with CGImageSource and noticed that the toll-free-bridging between CFDictionary and NSDictionary seems to run into problems in certain cases. I've managed to construct the below example to show what I mean:
func optionalProblemDictionary() -> CFDictionary? {
let key = "key"
let value = "value"
var keyCallBacks = CFDictionaryKeyCallBacks()
var valueCallBacks = CFDictionaryValueCallBacks()
let cfDictionary = CFDictionaryCreate(kCFAllocatorDefault, UnsafeMutablePointer(unsafeAddressOf(key)), UnsafeMutablePointer(unsafeAddressOf(value)), 1, &keyCallBacks, &valueCallBacks)
return cfDictionary
}
Fairly straightforward (and a bit silly) but its a function returning and optional CFDictionary. The "fun" starts when trying to create an NSDictionary from this function:
Why won't the following work?
if let problemDictionary = optionalProblemDictionary() as? NSDictionary {
print(problemDictionary) // never enters, no warnings, compiles just fine
}
While this works fine?
if let cfDictionary = optionalProblemDictionary() {
let problemDictionary = cfDictionary as NSDictionary
print(problemDictionary)
}
XCode 7.0 (7A220)
The reason seems to be that the function returns an optional
CFDictionary? and that can not be cast to a (non-optional)
NSDictionary.
Here is a simpler example demonstrating the same problem with CFString vs NSString:
let cfString = "foobar" as CFString?
if let s1 = cfString as? NSString {
print("s1 = \(s1)") // not executed
}
(The question remains why this does not give a compiler error or
at least a compiler warning because this optional cast can
never succeed.)
But a casting to an optional NSString? works:
if let s2 = cfString as NSString? {
print("s2 = \(s2)") // prints "s2 = foobar"
}
In your case, if you change the "problematic case" to
if let problemDictionary = cfDict as NSDictionary? {
print(problemDictionary)
}
then the if-block is executed.
Note that your method to build a CFDictionary in Swift is not correct
and actually caused program crashes in my test. One reason is that
the dictionary callbacks are set to empty structures.
Another problem is that unsafeAddressOf(key) bridges the Swift
string to an NSString which can be deallocated immediately.
I don't know what the best method is to build a CFDictionary in Swift,
but this worked in my test:
func optionalProblemDictionary() -> CFDictionary? {
let key = "key" as NSString
let value = "value" as NSString
var keys = [ unsafeAddressOf(key) ]
var values = [ unsafeAddressOf(value) ]
var keyCallBacks = kCFTypeDictionaryKeyCallBacks
var valueCallBacks = kCFTypeDictionaryValueCallBacks
let cfDictionary = CFDictionaryCreate(kCFAllocatorDefault, &keys, &values, 1, &keyCallBacks, &valueCallBacks)
return cfDictionary
}

Creating a CFDictionary

In an attempt to get this following code to work:
import ImageIO
if let imageSource = CGImageSourceCreateWithURL(self.URL, nil) {
let options: CFDictionary = [
kCGImageSourceThumbnailMaxPixelSize: max(size.width, size.height) / 2.0,
kCGImageSourceCreateThumbnailFromImageIfAbsent: true
]
let scaledImage = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, options).flatMap { UIImage(CGImage: $0) }
}
I need to know how to correctly initialize a CFDictionary. Unfortunately, it seems like it's not as easy as I predicted.
I've done some experimenting and research, and it seems like there is conflicting information.
For starters, here's an entry in the Apple Docs regarding the kCGImageSourceThumbnailMaxPixelSize key:
kCGImageSourceThumbnailMaxPixelSize
The maximum width and height in pixels of a thumbnail. If this key is not specified, the width and height of a thumbnail is not limited and thumbnails may be as big as the image itself. If present, this key must be a CFNumber value. This key can be provided in the options dictionary that you pass to the function CGImageSourceCreateThumbnailAtIndex.
After looking into how to initialize the CFNumber, I found an excerpt for CFNumber
CFNumber is “toll-free bridged” with its Cocoa Foundation counterpart, NSNumber. This means that the Core Foundation type is interchangeable in function or method calls with the bridged Foundation object
I then tried to do this:
let options: CFDictionary = [
kCGImageSourceThumbnailMaxPixelSize: NSNumber(double: 3.0)
]
and was greeted by the error message: '_' is not convertible to 'CFString!' and Type of expression is ambiguous without more context.
Here is your working code:
func processImage(jpgImagePath: String, thumbSize: CGSize) {
if let path = NSBundle.mainBundle().pathForResource(jpgImagePath, ofType: "") {
if let imageURL = NSURL(fileURLWithPath: path) {
if let imageSource = CGImageSourceCreateWithURL(imageURL, nil) {
let maxSize = max(thumbSize.width, thumbSize.height) / 2.0
let options : [NSString : AnyObject] = [
kCGImageSourceThumbnailMaxPixelSize: maxSize,
kCGImageSourceCreateThumbnailFromImageIfAbsent: true
]
let scaledImage = UIImage(CGImage: CGImageSourceCreateThumbnailAtIndex(imageSource, 0, options))
// do other stuff
}
}
}
}
From Docs:
The implicit conversions from bridged Objective-C classes
(NSString/NSArray/NSDictionary) to their corresponding Swift value
types (String/Array/Dictionary) have been removed, making the Swift
type system simpler and more predictable.
The problem in your case are the CFStrings like kCGImageSourceThumbnailMaxPixelSize. These are not automatically converted to String anymore.
reference from HERE.

Swift - Why can I not return an NSNumber or Double data type from this NSDictionary object?

The second line of the code segment below returns an error unless I change the portion that reads "as NSNumber" to "as String". The value returned in rowData["lngID"] is a numeric value. Can someone please explain this to me?
let rowData: NSDictionary = objReport as NSDictionary
let lngReportID = rowData["lngID"] as NSNumber
What I'm actually attempting to do here is take a JSON response and load it into an array of objects as follows. Perhaps there is a better way to achieve this. Any suggestions for a better approach is much appreciated. First, the function didReceiveAPIResults returns the results to the app. Then the function loadReportsIntoArray is called.
func loadReportsIntoArray(pReports: NSArray) {
arrayPoints = []
for (intRow, objReport) in enumerate(pReports) {
// index is the index within the array
// participant is the real object contained in the array
let rowData: NSDictionary = objReport as NSDictionary
let lngReportID = rowData["lngID"] as NSNumber
let lngReportTypeID = rowData["lngTypeID"] as NSNumber
let strOtherTypeName = rowData["strOtherTypeName"] as String
let strDescription = rowData["strDescription"] as String
let dtmFirstReport = rowData["dtmFirstReport"] as String
let dblLat = rowData["dblLat"] as NSNumber
let dblLong = rowData["dblLong"] as NSNumber
let strReportedByUsername = rowData["strReportedByUsername"] as String
let lngReportedByID = rowData["lngReportedBy"] as NSNumber
let lngCommentCount = rowData["lngCommentCount"] as NSNumber
let lngNumLikes = rowData["lngNumLikes"] as NSNumber
let blnUserLikedEvent = rowData["blnUserLikedEvent"] as Bool
var objReport = Report(plngReportID: lngReportID, plngReportTypeID: lngReportTypeID, pstrOtherTypeName: strOtherTypeName, pstrDescription: strDescription, pdtmFirstReport: dtmFirstReport, pdblLat: dblLat, pdblLong: dblLong, pstrReportedByUsername: strReportedByUsername, plngReportedByID: lngReportedByID, plngCommentCount: lngCommentCount, plngNumLikes: lngNumLikes, pblnUserLikedEvent: blnUserLikedEvent)
//arrayPoints.append(objReport)
}
}
func didReceiveAPIResults(results: NSDictionary) {
var success: NSInteger = results["success"] as NSInteger
if success == 1 {
var resultsArr = results["geopoints"] as NSArray
dispatch_async(dispatch_get_main_queue(), {
self.loadReportsIntoArray(resultsArr)
})
}
else {
// Error occurred
}
}
I was able to recreate your error using the following code:
let objReport = NSDictionary(object: "string", forKey: "lngID")
let rowData: NSDictionary = objReport as NSDictionary
let lngReportID = rowData["lngID"] as NSNumber // Error
However, changing the objReport to NSDictionary(object: NSNumber(integer: 0), forKey: "lngID") solved the problem. Therefore, I think your problem is the object stored for the key lngID isn't an NSNumber.
For the solution to this you should look at Kumar Nitin's answer to check you've got a number stored, or you could use the code, they both do the same thing pretty much:
if let lngID = rowData["lngID"] as? NSNumber {
// Do stuff with lngID.
}
In swift, you don't have NSNumber, however you can use the Obj C's NSNumber if need be.
The above code for NSNumber should be as follows if you are expecting a double or float or int. Add a check to ensure the value is not nil, or else it will crash your app.
if let lngReportID = rowData["lngID"] as? Int {
//Do the task required
}

UIColor in NSUserDefaults Swift / iOS 8

I'm writing a Swift app for iOS. I need to set and later retrieve a UIColor object to NSUserDefaults.
I set it like this:
var userSelectedColor : NSData? = (NSUserDefaults.standardUserDefaults().objectForKey("UserSelectedColor") as? NSData)
if (userSelectedColor == nil) {
var colorToSetAsDefault : UIColor = UIColor.redColor()
var data : NSData = NSKeyedArchiver.archivedDataWithRootObject(colorToSetAsDefault)
NSUserDefaults.standardUserDefaults().setObject(data, forKey: "UserSelectedColor")
NSUserDefaults.standardUserDefaults().synchronize()
println("SET DEFAULT USER COLOR TO RED")
}
But I can't seem to get it back because the unarchive method on NSKeyedArchiver seems to be missing in Swift.
var userSelectedColorData: NSData? = (NSUserDefaults.standardUserDefaults().objectForKey("UserSelectedColor") as NSData)
var userSelectedColor : UIColor? = NSKeyedArchiver.unarchiveObjectWithData(userSelectedColorData)
What is the proper way to do this in Swift / iOS 8?
Unarchiving is done with NSKeyedUnarchiver, not with NSKeyedArchiver. You also should
use conditional casts (as?) to ensure that the application does not crash if the
saved user default is not of the expected type:
if let userSelectedColorData = NSUserDefaults.standardUserDefaults().objectForKey("UserSelectedColor") as? NSData {
if let userSelectedColor = NSKeyedUnarchiver.unarchiveObjectWithData(userSelectedColorData) as? UIColor {
println(userSelectedColor)
}
}