iOS - calling Webservice and parsing JSON in Swift - ios

I am using NSURLSession to call my own Webservice which returns JSON, works fine with this code:
func getJSONFromDatabase(){
let url = NSURL(string: "http://www.myurl/mysqlapi.php")
let task = NSURLSession.sharedSession().dataTaskWithURL(url!) {(data, response, error) in
self.json = NSString(data: data!, encoding: NSUTF8StringEncoding)
print(self.json)
}
task.resume()
}
However, it seems that this Task is not executed in the right order, because when i run the following function after the "getJSONFromDatabase" function, the "print" statement in the Task is executed after the "print" statement from the "parseJSON" function.
func parseJSON(){
let data = self.json.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)
do {
let json = try NSJSONSerialization.JSONObjectWithData(data!, options: []) as! NSArray
for event in json {
let name = event["name"]
let startDate = event["startDate"]
let dateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd"
let date = dateFormatter.dateFromString(startDate as! String)
if date != nil {
self.events.append(Event(name: name as! String, startDate: date!))
}
else {
print("Date is nil")
}
}
for event in self.events {
print(event.name)
}
} catch let error as NSError {
print("Failed to load: \(error.localizedDescription)")
}
}
My goal is to save the JSON Data in an Object Array of "Event", but this doesn't seem to work because when i iterate over my "self.events" array, it is empty.
Another problem is: When i split this 2 things like i posted here (2 functions), the "parseJSON" throws an error:
Failed to load: The data couldn’t be read because it isn’t in the correct format.
However, if i add the content of "parseJSON" into the Task of the "getJSONFromDatabase" function, there is no such error, but the array is still empty

dataTaskWithURL is asynchronous so you your code won't execute from the top down. Use a completion handler to work on the result from the asynchronous call.
func getJSONFromDatabase(success: ((json: String)->())){
let url = NSURL(string: "http://www.myurl/mysqlapi.php")
let task = NSURLSession.sharedSession().dataTaskWithURL(url!) {(data, response, error) in
let json = NSString(data: data!, encoding: NSUTF8StringEncoding)
success(json: json)
}
task.resume()
}
in use
getJSONFromDatabase(success: {json in
//do stuff with json
})

Related

API call function with completion handler crashes when accessed from different VC

Can someone fix my function code because I have created a API call function which will get the imageURL for the specific object in my class and display the results in the second view controller. I have created custom completion handler so that the code from second VC is only executed when dowloading of the imageURL is completed.
However, when I am testing this function in the second view controller to print me data that it has arrived I am getting a crash on the print statement line.
Here is the code for my API call function located in Model class file:
func parseImageData(finished: () -> Void) {
let urlPath = _exerciseURL
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 fetchedImageData = try JSONSerialization.jsonObject(with: data, options: .mutableLeaves) as? [String:Any],
let images = fetchedImageData["results"] as? [[String: Any]] {
for eachImage in images {
let imageUrl = eachImage["image"] as! String
self._imageUrl = URL(string: imageUrl)
}
print(self._imageUrl)
}
}
catch {
print("Error while parsing data.")
}
}
}
task.resume()
finished()
}
And here in the second view controller I am just testing if I can access the code block:
override func viewDidLoad() {
super.viewDidLoad()
exercise.parseImageData() {
print("Arrived Here?") // I am getting crash on this line moving to debug navigator.
}
}
If the crash says something about force unwrapping nil then it's probably because let task = URLSession.shared.dataTask(with: url!) is unwrapping url which is a nil optional variable here.
But your completion handler is called in the wrong place anyway, try putting your finished() callback into the do statement instead. Because finished was executed the moment you called exercise.parseImageData()
if error != nil {
print("Error while parsing JSON")
}
else {
do {
if let data = data,
let fetchedImageData = try JSONSerialization.jsonObject(with: data, options: .mutableLeaves) as? [String:Any],
let images = fetchedImageData["results"] as? [[String: Any]] {
for eachImage in images {
let imageUrl = eachImage["image"] as! String
self._imageUrl = URL(string: imageUrl)
}
print(self._imageUrl)
finished()
}
}
catch {
print("Error while parsing data.")
}
}

How to parse JSON when there is no key but only an Integer / String value?

How can I parse this JSON file ? My code is working when both keys and values are available .
My code so far :
let url = URL(string: "http://uhunt.felix-halim.net/api/uname2uid/felix_halim")
let task = URLSession.shared.dataTask(with: url!, completionHandler: {
(data, response, error) in
print("Task Started")
if error != nil {
print("In Error!")
} else {
if let content = data {
do {
let myJSON =
try JSONSerialization.jsonObject(with: content, options: .mutableContainers) as AnyObject
print(myJSON)
} catch {
print("In Catch!")
}
}
}
})
task.resume()
print("Finished")
If the root object of the JSON is not a dictionary or array you have to pass .allowFragments as option (btw. never pass .mutableContainers, it's meaningless in Swift)
let url = URL(string: "http://uhunt.felix-halim.net/api/uname2uid/felix_halim")!
let task = URLSession.shared.dataTask(with: url) { data, response, error in
print("Task Started")
guard error == nil else {
print("In Error!", error!)
return
}
do {
if let myJSON = try JSONSerialization.jsonObject(with: data!, options: .allowFragments) as? Int {
print(myJSON)
}
} catch {
print("In Catch!", error)
}
}
task.resume()
print("Finished")
THIS ANSWER IS NOT CORRECT. IT IS POSSIBLE TO PARSE Int , etc like in vadian post
This is not a json object format specification.
JSON data must start with "{" for object
or "[" for array of elements.
http://www.json.org/
So, if you have got different formats I would suggest this:
Check the first letter. if "{" parse as object.
Check the first letter. if "[" parse as array.
Otherwise:
Just convert the String into Int something like this:
var num = Int("339")
If not use simple String.

iOS PickerView empty after read Json

I'm making an app in iOS and everything is going fairly well but for one bug that I can't fix. When the user starts the app for the first time the app request a json from my server. When the json is read, I show the result in a picker view. The problem is that the pickerview always shows empty until the user touches the screen. I've tried quite a few things but nothing works. In theory it is empty because the json hasn't been read, but this is not the case because in the console I can see that the json is ready.
Here are the relevant pieces of code:
override func viewDidLoad() {
super.viewDidLoad()
warning.isHidden = true
self.codeInput.delegate = self;
DispatchQueue.main.async {
self.readJson()
self.picker.reloadAllComponents()
}
}
And the part where I read the json
func readJson(){
let urlRequest: NSMutableURLRequest = NSMutableURLRequest(url: requestURL)
let session = URLSession.shared
let task = session.dataTask(with: urlRequest as URLRequest, completionHandler: {
(data, response, error) -> Void in
let httpResponse = response as! HTTPURLResponse
let statusCode = httpResponse.statusCode
if (statusCode == 200) {
print("Everyone is fine, file downloaded successfully.")
do{
let json = try JSONSerialization.jsonObject(with: data!, options:.allowFragments) as! [String:AnyObject]
if let events = json["events"] as? [[String: AnyObject]] {
for event in events {
//here I read the json and I save the data in my custom array
}
self.picker.reloadAllComponents()
}
print(self.eventsArray)
}
}catch {
print("Error with Json: \(error)")
}
}
else{
print(statusCode)
}
})
picker.reloadAllComponents()
task.resume()
}
You need to do a couple of things:
You need to move the call to reload the picker view to inside the completion handler for your data task. That closure gets called once the data has been loaded.
However, the completion methods of URLSession tasks get executed on a background thread. Thus you'll need to wrap your call in a GCD call to the main thread. Add this code as the very last line in your completion closure, right before the closing brace:
DispatchQueue.main.async{
picker.reloadAllComponents()
}
(That's Swift 3 syntax.)
EDIT:
The code would look like this:
func readJson(){
let urlRequest: NSMutableURLRequest = NSMutableURLRequest(url: requestURL)
let session = URLSession.shared
let task = session.dataTask(with: urlRequest as URLRequest, completionHandler: {
(data, response, error) -> Void in
let httpResponse = response as! HTTPURLResponse
let statusCode = httpResponse.statusCode
if (statusCode == 200) {
print("Everyone is fine, file downloaded successfully.")
do{
let json = try JSONSerialization.jsonObject(with: data!, options:.allowFragments) as! [String:AnyObject]
if let events = json["events"] as? [[String: AnyObject]] {
for event in events {
//here I read the json and I save the data in my custom array
}
//Delete this call to reloadAllComponents()
//self.picker.reloadAllComponents()
}
print(self.eventsArray)
}
//------------------------------------
//This is where the new code goes
DispatchQueue.main.async{
picker.reloadAllComponents()
}
//------------------------------------
}catch {
print("Error with Json: \(error)")
}
}
else{
print(statusCode)
}
})
//Delete this call to reloadAllComponents()
//picker.reloadAllComponents()
task.resume()
}

Swift How to update (modify, Delete, Add) entries of JSON

Hello i need some help here, I'm making an IOS app that gets data from a JSON API and then showing the results on a Table , when i tap on a result from the table it goes to a second view controller where i'm showing the details. What I want to do is to update the info I'm showing on the details, delete entries from the JSON by deleting them from the table itself, and add a new entry to be saved on the JSON.
This is the JSON structure:
{
_id: "57eec6c9dfc2fb03005c0dd0",
ssid: "nonummy",
password: "accumsan",
lat: 29.39293,
lon: 115.71771,
summary: "curae nulla dapibus dolor vel est donec odio justo sollicitudin ut",
__v: 0,
likes: 1,
unlikes: 0,
bssid: "EF:CD:AB:56:34:12"
},
I want to be able to update the SSID, Password and Summary.
this is the code I'm using to get the Result from the JSON and is working good
Code:
let url = URL(string:"https://fierce-peak-97303.herokuapp.com/api/wifi")!
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
if error != nil {
print(error)
}else {
if let urlContent = data {
do {
let jsonResult = try JSONSerialization.jsonObject(with: urlContent, options: JSONSerialization.ReadingOptions.mutableContainers)
//print(jsonResult))
for item in(jsonResult as? NSArray)! {
let ssid = (item as? NSDictionary)?["ssid"] as? NSString
//print(ssid)
}
self.tableData = jsonResult as! NSArray
DispatchQueue.main.sync(execute: {
self.table.reloadData()
})
}catch {
print("No Json Result Was Found")
}
}
}
}
task.resume()
For example if I click on one line of the table I want to be able to update password.
I managed to do it like this : all formatted for swift 3
//declare parameter as a dictionary which contains string as key and value combination.
let parameters = ["ssid": newSSID.text!,"password": newPass.text!,"lat": newLat.text!,"lon": newLon.text!,"summary": newSum.text!] as Dictionary<String, String>
//create the url with NSURL
let url = URL(string: "https://fierce-peak-97303.herokuapp.com/api/wifi")
//create the session object
let session = URLSession.shared
//now create the NSMutableRequest object using the url object
let request = NSMutableURLRequest(url: url!)
request.httpMethod = "POST"
var err : NSError?
do {
request.httpBody = try JSONSerialization.data(withJSONObject: parameters, options: [])
}catch{
print("error")
}
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.addValue("application/json", forHTTPHeaderField: "Accept")
//create dataTask using the session object to send data to the server
let task = session.dataTask(with: request as URLRequest, completionHandler: {data, response, error -> Void in
print("Response: \(response)")
let strData = NSString(data: data!, encoding: String.Encoding.utf8.rawValue)
print("Body: \(strData)")
var err: NSError?
do {
var json = try JSONSerialization.jsonObject(with: data!, options: .mutableLeaves) as? NSDictionary
}catch{
print("JSON error")
}
// Did the JSONObjectWithData constructor return an error? If so, log the error to the console
if(err != nil) {
print(err!.localizedDescription)
let jsonStr = NSString(data: data!, encoding: String.Encoding.utf8.rawValue)
print("Error could not parse JSON: '\(jsonStr)'")
}
else {
// The JSONObjectWithData constructor didn't return an error. But, we should still
// check and make sure that json has a value using optional binding.
do {
let json = try JSONSerialization.jsonObject(with: data!, options: .mutableLeaves) as? NSDictionary
if let parseJSON = json {
// Okay, the parsedJSON is here, let's get the value for 'success' out of it
let success = parseJSON["success"] as? Int
print("Success: \(success)")
}
else {
// Woa, okay the json object was nil, something went worng. Maybe the server isn't running?
let jsonStr = NSString(data: data!, encoding: String.Encoding.utf8.rawValue)
print("Error could not parse JSON: \(jsonStr)")
}
}catch{
print("JSON error")
}
}
You can get the contents of the json file as whatever data structure you want, then overwrite the values that you want to change and finally overwrite the original file when you want to save your changes:
So you're already getting the JSON as an array:
let jsonResult = try JSONSerialization.jsonObject(with: urlContent, options: JSONSerialization.ReadingOptions.mutableContainers)
let array = (jsonResult as? NSArray)!
So now you can just change the values in array, when you're done changing everything you want then you can overwrite the json file: (swift 3.0)
var jsonData: NSData!
// serialize json dataa
do
{
jsonData = try JSONSerialization.dataWithJSONObject(array, options: NSJSONWritingOptions())
let jsonString = String(data: jsonData as Data, encoding: String.Encoding.utf8)
print(jsonString)
}
catch let error as NSError
{
print("Array to JSON conversion failed: \(error.localizedDescription)")
}
// overwrite the contents of the original file.
do
{
let file = try FileHandle(forWritingToURL: url!)
file.writeData(jsonData)
print("JSON data was written to the file successfully!")
}
catch let error as NSError
{
print("Couldn't write to file: \(error.localizedDescription)")
}
I'm not sure exactly how writing to a server works, but since it's a URL i think it should act the same way.
Update:
So your original array is:
let array = (jsonResult as? NSArray)!
You can cast it to an array of dictionaries like so
var dictionaries : [[String: NSObject]] = array as? [[String: NSObject]]
and you get ssid by doing:
int i = 0;
for (dictionary in dictionaries)
{
let ssid = dictionary["ssid"]
let newSSID = ssid + "something I want to edit my values with"
// now we have our new value, so we update the array
dictionaries[i]["ssid"] = newSSID
i += 1
}
now after this we just overwrite the original file with the new dictionaries object, which I wrote out above.

How does NSURLSession exactly work?

The following lines of code are written inside a function and session_id and session_name are global variables.
let task = NSURLSession.sharedSession().dataTaskWithRequest(request) { data, response, error in
guard let _:NSData = data, let _:NSURLResponse = response where error == nil else {
print("error")
return
}
let dataString = NSString(data: data!, encoding: NSUTF8StringEncoding)
print("dataString1 = \(dataString)")
var json = try! NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.AllowFragments) as? NSDictionary
if(error != nil) {
let jsonStr = NSString(data: data!, encoding: NSUTF8StringEncoding)
print("Error could not parse JSON: '\(jsonStr)'")
print(error)
}
else
if let parseJSON = json {
print("jsonstr = \(json)")
if let success = parseJSON["success"] as? String {
if success == "true" {
let session_valid = parseJSON["session_valid"] as! String
if session_valid == "true"{
let response = parseJSON.objectForKey("response") as! NSDictionary
print("response = \(response)")
session_id = response.objectForKey("session_id") as! String
session_name = response.objectForKey("session_name") as! String
dispatch_async(dispatch_get_main_queue(), { () -> Void in
if let resultController = self.storyboard!.instantiateViewControllerWithIdentifier("splitViewController") as? UISplitViewController {
self.presentViewController(resultController, animated: true, completion: nil)
}
})
}
}
}
print("values1 are \(session_id) and \(session_name)") //print stmt 1
return
}//if
else {
print("json not parsed")
let jsonStr = NSString(data: data!, encoding: NSUTF8StringEncoding)
print("Error could not parse JSON: \(jsonStr)")
print(error)
}//else
}
print("values are \(session_id) and \(session_name)") //print stmt 2
task.resume()
The output relevant to the query is: (session_id and session_name are initialised to random values 1 and a)
values are 1 and a
and then it prints: (values of the two variables in the response from the php script)
values1 are ckj0uuj2q18m97m78m1uvje7f5 and d72d1363f44031cac4148b0e6fa295d6
My query is that how is 'print stmt 2' printed before 'print stmt 1'? I am new to swift. Am I missing any concept here?
Also, why does 'print stmt 2' print the initial values of the variables and not the new values? I know the two questions are related but I am not getting how
The code inside that big block (Swift calls it a closure) gets run after the request completes. There's not a way to get a value over the Internet immediately, because it can take up to 90 seconds, and you don't want your entire app blocked for that long (particularly because iOS will kill your app outright after 30).
The way you solve that is by changing the code that needs the values so that it provides code to run after the values have been retrieved, to do something with those values.
See https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/Closures.html for more info.

Resources