Function to read web via JSON - ios

I am getting this error while trying to write a function which returns an NSDictionary after reading data using JSON:
 Cannot convert the expression's type NSDictionary? to type Void
func readJsonData() -> NSDictionary{
let urlPath = "http://www.telize.com/geoip"
let url = NSURL(string: urlPath)
let session = NSURLSession.sharedSession()
let task = NSURLSession.sharedSession().dataTaskWithURL(url!, completionHandler:{(data , response, error) -> Void in
if (error != nil){
println(error)
}else{
let jsonresult = NSJSONSerialization.JSONObjectWithData(data, options: .MutableContainers, error: nil) as? NSDictionary
return jsonresult
}
})
task.resume()
}

Your completionHandler has a return type of Void but you are returning jsonresult which is of type NSDictionary.
You should remove the line:
return jsonresult
As you should not return a value to a completion handler.
If you want to set a variable in your class on the completion, you can execute a thread on the main queue and set the variable equal to jsonresult:
let task = NSURLSession.sharedSession().dataTaskWithURL(url!, completionHandler:{(data , response, error) -> Void in
if (error != nil){
println(error)
}else{
let jsonresult = NSJSONSerialization.JSONObjectWithData(data, options: .MutableContainers, error: nil) as? NSDictionary
dispatch_async(dispatch_get_main_queue(), {
self.jsonData = jsonresult
//Update GUI, etc.
})
}
})

You'll need to learn how an asynchronous call works. Your function readJsonData cannot work that way.
You call dataTaskWithURL. The call returns immediately, without any result. That's why readJsonData cannot return a dictionary. However, the data that dataTaskWithURL downloads arrives a long time later (100s of milliseconds, or seconds, or maybe a minute). When the data arrives, your callback function is called. The callback cannot return anything because its call has long since returned. The callback must deliver the data to whoever wants it.

Related

how do I array from a GET request function in viewdidload function in swift

I'm very new to swift, so I will probably have a lot of faults in my code but what I'm trying to achieve is send a GET request to a server with paramters inside a function. I want to use the array I receive from the server in my viewdidload and in other functions but cant seem to find a way to store the array so i can use it. in my function it is filled, but out of my function it is empty
var scenarioArray: Array<Any> = []
let idPersoon = UserDefaults.standard.object(forKey: "idPersoon") as! String
override func viewDidLoad() {
super.viewDidLoad()
ScenarioArray()
print(scenarioArray)
print(self.scenarioArray)
}
func ScenarioArray() {
var request = URLRequest(url: URL(string: "http://dtsl.ehb.be/app&web/ios_php/getAllScenariosByPersoon.php?persoonID="+idPersoon)!)
request.httpMethod = "GET"
let task = URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data, error == nil else { // check for fundamental networking error
print("error=\(error)")
return
}
do {
if let jsonResult = try JSONSerialization.jsonObject(with: data, options: []) as? NSDictionary {
self.scenarioArray = (jsonResult["Scenarios"] as! NSArray) as! Array<Any>
print("ASynchronous\(self.scenarioArray)")
}
} catch let error as NSError {
print(error.localizedDescription)
}
}
task.resume()
}
Your "problem" is that you are trying to GET data from a server, meaning that you are doing a network call.
Now...you don't know how long that network call will take when you launch it, if you are on a good network then it might be fast, but if you are on 3G network it might take a while.
If the call to your server was done synchronously, the result would be that each and every time you'd try to fetch data your code would focus on doing just that, meaning that nothing else would go on... that is not what you want :)
Instead, when you use URLSession, and call task.resume() that method is executed asynchronously, meaning that it starts on another thread in the background where it will fetch data.
In the meantime, your main thread is free to handle UI rendering and so on. At some point in the near future your network call finishes and you now have valid data and must inform whoever needs to know.
So when you do a call to dataTask(with: completionHandler:), what you are actually saying is something along the lines of:
"hey...go fetch this data in the background please, and when you're done, I'd like to execute the code I've passed you here in the completionHandler with the parameters you tell me about".
Hope that makes just a little sense :)
Now...you have this code:
let task = URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data, error == nil else { // check for fundamental networking error
print("error=\(error)")
return
}
do {
if let jsonResult = try JSONSerialization.jsonObject(with: data, options: []) as? NSDictionary {
self.scenarioArray = (jsonResult["Scenarios"] as! NSArray) as! Array<Any>
print("ASynchronous\(self.scenarioArray)")
}
} catch let error as NSError {
print(error.localizedDescription)
}
}
That last part of the function call ({ data, response, error in...) is the completionHandler, which is not executed straight away. It is not executed until the retrieval of data has completed.
And therefore when you do a call to your ScenarioArray() function in viewDidLoad, what will happen is that the asynchronous call to fetch data will start in the background and your viewDidLoad will continue what it is doing, meaning that when you say:
print(scenarioArray)
print(self.scenarioArray)
then you can not expect scenarioArray to be populated yet as your task is busy fetching that data in the background.
So...what you need to do, as #vadian says, is to update your UI once the data has been fetched, meaning, in the completionHandler.
let task = URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data, error == nil else { // check for fundamental networking error
print("error=\(error)")
return
}
do {
if let jsonResult = try JSONSerialization.jsonObject(with: data, options: []) as? NSDictionary {
self.scenarioArray = (jsonResult["Scenarios"] as! NSArray) as! Array<Any>
print("ASynchronous\(self.scenarioArray)")
//Now you have data, reload the UI with the right scenarioArray
}
} catch let error as NSError {
print(error.localizedDescription)
}
}
Hope that makes sense and helps you.

Calling methods in swift (Method doesnt finish before next line)

Trying to pull in some JSON data from an API and then save that to core data.
My current method of doing this is to pull in the JSON data and return that array which ill then iterate and save to core data.
Pull in Data: (Works fine)
func getPlayerDataFromAPI() -> [Dictionary<String,AnyObject>]{
let url: String = "http://api.fantasy.nfl.com/v1/players/stats?"
let request : NSMutableURLRequest = NSMutableURLRequest()
var jsonData = [Dictionary<String,AnyObject>]()
request.HTTPMethod = "GET"
request.URL = NSURL(string: url)
session.dataTaskWithRequest(request) { (data, response, error) -> Void in
do {
let jsonResult = try NSJSONSerialization.JSONObjectWithData(data!, options:NSJSONReadingOptions.MutableContainers) as? NSDictionary
if (jsonResult != nil) {
if let playerData = jsonResult?["players"] as? [Dictionary<String, AnyObject>]{
jsonData = playerData
print(jsonData.count)
}
} else {
print("No Data")
}
}
catch {
print("Error Occured")
}
}.resume()
return jsonData;
}
And then I wanted to test the returned Dictionary to ensure it was being populated:
func saveData(){
let players = getPlayerDataFromAPI()
print(players.count)
}
I call saveData() in the viewController viewDidLoad method and get an empty dictionary... Moments later, the print statement in the JSON function prints.
0
1427
Is there a reason the getPlayerDataFromAPI() function doesnt finish before the print(count) is being called? Do I have this wrong logically? I always get an empty dictionary returned in this instance and thats no good.
You're trying to synchronously return the results of an asynchronous function. session.dataTaskWithRequest is passed a closure, which doesn't execute until the request completes. So your jsonData = playerData statement doesn't get executed until after your getPlayerDataFromAPI() function has already returned (at which point jsonData is still the empty dictionary you defined at the beginning of the function).
One way to do what you're trying to do is to allow a closure to be passed in to your function; something like this (I haven't tested this code):
func getPlayerDataFromAPI(completion: (data: [String: AnyObject]) -> Void)
Then, at the point you assign jsonData = playerData, you can "return" the data to the caller like this:
completion(data: jsonData)
Calling this function would look something like this:
getPlayerDataFromAPI() { (data) -> Void in
print(data)
}

unexpectedly found nil while unwrapping an Optional value on return

I am calling Url which will give me Json in get() function.
I am calling get() function from another class and try to return result of Json in Array format. but it shows Found null error on return statement . when I tried to print values of Json it writing correctly.
This is my code in swift.
func get() -> NSArray
{
let postEndpoint: String = "Link_For_JSON_Data"
let session = NSURLSession.sharedSession()
let url = NSURL(string: postEndpoint)!
var jsonArray : NSArray?
var jsonArray1 : NSArray?
session.dataTaskWithURL(url, completionHandler: { ( data: NSData?, response: NSURLResponse?, error: NSError?) -> Void in
// Make sure we get an OK response
guard let realResponse = response as? NSHTTPURLResponse where
realResponse.statusCode == 200 else
{
print("Not a 200 response")
return
}
// Read the JSON
do
{
if let contentString = NSString(data:data!, encoding: NSUTF8StringEncoding)
{
// Print what we got from the call
jsonArray = try NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.MutableContainers) as? NSArray
print("jsonArray here", jsonArray)
// Update the label
dispatch_async(dispatch_get_main_queue())
{ () -> Void in
self.getDataFormREST(jsonArray!)
}
}
}
catch
{
print("bad things happened")
}
}).resume()
return jsonArray!
}
func getDataFormREST(resultArray: NSArray) //-> NSArray
{
// let resultDictionary = resultArray[(searchDetails)!-1] as! NSDictionary
testArray = resultArray
print("TESTArray ON ",testArray)
}
You can't write a function that does an async call and then returns the results as the function result. That's not how async code works. Your function queues up the async dataTaskWithURL request, and then returns before it has even had a chance to send the request, much less receive the results.
You have to rewrite your get() function to be a void function (no result returned) but take a completion block. Then, in your data task's completion handler you get the data from the jsonArray and call the get() function's completion block, passing it the jsonArray.
See this project I posted on Github that illustrates what I'm talking about:
SwiftCompletionHandlers on Github

NSURLSession task not running

I'm trying to use NSURLSession to get ID's from a url, but the code between let task = NSURLSession.... and task.resume is never executed (I placed a breakpoint inside to check, as you can see in the attached image).
What am I missing?
func getPlayingSongData() {
while loopItem < IDsForSongsToPlay.count {
self.getPlayingSongDataLoop()
loopItem++
}
self.stream()
}
func getPlayingSongDataLoop() {
playerStatus = "Paused"
let songIDToGet = IDsForSongsToPlay[loopItem]
let url:NSURL = NSURL(string: "http://url.com/ajax.php?call=song&id=\(songIDToGet)")!
print(url)
let task = NSURLSession.sharedSession().dataTaskWithURL(url) { (data:NSData?, response:NSURLResponse?, error:NSError?) -> Void in
// Convert Json data into an Array
let songID:[String:String] = (try! NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.MutableContainers)) as! [String:String]
print(songID["id"]!)
self.StreamIDsForSongs.append((songID["id"]!))
songPlaying = songID["title"]!
}
task.resume()
}
UPDATE:
I changed
let task = NSURLSession.sharedSession().dataTaskWithURL(url) { (data:NSData?, response:NSURLResponse?, error:NSError?) -> Void in
to
let task = NSURLSession.sharedSession().dataTaskWithURL(url) { (data, response, error) in
and then it worked for me.
My code with breakpoints
According to Apple the function dataTaskWithURL(_:completionHandler:):
Creates a task that retrieves the contents of the specified URL, then calls a handler upon completion.
If the request completes successfully, the data parameter of the completion handler block contains the resource data, and the error parameter is nil. If the request fails, the data parameter is nil and the error parameter contain information about the failure.
Then you should see the values for each parameter regarding your request.
I hope this help you.

Swift - Returning a JSON object from API call in Model as Dictionary to use in View Controller

I have recently started experimenting with Swift and am new to more strongly typed programming languages.
I am trying to build a basic API call to http://openweathermap.org/api which will have a searchbox in the UIView that takes a city name and returns the relevant weather data.
My problem is figuring out how to return the JSON response I get back from my API call in my Model as a Dictionary that I can then use as a variable in my ViewController.
I have experimented with a variety of methods but continue to get a 'Dictionary not convertible to Void' error. From research and this article (Dictionary is not convertible to Void) it seems returning a closure might offer the answer but I am struggling to implement given that I only want to pass a cityname string parameter in my ViewController searchButton function.
Detailed code snippets below, thanks for help!
My API call in Model below which currently works at pulling down JSON object
class API {
func weatherSearch(#urlSearch: String) -> Dictionary<String,AnyObject>
{
let urlPath = "http://api.openweathermap.org/data/2.5/weather?q=" + urlSearch
let url = NSURL(string: urlPath)
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithURL(url!, completionHandler: {data, response, error -> Void in
println("Task completed")
if(error != nil) {
// If there is an error in the web request, print it to the console
println(error.localizedDescription)
}
var err: NSError?
if let jsonResult = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers, error: &err) as? NSDictionary {
var dataOut = jsonResult as Dictionary<String,AnyObject>
return dataOut
//omitted some additional error handling code
}
})
task.resume()
}
}
My ViewController where instantiate API and take input from Searchfield
#IBOutlet weak var searchField: UITextField!
#IBAction func searchButton() {
let api = API()
var dataOut = api.weatherSearch(urlSearch: searchField.text!)
println(dataOut)
self.performSegueWithIdentifier("Search", sender: nil)
}
Using the callback technique as hinted to by the comment above, try something like this
func weatherSearch(#urlSearch: String, callback: (Dictionary<String,AnyObject> -> ())) {
let urlPath = "http://api.openweathermap.org/data/2.5/weather?q=" + urlSearch
let url = NSURL(string: urlPath)
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithURL(url!, completionHandler: {data, response, error -> Void in
println("Task completed")
if(error != nil) {
// If there is an error in the web request, print it to the console
println(error.localizedDescription)
}
var err: NSError?
if let jsonResult = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers, error: &err) as? NSDictionary {
var dataOut = jsonResult as! Dictionary<String,AnyObject>
callback(dataOut)
//omitted some additional error handling code
}
})
task.resume()
}
weatherSearch(urlSearch: "endpoint here") { dictionary in
println(dictionary)
}

Resources