I have some difficult to convert my Swift 2.2 app to Swift 3.0.
I have some errors and I don't find the solution yet. Currently, my worst problem is with NSFastEnumerationIteration, I try to get records from JSON but with this error I can't. This is the screenshot of my code with the problem :
In Swift 3 you need to specify the type of object,so specify the type of your data Array to [[String:Any]].
if let dataArr = data as? [[String: Any]] {
for dd in dataArr {
//your code for accessing dd.
}
}
For in loop only knows that your variable data is an array and doesn't know anything else, so you need to provide as well the type of the content of your variable data:
let dataToParse = dataweneed.data(using: String.Encoding.utf8.rawValue)!
let jsonOptions = [JSONSerialization.ReadingOptions.mutableContainers]
let data = try JSONSerialization.jsonObject(with: dataToParse, options: jsonOptions)
// now For in loop would know that you
// could have an array of dictionaries
if let data = data as? [[String: Any]] {
for dd in data {
// your code
}
}
Related
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
Hi i upgrade my xcode from 7 to 8 now i am getting error Ambiguous use of 'subscript', when m trying to iterate object.
You need a better idea of what you're passing into the function in order to access indices and values for keys. You can't subscript -index- an AnyObject unfortunately. Xcode needs more information first. Try something like this:
func makeDataSource(data:[[String:String]]) {
for i in 0..< data.count {
print(data[i]["make"]
}
}
If you don't know if you can pass your data as [[String:String]] you can safely unwrap it first by doing something like this before you make your function call:
guard let myData = data as? [[String:String]] else {
print("couldn't cast data as [[String:String]]")
return
}
makeDataSource(data:myData)
If the above guard statement fails then I would check the object types located in your data array/dictionary. You can cast it as multiple things. For example:
as? [[String:String]]
as? [[Int:String]]
as? [[String:AnyObject]]
etc.......
You just need to match what's in there and your code should run smoothly.
I am working on JSON in iOS swift,
I will query data from server as,
results = [{"type":"fruit", "name":"orange"},
{"type":"flower", "name":"rose"},
{"type":"fruit", "name":"apple"}]
I need to append above data to TableView based on the type, so that it will be displayed as shown below,
fruit
orange
apple
flower
rose
In order to display in TableView, the results should reform as,
updatedResult[String:[AnyObject]]() = {
"fruit":[{"name":"orange"},{"name":"apple"}],
"flower":[{"name":"rose"}]
}
How is it possible to reform the results to updatedResult using swift?
//Your example json
results = [{"type":"fruit", "name":"orange"},{"type":"flower","name":"rose"},{"type":"fruit", "name":"apple"}]
First you need to turn your json into an array of dictionaries to get the above object you think it will be:
if let results = try NSJSONSerialization.JSONObjectWithData(data,options: NSJSONReadingOptions.MutableContainers) as? [[String : AnyObject]]
{ //parse into form you want.}
// Your example desired result of dictionary with key:array of dictionaries
updatedResult[String:[AnyObject]]() = {"fruit":[{"name":"orange"},{"name":"apple"}],"flower":[{"name":"rose"}]}
Then you want to loop through the json result and grab out the values into the above format you want:
// Swift representation of your provided example
let results = [["type":"fruit", "name":"orange"],["type":"flower","name":"rose"],["type":"fruit", "name":"apple"]]
// Desired dictionary of key:array of dictionaries
var updatedResult = [String:[[String:String]]]()
for item in results {
if let name = item["name"], type = item["type"] {
if updatedResult.keys.contains(type) {
updatedResult[type]!.append(["name":name])
} else {
updatedResult[type] = [["name":name]]
}
}
}
I want to get result from json and save this result in NSUserDefault, after I want to use the json array saved in NSUserDefault to add multiple annotation on the MapKit.
Currently to get json result, I use this : ( Swift 2.x )
let defaults = NSUserDefaults.standardUserDefaults()
//Get user content//
let url = NSURL(string: "http://www.example.com/folder/coordonate.php")
let request = NSMutableURLRequest(URL: url!)
// modify the request as necessary, if necessary
let task = NSURLSession.sharedSession().dataTaskWithRequest(request) {
data, response, error in
if data == nil {
print("request failed \(error)")
return
}
do {
if let json = try NSJSONSerialization.JSONObjectWithData(data!, options: []) as? NSDictionary {
print(json)
// print : {tab = "[london,paris]";}
var test = json["tab"]
defaults.setObject(test, forKey: "annotation") as? NSArray
}
} catch {
print(error)
}
}
task.resume()
NSUserDefaults.standardUserDefaults().synchronize()
And to get the NSUserdefault in another view, I try this code :
let defaults = NSUserDefaults.standardUserDefaults()
let test = NSUserDefaults.standardUserDefaults().objectForKey("annotation") as! NSArray
map.addAnnotations(test as! [MKAnnotation])
print("We saved this data: \(test)")
//No print, error before
But I have an error when I try with this method.
Could not cast value of type '__NSCFString' (0x1971a3958) to 'NSArray' (0x1971a4308).
If the "tab" property JSON actually being returned is:
[london, paris]
Then the property is not an array. What you're looking for would be:
['london', 'paris']
But additionally I can tell you that even if the "tab" property is a properly formatted JSON array your code will fail when it attempts to convert it to [MKAnnotation] anyway. That's because iOS's JSON library does not know how to convert a generic NSArray into it's typed equivalent. The NSJSONSerialization documentation indicates all the types that JSON will convert to. Best case scenario the "tab" property is an array of items with the same structure as MKAnnotation and are being converted to an array of dictionaries that you will have to convert to MKAnnotation yourself. But the JSON provided currently evaluates as a string. With my suggested change it will instead evaluate to an array of strings- still not sufficient to create an MKAnnotation from.
Your JSON data has to be one of the valid NSUserDefault types (String, NSArray, NSDictionary, NSData).
The quickest fix would be to store the JSON in NSUserDefaults as the NSData that comes back from the server. Then deserialize the NSData on the reading of NSUserDefaults.
If storing a subset of the JSON from the server is really needed, I would use Dictionaries, Arrays and validate the data before storing it. As a general Swift rule, I avoid using NSDictionary and NSArray to ensure the types are what I expect and won't cause a runtime crash.
change your php code to
$results = Array("tab" => ["london","paris"]);
instead of
$results = Array("tab" => "[london,paris]");
p.s
if using php earlier than 5.5 (or 5.4 not quite remember) then use:
$results = Array("tab" => Array("london","paris"));
==========================
you are casting the setObject func to NSArray and not the test object
defaults.setObject(test, forKey: "annotation") as? NSArray
should be
if let arrayTest = test as? NSArray{
defaults.setObject(arrayTest, forKey: "annotation")
}
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".