I am trying to get JSON image urls from a different endpoint. At the moment I am able to call first endpoint fetching data for exercise name, description and id. Then for each exercise I am trying to call to a different endpoint using an ID value so then I can get image url for the specific exercise.
The only idea I had is to create nested API call to a different endpoint, but I am getting too many syntax errors and it does not work.
The question is how can I reformat my code to remove existing syntax errors.
Here is my code. I never actually seen a way to do this type of API calls.
func parseData() {
fetchedExercise.removeAll()
let url = URL(string: urlPath)!
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
if error != nil {
print("Error while parsing JSON")
}
else {
do {
if let data = data,
let fetchedData = try JSONSerialization.jsonObject(with: data, options: .mutableLeaves) as? [String:Any],
let exercises = fetchedData["results"] as? [[String: Any]] {
for eachExercise in exercises {
if eachExercise["license_author"] as! String == "wger.de" {
let name = eachExercise["name"] as! String
let description = eachExercise["description"] as! String
let id = eachExercise["id"] as! Int
}
It has been fixed by adding missing closure brackets at the end which were causing syntax error while trying to run the code.
Related
As a part of teaching myself Swift, I am working on a Weather App. I am currently attempting to integrate weather alerts. I use a struct called AlertData to initialize data returned from the API call to weather.gov after serializing the returned data from an API call. Or, at least that is the plan. I have modeled my classes off of other classes that request data from weather.gov, but to get an alert, I need to be able to send variable parameters in my dataTask. I use the URL extension from Apple's App Development with Swift (code below) and have the code set to issue the parameters with the users current location to get alerts where the user is currently.
My problem comes when I attempt to construct the API call to weather.gov in my AlertDataController class(code below). Xcode keeps throwing different errors and I am not sure why. I would like to use a guard statement as I have in my code below, but that throws an error of "Cannot force unwrap value of non-optional type '[[String : Any]]'" in my code where shown. It also throws the same error when I make it a simple constant assignment after unwrapping as the extension returns an optional URL.
The same code works flawlessly when I construct the URL from a string in the guard statement directly as in:
guard let url = URL(string: (baseURL + locationString + stations)) else {
What am I missing? Where my error is thrown is inside the dataTask, and regardless of how it got there, the variable url is an unwrapped URL. Thanks in advance.
Controller class:
import Foundation
import CoreLocation
struct AlertDataController {
func checkWxAlert(location: CLLocation, completion: #escaping (AlertData?) -> Void) {
let baseURL = URL(string: "https://api.weather.gov/alert")!
let locationString = "\(location.coordinate.latitude),\(location.coordinate.longitude)"
var query = [
"active": "1",
"point": locationString
]
guard let url = baseURL.withQueries(query) else {
completion(nil)
print("Unable to build URL in AlertDataController.checkWxAlert with supplied queries.")
return
}
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
if let data = data,
let rawJSON = try? JSONSerialization.jsonObject(with: data),
let json = rawJSON as? [String: Any],
let featuresDict = json["features"] as? [[String: Any]],
let propertiesArray = featuresDict!["properties"] as? [String: Any] {
Error: Cannot force unwrap value of non-optional type '[[String : Any]]'
let alertData = AlertData(json: propertiesArray)
completion(alertData)
} else {
print("Either no data was returned in AlertDataController.checkWxAlert, or data was not serialized.")
completion(nil)
return
}
}
task.resume()
}
}
URL extension:
import Foundation
extension URL {
func withQueries(_ queries: [String: String]) -> URL? {
var components = URLComponents(url: self, resolvingAgainstBaseURL: true)
components?.queryItems = queries.flatMap { URLQueryItem(name: $0.0, value: $0.1) }
return components?.url
}
func withHTTPS() -> URL? {
var components = URLComponents(url: self, resolvingAgainstBaseURL: true)
components?.scheme = "https"
return components?.url
}
}
If featuresDict is really an array, you cannot use featuresDict["properties"] syntax. That subscript with string syntax is only for dictionaries. But you've apparently got an array of dictionaries.
You could iterate through the featuresDict array (which I'll rename to featuresArray to avoid confusion), you could do that after you finish unwrapping it. Or, if just want an array of the values associated with the properties key for each of those dictionaries, then flatMap is probably a good choice.
For example:
let task = URLSession.shared.dataTask(with: url) { data, _, error in
guard let data = data,
error == nil,
let json = (try? JSONSerialization.jsonObject(with: data)) as? [String: Any],
let featuresArray = json["features"] as? [[String: Any]] else {
print("Either no data was returned in AlertDataController.checkWxAlert, or data was not serialized.")
completion(nil)
return
}
let propertiesArray = featuresArray.flatMap { $0["properties"] }
let alertData = AlertData(json: propertiesArray)
completion(alertData)
}
Or, if AlertData is expecting each of those properties to be, themselves, a dictionary, you might do:
let propertiesArray = featuresArray.flatMap { $0["properties"] as? [String: Any] }
Just replace that cast with whatever type your AlertData is expecting in its array, json.
Or, if you're only interested in the first property, you'd use first rather than flatMap.
The error
Cannot force unwrap value of non-optional type '[[String : Any]]'
is very clear. It occurs because in the optional binding expression featuresDict is already unwrapped when the next condition is evaluated.
Just remove the exclamation mark
... let propertiesArray = featuresDict["properties"] as? [String: Any] {
The error is not related at all to the way the URL is created.
Having an issue with my program. I would appreciate it if someone could help out. I have tried for weeks to parse the JSON files fetched from the iTunes API
(itunes.apple.com/search?term=song+you+want+to+search&entity=songTrack).
However, my answers are never displayed on my tableview and an error always shows up in the terminal:
"2017-11-14 17:25:28.809190+0100 Itunes Learning[32409:6240818] [MC] Lazy loading NSBundle MobileCoreServices.framework
2017-11-14 17:25:28.810264+0100 Itunes Learning[32409:6240818] [MC] Loaded MobileCoreServices.framework
2017-11-14 17:25:28.823734+0100 Itunes Learning[32409:6240818] [MC] System group container for systemgroup.com.apple.configurationprofiles path is /Users/cyprianzander/Library/Developer/CoreSimulator/Devices/D52FD9D5-B6E4-4CE0-99E4-6E0EE15A680D/data/Containers/Shared/SystemGroup/systemgroup.com.apple.configurationprofiles
Could not cast value of type '__NSDictionaryI' (0x103b911d8) to 'NSArray' (0x103b90d28).
2017-11-14 17:25:29.875534+0100 Itunes Learning[32409:6240900] Could not cast value of type '__NSDictionaryI' (0x103b911d8) to 'NSArray' (0x103b90d28).
(lldb) "
This is approximately how the JSON file is set up:
{“resultCount” : 50, “results”: [ {“trackName”:”name”, ”artistName”:”name2”}, {“trackName”:”name3”, “artistName”:”name4”} ] }
(An array of objects inside an array - meaning the first object is on the far outside).
I have tried my function with another API, which did work. I have the feeling that the main reason as to why this happens, is because the iTunes API JSON file is very complex. It is an assortment of very long objects inside an array, which is inside a smaller list of objects. However, the other one was only and array of objects.
Here is my code: (I have noticed that the problem occurs while parsing the data I need. The only thing I need to know is how to properly unwrap my JSON file)
func parseData(searchTerm: String) {
fetchedSong = []
let itunesSearchTerm = searchTerm.replacingOccurrences(of: " ", with: "+", options: .caseInsensitive, range: nil)
let escapedSearchTerm = itunesSearchTerm.addingPercentEncoding(withAllowedCharacters: [])!
let urlString = "https://itunes.apple.com/search?term=\(escapedSearchTerm)&entity=song"
let url = URL(string: urlString)!
URLSession.shared.dataTask(with: url) { (data, response, error) in
if let error = error {
// If there is an error in the web request, print it to the console
print(error)
return
}
else {
do {
let fetchedData = try JSONSerialization.jsonObject(with: data!, options: .mutableLeaves) as! NSArray
print(fetchedData)
for eachFetchedSong in fetchedData {
let eachSong = eachFetchedSong as! [String: Any]
let song = eachSong["trackName"] as! String
let artist = eachSong["artistName"] as! String
self.fetchedSong.append(songs(song: song, artist : artist))
}
self.SongTableView.reloadData()
}
catch {
print("An error occured while decoding the JSON object")
}
}
}.resume()
}
If anyone could help me, I would be extremely happy, especially because I have been stuck with this for three weeks, continuously trying different techniques (this one seemed the most successful).
Your JSON data is not an array. It is a dictionary with two key/value pairs. The first is the key "resultCount" with a value of 50, and the second is the key "results" with an array as its value.
Never use as! when parsing JSON, since this will crash your app if you get an unexpected result. Don't use .mutableLeaves unless you can explain to us what it does and why you need it. Don't use NSArray in your Swift code.
Handling one error and crashing on others is pointless. I'd write
if let fetchedDict = try? JSONSerialization(...) as? [String:Any],
let fetchedArray = fetchedDict ["results"] as? [[String:Any]] {
for dict in fetchedArray {
if let song = dict ["trackName"] as? String,
let artist = dict ["artistName"] as? String {
...
}
}
}
I am getting unexpected values back when i am parsing my json data from my api, i may be doing something wrong here as i'm quite new to swift but i was getting correct values before when i was receiving one "key" but now i have added two i cannot seem to parse the values properly.
This is the json collected from the address my code is receiving, (sorry if its hard to read havn't worked out how to do line breaks yet in my ruby api)(as long as its functional im not too worried at the moment)
{
"ratings":{
"elements":{"Ready Position":[{"description":"Neutral Grip","values":"1,2,3,4,5"},{"description":"Back Straight (Concave ir Convex?)","values":"1,2,3,4,5"},{"description":"Body Low \u0026 Feet a little more than sholder width apart","values":"1,2,3,4,5"},{"description":"Weight on Balls of Feet","values":"1,2,3,4,5"},{"description":"Head Up","values":"1,2,3,4,5"},{"description":"Sholder Blades Close","values":"1,2,3,4,5"},{"description":"Eyes Drilled","values":"1,2,3,4,5"}],"Split Step":[{"description":"Ready Position Conforms","values":"Yes,No"},{"description":"Body Position Low","values":"1,2,3,4,5"},{"description":"Legs Loaded/Prepared","values":"1,2,3,4,5"}]}
},
"comments":{}
}
Now, My swift code looks like this
let playerAPIurl = "http://linkcoachuat.herokuapp.com/api/v1/session/element?organisation=" + userorganisation + "&group=" + urlGroupSelected + "&sport=" + usersport
print(playerAPIurl)
var request = URLRequest(url: URL(string: playerAPIurl)!)
request.httpMethod = "GET"
let configuration = URLSessionConfiguration.default
let session = URLSession(configuration: configuration, delegate: nil, delegateQueue: OperationQueue.main)
let task = session.dataTask(with: request) { (data, response, error) in
if error != nil {
print("ERROR")
}
else{
do {
let json = try JSONSerialization.jsonObject(with: data!) as? [String: AnyObject]
print(json)
And this is the output im getting from this print(json)
Optional({
comments = {
};
ratings = {
};
})
I know i shouldnt be getting anything more in the comments part, but in the ratings part there should be some data?
so after recieving the json and dealing with parsing it i need to access this part of it ["ratings"]["elements"] and after that im all good
thanks in advance and please bare in mine im very new to swift
Thanks
Try the below code. The url used in below code has your JSON data. This code is printing the output correctly.
func testApi(){
let url = URL(string: "https://api.myjson.com/bins/jfccx")
let session = URLSession.shared
let request = URLRequest(url: url!)
//create dataTask using the session object to send data to the server
let task = session.dataTask(with: request as URLRequest, completionHandler: { data, response, error in
guard let data = data, error == nil else {
return
}
do {
//create json object from data
if let json = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
print(json)
}
} catch let error {
print(error.localizedDescription)
}
})
task.resume()
}
I am trying to populate a UITable with a json result. I call a function that gets the json from the server and stores the result in NSDictionary. I want to be able to use this collection, and then populate a table. I run into a problem however because for the func numberOfRowsInSection I need the count of the collection, and since my json result is within another function inside a try/catch I cant seem to return the value.
This is what I have for the function which I call in ViewDidLoad():
func getSubjects() -> NSDictionary{
let myUrl = NSURL(string: "www.mydomain.com/script.php");
let request = NSMutableURLRequest(url:myUrl as! URL)
let user_id = UserDetails[0]
request.httpMethod = "POST";
let postString = "user_id=\(user_id)";
request.httpBody = postString.data(using: String.Encoding.utf8);
let task = URLSession.shared.dataTask(with: request as URLRequest){
data, response, error in
if error != nil {
print("error=\(error)")
return
}
var err: NSError?
do {
let json = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as? NSDictionary
if let parseJSON = json {
let resultValue: NSDictionary = parseJSON["subjects"] as! NSDictionary
}
} catch let error as NSError {
err = error
print(err!);
}
}
task.resume();
}
If I print resultValue I get what I need, in this example being:
{
1 = (
Maths,
Lecture
);
2 = (
Science,
Lecture
);
3 = (
English,
Seminar
);
}
But the confusion is, how do I go about returning this value? and where? and how would I implement it in the table? If I try to return resultValue when I parse the JSON I get the error that it is unexpected non-void return in void function and if I try to return the value at the end of the function, I get an unresolved identifier error
I feel I am implementing this incorrectly. I have checked many tutorials on this, and no one seems to populate a table with a POST JSON so I don't know how to go about returning the value, or the proper method of implementation. Any help would be greatly appreciated!
The problem your having is that the dictionary hasn't been retrieved by the time you try to return it. You can use an asynchronous callback to get the dictionary after it has been retrieved from your database.
func getSubjects(callback: #escaping (NSDictionary)-> Void){
let myUrl = NSURL(string: "www.mydomain.com/script.php");
let request = NSMutableURLRequest(url:myUrl as! URL)
let user_id = UserDetails[0]
request.httpMethod = "POST";
let postString = "user_id=\(user_id)";
request.httpBody = postString.data(using: String.Encoding.utf8);
let task = URLSession.shared.dataTask(with: request as URLRequest){
data, response, error in
if error != nil {
print("error=\(error)")
return
}
var err: NSError?
do {
let json = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as? NSDictionary
if let parseJSON = json {
let resultValue: NSDictionary = parseJSON["subjects"] as! NSDictionary
callback(resultValue)
}
} catch let error as NSError {
err = error
print(err!);
}
}
task.resume();
}
and then you would call this function like...
getSubjects(callback: {(resultValue)-> Void in
print(resultValue)
//here you could set your tableView data equal to the values in the dictionary that was just received and then call self.tableView.reloadData to update your table view
})
So perhaps in the viewDidLoad() function of you UITableViewController or in the viewDidAppear(_:), depending on the life cycle of your view, you would call getSubjects(...) as i have shown above and then when the callback is called you call self.tableView.reloadData() as I have explained in the function call. If you are unsure how to setup a tableview datasource and delegate then you should probably open another question for that
EDIT
In response to your comment asking how to use the retrieved value from your server as a variable available to your whole class, you could do something like this...
class: ExampleViewController {
var resultsDictionary: [Int: [String: String]]?
override func viewDidLoad(){
getSubjects(callback: {(resultValue)-> Void in
resultsDictionary = resultValue
})
}
//Use the actual getSubjects function I have already shown you above
func getSubjects(){...}
}
i am storing images on mysql db as a medium blob, i am sending web request from ios device to nodejs server which fetches images from mysql and sends them back to the ios device in the response, i am having trouble parsing the data send back from the nodejs server. What i need to accomplish is transform the data that is sent back into UIImages so i can use them in my ios app. Also i know its not ideal to store images in database but thats what im doing. Any help is appretiated.
let task = session.dataTask(with: request)
{ data, response, error in
if error != nil
{
print("error in web request")
}
else
{
DispatchQueue.main.async
{
self.parseWebPhotos(stuff: data) //stoer results in class member
/*need to dispatch this to main queue since this is in closure, so needs to update ui so needs to
run on the main thread*/
self.getPhotosFinished()
}
}
}//completion handler end
func parseWebPhotos(stuff: Data?)
{
if stuff != nil
{
if let dataAsAny: NSArray = try? JSONSerialization.jsonObject(with: stuff!, options: .mutableContainers) as! NSArray//checks that parsing didnt return error
{
let dic = dataAsAny[0] as! [String: AnyObject] //dictionary of all the pictures
let pic0 = dic["pic0"] as! [String: AnyObject] //dictionary of pic0
let data = pic0["data"] as! NSArray
NotificationCenter.default.post(name: Notification.Name("foo"), object: nil)
}
}
}