How to access a variable using its name as a string - ios

I was thinking of using a plist file to configure how I would upload a form to my server but then I realised I don't know how I could do one crucial part of this or even if this is possible since you can't dynamically declare variables with swift.
Basically I was thinking of using a dictionary like this
form:
property_name: backend_name
and then in my form upload method I would do something that would look like this
formValues: [String:String] = [:]
form.forEach { (property_name, backend_name) in
let value = someController[property_name] // to do the equivalent of someController.property_name
formValues[backend_name] = value
}
// ...
formValues.forEach { (backend_name, value) in
multipartFormData.append(value.data(using: String.Encoding.utf8)!, withName: backend_name)
}
the problem is I don't know how to this part
let value = someController[property_name]

The problem you're trying to solve is serializing your data for a server. I don't know whether the approach you're trying is possible in Swift, but it's definitely not the right one. If you're using Swift 4, you could create Codable model objects, so this serialization will be all under the hood. Otherwise, you'll most likely need to create this [String: Any] manually.
Using Codable to serialize/deserialize objects

I found how I could do it.
formValues: [String:String] = [:]
form.forEach { (property_name, backend_name) in
let value = Mirror(reflecting: someController).children.filter({ (child) -> Bool in
return child.label == property_name
}).first!.value as! String
formValues[backend_name] = value
}
// ...
formValues.forEach { (backend_name, value) in
multipartFormData.append(value.data(using: String.Encoding.utf8)!, withName: backend_name)
}
I know this is unoptimised since I recreate a mirror each cycle but it's for demonstration purposes

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

How to fetch the key from JSON in Swift?

I am working on JSON parsing in Swift.
var results = [String:[AnyObject]]()
The above results is having the data as shown below,
"fruit" = (
"apple",
"orange"
);
Here, data is appended dynamically during runtime. All I need is to get the keys and display them in table view as header.
How to get thekey from results in swift?
NSJSONSerialization code example...
var results = [String:[AnyObject]]()
let jsonResult = try NSJSONSerialization.JSONObjectWithData(results, options:NSJSONReadingOptions.MutableContainers);
for (key, value) in jsonResult {
print("key \(key) value2 \(value)")
}
You can convert JSON to dictionary as mentioned in the above link proposed by Birendra. Then suppose jsonDict is your json parsed dictionary. Then you can get collection of all keys using jsonDict.keys.
You need to use NSJSONSerialization class to convert in json format (eg. to convert in dictionary) and then get all keys from it.
I have used,
var results = [String:Array<DataModel>]
where,
class DataModel {
var name: String?
}
and to fetch the keys and value,
for i in 0...(results.length-1){
// To print the results keys
print(results.keys[results.startIndex.advancedBy(i)])
// To get value from that key
let valueFromKeyCount = (results[results.keys[results.startIndex.advancedBy(i)]] as Array<DataModel>).count
for j in 0...(valueFromKeyCount-1) {
let dm = results[results.keys[results.startIndex.advancedBy(i)]][j] as DataModel
print(dm.name)
}
}
Tested with Swift 4.2 to get first key or list of keys:
This "one-liner" will return the first key, or only key if there is only one.
let firstKey: String = (
try! JSONSerialization.jsonObject(
with: data,
options: .mutableContainers
) as! [String: Any]).first!.key
This one will get a list of all the keys as and array of Strings.
let keys: [String] = [String] ((
try! JSONSerialization.jsonObject(
with: data,
options: .mutableContainers
) as! [String: Any]).keys)
Both of the above examples work with a JSON object as follows
let data = """
{
"fruit" : ["apple","orange"],
"furnature" : ["bench","chair"]
}
""".data(using: .utf8)!

How to cast Dictionary in Swift to related type?

This is what I am trying to do with the dictionary:
if let deliveries = dictionary["deliveries"] as? NSDictionary {
var castedDeliveries = [Double: Double]()
for delivery in deliveries {
if let value = delivery.value as? Double {
castedDeliveries[Double(delivery.key as! NSNumber)] = value //Could not cast value of type 'NSTaggedPointerString' (0x1a1e3af20) to 'NSNumber' (0x1a1e458b0).
}
}
settings!.deliveries = castedDeliveries
}
And this is what I try to cast, as a part of JSON response from server:
deliveries = {
2 = 0;
5 = "2.59";
7 = "3.59";
};
It doesnt work, because there is an error at commented line:
Could not cast value of type 'NSTaggedPointerString' (0x1a1e3af20) to 'NSNumber' (0x1a1e458b0).
You are trying to cast dictionary directly but instead you need to cast each key - value pair. If you want generic solution to this problem take a look at SwiftyJSON library which address JSON parsing problem for you.
Casting doens't mean data transformation from a type to another.
Your dictionary seems to be composed by Integer keys and String values.
If you want to transform in something else you ca use the map function.
let converted = deliveries.map{[Double($0) : Double($1)]}
But pay attention.
Here we are saying, iterate over the dictionary (in the $0 there is the dictionary key in the $1 there is the value) and create a new dictionary that has as a key a Double initialized at the key value and as a new value a Double initialized as the old dictionary value. The last conversion can fail, so the returned data is an optional.
As I noted in the comments, this isn't casting. You want a data conversion. You need to do that explicitly, especially in this case since it might fail.
Looking at the error, I think you really have a dictionary of [String:String] here (in NSDictionary form). That suggests the JSON is badly encoded, but such is life. Assuming that dictionary looks something like this:
let dictionary: NSDictionary = ["deliveries": ["2":"0", "5": "2.59", "7": "3.59"]]
You would convert it to [Double:Double] like this:
if let jsonDeliveries = dictionary["deliveries"] as? [String:String] {
var deliveries: [Double: Double] = [:]
for (key, value) in jsonDeliveries {
if let keyDouble = Double(key),
valueDouble = Double(value) {
deliveries[keyDouble] = valueDouble
}
}
// Use deliveries
}
This silently ignores any values that can't be converted to Double. If you would rather generate errors, use a guard let rather than an if let.

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".

Reading keys in plist and get the values

I have the following plist file in my Swift iOS project. I want to read the keys without specifically mention and get the value for all the keys. I want to write this in Swift code. I tried the below code and getting all the keys and values. But, I want to check for each key(not mentioned specifically like "1234" etc.) and get the value for it. Please help how to achieve this?
var myDict: NSDictionary?
if let path = NSBundle.mainBundle().pathForResource("UserDetails", ofType: "plist") {
myDict = NSDictionary(contentsOfFile: path)
NSLog("myDict: %#", myDict!)
}
Just use this code
for (key, val) in myDict! {
println("\(key):\(val)")
}
to print the single keys/values.
You can use a tuple to loop in a dictionary:
for (key, value) in myDict! {
println("Key is \(key) and value is \(value)")
}

Resources