I'm runtime errors on my existing project when migrating versions of Xcode. Can someone help with this? Is my typecasting wrong?
Code:
let x = d[ChildItem.KEY_LOOK] as! NSArray as Array
let y = d[ChildItem.KEY_COOR] as! NSArray as Array
let item = ChildItem(x as! [Float], y as! [Float])
Error:
Thread 5: Fatal error: Unable to bridge NSNumber to Float
I know how to cast an NSNumber to a Float as I found on SO (Unable to bridge NSNumber to Float in JSON parsing):
if let n = d.value(forKey: "probability") as? NSNumber {
let f = n.floatValue }
How can I do this for an NSNumber array?
In each of the d[] keys there are JSON strings like this:
d[ChildItem.KEY_LOOK] = [465918.2, 5681518.0,4462.3203]
you need to use the floatValue property of NSNumber, not casting directly.
Let's say you have an array of numbers you got from the server calls like this
d[ChildItem.KEY_LOOK] = [465918.2, 5681518.0,4462.3203] // from your example
you can do the following
//As from your example that can be directly convert to Array of Doubles like this
if let arrayOfDoubles = d[ChildItem.KEY_LOOK] as? [Double] {
debugPrint(arrayOfDoubles)
}
And if for some reason you want those to be [NSNumber] then you can do like this
if let arrayOfNumbers = d[ChildItem.KEY_LOOK] as? [NSNumber] {
//to make this array of double
let doubleArray = arrayOfNumbers.map { $0.doubleValue }
//to make int
let intArray = arrayOfNumbers.map { $0.intValue }
... and so on
}
Related
In the former version, to get a float value from a [String: Any] dictionary, I can use let float = dict["somekey"] as? Float, but in swift4.1, it doesn't work. It seems the type of dict["somekey"] has been implicitly inferred as Double before I get it, so casting from Double to Float always fails. I wonder if it is a new characteristic or just a bug.
--Here is the update.
I re-downloadeded an Xcode9.2 and did some experiments, now I think I figure out what's going on. Here is the test code:
let dict: [String : Any] = ["key": 0.1]
if let float: Float = dict["key"] as? Float {
print(float)
} else {
print("nil")
}
let dict1: [String : Any] = ["key": NSNumber(value: 0.2)]
if let float: Float = dict1["key"] as? Float {
print(float)
} else {
print("nil")
}
let number = NSNumber(value: 0.3)
if let float: Float = number as? Float {
print(float)
} else {
print("nil")
}
let number1 = NSNumber(floatLiteral: 0.4)
if let float = number1 as? Float {
print(float)
} else {
print("nil")
}
Running this code in Playground of Swift4 and Swift4.1, the results are different. In Swift4, the results are nil 0.2 0.3 0.4, and In Swift4.1 the results are nil nil nil nil. From the result, I can learn two points:
1. When we convert JSON data into a [String : Any] dictionary with the JSONSerialization class, the numeric value is saved as an NSNumber object, but not Int, Double or Float.
2. In Swift4, we can use let float = NSNumberOjbect as? Float to get a Float value, but in Swift4.1 we can't. But still, we can get Int or Double value in this way, either in Swift4 or Swift4.1.
Finally again, is this a new feature or a bug? If someone knows, can you guys show up the announcement link?
You need to distinguish two cases (in Swift 3, it was three cases):
Any containing a Swift native Double
Any containing an NSNumber
(In Swift 3, there was type preserving NSNumber case other than the normal NSNumber.)
When you create a native Swift Dictionary such as [String: Any], and set Double value in a normal way like this in your update:
let dict: [String : Any] = ["key": 0.1]
In this case, Any holds the metadata representing Double and the raw value 0.1 as Double.
And casting Any holding Double to Float always fails. As Double to Float cannot be converted with as-castings.
let dblValue: Double = 0.1
if let fltValue = dblValue as? Float { //<-Cast from 'Double' to unrelated type 'Float' always fails
print(fltValue)
} else {
print("Cannot convert to Float")
}
//->Cannot convert to Float
But, in case of Any holding NSNumber, as always happens when the Array or Dictionary is bridged from NSArray or NSDictionary, the behaviors are different between former Swifts and Swift 4.1.
The result of JSONSerialization matches this case.
In former Swifts, (normal) NSNumber to Float was an always-success operation.
And in Swift 4.1, the behavior has changed with the new feature which I have shown in my comment:
SE-0170 NSNumber bridging and Numeric types
I omit the third case once found in Swift 3, it's past.
But it is very important how you get your Dictionary or Array, and how you set the value to them, to solve the issue Any to Float.
Finally again, is this a new feature or a bug? If someone knows, can you guys show up the announcement link?
This is a new feature, not a bug. See the link above, and related threads below.
Unable to bridge NSNumber to Float Swift 3.3
Unexpected behavior when casting an NSNumber to Float
I am trying to read from a JSON String and draw a graph with its data:
{"y-axis-data":{"min":0.0,"max":1000,"Step":100.0},"x-labels":[1994,2000,2005],"y-values":[20,305,143]}
I wrote a function to create a dictionary from the string:
func jsonToDictionary(jsonString: String) -> [String: Any]? {
if let jsonData: Data = jsonString.data(using: String.Encoding.utf8) {
do {
return try (JSONSerialization.jsonObject(with: jsonData, options: []) as? [String: Any])!
} catch {
some bla bla
}
}
return nil
}
The return dictionary should count 3 elements inside when I pass my JSON string, and it does.
I can then change of some variables (Double) which are 0 until now and give them the values of min max and Step from the "y-axis-data" key of my dictionary, using {"min":0.0,"max":1000,"Step":100.0} as a dictionary it self. Works fine.
My trouble comes when trying to initialize other atributes:
self.my_view!.x-labels = (jsonToDictionary!["x-labels"]) as? NSMutableArray
my_view has already been initialized as UIViewCustomClass(frame: someFrame)
myview.x-labels is an NSMutableArray and it is initialized as nil. After executing that line of code it is still nill, of course myview.x-labels.count is nil. if I do it this way:
self.my_view!.x-labels = (jsonToDictionary!["x-labels"]) as! NSMutableArray
I get a warning :
Treating a forced downcast to NSMutableArray as optional will never produce nil.
It then crashes on runtime with this error:
Could not cast value of type '__NSArrayI' (0x110ed5448) to 'NSMutableArray' (0x110ed4598).
of course the exact same thing happens with "y-values"
What is the right way to do this?
It was because your json!["x-labels"] is implicitly treated as NSArray, so you somehow had to do a "double-force-cast"
// get the converted json
let j = jsonToDictionary(jsonString: dict)
// double cast
let m = (j!["x-labels"] as! NSArray).mutableCopy() as! NSMutableArray
Result:
I think, JSONSerialization class converts into Array, and then Swift cannot cast Array to NSMutableArray. You can do this (Swift4):
let array = (jsonToDictionary!["x-labels"]) as? [Int]
if array != nil {
self.my_view!.x-labels = NSMutableArray(array: array!)
}
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)
}
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
}
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)