How do I convert a optional NSInteger to a non-optional one? - swift

I need to retrieve a setting (with Swift):
var highScoreNumber: NSInteger = 0
var returnValue: [NSInteger]? = NSUserDefaults.standardUserDefaults().objectForKey("food") as? [NSInteger]
if (returnValue != nil) {
highScoreNumber = returnValue as NSInteger
}
I tried this and other variations of the code and I always get
'NSInteger?' not convertible to 'NSInteger'
This is my first foray into Swift, so
Am I missing something simple in the conversion?
Is there a better way to retrieve an NSInteger from settings?

When converting a nullable value to a non-nullable one, there are many good options to choose from.
But in your case, you have two issues. One is the array you’re fetching from NSUserDefaults is an array of integers, so you need to decide which one you want from that array.
If it’s the first one you want, you can use the first property. You can use optional chaining to get it. And since it’s a high score, you probably want to default to zero if it’s not present.
Here’s that in one line:
let returnValue = NSUserDefaults.standardUserDefaults().objectForKey("food") as? [Int]
let highscore = returnValue?.first ?? 0
Things to note about the above: there’s no need to give the type to the left of the = if the type is unambiguously determined by what lies to the right of the =. And it’s better to prefer Int over NSInteger.
The returnValue?.first says “if returnValue is nil, then nil, else the value of first (which, itself, returns nil if the array is empty).” The ?? says “if the left of ?? is nil, then the value on the right of nil, else the unwrapped value from the left”.
That said – do you really mean to store an array? Or do you really just want to store a single integer in the defaults and then get that out directly?
let highscore = NSUserDefaults.standardUserDefaults().integerForKey("food")
// and elsewhere in your code, when saving
NSUserDefaults.standardUserDefaults().setInteger(highscore, forKey: "food")
(integerForKey already returns a 0 default when not present rather than an optional, so no need for any unwrapping)

Related

Swift 3 cast UITextField to Int

I'm receiving a compiler error and I'm not really sure why. I'm sure there is a simple answer for this. I have a core data attribute I'm trying to assign before saving. In my Core Data Property file it's defined as this:
#NSManaged public var age: Int32
I am using a UIPicker to select it and put it into an inputView. That works fine, so ageTextField: UITextField! holds the value. As I try to assign this to the CoreData object just before saving I get the following
person.age = ageTextField.text -> Cannot assign String? to Int32.
Ok, I understand that, so I cast it
person.age = Int(ageTextField.text) -> Value of Optional String not unwrapped...
Ok, I get that, so I unwrapped it, it asks to unwrap again and I agree:
person.age = Int(ageTextField.text!)! -> Type of expression is ambiguous without more context
I'm not sure what is wrong here, just looking over some old Swift 2 code of mine and this worked. This is my first code with Swift 3 though.
That compiler error is obscure at best and misleading at worst. Change your cast to Int32:
person.age = Int32(ageTextField.text!)!
Also: unless you are absolutely sure that the user will always enter a valid number into the textfield, use optional binding instead of force unwrap:
if let text = ageTextField.text,
let age = Int32(text)
{
person.age = age
}
The immediate issue is the use of the wrong type. Use Int32, not Int. But even once that is fixed, you have lots of other issues.
You should safely unwrap the text and then the attempt to convert the string to an integer.
if let text = ageTextField.text, let num = Int32(text) {
person.age = num
}
The use of all of those ! will cause a crash if the text is nil or it contains a value that isn't a valid number. Always use safe unwrapping.
Just make sure to unwrap the optional before convert. Make your code safe.

Swift ?? on a type

I know what the ?? is when used as an operator but what does it mean when it is on a Type?
I have a struct in my project called MyStruct and the autocompletion of a particular var tells me it is of type MyStruct??
Not really sure what that means or how to unwrap it safely.
As others have said, it's a double Optional, completely unrelated to the nil coalescence operator (??).
To unwrap it, you just unwrap a regular optional, twice:
let doubleOptional: MyStruct?? = MyStruct()
guard let singleOptional = doubleOptional else {
//first unwrapping failed
}
guard let wrappedValue = singleOptional else {
//second unwrapping failed
}
//use wrappedValue
These are quite uncommon, but there are times when they're useful.
For example, consider you have a data structure that stores an average of an array. The array might be empty, thus the average should be allowed to be nil, to indicate there is no average. Suppose calculating this average is very expensive and we want to store it in a caching layer. This caching layer could have a double optional representing that average. If the value is Optional.None (i.e. nil), we know the cache doesn't have a value yet, thus it needs to be computed. If the value is Optional.Some(Optional.None), we know the cache has a value, but that there is no valid average (i.e. the array was empty. Lastly, the value could be Optional.Some(Optional.Some(/*...*/)), which represents a valid cache value of a valid average.

most concise way of unwrapping and casting optional

I have a bit of code to get a string out of userDefaults:
if let userString = (userDefaults.objectForKey("user")) {
userTextField.stringValue = userString as! String
}
First, I have to see if the optional is not nil. Then I have to cast it as a string from AnyObject.
Is there a better way of doing this? maybe a one liner?
Note that your forced cast as! String will crash if a default value for the key "user" exists, but
is not a string. Generally, you can combine optional binding (if let) with an optional cast (as?):
if let userString = userDefaults.objectForKey("user") as? String {
// ... default value for key exists and is a string ...
userTextField.stringValue = userString
}
But actually NSUserDefaults has a dedicated method for that purpose:
if let userString = userDefaults.stringForKey("user") {
// ... default value for key exists and is a string ...
userTextField.stringValue = userString
}
If you want to assign a default string in the case that
the default does not exist, or is not a string, then use the
nil-coalescing operator ??, as demonstrated in
Swift issue with nil found while unwrapping an Optional value NSDefautlts, e.g.:
userTextField.stringValue = userDefaults.stringForKey("user") ?? "(Unknown)"
For the special case NSUserDefaults the best – and recommended – way is to use always non-optional values.
First register the key / value pair in AppDelegate as soon as possible but at least before using it.
let defaults = NSUserDefaults.standardUserDefaults()
let defaultValues = ["user" : ""]
defaults.registerDefaults(defaultValues)
The benefit is you have a reliable default value of an empty string until a new value is saved the first time. In most String cases an empty string can be treated as no value and can be easily checked with the .isEmpty property
Now write just
userTextField.stringValue = userDefaults.stringForKey("user")!
Without arbitrary manipulation of the defaults property list file the value is guaranteed to be never nil and can be safely unwrapped, and when using stringForKey there is no need for type casting.
Another way that i like much to clean this up is to do each of your checks
first, and exit if any aren’t met. This allows easy understanding of what
conditions will make this function exit.
Swift has a very interesting guard statements which can also be used to avoid force unwrap crashes like :
guard let userString = userDefaults.objectForKey("user") as? String else {
// userString var will accessible outside the guard scope
return
}
userTextField.stringValue = userString
Using guards you are checking for bad cases early, making your
function more readable and easier to maintain. If the condition is not
met, guard‘s else statement is run, which breaks out of the function.
If the condition passes, the optional variable here is automatically
unwrapped for you within the scope that the guard statement was
called.

I don't want to 'unwrap' an NSNumber - Swift

What an utter nightmare this portion of Swift seems to be...
let priceNumber = dictionary[kSomeKey] as? NSNumber
cell.titleLabel.text = String(format: "The price is £%li",priceNumber?.longValue)
Compile error: value of optional NSNumber not unwrapped
Now, forgive me for potentially being a complete cretin, but I just want my old Obj-C style if nil, display 0... what on earth is going on? if this dictionary key is nil, putting the suggested (priceNumber?.longValue)! will crash right?
Closest approach
let priceNumber = dictionary[kSomeKey] as Int? ?? 0
cell.titleLabel.text = "The price is £\(priceNumber)"
The nil coalescing operator ?? unwraps the optional if it's not nil
I'll try this way:
%li is expecting a long integer.
In Swift nil is not the equivalent of zero like it might happen in some cases in Obj-C. Nil is nothing
Since %li is expecting a long integer, and nil is not literal convertible, and Swift is statically typed, you have to unwrap the number and make sure it is not nil (Zero is not nil).
This might not be the 100% exact technical explanation. But it should give the rough idea.
You are making it more difficult than it has to be. Values in dictionaries are optional because you might not get a value for a particular key. To be safe, you bind the value from the dictionary in an if let to make sure you actually have an object, then use string interpolation to set the cell's text property.
// This is all so you can test it in a playground
var dictionary = [String : AnyObject]()
let kSomeKey = "price"
dictionary[kSomeKey] = NSNumber(long: 12)
// Bind the value out of the dictionary
if let priceNumber = dictionary[kSomeKey] as? NSNumber {
// priceNumber is guaranteed to be an instance of NSNumber at this point
cell.titleLabel.text = "The price is £\(priceNumber)")
}

Swift NSUserDefaults first time nil

Hi my app crashes the first time I run it. This is my code:
let State = save.stringForKey("StateSave")
let City = save.stringForKey("CitySave")
let Vehicle = save.stringForKey("ModelNumberSave")
let ExtensionPeriod = save.stringForKey("ExtensionPeriodChoosed")
let Location = "Location"
if ExtensionPeriod == nil {
let name = ""
var FieldChoosed: Void = save.setObject(name, forKey: "ExtensionPeriodChoosed")
save.synchronize()
}
save.synchronize()
var DetailNames = ["\(State!)","\(City!)","\(Location)","\(Vehicle!)","\(ExtensionPeriod!)"]
I get a nil on this line:
var DetailNames =
["(State!)","(City!)","(Location)","(Vehicle!)","(ExtensionPeriod!)"]
In fact ExtensionPeriod is nil. But I don't understand why... ExtensionPeriod is nil, but with the code I write, ExtensionPeriod will be like "" so it's not nil. Please help me.
stringForKey returns nil when there has not been a value saved already.
You need to give your values a default. The easiest way to do this is with the ?? operator, that replaces nil with another value:
let State = save.stringForKey("StateSave") ?? "-"
Some general advice: you need to stop using ! so much. Usually when something returns nil, it’s for a good reason – it might be nil. When you unwrap it with !, your program will crash (with not much helpful info as to why). Similarly, it’s usually a bad sign if you’re comparing values to nil.
Instead, take a look at this list of optional handling techniques for some alternatives.
Airspeed Velocity has a good solution for the proper way to accomplish what you want to do, but he did not really explain why what you did does not work, so I will address that aspect of this question.
if ExtensionPeriod == nil {
let name = ""
var FieldChoosed: Void = save.setObject(name, forKey: "ExtensionPeriodChoosed")
save.synchronize()
}
That block of code does not set ExtensionPeriod, thus ExtensionPeriod is still nil. All it does is set the value for the key "ExtensionPeriodChoosed" in the NSUserDefaults to no longer be nil. The local variable ExtensionPeriod, however, still has nil. ExtensionPeriod doesn't just magically point to the variable stored in NSUserDefaults, such that when you update NSUserDefaults, it automatically updates the variable. Instead, it copies the variable at the time that it is created.
Here is some sample code that demonstrates this:
NSUserDefaults.standardUserDefaults().setValue("string", forKey: "s")
NSUserDefaults.standardUserDefaults().synchronize()
var s = NSUserDefaults.standardUserDefaults().valueForKey("s")
NSUserDefaults.standardUserDefaults().setValue("string2", forKey: "s")
NSUserDefaults.standardUserDefaults().synchronize()
var y = NSUserDefaults.standardUserDefaults().valueForKey("s")
println(s)
println(y)
outputs:
"string"
"string2"
For your code to work, if you were to keep it the same structure (although you really shouldn't), you would need to change ExtensionPeriod to a var, and then in your if block, after you synchronize save, you would have to reassign ExtensionPeriod to save.valueForKey("ExtensionPeriodChoosed").
One way to make sure that your app's defaults are set is to use NSUserDefault's registerDefaults(_: [NSObject : AnyObject]) function. In Objective-C, I often put in the + (void)initialize class method, but overriding the init() of the application delegate should work just as well.