I'm trying to build an IOS App for my php/mysql website. The problem is I can't get the following code working with my created JSON output. But the same code works with other APIs like :
http://www.telize.com/geoip
Here is my code:
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
// Playground - noun: a place where people can play
let urlPath = "https://www.example.com/API_OUT.php?API_KEY=785d...e5f5"
let url: NSURL = 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?
var jsonResult = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers, error: &err) as NSDictionary
if(err != nil) {
// If there is an error parsing JSON, print it to the console
println("JSON Error \(err!.localizedDescription)")
} else {
println(jsonResult)
}
})
task.resume()
}
}
This JSON is an array, not a dictionary (unlike your original JSON, which was a dictionary). Thus your assignment of this new JSON to a dictionary will fail.
I would suggest (a) use NSArray rather than NSDictionary; and (b) use if let ... syntax rather than just let, to gracefully handle these errors in the future.
Thus, that yields:
var parseError: NSError?
if let jsonResult = NSJSONSerialization.JSONObjectWithData(data, options: nil, error: &parseError) as? NSArray {
println(jsonResult)
} else {
// If there is an error parsing JSON, print it to the console
println("JSON Error \(parseError?.localizedDescription)")
}
Related
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)
}
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let url = NSURL(string: "https://api.forecast.io/forecast/MYKEYHERE/")
let session = NSURLSession.sharedSession()
let task: NSURLSessionDownloadTask = session.downloadTaskWithURL(url!, completionHandler: { (location: NSURL!, response: NSURLResponse!, error: NSError!) -> Void in
if error == nil {
let data = NSData(contentsOfURL: location)
let json: NSDictionary = NSJSONSerialization.JSONObjectWithData(data!, options: nil, error: nil) as! NSDictionary!
println(json)
}
})
task.resume()
}
This is code for a download task to a weather API. Just wondering why I am getting the error:
Thread 6: EXC_BAD_INSTRUCTION(code=EXC_1386_INVOP, subcode=0x0).
Thanks a lot.
You're getting this error because the response is not JSON (or the JSON is not a dictionary). So, when parsing the JSON, use optional binding to gracefully handle nil or non-dictionary errors, perhaps examining the body of response if it fails, e.g.:
let task = session.downloadTaskWithURL(url!) { location, response, error in
if error == nil {
let data = NSData(contentsOfURL: location)
var error: NSError?
if let json = NSJSONSerialization.JSONObjectWithData(data!, options: nil, error: &error) as? NSDictionary {
println("json = \(json)")
} else {
println("error = \(error)")
let responseString = NSString(data: data!, encoding: NSUTF8StringEncoding)
println("not json; responseString = \(responseString)")
println(response)
}
}
}
task.resume()
Also, note, when using JSONObjectWithData, you not only want to gracefully check for an error, but you generally want to use the error parameter, too, as noted above.
BTW, make sure you include the latitude and longitude in the URL as described in the forecast.io API documentation, or else you'll get a non-JSON error response. Even if you fix the URL to avoid this error, you should still implement some graceful handling of errors, like above, or else your app might crash whenever there are any server issues.
Hi,
I'm trying to parse data on to the listview and am able to get it and
display it on the tableView but the problem is it is taking hell lot
of time to display it on the tableview. Please find the my code below.
func jsonParsing()
{
activityIndicatorView.startAnimating()
let session = NSURLSession.sharedSession()
let request = NSMutableURLRequest (URL: deviceListURL)
request.HTTPMethod = "GET"
let task = session.dataTaskWithRequest(request, completionHandler: {data, response, error -> Void in
if error != nil {
// If there is an error in the web request, print it to the console
println(error.localizedDescription)
}
var err: NSError?
if(data != nil) {
var jsonResult = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers, error: &err) as! NSMutableArray
//println("Data: \(jsonResult)")
var dataDict: NSDictionary
for dataDict : AnyObject in jsonResult {
var device_id: NSString = dataDict.objectForKey("deviceId") as! NSString
var device_name: NSString = dataDict.objectForKey("deviceName") as! NSString
var device_status: NSInteger = dataDict.objectForKey("status") as! NSInteger
let dictionary = [self.deviceID: device_id, self.deviceName: device_name, self.Status: device_status]
self.myObject.addObject(dictionary)
}
println("My object = %#", self.myObject)
println(self.myObject.count)
if self.myObject.count != 0 {
self.reloadTable()
}
}
if err != nil {
// If there is an error parsing JSON, print it to the console
println("JSON Error \(err!.localizedDescription)")
}
})
task.resume()
}
The completion handler is running in a background queue, not the main thread. UI updates have to happen on the main thread, though.
Try calling reloadTable() on the main thread:
dispatch_sync(dispatch_get_main_queue(), {
self.reloadTable()
})
(I just typed this in here untested, so I hope it works this way)
I am trying to create a dictionary from data that is held in a server, I receive the data but I cannot convert the data to an NSDictionary, I believe it is held in an NSData Object
let JSONDictionary: Dictionary = NSJSONSerialization.JSONObjectWithData(JSONData!, options: nil, error: &error) as NSDictionary
This line of code is the one giving me the problem, it throws a BAD_EXEC_INSTRUCTION.
MY Question: How can I turn a JSON into an NSDictionary?
Your code does not do any error handling. But it can (and if this data comes from a web service, will) fail in multiple ways.
You have to make sure that your data object actually exists
You have to make sure that the data object can be converted to JSON
You have to make sure that the JSON actually contains a Dictionary
You should use Swifts conditional cast and it's optional binding capabilities.
The optional binding if let JSONData = JSONData checks that JSONData is not nil. The force unwrap (JSONData!) you use might crash if no data could be received.
The optional binding if let json = NSJSONSerialization.JSONObjectWithData checks if the data could be converted to a JSON object. The conditional cast as? NSDictionary checks if the JSON object is actually a dictionary. You currently don't use these checks, you cast the objects as NSDictionary. Which will crash, if the object is not valid json, or if its not a dictionary.
I would recommend something like this:
var error: NSError?
if let JSONData = JSONData { // Check 1
if let json: AnyObject = NSJSONSerialization.JSONObjectWithData(JSONData, options: nil, error: &error) { // Check 2
if let jsonDictionary = json as? NSDictionary { // Check 3
println("Dictionary received")
}
else {
if let jsonString = NSString(data: JSONData, encoding: NSUTF8StringEncoding) {
println("JSON String: \n\n \(jsonString)")
}
fatalError("JSON does not contain a dictionary \(json)")
}
}
else {
fatalError("Can't parse JSON \(error)")
}
}
else {
fatalError("JSONData is nil")
}
You could merge check 2 and 3 into one line and check if NSJSONSerialization can create a NSDictionary directly:
var error: NSError?
if let JSONData = JSONData { // Check 1.
if let JSONDictionary = NSJSONSerialization.JSONObjectWithData(JSONData, options: nil, error: &error) as? NSDictionary { // Check 2. and 3.
println("Dictionary received")
}
else {
if let jsonString = NSString(data: JSONData, encoding: NSUTF8StringEncoding) {
println("JSON: \n\n \(jsonString)")
}
fatalError("Can't parse JSON \(error)")
}
}
else {
fatalError("JSONData is nil")
}
Make sure to replace fatalError with appropriate error handling in your production code
Update for Swift 2.
Now you must use it inside a try catch block.
do {
let responseObject = try NSJSONSerialization.JSONObjectWithData(data, options: []) as! [String:AnyObject]
} catch let error as NSError {
print("error: \(error.localizedDescription)")
}
Here jsonResult will give you the response in NSDictionary:
let url = NSURL(string: path)
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?
var jsonResult = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers, error: &err) as NSDictionary
if(err != nil) {
// If there is an error parsing JSON, print it to the console
println("JSON Error \(err!.localizedDescription)")
}
})
task.resume()
Before i start, i have went through each and every question relating to this issue. didnt help.
I want to refresh the json contents of my tableView which are fetched from a website.
The URL for each category comes from a different file that has All the categories listed in it.
The code that fetches the content is this
func animalSelected(animal: Animal) {
var request: NSURLRequest = NSURLRequest(URL: animal.url!)
self.refresh(animal.url!)
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithURL(animal.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?
var jsonResult = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers, error: &err) as NSDictionary
if(err != nil) {
// If there is an error parsing JSON, print it to the console
println("JSON Error \(err!.localizedDescription)")
}
let results: NSArray = jsonResult["posts"] as NSArray
self.didReceiveResults(jsonResult)
})
task.resume()
delegate?.collapseSidePanels?()
}
func didReceiveResults(results: NSDictionary) {
var resultsArr: NSArray = results["posts"] as NSArray
dispatch_async(dispatch_get_main_queue(), {
self.tableData = resultsArr
self.tableView.reloadData()
})
}
My ViewDidLoad method and the refresh method is as shown below
override func viewDidLoad() {
super.viewDidLoad()
refresher = UIRefreshControl()
refresher.attributedTitle = NSAttributedString(string: "Pull to Refresh")
refresher!.addTarget(self, action: "refresh:", forControlEvents: UIControlEvents.ValueChanged)
self.tableView.addSubview(refresher)
}
func refresh(categoryUrl : NSURL) {
var request: NSURLRequest = NSURLRequest(URL: categoryUrl)
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithURL(categoryUrl, completionHandler: {data, response, error -> Void in
if(error != nil) {
// If there is an error in the web request, print it to the console
println(error.localizedDescription)
}
var err: NSError?
var jsonResult = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers, error: &err) as NSDictionary
if(err != nil) {
// If there is an error parsing JSON, print it to the console
println("JSON Error \(err!.localizedDescription)")
}
let results: NSArray = jsonResult["posts"] as NSArray
self.didReceiveResults(jsonResult)
})
task.resume()
self.refresher.endRefreshing()
}
I need to refresh the contents of the same URL that has been selected i.e refresh the same category.
I get this error : [UIRefreshControl absoluteURL]: unrecognized selector sent to instance 0x7fa0b3d73750
I think it is something related to absoluteURL. Bt don't know what is it..
plsss plssssss plssss help
You're trying to get the absoluteURL of a UIRefreshControl that you think is actually an NSURL.
To be exact, your sender in refresh(categoryUrl: NSURL) is the UIRefreshControl not NSURL.