Optional Values in NSMutableDictionary - ios

I'm new to Swift, very experienced in Objective-C.
In my app, I am receiving data from a server and mapping it to an NSMutableDictionary. The reason I am using an NSMutableDictionary is that the values are not consistent coming from the server, it's a mix of strings and numbers. And that appears to break a Swift Dictionary that expects only one type of value.
Sometimes the server is not sending a value that the NSMutableDictionary is expecting and it crashes the app.
In my research, it appears that I have to check every object to see if the value exists in Swift before setting it into the NSMutableDictionary.
This is my current code:
let userDictionary:NSMutableDictionary = [
"name": data.objectForKey("name") as! String,
... // many more values
This crashes if there is no "name" value in the response from the server.
It appears the solution would be:
if let nameValue = data.objectForKey("name") as! String {
//not sure what to do in here since the var is assigned as I need
}
// ... check many more values
let userDictionary:NSMutableDictionary = [
"name": nameValue,
// ... assign many more values that I checked above
This seems like a lot of extra code to check every single value from the server. Is there a simpler solution?
Thank you for your time.
#Matt below. Here is the code in detail (took out some of the values in the userDictionary for brevity). I'm taking data from Facebook, adding additional info and saving it to Firebase:
//check all of the values
var birthdayValue:String? = "";
if let value:String? = data.objectForKey("birthday") as? String {
birthdayValue = value;
}
let userDictionary:NSMutableDictionary = [
"name": data.objectForKey("name") as! String,
"birthday": birthdayValue!,
"email": data.objectForKey("email") as! String,
"firstName": data.objectForKey("first_name") as! String,
"lastName": data.objectForKey("last_name") as! String,
"description": "",
"distance": 50,
"facebookID": data.objectForKey("id") as! String,
"location":[ 37.12314, -122.49182 ], //TODO: Get location
"points" : 0,
"rating" : 1,
"school" : "",
]
//we need to convert the FB profile pic to a base 64 string and add it here
let imagePath:String = "https://graph.facebook.com/\(data.objectForKey("id") as! String)/picture?width=375&height=667"
self.getDataFromUrl(NSURL(string: imagePath)!) { (imageData, response, error) -> Void in
//convert the data to base 64
let imgString:String = self.convertDataToBase64(imageData);
let images:Array<String> = [imgString];
userDictionary.setValue(images, forKey: "profilePics")
//save the user to Firebase
userRef.childByAppendingPath(data.objectForKey("id") as! String).setValue(userDictionary)
self.currentUserID = (data.objectForKey("id")) as! String
}

Swift actually support multiple types in a dictionary. The following is legal.
let arr: [NSObject: AnyObject] = [1: "hello", "a": 19, 2: "Swift"]
And you can store optional object in the dictionary:
let arr: [NSObject: AnyObject?] = [1: "hello", "a": 19, 2: nil]
And yes, you might need to check the existence of the value if you do care about it. Instead of if, I would use guard to make sure you can use the variable later.
guard let nameValue = data.objectForKey("name") as? String else {
return
}
// Now you can safely use nameValue.

In my app, I am receiving data from a server and mapping it to an NSMutableDictionary
There is no need for this at all. The data is coming to you as a dictionary (at least I presume it is; if it weren't, you could hardly be calling objectForKey, it seems to me). So now just proceed to use it. There is nothing to "map".
For example, suppose that data is the result of calling NSJSONSerialization's JSONObjectWithData:options: on an original NSData d. And suppose what you expect this to serialize to is a dictionary. So after you've said
var data = try! NSJSONSerialization.JSONObjectWithData(
d, options:[]) as! [NSObject:AnyObject]
...you are finished: that's a mutable dictionary and you are off to the races.

If your data is a mix of strings and numbers you could perhaps try to store it as AnyObject in the dictionary, e.g. [String: AnyObject]
Then you could just try to save your data like this:
var userDictionary: [String: AnyObject] = [:]
userDictionary["name"] = data.objectForKey("name")
And when you need to access it:
if let name = userDictionary["name"] as? String {
// Do something with name
}
If you don't want to save it as AnyObject I'd suggest you create a struct instead.

as! is a force downcast, which causes an exception if this downcast isn't possible (which obviously is the case if the value is nil). as?, on the other hand, only downcasts where possible, and results in nil otherwise (which sounds like what you want).
let userDictionary:NSMutableDictionary = [
"name": data.objectForKey("name") as? String,
... // many more values
]
This should work.
Edit: Never mind, this would only work if you'd use a native Swift dictionary. It's still useful to know about the difference between as? and as!, though.

Related

How to convert string to array in Swift 3

I am inserting an Array into my database as a String and after fetching it I want it to convert it again to Array. So that I can fetch my values again and I can do next operation.
Here below is my array inserting into database(TestQuestion) as a String:
let testQuestionModel : TestQuestion = NSEntityDescription.insertNewObject(forEntityName: "TestQuestion", into: AppDelegate.getContext()) as! TestQuestion
testQuestionModel.optionsArray = "\(question["options"] as! NSArray)"
Example: String Array I am getting from Database
(\n \"Rahul Abhyankar\",\n \"Pinkesh Shah\",\n \"Ramanan
Ganesan\",\n \"Dr. Marya Wani\",\n \"\",\n \"\"\n)".
Here is 4 options you can see this is my string after fetching from Database.
1) Rahul Abhyankar.
2) Pinkesh Shah.
3) Ramanan Ganesan.
4) Dr. Marya Wani.
Now how can I convert it into array?
I tried some methods.
let arr = NSArray(object: quetion.optionsArray!).
But I am getting only one object. How can I get my array values same as previous from this string array?
I don't know about the actual type of the "option" in your code, so I set up a fake Elem struct to represent it. The remaining logic is independent of the type as long as you provide a conversion logic to and from String.
struct Elem {
// let's say this is your element type in your array
let foo: Int;
}
extension Elem: CustomStringConvertible {
var description: String {
// provide a logic to convert your element to string
return "\(foo)";
}
}
let arrayToSave = [
Elem(foo: 1),
Elem(foo: 2),
Elem(foo: 3)
]
extension Elem {
init(string: String) {
// provide a function to construct your element type from a string
self.init(foo: Int(string)!)
}
}
let stringToSave = arrayToSave.map { $0.description }.joined(separator: "|")
// save this string
// at some point retrieve it from database, which hopefully same as the saved one
let retrivedString = stringToSave;
let retrivedArray = retrivedString.split(separator: "|").map { Elem(string: String($0)) }
print(retrivedArray) // [1, 2, 3]
Here below is my array inserting into database (TestQuestion) as a
String :
let testQuestionModel : TestQuestion = NSEntityDescription.insertNewObject(forEntityName: "TestQuestion", into: AppDelegate.getContext()) as! TestQuestion
testQuestionModel.optionsArray = "\(question["options"] as! NSArray)"
No, and No.
You are using -description method of an array to save it. Clearly no.
What's wrong? Apple can't affirm that in next OS release, it won't add an extra character. In some more complex cases, it's added <NSArray <0x address> or stuff similar like that.
Suggestion 1:
Modify your entity to have an ARRAY (or usually a Set) of String.
Learn about Core-Data relationship (but that's clearly a DataBase basic knownledge). A relationship one to many should be the thing to do.You could even keep in memory what were the choices, by adding for creating the entity Options, with a String property name (name of the option), another one boolean isChecked, etc.
Suggestion 2:
If you have a limited number of options (like says one to 5), add 5 options string to your entity, and iterate to set them
testQuestionModel.option1 = question["option"][0]
testQuestionModel.option2 = question["option"][1] (if it's of course not out of range for the array)
...
Suggestion 3:
Not really recommended (in my opinion it's missing the whole advantage of the database, especially fetch and predicates, on previous sample you could fetched easily which options were checked), but if you still want to save them as a String, save them as JSON (ie. stringified).
In pseudo code (I'm not sure about the exact syntax, there are no fail safe like try/catch, optional/wrapping):
let options = questions["options"] as [String]
let jsonData = JSONSerialization.data(withJSONObject: (question["options"], options:[])
let jsonString = String.init(data:jsonData encoding:.utf8)
To retrieve them:
let options = JSONSerialization.jsonObject(with data: myJSONString.data(encoding:.utf8), options:[]) as [String]
done using Library SwiftyJSON.
if let dataFromString = yourString?.data(using: String.Encoding.utf8, allowLossyConversion: false) {
do{
let json = try JSON(data: dataFromString)
print(json)
let arrayValue = json.rawValue as! NSArray
print(arrayValue)
} catch{
print(error.localizedDescription)
}
}
Source: https://github.com/SwiftyJSON/SwiftyJSON

Parse Dictionary of Array of Dictionary in Swift 3

I am working on a project where I am getting a dictionary of an array which again containt the dictionary
My Json Response is
{
"code": 200,
"status": "OK",
"success": "true",
"message": "success",
"data": {
"vehicletypeData": [
{
"vehicle_type_id": "1",
"vehicle_type": "Any"
},
{
"vehicle_type_id": "11",
"vehicle_type": "Bike"
}
]
}
}
And I am parsing the data like
if response.success {
let resObj = response.responseObject as! Dictionary<String, Any>
let catArray = resObj["data"] as! Dictionary<String,Array<Dictionary<String,Any>>> // Crashes here
let vehicleData = catArray["vehicletypeData"] as! Array<Dictionary<String, Any>>
for vehicle in vehicleData {
self.jobCategories.append(PreJobVehicleData.mj_object(withKeyValues: vehicle))
}
}
I am trying to parse it in my model
Here I am getting an error like
- Could not cast value of type '__NSArrayM' (0x109b82e00) to 'NSDictionary' (0x109b832d8)
Any help will be Thankful.
Don't say
resObj["data"] as! Dictionary<String,Array<Dictionary<String,Any>>>
That's too specific. Just say
resObj["data"] as! [String:Any]
You know that when you get something by key out of that dictionary, it will be an array, but you can literally cross that bridge when you come to it.
The same rule applies to your other casts. Just cast to Swift dictionary or array using the broadest simplest possible type.
(Note that all this will be solved in Swift 4, where you can build a knowledge of the JSON structure right into your fetch.)
As you are parsing the nodes separately anyway avoid to cast to (more than two levels) nested types.
According to the error message the value for key data seems to be an array (Array<Dictionary<String, Any>>)
if response.success {
let resObj = response.responseObject as! Dictionary<String, Any>
let dataArray = resObj["data"] as! Array<Dictionary<String, Any>>
...
That implies however that the JSON output is wrong and I have no idea what's next...

Unable to retrieve dictionary data from Userdefaults in Swift 3

Do you guys can bring some light on the use of dictionaries with UserDefaults ?
What I want to get is to pass a certain dictionary from a view controller to another one using UserDefaults.
var dict = [String : AnyObject?]()
dict = ["Name": "Henri", "Group": "A", "Category": "4"]
UserDefaults.standard.set.(dict, forKey: "MyDict")
Then I try to retrieve the data
let retrieveDict = UserDefaults.standard.dictionary(forKey:"MyDict")
I got a nice nil answer in the console. When I try with int or float types it is fine... Is there anything I am missing to make this dictionary ?
First of all, your code won't compile.
Second, a dictionary with type [String, AnyObject?] make no sense because you'll be not able to set nil in UserDefaults. You should use empty String maybe for that purpose.
The working code could look like that :
var dict = [String : Any]() // or maybe [String : String] if you sure about value type
dict = ["Name": "Henri", "Group": "A", "Category": "4"]
UserDefaults.standard.set(dict, forKey: "MyDict")
let retrieveDict = UserDefaults.standard.dictionary(forKey:"MyDict")
Hope it helps you :)

iOS 9 JSON Parsing loop

I'm creating an app that should retrieve some JSON from a database.
This is how my JSON looks:
[{"id":"1","longitude":"10","latitude":"10","visibility":"5","timestampAdded":"2015-10-01 15:01:39"},{"id":"2","longitude":"15","latitude":"15","visibility":"5","timestampAdded":"2015-10-01 15:06:25"}]
And this is the code i use:
if let jsonResult = JSON as? Array<Dictionary<String,String>> {
let longitudeValue = jsonResult[0]["longitude"]
let latitudeValue = jsonResult[0]["latitude"]
let visibilityValue = jsonResult[0]["visibility"]
print(longitudeValue!)
print(latitudeValue!)
print(visibilityValue!)
}
As you can see it only gets the first chunk from the JSON and if there are no JSON at all it will crash, but if i want it to count the amount and make an array out of it like this:
var longitudeArray = [10, 15]
var latitudeArray = [10, 15]
And so on...
I also need this to be apple watch compatible so i can't use SwiftyJSON.
What do i do? I really hope you can help me!
Thanks.
SOLVED!
Problems was solved by "Eric D."
This is the code:
do {
if let url = NSURL(string: "YOU URL HERE"),
let data = NSData(contentsOfURL: url),
let jsonResult = try NSJSONSerialization.JSONObjectWithData(data, options: []) as? [[String:AnyObject]] {
print(jsonResult)
let longitudeArray = jsonResult.flatMap { $0["longitude"] as? String }
let latitudeArray = jsonResult.flatMap { $0["latitude"] as? String }
print(longitudeArray)
print(latitudeArray)
}
} catch let error as NSError {
print(error.description)
}
Thank you soo much Eric!! :-)
You could use flatMap to get an array of your elements:
let longitudeArray = jsonResult.flatMap { $0["longitude"] as? String }
let latitudeArray = jsonResult.flatMap { $0["latitude"] as? String }
etc.
flatMap is like map but unwraps optionals, which is adequate because we need to safely cast the type of the object we get from each dictionary in the json array.
$0 represents the object in the current iteration of flatMap of the array it's applied to.
If you're currently using SwiftyJSON, then that would be:
let longitudeArray = jsonResult.flatMap { $1["longitude"].string }
let latitudeArray = jsonResult.flatMap { $1["latitude"].string }
because .string is SwiftyJSON's optional String value getter.
But as you said, you don't want to use it (anymore), so you need to use NSJSONSerialization to decode your JSON data, there's plenty of examples on the Web and on SO. Then you will be able to use my original answer.
You're already getting an array with all of the elements (not just the first one. you're simply only accessing the first one). jsonResult is an array of dictionaries. Each dictionary (in this case, based on the json you provided) contains these elements: id, longitude, latitude, visibility and timestampAdded. In order to access each of them, you can simply loop over jsonResult and access the i'th element (and not always the 0 element). This will also prevent the crash you're experiencing with the json is blank or invalid (since you'll only be going over the valid elements in jsonResult.
This will give you the flexibility to create the custom arrays you wish to create (in order to create an array of all of the longitudes, for example, you will simply add that element to the new array while looping over jsonResult). However, if you'd like to save yourself the trouble of manually building these arrays and assuming you have control over the json structure, I would recommend changing the received json to the relevant structure (a dictionary or arrays instead of an array of dictionaries), so it would better fit your needs and provide you the results in the relevant format right "out of the box".

Swift Subscript Error

I believe it has something to do with optionals, but I'm safely unwrapping sourceURL so I'm still not sure where the error is! I'm trying to access a JSON object's array's dictionary value.
However, I'm still getting the "could not find overload for 'subscript' that accepts the supplied arguments.
It seems simple, but I just can't seem to figure it out!
var dictTemp: NSDictionary! = NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.MutableContainers, error: &localError) as? NSDictionary
var finalURL: String
// error line below
if let sourceURL = dictTemp[0]["source"]["sourceUrl"] as? NSString {
finalURL = sourceURL as String
}
NSDictionary accessed from Swift is an interesting beast.
As long as Swift only knows something is an NSDictionary (not a more specific [Key: Value] Swift-style dictionary), you can only retrieve AnyObject?s out of it.
let dictTemp: NSDictionary = // from somewhere...
let step1 = dictTemp[0] // step1 is an AnyObject?
But then, since you've imported Foundation, you can keep going with a magical subscript operator that works on AnyObject, and checks whether the thing is a dictionary:
let step2 = step1?["source"] // step2 is any AnyObject??
Here's where it gets interesting, because
if step1 was a dictionary with a "source" key inside it, step2 will be the corresponding value.
if step1 was a dictionary without a "source" key, step2 will be nil — in particular, it's AnyObject??.Some(AnyObject?.None).
if step1 was nil (the original dictionary didn't have 0 as a key), or not a dictionary (it had a 0 key with some other kind of value), then step2 will be nil — in particular, AnyObject??.None.
(The distinction between the last 2 cases is mostly unimportant and you shouldn't worry about it, but if you're interested you can see it by using dump).
And of course, we can apply the same principle again:
let step3 = step2??["sourceUrl"] // step3 is AnyObject?? again
Now, binding them all in one if:
if let url = dictTemp[0]?["source"]??["sourceUrl"] as? String {
// do something with url...
}
Caveat
This type of syntax can be dangerous, since it works with arrays and dictionaries at the same time. What would you expect in these situations?
let dict: NSDictionary = [0: ["source": [3: "result"]]]
dict[0]?["source"]??[3] // returns nil (surprise!)
dict[0]?["source"]??[3 as NSNumber] // returns "result"
let dict2: NSDictionary = [0: ["source": [8, 7, 6, 5, 4]]]
dict2[0]?["source"]??[3] // returns 5
dict2[0]?["source"]??[3 as NSNumber] // returns nil (surprise!)

Resources