swift for loop not looping enough times - ios

I have a tableView in my app, and when i load the app I want the view to be populated with a list of dogs (retrieved from a server).
I have this working, but it will only load the first dog in the list from the server.
here's the code starting from where it serialises the JSON response from the server
do {
let json = try NSJSONSerialization.JSONObjectWithData(data!, options: .AllowFragments) as! [AnyObject]
dispatch_async(dispatch_get_main_queue(), {
self.tableView.beginUpdates()
if let theDogs = json[0] as? [[String: AnyObject]] {
for dog in theDogs {
print("Dog")
if let ID = dog["ID"] as? String {
print(ID + " Safe")
let thisDog = Dog(name: (dog["Name"] as? String)!, surname: (dog["Surname"] as? String)!, id: (dog["ID"] as? String)!, boarding: true)
let newIndexPath = NSIndexPath(forRow: self.dogs.count, inSection: 0)
// code here
self.dogs.append(thisDog)
self.tableView.insertRowsAtIndexPaths([newIndexPath], withRowAnimation: .Bottom)
}
}
}
self.tableView.endUpdates()
})
} catch {
print("error serializing JSON: \(error)")
}
here's a copy of the logs (which includes a printed response from the server)
Optional([[{"ID":"47","Name":"Sparky","Surname":"McAllister"}],
[{"ID":"31","Name":"Maddie","Surname":"Crawford"}]])
Dog
47 Safe
as you can see from the log, there are 2 dogs on this list.
I would expect to see Dog printed twice in the log if the loop was working and 31 Safe if it was getting to the part of the code that creates a new Dog object.
I can't work out what i've done wrong, can anyone see my problem?
thanks

Because the JSON is an Array of Arrays containing one dictionary each when you call if let theDogs = json[0] you get this part of the JSON: [{"ID":"47","Name":"Sparky","Surname":"McAllister"}]
You would need to call if let theDogs = json[1] to get this part of the JSON:
[{"ID":"31","Name":"Maddie","Surname":"Crawford"}]

Ok, thanks to Travis' answer I was able to see where i'm going wrong. I just made a little tweak to his suggestion so i'm posting as an answer.
as Travis said, I need too access json[1] but i could have 7 different dogs on that list!
so i made the following changes:
if let theDogs = json[0] as? [[String: AnyObject]] {
is now:
if let theDogs = json as? [[AnyObject]] {
which means in the for loop i'm accessing the root array.
I then changed the for loop from:
for dog in theDogs{
to:
for aDog in theDogs{
let dog = aDog[0]
which means for every array in theDogs, i'll get the only object in the array and call it dog.
problem solved, and future proofed.
thanks to everyone that helped!

Related

How does many to many relationships work.

Okay I have read apple Core Data Guide. I also look on the web but only found posts about 2 years ago. So I don't know how many to many relationship work. I'm building an app using core data. The app has 3 entities Pokemon, Type, Ability. A Pokemon can have 1 or more type so I set the relationship to to-many. A type can have multiple Pokemon associated with it, so I also should set the relationship to to-many but I don't understand many-to-many relationship. Same goes for the abilities. This is how my data model looks like. There are another 2 entities but I don't really care about those right now.
This is the function I'm using to parse the following API. It grabs the first 20 Pokemon save the name and uses the url to fetch more information about that Pokemon. So I added another task that adds the type and ability to that Pokemon.
private func loadPokemon(url: String) {
let context = coreData.persistentContainer.viewContext
let request = URLRequest(url: URL(string: url)!)
let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
if error != nil {
print(error!)
}
do {
let jsonResults = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as! NSDictionary
let pokemonArray = jsonResults.value(forKey: "results") as! [[String: Any]]
for pokemonData in pokemonArray {
guard let name = pokemonData["name"] as? String else {
return
}
guard let pokemonInfoURL = pokemonData["url"] as? String else {
return
}
let pokemon = Pokemon(context: context)
pokemon.name = name
print(1)
self.pokemonMoreInfo(for: pokemon, url: pokemonInfoURL, context: context)
}
}
catch let err {
print(err.localizedDescription)
}
}
task.resume()
}
private func pokemonMoreInfo(for pokemon: Pokemon, url: String, context: NSManagedObjectContext) {
let request = URLRequest(url: URL(string: url)!)
let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
if error != nil {
print(error!)
}
do {
let jsonResults = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as! NSDictionary
//MARK: Pokemon Abilities
if let abilityArray = jsonResults.value(forKey: "abilities") as? [[String: Any]] {
let abilities = pokemon.ability?.mutableCopy() as! NSMutableSet
for abilityData in abilityArray {
guard let abilityDic = abilityData["ability"] as? NSDictionary else {
return
}
let name = abilityDic.value(forKey: "name") as! String
guard let isHidden = abilityData["is_hidden"] as? Bool else {
return
}
guard let slot = abilityData["slot"] as? Int16 else {
return
}
let ability = Ability(context: context)
ability.name = name
ability.isHidden = isHidden
ability.slot = slot
abilities.add(ability)
pokemon.addToAbility(abilities)
}
}
//MARK: Pokemon Type
if let typeArray = jsonResults.value(forKey: "types") as? [[String: Any]] {
let types = pokemon.type?.mutableCopy() as! NSMutableSet
for typeData in typeArray {
guard let typeDic = typeData["type"] as? NSDictionary else {
return
}
let name = typeDic.value(forKey: "name") as! String
guard let slot = typeData["slot"] as? Int16 else {
return
}
let type = Type(context: context)
type.name = name
type.slot = slot
types.add(type)
pokemon.addToType(types)
}
}
}
catch let err {
print(err.localizedDescription)
}
self.coreData.saveContext()
}
task.resume()
}
I'm using this app called SQLight Read-Only. The ability and type are matching to the correct Pokemon. These are screenshots on how my SQLight looks like.
I'm not sure if you guys know about Pokemon, but charizard type is fire and flying and have the abilities of solar-power and blaze. So I know that I'm saving the data correctly. However my SQLight have the same type repeating like fire, grass, poison same goes for the abilities but with the correct Pokemon associated with them. This is how my complete SQLight looks like.
Not sure if it will keep repeating with a many-to-many relationship. So my question is how would I use a many-to-many relationship with Pokemon to type and ability. So how would I add a Pokemon with the same type or same abilities. So later, I can perform a fetch that grabs all Pokemon that have a type of fire or same ability. I'm not sure if I explained my question correctly might be a little confusing.
Would appreciate any help. :)
EDIT:
Actually what I wrote below isn't correct for Core Data. (Thanks for pointing that out in the comments Paulw11.) From the Core Data Guide:
Many-to-Many Relationships
You define a many-to-many relationship using two to-many relationships. The first to-many relationship goes from the first entity (the source entity) to the second entity (the destination). The second to-many relationship goes from the second entity (the original destination entity) to the first entity (the original source entity). You then set each to be the inverse of the other. (If you have a background in database management and this causes you concern, don't worry: if you use an SQLite store, Core Data automatically creates the intermediate join table for you.)
Old answer (incorrect for Core Data):
To create a many-to-many relationship in a relational database you have to add a helper table. In your case you could call it PokemonWithType. It has two columns, one for the Pokemon id and another for the Type id. If you want to find all Types for one specific Pokemon you just query all entries in PokemonWithType with the specific Pokemon id. If you want to find all Pokemon that have a specific Type you do the same just with the Type id.

How to parse json data that provides another url with more data

I'm a little confused how would I parse a json API that gives me 20 objects but then gives me a key of "next" having a url that gives me another 20 objects. I'm using this Pokemon API. It gives me 4 keys: count, previous, results and next. I'm trying to display them all in a collection view but not all at the same time. I would like to load more when the collection view is scrolling down.
I'm just trying to get the name at the moment. This is how my code looks like.
I get it to load the first 20 Pokemon in the collection view. However I don't know how to load the next 20 Pokemon or the 20 after. This is how the json file looks like if the link didn't work.
I would appreciate any help given. :)
You can try using a recursive function reusing the loadPokemonsData function something like this:
func loadPokemonsData(url: String, quantity: Int?) {
let request = URLRequest(url: URL(string: url)!)
let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
if error != nil {
print(error!)
}
do {
let jsonResults = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as! NSDictionary
let pokemonArray = jsonResults.value(forKey: "results") as! [[String: Any]]
var isPokemonsEqualsToQuantity: Bool = false
for pokemonData in pokemonArray {
if let quantity = quantity {
guard self.pokemons.count < quantity else {
isPokemonsEqualsToQuantity = true
break
}
}
guard let name = pokemonData["name"] as? String else {
return
}
self.pokemon = Pokemon(name: name)
self.pokemons.append(self.pokemon)
}
guard let nextURL = jsonResults.value(forKey: "next") as? String, !isPokemonsEqualsToQuantity else {
for pokemon in self.pokemons {
print(pokemon.name)
}
print(self.pokemons.count)
return
}
self.loadPokemonsData(url: nextURL, quantity: quantity)
} catch let err as NSError {
print(err.localizedDescription)
}
}
task.resume()
}
Attach a screen of algorithm function running... it prints 791 pokemons.
Hope it helps you!
EDITED
Next time you ask put your code please... it will be easier help you!.
I've updated the code to set the quantity you want (nil if you want to get all pokemons), Therefore it will only get the pokemons in the order API returns it, if you want a specific pokemons from ALL pokemons you may do a sort after obtaining all pokemons.

JSONSerialization AnyObject SWIFT3 Conversion Issues

I have converted to Swift 3 and I have received the following errors when assigning to AnyObject the JSONSerialization.jsonObject. Has anyone come across this issue and know the fix?
Since the last Swift 3 update most of the return types changed from AnyObject to Any and downcast is not allowed, so in such situation you are forced to use explicit cast. That means you should make a couple of guard statements or use optional chaining if let defining each necessary field. Consider using map, filter, reduce if possible to make your code more elegant. Example:
guard way:
guard let object = try JSONSerialization.jsonObject(with: data) as? [[String: Any]] else { return nil }
guard let field1 = object[0]["field1_token"] as? [Any] else { return nil }
//do your thing
if let way:
if let object = try JSONSerialization.jsonObject(with: data) as? [[String: Any]],
let field1 = object[0]["field1_token"] as? [Any] {
//do your thing
}
You may want to check Apple's article Working with JSON in Swift
Also you can use some of the json parsing/mapping libriaries like these:
SwiftyJSON
Gloss
Please replace let object : AnyObject with let object : Any.
Error showing because of wrong casting.

How to access data in nested JSON in swift

I have a json file
I need to get the latest "id": "article" "createdAt": "2016-04-22T03:38:39.130Z" date. How do I go about getting this data from the request in swift?
Note: Sorry im a swift newb.
let url = "https://cdn.contentful.com/spaces/maz0qqmvcx21/entries?access_token=ae8163cb8390af28cd3d7e28aba405bac8284f9fe4375a605782170aef2b0b48";
var jsonData:NSData?
do{
jsonData = try NSData(contentsOfURL: NSURL(string: url)!, options: NSDataReadingOptions.DataReadingUncached)
let jsonObject:AnyObject? = try NSJSONSerialization.JSONObjectWithData(jsonData!, options: NSJSONReadingOptions.AllowFragments)
if let itemArray = jsonObject?.objectForKey("items") as? NSArray{
for item in itemArray{
if let sysItem = item.objectForKey("sys"){
//this is createdAt
if let createdAt = sysItem.objectForKey("createdAt") as? String{
print("createdAt:\(createdAt)")
}
if let contentTypeItem = sysItem.objectForKey("contentType")!.objectForKey("sys"){
//this is id
if let id = contentTypeItem.objectForKey("id") as? String{
print("id:\(id)")
}
}
}
}
}
}catch let err as NSError{
print("err:\(err)")
}
This code dosen't use any libraries,but you can use SwiftyJSON,this is will be easy to parse json.
Hope this help.
This can be done in simple way. I am assuming that you have parsed your json to dictionary
You have a key with items which is an array of dictionary and inside that dictionary you have createdAt and id(well it is deeper into the hierarchy but I will show you how to get it) keys. You can access it by simply doing this.
for dict in jsonDict["items"] as! Array<NSDictionary> {
let sysDict = dict["sys"] as! NSDictionary
print(sysDict["createdAt"]) //prints all createdAt in the array
let contentDict = sysDict["contentType"]
print((contentDict["sys"] as! NSDictionary)["id"]) // prints all ids
}
Hope this helps.

Swift 2.0: Could not cast value of type '__NSArrayM' (0x10a9348d8) to 'NSDictionary' (0x10a934d60)

I have been reading some of the responses to questions with similar problems but I just can't figure it out...
I have PostService that does a JSON POST request and fetches the data from a MySQL database. Everything was working before I did the conversion to Swift 2.0 and now it's giving me gears. (Code comes from Skip Wilson's Youtube series - Swift: Using External Databases and API's)
It gives the above error in the output and stops and highlights this line -
"let response = (try! NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.MutableContainers)) as! NSDictionary"
var settings:Settings!
init() {
self.settings = Settings()
}
let userLoginEmail = "admin#email.co.za";
let userLoginPassword = "1234";
func getPosts(callback:(NSDictionary) -> ()) {
request(settings.viewPosts, callback: callback)
}
func request(url:String, callback:(NSDictionary) -> ()) {
let myURL = NSURL(string: url)
let requested = NSMutableURLRequest(URL:myURL!);
requested.HTTPMethod = "POST";
let postString = "email=\(userLoginEmail)&password=\(userLoginPassword)";
print("email=\(userLoginEmail)&password=\(userLoginPassword)")
requested.HTTPBody = postString.dataUsingEncoding(NSUTF8StringEncoding);
let task = NSURLSession.sharedSession().dataTaskWithRequest(requested) {
(data, response, error) in
let response = (try! NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.MutableContainers)) as! NSDictionary
callback(response)
}
task.resume()
}
Here's my JSON post... With my limited knowledge and reading up on json.org, it looks like an object with an array inside it containing a bunch of objects (A dictionary?) The format of all of this did not change and my app would fetch the data from the database and display it correctly before the conversion..
{"posts":[{"Post":{"Id":"5","idno":"4","product":"Aspen Simvastatin","quantity":"30","due_date":"2015-04-11","last_repeat":"2015-04-10","doctor":"Dr. Jim Jones","store":"Central","currentrepeat":"2","totalrepeat":"6","active_ingredient":"Simvastatin","strength":"20mg","manufacturer":"Aspen Pharmacare","dosage_form":"Tabs","directions":"Take one tablet daily","repeatflag":"0","repeattimestamp":"2015-08-17 20:38:13"}},{"Post":{"Id":"6","idno":"4","product":"Mybulen","quantity":"45","due_date":"2015-04-11","last_repeat":"2015-04-10","doctor":"Dr. Jim Jones","store":"Central","currentrepeat":"3","totalrepeat":"6","active_ingredient":"Codeine Phosphate;Ibuprofen;Paracetamol","strength":"10mg;200mg;250mg","manufacturer":"Aspen Pharmacare","dosage_form":"Tabs","directions":"Take one or two tablets four times a day after meals","repeatflag":"0","repeattimestamp":"2015-08-17 20:38:13"}},{"Post":{"Id":"7","idno":"4","product":"Ecotrin XL","quantity":"30","due_date":"2015-04-11","last_repeat":"2015-03-11","doctor":"Dr. Jim Jones","store":"Central","currentrepeat":"4","totalrepeat":"6","active_ingredient":"Aspirin","strength":"81mg","manufacturer":"Litha Pharma","dosage_form":"Tabs","directions":"Take one tablet in the morning","repeatflag":"0","repeattimestamp":"2015-08-17 20:38:13"}},{"Post":{"Id":"8","idno":"4","product":"Lorien","quantity":"28","due_date":"2015-04-11","last_repeat":"2015-03-11","doctor":"Dr. J. Eckel","store":"Central","currentrepeat":"4","totalrepeat":"6","active_ingredient":"Fluoxetine HCl","strength":"20mg","manufacturer":"Aspen Pharmacare","dosage_form":"Caps","directions":"Take one capsule in the morning","repeatflag":"0","repeattimestamp":"2015-08-17 20:38:13"}}]}
I would be extremely grateful for any help on this.
In my masterViewController's viewDidLoad(), I have this code which process the information fetched...
service = PostService()
service.getPosts {
(response) in
self.loadPosts(response["posts"]! as! NSArray)
}
}
func loadPosts(posts:NSArray) {
for post in posts {
let post = post["Post"]! as! NSDictionary
let Id = Int((post["Id"]! as! String))!
let idno = Int((post["idno"]! as! String))!
let product = post["product"]! as! String
let quantity = Int((post["quantity"]! as! String))!
let doctor = post["doctor"]! as! String
let store = post["store"]! as! String
let currentrepeat = Int((post["currentrepeat"]! as! String))!
let totalrepeat = Int((post["totalrepeat"]! as! String))!
let active_ingredient = post["active_ingredient"]! as! String
let strength = post["strength"]! as! String
let manufacturer = post["manufacturer"]! as! String
let dosage_form = post["dosage_form"]! as! String
let directions = post["directions"]! as! String
let postObj = Post(Id: Id, idno: idno, product: product, quantity: quantity, doctor: doctor, store: store, currentrepeat: currentrepeat, totalrepeat: totalrepeat, active_ingredient: active_ingredient, strength: strength, manufacturer: manufacturer, dosage_form: dosage_form, directions: directions)
postsCollection.append(postObj)
dispatch_async(dispatch_get_main_queue()) {
self.tableView.reloadData()
}
}
You are telling NSJSONSerialization that you are absolutely sure that the JSON can be parsed and that you want your app to crash if it doesn't. (That's the try! ). Well, there are plenty of situations where you are asking for JSON and you are getting html back, so your users won't be happy with that, let's say if they use your app in a hotel or at the nearest starbucks.
Next, you are telling NSJSONSerialization that you are absolutely sure that the JSON contains a dictionary, and that you want your app to crash if it doesn't (as! NSDictionary). Guess what, you were given an array. You better read the documentation for your API, and check what you are given here.
BTW. I don't care what you are posting what JSON you are supposedly getting - I know that you received an array. Don't believe it? First rule of debugging: What you know is wrong.

Resources