I have tested with the debugger and both the Dictionary and the value I am interested in are not nil. When I enable the App Sandbox the app crashes, but when I disable it everything works ok. Is there something to do when using UserDefaults with Sandbox enabled?
Code where the crash occurs:
func getPreferenceSettings(_ aKey: String) -> Bool
{
var lastPreferenceSetting: Bool!
if let currentSetting: Dictionary<String, Bool> = UserDefaults.standard.object(forKey: "defaultValues") as? Dictionary<String,Bool>{
lastPreferenceSetting = currentSetting[aKey]! //crash at this line
}else{
//show error to the user
}
return lastPreferenceSetting
}
I am using Xcode 9.4
You have to check if both keys defaultValues and aKey exist. As the return value is non-optional return false as default value.
This code uses the dedicated method dictionary(forKey
func getPreferenceSettings(_ aKey: String) -> Bool
{
if let currentSetting = UserDefaults.standard.dictionary(forKey: "defaultValues"),
let lastPreferenceSetting = currentSetting[aKey] as? Bool {
return lastPreferenceSetting
} else {
//show error to the user
}
return false
}
The key value was nil (i was using the Release schemes instead of the Debug, that is why i was not able to see its value and assumed it was not nil). Also with the help of the debugger i was able to see that the key was never saved in the dictionary. The strange thing is that when disabling the Sandbox the crash doesn't occur. I have modified the method in the following way so it returns an optional and then the caller checks with optional binding if there is a key or not.
func getPreferenceSettings(_ aKey: String) -> Bool?
{
var lastPreferenceSetting: Bool?
if let currentSetting: Dictionary<String, Bool> = UserDefaults.standard.object(forKey: "defaultValues") as? Dictionary<String,Bool>{
lastPreferenceSetting = currentSetting[aKey]
}else{
//show error to the user
}
return lastPreferenceSetting
}
EDIT
#vadian
I was reading when to use correctly the ? type and it's commonly used to indicate the absence of a value for example the mid name of a Person. But in this case the preference property is always present in the project because it's hard coded by the developer (except, like happened to me when i forgot to add it to the UserDefaults and had first to clear it before update the dictionary with the new key ). If i understand correctly, from what you have write, i should return an Bool? value and then check with optional binding in the caller method if it's nil or not; this should be done not only for the mid name case for the user side but also for the developer side because there could be another developer that could pass a wrong aKey, right?
Also is it correct to declare the lastPreferenceSetting as ?, because if i use a default value and the if let evaluate to false, the function could return not the expected value.
Related
func responseDataHandler(data: NSDictionary) {
let temperature_c = data.value(forKeyPath: "data.current_condition.temp_C")
DispatchQueue.main.async{
self.Temperature.text = temperature_c as? String
}
}
I have the above code where I am accessing a weather API which returns data in the form of an NSDictionary to this function. I need to access the value in temperature_c which when I try to print it, it says that it is: Optional(<__NSSingleObjectArrayI 0x600002147fd0>(
25
)
). Temperature is the outlet for label on my storyboard which I want to take on the value of 25 however as written now, it doesn't work and I have tried everything to try and access the value in the Single Object Array but nothing is working. I found this stack overflow question that was similar but it doesn't work for my situation because I keep getting the error that temperature_c is of type any and doesn't have subscripts.
The issue is that you can't cast to String an array, you should try to convert it to [String]. So could change your code to:
self.Temperature.text = (temperature_c as? [String])?.first ?? "Not available"
Let's go step by step:
temperature_c as? [String] tries to convert the NSDictionary to a String array which is the expectable type.
Since the previous step may return nil we have to use optional chaining ?. If we got a valid array using first return the the arrays first element.
Since both previous steps can return nil we can use nil coalescing operator to return a default value. In this case I use "Not available" but you can set any value.
You could write it in a more verbose way like this:
var text2Display2 = "Not available"
if let theArray = temperature_c as? [String] {
if let element = theArray.first {
text2Display2 = element
}
}
self.Temperature.text = text2Display2
I have:
let value: Any? = myObject[keyPath: myParamKeyPath]
where myParamKeyPath refers to a String?.
Then, when myParam is supposed to be nil, I have:
value == nil returns false
(value as? String?) == nil returns true
Is it possible to check if value equals nil without having to cast it to a String? in the first place? Something like comparing it to NSNull maybe?
Also, I can't change the value type to String? directly as it is also used for other type in my code.
EDIT:
(value as? String?) == nil returns true is irrelevant indeed.
But I can still go print my value pointed by the keypath and it will actually be nil. So I still don't get why value == nil returns false when Im expecting it to be true...
EDIT2 with more code:
let setting = appSettings.settings[indexPath.row]
let value = appSettings[keyPath: setting.keyPath]
let fontAwesome: FontAwesome
switch setting.keyPath {
case \PrinterSettings.printableImageIDs:
fontAwesome = blablabla
case \WeatherSettings.lockscreenImageIDs:
fontAwesome = blablabla
default:
if let value = value as? FakeButtonPlacementSubSettings {
fontAwesome = blablabla
} else {
fontAwesome = value != nil ? .checkSquareO : .squareO
}
}
I am expecting to get fontAwesomeIcon = .squareO but I am getting checkSquareO when the value pointed by myObject[keyPath: myParamKeyPath] is String? (it does the same for another value which is a Bool? later on).
I must be missing something somewhere...
EDIT 3 screenshot:
EDIT 4 more clarification of what I'm trying to do here:
First, thank you again for your help if you get there.
Here are 2 photos about my project current design:
I am using with KVO in my project. I was previously using the objective-c string #keyPath for this. It was working great, but almost all my model had to be converted in #objc. So my current goal here is to remove it and switch to the new Swift 4 keypath system.
To resume: I have a user class containing lot of settings (more than on the screenshot) which can be of several type (and some custom types also).
On the other hand, I have created around 10 "setting editor view controllers": one by type of settings. I would like to use the same setting editor VC to edit each one of the same type of settings.
For example, if the setting to edit is a boolean, I would like to use the "boolean editor VC" and get my selected setting edited by the user's new choice. This is why I am using KVO system. I'm not willing to keep it if you guys have a better solution for this. It would then also get rid of my Any? == nil issue.
How about the following?
struct S {
let x: String?
}
let v1: Any? = S(x: "abc")[keyPath: \S.x]
print(v1 is String? && v1 == nil) // false
let v2: Any? = S(x: nil)[keyPath: \S.x]
print(v2 is String? && v2 == nil) // true
The issue is that your code is assuming the value is String?, whereas it is really String??. I believe this stems from the use of AnyKeyPath:
struct Foo {
let bar: String?
}
let foo = Foo(bar: "baz")
let keyPath: AnyKeyPath = \Foo.bar
print(foo[keyPath: keyPath]) // Optional(Optional("baz"))
let keyPath2 = \Foo.bar
print(foo[keyPath: keyPath2]) // Optional("baz")
I wonder if there is some way to adapt your code to use KeyPath<T, String?> pattern, so that, as you say, your myParamKeyPath really does explicitly refer to a String?, .e.g.:
let foo = Foo(bar: "baz")
let keyPath = \Foo.bar
func keyPathExperiment(object: Any, keyPath: AnyKeyPath) {
print(object[keyPath: keyPath])
}
func keyPathExperiment2<T>(object: T, keyPath: KeyPath<T, String?>) {
print(object[keyPath: keyPath])
}
keyPathExperiment(object: foo, keyPath: keyPath) // Sigh: Optional(Optional("baz"))
keyPathExperiment2(object: foo, keyPath: keyPath) // Good: Optional("baz")
There's not enough in your code snippet for us to see how precisely this might be adapted to your situation.
As Rob Napier said, I wonder if there might be Swiftier approaches to the problem. E.g. when I see switch statements like this, I might tend to gravitate towards enum pattern.
I'm new to swift and I don't really understand how to use optional value correctly.
The situation is: Firstly, I have a model class to store some values of its properties and send the request to server to get values. code:
import Foundation
class User: NSObject {
var name: String?
func getInfo(updateUI: () -> ()) {
let manager = AFHTTPSessionManager()
manager.POST(URLString, parameters: nil, success: { (task: NSURLSessionDataTask, responseObject: AnyObject?) in
let dic = responseObject as? NSDictionary
self.name = dic?.objectForKey("name")
updateUI()
}, failure: { (task: NSURLSessionDataTask?, error: NSError) in
log.obj(error)
})
}
Secondly, I want to use this model in a ViewController to get values from server and update UI. code:
class UserViewController: UIViewController {
#IBOutlet var nameLabel: UILabel!
var user = User()
override func viewDidLoad() {
super.viewDidLoad()
user.getInfo({
nameLabel.text = user.name!
})
}
}
I think it's a dangerous way to handle this work because there is nothing can ensure that I have a certain name when I want to put it on the label. It works pretty well in most cases but it can crash when fail to get a right value from server(for example, there is no value for key "name").
I have two ideas about questions above:
Give a default value for the properties like: var name = ""
Test the value before unwarp it like:
if let foo = user.name {
nameLabel.text = foo
}
I want to choose the first way because:
I think showing a default string on the label is much more acceptable than crash because fail to unwarp a optional value.
When I have a lot more values to use, the code will be very long and diffcult to read.
Did I choose a right one? Or both of them didn't understand the usage of optional value and there is a better way? Someone can help me?
You can safely unwrap an optional or give it a default value without declaring a new variable like so:
nameLabel.text = user.name ?? ""
This is called the nil coalescing operator and will try to unwrap an optional, and if the unwrap is unsuccessful will give it the default value that you have placed on the rhs of the operator.
Your instincts are correct that force unwrapping optional is ill-advised. You are also correct that providing a non-nil default value defeats the purpose of an optional.
if let foo = user.name binding is a sound way of doing this, but if all you're doing is assigning a value, you can spare yourself the curly braces by using the nil coalescing operator, as pbush25 states:
nameLabel.text = user.name ?? "Default Label Text"
This means "unwrap user.name and if its value is not nil, use it. Otherwise, use the following value:" You can even chain them together if, for example, you wanted to use user's ID as the fallback label text, but the ID might also not exist:
nameLabel.text = user.name ?? user.id ?? "No name or ID".
Even if you want to crash in the case that the optional value is nil, it would be better to assert or use a preconditionFailure() than to simply force unwrap the optional so that you can at least provide an error message.
I'd like the user to input a String to access a key in the dictionary which then returns its coupled value. I'm sure this isn't the only thing wrong with my code but trying to see if I have the right idea and hoping for some additional direction:
Main two issues are writing the correct syntax to connect the user input getInput() (String) inside the function callfunc planetAndTime()The function is passing my dictionary as a parameter.
My other issue is returning my function's double value. ->Double`
func getInput() -> String{
let kb: NSFileHandle = NSFileHandle.fileHandleWithStandardInput()
let inputData: NSData = kb.availableData
let strData = NSString(data: inputData, encoding:NSUTF8StringEncoding) as! String
return strData
}
/////////
let nameOfPlanetsAndTime:[String:Double] = ["Mercury":0.00000816,
"Venus":0.00044, "Mars":0.0000239, "Jupiter":0.00062, "Saturn":0.000204,
"Uranus":0.000289, "Neptune":0.000459, "Pluto":0.000794,
"Center of the Milky Way":25000.000000, "Earth's Cousin":1400.000000]
print("Choose planet")
func planetAndTime(nameOfPlanetsAndTime: [String:Double], userLocation:String) ->Double{
let userLocation = getInput()
if (nameOfPlanetsAndTime[userLocation] < 0){
print("Not a Valid Location, Please Try Again")}
else{
nameOfPlanetsAndTime[userLocation]!
print(nameOfPlanetsAndTime)
}
}
There are a few issues with your code that are due to a misunderstanding of how the Swift language works so I would recommend going through the official Swift documentation for some good examples of the Swift features. To give you a little head start however I will choose a section of your code. For example, instead of writing"
if (nameOfPlanetsAndTime[userLocation] < 0){
print("Not a Valid Location, Please Try Again")}
you can write
guard let planet = nameofPlanetsAndTime[userLocation] {
print("Not a Valid Location, Please Try Again")
return nil
}
return planet
and change the return type of your function to an optional Double. So the method signature would look like
func planetAndTime(nameOfPlanetsAndTime: [String:Double], userLocation:String) ->Double?
Understanding optionals is super important in the Swift "world" and I highly recommend taking a good look at how to use them. I hope this little example is helpful.
https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/TheBasics.html#//apple_ref/doc/uid/TP40014097-CH5-ID309
So far, I have a function that tries to see if someone already has a code, and if they do not already have one, then it would generate one for them.
func checkID() -> Int{
if (NSUserDefaults.standardUserDefaults().integerForKey("Code") != nil) {
}
else{
var code = Int(arc4random_uniform(1000000000))
NSUserDefaults.standardUserDefaults().setInteger(code, forKey: "Code")
}
return NSUserDefaults.standardUserDefaults().integerForKey("Code")
}
I get an error message when I try to to say NSUserDefaults.standardUserDefaults().integerForKey("Code") != nil
The error message I get is "Type 'Int' does not conform to protocol 'NilLiteralConvertible'"
What can I do to try to get around this? What am I doing wrong?
The integerForKey always returns a value. If nothing's there, just 0.
So you should check like that:
if let currentValue = NSUserDefaults.standardUserDefaults().objectForKey("Code"){
//Exists
}else{
//Doesn't exist
}
The short answer is "you can't." There's no way to tell if a zero result for integerForKey represents a stored zero value, or no value.
Christian's answer of using objectForKey (which returns an optional) and optional binding is the correct answer.
Edit:
It would be quite easy to add an extension to UserDefaults with a function that returned an Optional Int:
extension UserDefaults {
func int(forKey key: String) -> Int? {
return object(forKey: key) as? Int
}
}
(I suspect that if Apple were designing the Foundations framework today, using Swift, integer(forKey:) would return an Optional(Int).)
You should be able to remove the '!= nil'. For instance,
if (NSUserDefaults.standardUserDefaults().integerForKey("Code")) {
}
else{
var code = Int(arc4random_uniform(1000000000))
NSUserDefaults.standardUserDefaults().setInteger(code, forKey: "Code")
}
return NSUserDefaults.standardUserDefaults().integerForKey("Code")
Simply retrieving the value of the key should return false if the integer is not found.