using JSON data in SWIFT UI controls - ios

I need to use data from a JSON web service to update controls in a SWIFT app. I can get the data no problem, but I can't seem to access the UI controls from within the task block and I can't get the JSON to persist out of the block. I've searched around and haven't found the answer. Here's my test code. The current result is that value1Result has a value inside task, but is nil outside. Thanks in advance.
var jsonResult:NSDictionary!
var value1Result:String!
let task = NSURLSession.sharedSession().dataTaskWithURL(url!) { data, response, error in
var error: NSError?
jsonResult = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.AllowFragments, error: &error)!
as! Dictionary<String, AnyObject>
println(jsonResult)
if let value1 = jsonResult["value1"] {
println(value1)
value1Result = value1 as! String
}
}
task.resume()
self.textView1.text = value1Result

You can use asynchronous block to update the main UI
dispatch_async(dispatch_get_main_queue()) {
//Update your UI
}
With your code
let task = NSURLSession.sharedSession().dataTaskWithURL(url!) { data, response, error in
var error: NSError?
jsonResult = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.AllowFragments, error: &error)!
as! Dictionary<String, AnyObject>
println(jsonResult)
if let value1 = jsonResult["value1"] {
println(value1)
dispatch_async(dispatch_get_main_queue()) {
//Update your UI
value1Result = value1 as! String
self.yourtextview.text = value1 as! String
}
}
}
task.resume()

Doing proper network coding is hard. There's a lot of problems with your code both stylistically, in terms of robustness, and actual functionality. That is why networking vs. UI is always layered with a library like AFNetworking. Doing it right yourself is just too much manual labor.
Consider what it takes to check for errors properly and hand off the code properly to the UI thread:
let task = session.dataTaskWithURL(url) {
[unowned self]
(data: NSData?, response: NSURLResponse?, netError: NSError?) in
if let statusCode = (response as? NSHTTPURLResponse)?.statusCode {
if statusCode == 200 {
if let jsonResult = NSJSONSerialization.JSONObjectWithData(data, options: .AllowFragments, error: &error) as? Dictionary<String, AnyObject> {
if let value1 = jsonResult["value1"] as? String {
dispatch_async(dispatch_get_main_queue()) {
self.textView1.text = value1
}
}
else {
println("JSON format error, key value1 not defined")
}
}
else {
println("JSON parsing error: \(error.localizedDescription)")
}
else { // status code other than 200
println("HTTP Error \(statusCode)")
}
}
else { // No HTTP response available at all, couldn't hit server
println("Network Error: \(netErr!.localizedDescription)")
}
}
task.resume()

Related

Json Serialisation Swift 3

I am trying to serialise the json in the code below, the logs print out the display names successfully but I get a crash with an error:
fatal error: unexpectedly found nil while unwrapping an Optional value
on the following lines:
print(item["display-name"]! as!String)
Blockquoteself.tableData.append(item["display-name"] as! String)
I can't seem to figure out why, any help much appreciated!
let url = NSURL(string: "https://www.asmserver.co.uk/sally/parsexml.php")!
let task = URLSession.shared.dataTask(with: url as URL) { (data, response, error) -> Void in
if let urlContent = data {
do {
if let jsonResult = try JSONSerialization.jsonObject(with: urlContent, options: []) as? [[String:AnyObject]] {
for item in jsonResult {
print(item["display-name"]! as!String)
self.tableData.append(item["display-name"] as! String)
}
}
} catch {
print("JSON serialization failed")
}
} else {
print("ERROR FOUND HERE")
}
DispatchQueue.main.async(execute: { () -> Void in
self.tableView.reloadData()
})
self.tableView.isUserInteractionEnabled = true
}
task.resume()
You should make sure that you really have a value before you use it and specially before using as!.
Do like this instead:
for item in jsonResult {
guard let name = item["display-name"] as? String else { continue }
print(name)
self.tableData.append(name)
}
If the guard succeeds then you have a value and can use the name variable. You can also add several conditions to the guard statement.
As an alternative to the guard statement, you could also use the similar if let construct:
if let item = item["display-name"] as? String {
print(item)
} else {
print("No display name")
}

I keep getting a use unresolved identifier error swift

When I try to run this project I am greeted with a "Use of unresolved identifier error." Here is the code I get the error on the line with
var jsonDict = try NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.MutableContainers)
as! NSDictionary
let task : NSURLSessionDataTask = session.dataTaskWithRequest(request, completionHandler: {data, response, error -> Void in
if((error) != nil) {
print(error!.localizedDescription)
} else {
let err: NSError?
do {
var jsonDict = try NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.MutableContainers) as! NSDictionary
} catch {
if(err != nil) {
print("JSON Error \(err!.localizedDescription)")
}
else {
//5: Extract the Quotes and Values and send them inside a NSNotification
let quotes:NSArray = ((jsonDict.objectForKey("query") as! NSDictionary).objectForKey("results") as! NSDictionary).objectForKey("quote") as! NSArray
dispatch_async(dispatch_get_main_queue(), {
NSNotificationCenter.defaultCenter().postNotificationName(kNotificationStocksUpdated, object: nil, userInfo: [kNotificationStocksUpdated:quotes])
})
}
}
}
})
can someone please help. Thank you.
You problem could be this line of code in the catch block.
let quotes:NSArray = ((jsonDict.objectForKey("query") as! NSDictionary).objectForKey("results") as! NSDictionary).objectForKey("quote") as! NSArray
In the above statement jsonDict is out of scope. You declared jsonDict in the do block but are trying to use it in the catch block.
Try Following:-
(Assuming JSON has a root node structure)
let urlRequest: NSMutableURLRequest = NSMutableURLRequest(URL: yourURL)
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithRequest(urlRequest) {
(data, response, error) -> Void in
let httpResponse = response as! NSHTTPURLResponse
let statusCode = httpResponse.statusCode
if (statusCode == 200) {
do{
let json = try NSJSONSerialization.JSONObjectWithData(data!, options:.AllowFragments)
if let queries = json["query"] as? [[String: AnyObject]] {
for query in queries {
if let quote = query["quote"] as? String {
self.quoteArr.append(quote)
}
}//for loop
dispatch_async(dispatch_get_main_queue(),{
// your main queue code
NSNotificationCenter.defaultCenter().postNotificationName(kNotificationStocksUpdated, object: nil, userInfo: [kNotificationStocksUpdated:quotes])
})//dispatch
}// if loop
}
catch
{
print("Error with Json: \(error)")
}
}
else
{
// No internet connection
// Alert view controller
// Alert action style default
}
this cobweb of code is exactly why SwiftyJSON library exists. I recommend it highly, it can be imported into your project using cocoapods.
using this library the resultant code would be
jsonQuery["query"]["results"]["quote"]
which is more readable and as you implement more APIs, much faster.

Error while parsing JSON in swift 2.0

I am trying to download a list of articles and insert it into a table view. However I seem to be having an issue retrieving the JSON file and parsing it.
My code is as follows:
override func viewDidLoad() {
super.viewDidLoad()
self.downloadArticles()
self.tableView.reloadData()
}
func downloadArticles(){
var url: NSURL
url = NSURL(string: "https://query.yahooapis.com/v1/public/yql?q=select%20*%20from%20feed%20where%20url=%27www.abc.net.au%2Fnews%2Ffeed%2F51120%2Frss.xml%27&format=json")!
print(url)
let task = NSURLSession.sharedSession().dataTaskWithURL(url){
(data, response, error) in
if (error != nil){
print("Error \(error)")
} else{
self.parseArticleJSON(data!)
}
self.syncCompleted = true
self.tableView.reloadData()
}
task.resume()
}
func parseArticleJSON(articleJSON:NSData)
{
do{
let result = try NSJSONSerialization.JSONObjectWithData(articleJSON, options: NSJSONReadingOptions.MutableContainers) as? NSArray
//let jsonData:NSArray = (try NSJSONSerialization.JSONObjectWithData(articleJSON, options:NSJSONReadingOptions.MutableContainers) as? NSArray)!
let newArticlesArray = result as NSArray!
//NSLog("Found \(newArticlesArray.count) new articles!")
for article in (newArticlesArray as NSArray as! [NSDictionary])
{
print (article.objectForKey("title")! as? String)
//let a = Article (t: <#T##String#>, da: <#T##String#>, de: <#T##String#>, i: <#T##NSURL#>)
//articlesArray.addObject(a);
}
}catch {
print("JSON Serialization error")
}
}
In the parseArticleJSON method (I know it is not all completely finished). I get the error at line:
for article in (newArticlesArray as NSArray as! [NSDictionary])
it says:
fatal error: unexpectedly found nil while unwrapping an Optional value
I have tried doing some research here on these forums, but I was unable to find any response that would be of help to me so I was wondering if somebody would be able to help me.
I need to use the native swift JSON methods to do all this.
Thanks in advance!
The JSON is much more nested:
typealias JSONDictionary = Dictionary<String,AnyObject>
func parseArticleJSON(articleJSON:NSData) {
do {
let jsonObject = try NSJSONSerialization.JSONObjectWithData(articleJSON, options: [])
if let jsonResult = jsonObject as? JSONDictionary,
query = jsonResult["query"] as? JSONDictionary,
results = query["results"] as? JSONDictionary,
newArticlesArray = results["item"] as? [JSONDictionary] {
for article in newArticlesArray {
print(article["title"] as! String)
}
}
} catch let error as NSError {
print(error)
}
}
For that deeply nested JSON it's recommended to use a library like SwiftyJSON.
Since the code is only reading the JSON object, the option MutableContainers is not needed at all and in Swift always use native collection types unless you have absolutely no choice.
try this code,
if let jsonObject: AnyObject = NSJSONSerialization.JSONObjectWithData(articleJSON, options: nil, error:&error) {
if let dict = jsonObject as? NSDictionary {
println(dict)
} else {
println("not a dictionary")
}
} else {
println("Could not parse JSON: \(error!)")
}
hope its helpful

See any risks / potential crashes in this code? Would like to improve it

After releasing my app and putting Crashlytics in, I noticed i'm getting a few crashes each day in a particular area of code. I wanted to see if the community could help me discern it, as the data gathered isn't helpful enough to identify what's going on.
I wasn't able to see this during my testing and with the 25 beta testers, but now that I have several thousand users it's a problem each day.
Basically the idea is to send a request out to grab some content (as a JSON array) and then pull it into the phone.
I went a bit more generic in approach, so hopefully I made a few mistakes that others can find? Thanks so much!
This makes a request out, gets an array, and then saves. I see where I could put a try/catch, but other than that not sure why it could crash. Maybe the server i'm on occasionally times out or something else happens?
Crashlytics seems to suggest it's on this line:
NSJSONSerialization.JSONObjectWithData(data!, options: .MutableContainers)
0x00000001001117c4
_TFZFC118MyProxy8GetAsyncFMS0_U_S_14Deserializable__FTGCS_10RequestQ__8callbackFGCS_11ResponseGSaQ0___T__T_U_FTGSQCSo6NSData_GSQCSo13NSURLResponse_GSQCSo7NSError__T_
(MyProxy.swift:141)
Ok here are the proxy functions:
class func GetAsync<R, T: Deserializable>(request: Request<R>, callback: (Response<Array<T>>) -> ())
{
var list = Array<T>()
var response = Response<Array<T>>()
let serverRequest = NSMutableURLRequest(URL: NSURL(string: API_URL + request.Url)!)
serverRequest.HTTPMethod = "GET"
let task = NSURLSession.sharedSession().dataTaskWithRequest(serverRequest,
completionHandler: {
data, r, error in
if error != nil {
response.Status = .ERROR
} else {
let responseArray = try! NSJSONSerialization.JSONObjectWithData(data!, options: .MutableContainers) as! NSArray
//Append each item from the array to a list
for item: AnyObject in responseArray {
let row = T(dict: item as! NSDictionary)
list.append(row)
}
response = Response<Array<T>>(status: ResponseCode.OK, value: list)
}
}
callback(response)
})
task.resume()
}
And for the cases where I retrieve a single JSON item from my server, I wrote a similar one (the callback is for a single object, not an array:
class func GetAsync<R, T: Deserializable>(request: Request<R>, callback: (Response<T>) -> ())
{
var response = Response<T>()
let serverRequest = NSMutableURLRequest(URL: NSURL(string: API_URL + request.Url)!)
serverRequest.HTTPMethod = "GET"
let task = NSURLSession.sharedSession().dataTaskWithRequest(serverRequest,
completionHandler: {
data, r, error in
if error != nil {
response.Status = .ERROR
} else {
var responseStatus: ResponseCode = .NO_CODE
let responseObject: AnyObject?
do {
responseObject = try NSJSONSerialization.JSONObjectWithData(data!, options: .MutableContainers)
} catch _ {
responseObject = nil
}
if (responseObject != nil) {
let responseDictionary = responseObject as! NSDictionary
let code = responseDictionary["code"] as! String?
if (code != nil) {
let c = ResponseCode(rawValue: code!)
if (c != nil) {
responseStatus = c!
}
}
response = Response<T>(status: responseStatus, value: T(dict: responseDictionary))
}
}
callback(response)
})
task.resume()
}
See any bad coding practices or areas I can improve on? Thank you!
At least this line:
let responseArray = try! NSJSONSerialization.JSONObjectWithData(data!, options: .MutableContainers) as! NSArray
should be changed to something like:
do {
if let data = data, let responseArray = try NSJSONSerialization.JSONObjectWithData(data, options: []) as? NSArray {
// use responseArray here
} else {
// the data was nil or the JSON wasn't an array
}
} catch {
print(error)
}
to avoid crashing with nil data or bad JSON.

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