Swift convert object that is NSNumber to Double - ios

I have this code in Swift and it works, but I would think there is a better way to get my object from NSNumber and convert it to a Double:
var rating: NSNumber
var ratingDouble: Double
rating = self.prodResult?.prodsInfo.prodList[indexPath.row].avgRating as NSNumber!!
ratingDouble = Double(rating.doubleValue)

Update
Swift's behavior here has changed quite a bit since 1.0. Not that it was that easy before, but Swift has made it harder to convert between number types because it wants you to be explicit about what to do with precision loss. Your new choices now look like this:
var rating: NSNumber
var ratingDouble: Double
ratingDouble = rating as! Double // 1
ratingDouble = Double(exactly: rating)! // 2
ratingDouble = Double(truncating: rating) // 3
ratingDouble = rating.doubleValue // 4
if let x = rating as? Double { // 5
ratingDouble = x
}
if let x = Double(exactly: rating) { // 6
ratingDouble = x
}
This calls Double._forceBridgeFromObjectiveC which calls Double(exactly:) with Double, Int64, or UInt64 based on the stored type in rating. It will fail and crash the app if the number isn't exactly representable as a Double. E.g. UInt64.max has more digits than Double can store, so it'll crash.
This is exactly the same as 1 except that it may also crash on NaN since that check isn't included.
This function always returns a Double but will lose precision in cases where 1 and 2 would crash. This literally just calls doubleValue when passing in an NSNumber.
Same as 3.
This is like 1 except that instead of crashing the app, it'll return nil and the inside of the statement won't be evaluated.
Same as 5, but like 2 will return nil if the value is NaN.
If you know your data source is dealing in doubles, 1-4 will probably all serve you about the same. 3 and 4 would be my first choices though.
Old Answer for Swift 1 and 2
There are several things you can do:
var rating: NSNumber
var ratingDouble: Double
ratingDouble = rating as Double // 1
ratingDouble = Double(rating) // 2
ratingDouble = rating.doubleValue // 3
The first item takes advantage of Objective-Cbridging which allows AnyObject and NSNumber to be cast as Double|Float|Int|UInt|Bool.
The second item presumably goes through a constructor with the signature init(_ number: NSNumber). I couldn't find it in the module or docs but passing AnyObject in generated an error that it cannot be implicitly downcast to NSNumber so it must be there and not just bridging.
The third item doesn't employ language features in the same way. It just takes advantage of the fact that doubleValue returns a Double.
One benefit of 1 is that it also works for AnyObject so your code could be:
let ratingDouble = self.prodResult!.prodsInfo.prodList[indexPath.row].avgRating! as Double
Note that I removed the ? from your function and moved the ! in. Whenever you use ! you are eschewing the safety of ? so there's no reason to do both together.

Related

Int64 does't work while Int works

I'm receiving a JSON and I want to parse it to get a value. I'm doing this
let ddd = oneJson["restaurantId"] as! Int
print("ddd = \(ddd)")
let restaurantId = oneJson["restaurantId"] as! Int64
as you see, I'm parsing the same field. Here's my JSON
"location":"location here location","restaurantId":0
The print statement works just fine, but I get an exception on oneJson["restaurantId"] as! Int64
I love this quirk in swift (NOT).
It's one of the least intuitive gotchas of the language I know of. So it turns out that when you get a Dictionary with type AnyObject, Ints, Doubles, Floats, ARE NOT stored as the Swift native types. They're stored as... surprise! NSNumber.
Which leads to a whole host of unintuitive behavior, for instance type checking AnyObjects to see whether you have a Double or an Int (it can't be done).
For the same reason, your code is failing. Change it to:
let ddd = oneJson["restaurantId"] as! Int
print("ddd = \(ddd)")
let restaurantId = (oneJson["restaurantId"] as? NSNumber)?.longLongValue
And remind yourself again and again that when it's an AnyObject you're casting from, Swift is hiding from you the fact that it does a cast from NSNumber to Swift base types, and that in truth, they're still just NSNumbers.
I would recommend, not to use Int64 (or Int32). Int will be working in most cases.
See this post about the different integers in Swift: https://stackoverflow.com/a/27440158/4687348
Yes, it's a known bug in Swift 3, which has been solved in Swift 4.
Now, you just write like this,
let n = NSNumber.init(value: 9223372036854775807) // 2^63 - 1
print(n, n as! Int64) // will print the right answer.

SWIFT: var someVariable:int = textfield.text.toInt() x textfield2.text.toInt() and force unwrap

First post here so please be gentle. Am fairly new to coding and am trying to get my head around SWIFT and its optionals. Would really appreciate some advice from the pros!
I am writing a simple app whereby textfields are entered by the user and then some multiplication occurs in app before spitting out an answer into another textfield on the press of a button: "calculateTM".
I am having some trouble with the calculation itself and perhaps it's because I am trying to do too much on one line - take the textfield entry, convert to integer, multiply with another textfield entry converted to an integer, essentially what I wrote in the title:
var someVariable: Int = textfield.text.toInt() * textfield2.text.toInt()
The problem is, Xcode is wanting my to force unwrap and add an ! to the end of both toInt(). This is fine, except of course when the user doesn't enter anything into the boxes and presses calculate, at which point the nil value causes the program to crash, e.g.:
var someVariable: Int = textfield.text.toInt() x textfield2.text.toInt()
var someVariable2: Int = textfield3.text.toInt() x textfield4.text.toInt()
where the user doesn't enter anything into textfield3 or 4
Following this simple arithmetic, the code updates the labels (which are textfields) as such:
label1.text = String(someVariable)
label2.text = String(someVariable2)
So this final conversion back to a string might also create some issues as to how the optionals are treated in the first part of the code.
Apologies for the long-winded explanation, and I hope I've been clear enough, but I imagine I am missing something really basic with the first part of the code. I have tried using the optional ? and also the nil-coalescing operator (to set to 0 in case of nil) but can't get it to work. Please help?
Many thanks in advance!
Use if let for optional binding:
if let var1 = textField.text?.toInt(),
let var2 = textField2.text?.toInt() {
someVariable = var1 * var2 // or directly label1.text = String(var1 * var2)
}
The method toInt() actually returns an optional type 'Int?' that can be nil or integer value, so you need to check if the String->Int cast successful returns an Int or a nil.
For the most basic way:
var intValue: Int? = text.toInt()
if intValue != nil {
// operations using intValue!
}
In swift, you can try:
if let intValue = text.toInt() {
// operations
}

Basic Xcode - reference in random number generator not working

I am working on a very basic (I think) program in Xcode. I am trying to write an app for "drawing straws" where the last person who is chosen is the loser. There are a few things I'd like to do. First, see the code below:
import UIKit
let players = 4
var playerNames: [String] = ["John", "Tyler", "Pete", "Dave"]
var draw = Int(arc4random_uniform(4))
playerNames.removeAtIndex(draw)
print(playerNames)
let round2 = playerNames.count
var draw2 = Int(arc4random_uniform(2))
playerNames.removeAtIndex(draw2)
print(playerNames)
let round3 = playerNames.count
var draw3 = Int(arc4random_uniform(1))
playerNames.removeAtIndex(draw3)
print(playerNames)
The first thing that's wrong is I'm currently hard-coding the random integer being drawn in var draw = Int(arc4random_uniform(4)). When I try to reference players instead of just typing in 4, I get an error. Can someone please help explain the problem there?
I'll stop there for now to see if I can fix that, and I'll wait until that is fixed before posting a new question. Thank you.
The function arc4random_uniform() takes a UInt32, so you need to declare the variable players as UInt32
let players: UInt32 = 4
var draw = (arc4random_uniform(players))
For handling the round variables, you would cast the count to UInt32.
let round2 = UInt32(playerNames.count)
You can also refactor your code
let players: UInt32 = 4
var playerNames: [String] = ["John", "Tyler", "Pete", "Dave"]
println(playerNames)
for loop in 1...players {
RemovePlayer(&playerNames)
println(playerNames)
}
And the code for the RemovePlayer function
func RemovePlayer(inout names: [String]) {
// get the number of names in the array
var maxNames = UInt32(names.count)
var draw = Int((arc4random_uniform(maxNames)))
names.removeAtIndex(draw)
}
As Blackfrog already stated your randomization function requires a UInt32
Since you stated that you just started programing I would like to give you some other advice. Your code can be written as:
var playerNames = ["John", "Tyler", "Pete", "Dave"]
for var index = playerNames.count ; index > 1 ; index -= 1{
var random:Int = Int(arc4random_uniform(UInt32(index)))
playerNames.removeAtIndex(random)
}
println(playerNames[0])
This has a few advantages. At the moment you are hardcoding the values for how many players join a game. You will need to re-write an unnecessary amount of code if you want to add a player.
Let’s see what the above code does:
We start by declaring the playerNames adding a type declaration of [String] isn’t necessary, swift already knows that it is this type by its initial value.
for var index = playerNames.count; index > 1 ; index -= 1{
We create a new variable called index this will be set to the amount of items in the playerNames array (4 in this case)
After this we will declare that we want the loop as long as our index is greater than 1 this will make it run 3 times and make sure we are left with 1 item in our array
The index -=1 will subtract 1 from the index after each iteration through the loop.
var random:Int = Int(arc4random_uniform(UInt32(index)))
Here we declare a new variable random this will be of type Integer. Let’s work from the inside to the outside. UIt32(index) will convert our index which is of type Int to a type of UInt32, this is needed cause our random function requires a UInt32
Next up we request a random value which will lay between the index and 0 (thus between the bounds of the array). We want to remove the player who belongs to this random value in our array. To do this we need to convert our random Uint32 back to an Int we can do this by using Int().
Next we remove the player at the index using
playerNames.removeAtIndex(random)
Lastly we print the first (and only) item left in the array using
println(playerNames[0])

error: Generic parameter 'R.Generator.Element' cannot be bound to non-#objc protocol type 'AnyObject'

I am querying HealthKit and saving it to CoreData. I fetch the data in a separate class. In TableViewController I append the data to an array:
if NSUserDefaults.standardUserDefaults().boolForKey("weightSwitch") == true {
xAxisDatesArray.append(cdFetchWeight.queryCoreDataDate())
yAxisValuesArray.append(cdFetchWeight.queryCoreDataData())
and pass it at tableView.dequeueReusableCellWithIdentifier
myCell.xAxisDates = xAxisDatesArray[indexPath.row]
myCell.yAxisValues = yAxisValuesArray[indexPath.row]
In UITableViewCell I initialise the variables (yAxisValues, xAxisDates) and pass them into a charting library which takes the x and y values and plot a chart.
class TableViewCell: UITableViewCell, TKChartDelegate {
var xAxisDates = []
var yAxisValues = []
plot....
I need to get the min and max values of yAxisValues so that I can set the appropriate y-axis range to the data.
I have tried to get the min and max with the following code:
func rangeMinAndMax(){
let minYvalue = minElement(yAxisValues)
let maxYvalue = maxElement(yAxisValues)
}
But this generates the error: Generic parameter 'R.Generator.Element' cannot be bound to non-#objc protocol type 'AnyObject'
- Question: Why and how can I fix it?
Any help would be much appreciated !
The thing to do in a situation like this is to throw away all the misleading dross and boil it down to the simplest possible case:
let arr : [AnyObject] = [1,2,3]
let min = minElement(arr) // same error: "Generic parameter blah-de-blah..."
So, you see, minElement doesn't work on an array of AnyObject, and that's what the error is telling you. And the reason is obvious: an AnyObject is not a Comparable. There is no "minimum" for a bunch of AnyObject things; the entire concept of one AnyObject being "less than" another AnyObject is undefined. You need to cast your array down to an array of something that minElement can work on, namely a Comparable of some kind.
For example, in that code, I can fix the problem like this:
let arr : [AnyObject] = [1,2,3]
let min = minElement(arr as [Int])
That is the sort of thing you need to be doing. Of course, what you cast down to depends upon what these elements actually are. It looks to me as if will probably be Double and NSDate respectively, but that's just a guess; I don't know what's in your arrays. You do (presumably). Note that an NSDate is not a Comparable so you will have a bit more work to do with that one.

Swift Int is not convertible to DictionaryIndex?

I'm trying to convert this piece of ojb-c code:
MPMovieFinishReason finishReason = [notification.userInfo[MPMoviePlayerPlaybackDidFinishReasonUserInfoKey] integerValue];
to swift, and as such I have this:
var finishReason: MPMovieFinishReason = notification.userInfo[MPMoviePlayerPlaybackDidFinishReasonUserInfoKey.integerValue];
However this gives me the error in the title.
What am I doing wrong here?
You have a few problems, first of all in your direct translation:
var finishReason: MPMovieFinishReason = notification.userInfo[MPMoviePlayerPlaybackDidFinishReasonUserInfoKey.integerValue];
The ] is in the wrong place, so you're trying to call integerValue on MPMoviePlayer... instead of on the dictionary lookup.
var finishReason: MPMovieFinishReason = notification.userInfo[MPMoviePlayerPlaybackDidFinishReasonUserInfoKey].integerValue;
This is still failing because userInfo is an NSDictionary and not a Dictionary, so it gets mapped to [AnyObject:AnyObject] It seems like the automatic morphing is failing you, so it's falling back to a CFString which isn't mappable to AnyObject.
The process becomes a little clearer if we break it up more:
// Recover the original dictionary, and cast is by input and output, to it
// all makes sense. This will allow String as a parameter and return an NSNumber
// as a result.
let userInfo = notification.userInfo as [String:NSNumber]
// From the userInfo, let's extract the raw NSNumber using lookup
let reason = userInfo[MPMoviePlayerPlaybackDidFinishReasonUserInfoKey]
// Convert the NSNumber into an MPMovieFinishReason enum. Note that it's
// 0215 and I'm on ambien so I don't care about the force unwrapping that
// should be optional unwrapping
let finishReason = MPMovieFinishReason.fromRaw(reason!.integerValue)
Many problems can be broken down into much simpler problems if you just take it a single step at a time. Modern compilers aren't even too bothered by it as the can recognize when variables are no longer in use and do proper reclamation. Also note using let instead of var for those temporary values since they shouldn't be reused anyway.
Try the type-casting function Int()
ie.,
var finishReason = Int(notification.userInfo[MPMoviePlayerPlaybackDidFinishReasonUserInfoKey]) as MPMovieFinishReason;

Resources