I have this Swift code:
func getData(completion: (array: Array<AnyObject>)->Void) {
UIApplication.sharedApplication().networkActivityIndicatorVisible = true
let session: NSURLSession = NSURLSession(configuration: sessionConfiguration)
var request: NSURLRequest = NSURLRequest(URL: NSURL(string: "http://coinabul.com/api.php"))
let task: NSURLSessionDataTask = session.dataTaskWithRequest(request, completionHandler: {(data, response, error) in
if !error {
let httpResp: NSHTTPURLResponse = response as NSHTTPURLResponse
if httpResp.statusCode == 200 {
var json: NSDictionary = self.convertDataToJSON(data)
var myArray: Array = Array<Currency>()
for (key: AnyObject, object: AnyObject) in json {
let info: NSDictionary = object as NSDictionary
let grams: CGFloat = info["Grams"] as CGFloat
let ounces: CGFloat = info["Ounces"] as CGFloat
let usd: CGFloat = info["USD"] as CGFloat
// Do something with those values
}
}
}
})
task.resume()
}
func convertDataToJSON(data: NSData) -> NSDictionary {
var error: NSError?
var jsonDictionary: NSDictionary = NSJSONSerialization.JSONObjectWithData(data, options: .MutableContainers, error: &error) as NSDictionary
return jsonDictionary
}
The "json" NSDictionary is correct. If I print it:
{
BTC = {
Grams = "15.398340412292";
Ounces = "0.49506814017307";
SilverGrams = "1010.0959168583";
SilverOunces = "32.475337832916";
USD = "624.825500000000";
};
Gold = {
Grams = "0.06494206341493";
Ounces = "2.0199239627704";
USD = "1262.100000000000";
};
Silver = {
Grams = "0.0009900049917623401";
Ounces = "0.030792597293164";
USD = "19.240000000000";
};
}
I can do json["Silver"] and get the corresponding object. But if I try to do json["Silver"]["USD"] my app crashes.
What am I missing here?
The expression:
json["Silver"]
returns AnyObject? which doesn't allow [] because it's unknown what it is, so you have to typecast it for further reference. Note that Xcode will currently crash if you try to do this in a workspace
json["Silver"] as? Dictionary
You can string it all together using optional chaining, as:
(json["Silver"] as? NSDictionary)?["USD"]
Note that the result of indexing operations are almost always optionals, and optional chaining means this one is definitely going to be optional, so you'll have to deal with checking and unwrapping.
Related
I am using following Class to receive data from an external database:
import Foundation
protocol HomeModelProtocal: class {
func itemsDownloaded(items: NSArray)
}
class HomeModel: NSObject, NSURLSessionDataDelegate {
//properties
weak var delegate: HomeModelProtocal!
var data : NSMutableData = NSMutableData()
var mi_movil: String = ""
let misDatos:NSUserDefaults = NSUserDefaults.standardUserDefaults()
var urlPath: String = "http:...hidden here.."
let parametros = "?id="
func downloadItems() {
mi_movil = misDatos.stringForKey("ID_IPHONE")!
print ("mi_movil en HOMEMODEL:",mi_movil)
urlPath = urlPath + parametros + mi_movil
let url: NSURL = NSURL(string: urlPath)!
var session: NSURLSession!
let configuration = NSURLSessionConfiguration.defaultSessionConfiguration()
print ("LA URL ES: ",url)
session = NSURLSession(configuration: configuration, delegate: self, delegateQueue: nil)
let task = session.dataTaskWithURL(url)
task.resume()
}
func URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask, didReceiveData data: NSData) {
self.data.appendData(data);
}
func URLSession(session: NSURLSession, task: NSURLSessionTask, didCompleteWithError error: NSError?) {
if error != nil {
print("Failed to download data")
}else {
print("Data downloaded")
self.parseJSON()
}
}
func parseJSON() {
var jsonResult: NSMutableArray = NSMutableArray()
do{
jsonResult = try NSJSONSerialization.JSONObjectWithData(self.data, options:NSJSONReadingOptions.AllowFragments) as! NSMutableArray
} catch let error as NSError {
print(error)
}
var jsonElement: NSDictionary = NSDictionary()
let locations: NSMutableArray = NSMutableArray()
for(var i = 0; i < jsonResult.count; i++)
{
jsonElement = jsonResult[i] as! NSDictionary
print (jsonElement)
let location = MiAutoModel()
//the following insures none of the JsonElement values are nil through optional binding
if let id_mis_autos = jsonElement["id_mis_autos"] as? String,
let modelo = jsonElement["modelo"] as? String,
let ano = jsonElement["ano"] as? String,
let id_movil = jsonElement["id_movil"] as? String
{
location.id_mis_autos = id_mis_autos
location.modelo = modelo
location.ano = ano
location.id_movil = id_movil
}
locations.addObject(location)
}
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.delegate.itemsDownloaded(locations)
})
}
}
If there are received data, it works fine but if there are no data an exception is thrown:
Could not cast value of type '__NSArray0' (0x1a0dd2978) to 'NSMutableArray' (0x1a0dd3490)
What should I change to detect if there are no data to avoid the exception?
Since you don't seem to be modifying jsonResult anywhere, the obvious choice is to make it an NSArray instead of an NSMutableArray, and change the downcasting to match that.
I'm not sure why you're using NSDictionary and NSMutableArray but this is how I would do it:
for result in jsonResult {
guard let jsonElement = result as? [String:AnyObject] else { return }
let locations: [MiAutoModel] = []
let location = MiAutoModel()
//the following insures none of the JsonElement values are nil through optional binding
let id_mis_autos = jsonElement["id_mis_autos"] as? String ?? ""
let modelo = jsonElement["modelo"] as? String ?? ""
let ano = jsonElement["ano"] as? String ?? ""
let id_movil = jsonElement["id_movil"] as? String ?? ""
location.id_mis_autos = id_mis_autos
location.modelo = modelo
location.ano = ano
location.id_movil = id_movil
locations.append(location)
}
You might have to change some of the code depending on your situation.
My problem arises when I want to populate data from my mysql database into a class object. I am trying to return an array of objects and it returns nil and then it fills itself somehow. How can I make it fill before returning the blank array?
Here is my code and a screenshot of code output
import Foundation
class Research
{
var mainResearchImageURL:String = ""
var userProfileImageURL:String = ""
var caption:String = ""
var shortDescription:String = ""
init(mainResearchImageURL :String, userProfileImageURL:String, caption:String, shortDescription:String)
{
self.mainResearchImageURL = mainResearchImageURL
self.userProfileImageURL = userProfileImageURL
self.caption = caption
self.shortDescription = shortDescription
}
class func downloadAllResearches()->[Research]
{
var researches = [Research]()
let urlString = "http://localhost/test/index.php"
let request = NSMutableURLRequest(URL: NSURL(string: urlString)!)
request.HTTPMethod = "POST"
let postString = "action=listresearches"
request.HTTPBody = postString.dataUsingEncoding(NSUTF8StringEncoding)
let task = NSURLSession.sharedSession().dataTaskWithRequest(request, completionHandler: {data, response, error in
if (error == nil) {
do {
let json = try NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.MutableContainers) as? NSArray
//let dictionary = json!.firstObject as? NSDictionary
var counter:Int = 0;
for line in json!{
let researchData = line as! NSDictionary
let researchLineFromData = Research(mainResearchImageURL: researchData["research_mainImageURL"] as! String, userProfileImageURL: researchData["research_creatorProfileImageURL"] as! String, caption: researchData["research_caption"] as! String, shortDescription: researchData["research_shortDescription"] as! String)
researches.append(researchLineFromData) //researches bir dizi ve elemanları Research türünde bir sınıftan oluşuyor.
counter += 1
print ("counter value \(counter)")
print("array count in loop is = \(researches.count)")
}
}catch let error as NSError{
print(error)
}
} else {
print(error)
}})
task.resume()
print("array count in return is = \(researches.count)")
return researches
}
}
And this is the output:
add this on you completionHandler ( it works if you update a view)
dispatch_async(dispatch_get_main_queue(), {
if (error == nil) { ...... }
})
Advice 1:
return the task and use a completion param in your method,
you can cancel the task if it's too slow.
Advice 2 :
Use alamofire and swiftyJson framework
What happen here is that you are returning the value before finish (remember that the call is Asynchronous), you can make something like this:
class func downloadAllResearches(success:([Research])->Void,failure:(String)->Void)
{
var researches = [Research]()
let urlString = "http://localhost/test/index.php"
let request = NSMutableURLRequest(URL: NSURL(string: urlString)!)
request.HTTPMethod = "POST"
let postString = "action=listresearches"
request.HTTPBody = postString.dataUsingEncoding(NSUTF8StringEncoding)
let task = NSURLSession.sharedSession().dataTaskWithRequest(request, completionHandler: {data, response, error in
if (error == nil) {
do {
let json = try NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.MutableContainers) as? NSArray
//let dictionary = json!.firstObject as? NSDictionary
var counter:Int = 0;
for line in json!{
let researchData = line as! NSDictionary
let researchLineFromData = Research(mainResearchImageURL: researchData["research_mainImageURL"] as! String, userProfileImageURL: researchData["research_creatorProfileImageURL"] as! String, caption: researchData["research_caption"] as! String, shortDescription: researchData["research_shortDescription"] as! String)
researches.append(researchLineFromData) //researches bir dizi ve elemanları Research türünde bir sınıftan oluşuyor.
counter += 1
print ("counter value \(counter)")
print("array count in loop is = \(researches.count)")
}
success(researches)
}catch let error as NSError{
print(error)
failure("Can be extract from NSERROR")
}
} else {
print(error)
failure("Error - Can be extract for NSERROR")
}})
task.resume()
}
And for call this Fuction use something like this:
Research.downloadAllResearches({ (objects:[Research]) -> Void in
dispatch_async(dispatch_get_main_queue(), { () -> Void in
//Do whatever you like with the content
})
}) { (failureLiteral:String) -> Void in
}
I am trying the get the preview link from the itunes search api. I have a function that will get the information when the button is clicked, but the function is not being called. I want to get the preview link of the first object in the results array How do I fix this?
var d: NSMutableData = NSMutable Data()
var tData: NSArray = NSArray()
func ctn(receivedResponse: NSURLConnection!, receivedResponse response: NSURLResponse!) {
// clear out the data object if a new request was received.
self.d = NSMutableData()
}
func ctn(ctn: NSURLConnection!, receivedData d: NSData!) {
self.d.appendData(d)
}
func ctnFinishedLoading(ctn: NSURLConnection!) throws {
var err: NSError
var jResult: NSDictionary = try NSJSONSerialization.JSONObjectWithData(d, options: NSJSONReadingOptions.MutableContainers) as! NSDictionary
if jResult.count>0 && jResult["results"]!.count>0 {
var results: NSArray = jResult["results"]as! NSArray
self.tData = results
print(tData)
//self.appsTableView.reloadData()
}
}
func playit(sender: UIButton!) {
let cell = table.dequeueReusableCellWithIdentifier("cell")
let playButtonrow = sender.tag
print(ret[playButtonrow])
let searchTerm: String = ret[playButtonrow]
let itunesSearchTerm = searchTerm.stringByReplacingOccurrencesOfString(" ", withString: "+", options: NSStringCompareOptions.CaseInsensitiveSearch, range: nil)
if let escapedSearchTerm = itunesSearchTerm.stringByAddingPercentEscapesUsingEncoding(NSUTF8StringEncoding) {
let urlPath = "https://itunes.apple.com/search?term=\(escapedSearchTerm)&media=music"
let url: NSURL = NSURL(string: urlPath)!
let request: NSURLRequest = NSURLRequest(URL: url)
let ctn: NSURLConnection = NSURLConnection(request: request, delegate: self, startImmediately: false)!
print("Search iTunes API at URL \(url)")
ctn.start()
}
The way to grab the previewUrl is to grab the value for a single result array member such as the first one having index 0 and then pulling the value for the previewUrl key. For example:
if jResult.count>0 && jResult["results"]!.count>0 {
var results: NSArray = jResult["results"]as! NSArray
if let previewUrl = results[0]["previewUrl"] {
print(previewUrl!)
}
}
Since NSURLConnection is deprecated in iOS 9, I'll also give you a way of using NSURLSession to retrieve the previewUrls from the iTunes API.
let urlPath = "https://itunes.apple.com/search?term=\(escapedSearchTerm)&media=music"
let url: NSURL = NSURL(string: urlPath)!
let task = NSURLSession.sharedSession().dataTaskWithURL(url) {(data, response, error) -> Void in
do {
if let dict: NSDictionary = try NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.MutableContainers) as? NSDictionary
{
// The search is complete.
for result in dict["results"] as! NSArray {
if let previewUrl = result["previewUrl"] {
print(previewUrl!)
}
}
}
} catch let jsonError as NSError {
// Handle error.
}
}
task.resume()
So I have JSON data that is formatted as a list of dictionaries, and I have it stored as an NSArray Object, but I'm unsure how to convert each entry into a dictionary object when it is currently AnyObject
The AnyObject data is already formatted like a JSON dictionary
Here is the code I used to create the Array
func startConnection(){
let urlPath: String = "http://api.mtgdb.info/search/omni"
var url: NSURL = NSURL(string: urlPath)!
var request: NSURLRequest = NSURLRequest(URL: url)
var connection: NSURLConnection = NSURLConnection(request: request, delegate: self, startImmediately: false)!
connection.start()
}
func connection(connection: NSURLConnection!, didReceiveData data: NSData!){
self.data.appendData(data)
}
func connectionDidFinishLoading(connection: NSURLConnection!){
var err: NSError
var jsonResult: NSArray = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers, error: nil) as! NSArray
for var i = 0; i<jsonResult.count; ++i{
...
}
}
I tried this sample code to solve your problem.
First of all run this "http://api.mtgdb.info/search/omni" URL in web browser and copy response then paste into "http://jsonlint.com", response is valid and I get array of 8 dictionaries, Like id: 37113, 39932, 83737, 106426, 228247, 288937, 382286, 386302 -- 8 data.
In Objective C, It works perfect and I get same result as web browser.
But in Swift, It behave weird, Can't parse whole respose, get only half dictionary as object of array. Only get this much part of response,
Printing description of jsonResult:
(
{
artist = "Arnie Swekel";
cardSetId = JUD;
cardSetName = Judgment;
colors = (
green,
white
);
convertedManaCost = 7;
description = "Trample\nPhantom Nishoba enters the battlefield with seven +1/+1 counters on it.\nWhenever Phantom Nishoba deals damage, you gain that much life.\nIf damage would be dealt to Phantom Nishoba, prevent that damage. Remove a +1/+1 counter from Phantom Nishoba.";
flavor = "";
formats = (
{
legality = Legal;
name = "Odyssey Block";
},
{
legality = Legal;
name = Legacy;
},
{
legality = Legal;
name = Vintage;
},
{
legality = Legal;
name = Freeform;
},
{
legal
I tried this sample of code
class ViewController: UIViewController, NSURLConnectionDelegate {
var data:NSMutableData!
var arrvehicls:NSMutableArray!
override func viewDidLoad() {
super.viewDidLoad()
self.data = NSMutableData()
self.arrvehicls = NSMutableArray()
self.startConnection()
}
func startConnection(){
let urlPath: String = "http://api.mtgdb.info/search/omni"
var url: NSURL = NSURL(string: urlPath)!
var request: NSURLRequest = NSURLRequest(URL: url)
var connection: NSURLConnection = NSURLConnection(request: request, delegate: self, startImmediately: false)!
connection.start()
}
func connection(connection: NSURLConnection!, didReceiveData data: NSData!){
self.data.appendData(data)
}
func connectionDidFinishLoading(connection: NSURLConnection!) {
var err: NSError
var jsonResult:NSArray = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers, error: nil) as! NSArray
for var i = 0; i<jsonResult.count; ++i {
var dictResult = jsonResult.objectAtIndex(i) as! NSDictionary
var vehicleInfo = Vehicle()
vehicleInfo.id = dictResult.valueForKey("id") as! Int
vehicleInfo.artist = dictResult.valueForKey("artist") as! String
vehicleInfo.cardID = dictResult.valueForKey("cardSetId") as! String
vehicleInfo.cardName = dictResult.valueForKey("cardSetName") as! String
vehicleInfo.colors = dictResult.valueForKey("colors") as! NSArray
vehicleInfo.details = dictResult.valueForKey("description") as! String
vehicleInfo.flavour = dictResult.valueForKey("flavor") as! String
vehicleInfo.formats = NSMutableArray()
var arr = dictResult.valueForKey("formats") as! NSArray
for var j = 0; j<arr.count; ++i {
var dictFormats = arr.objectAtIndex(i) as! NSDictionary
var formats = Formats()
formats.legality = dictFormats.valueForKey("legality") as! String
formats.name = dictFormats.valueForKey("name") as! String
vehicleInfo.formats.addObject(formats)
}
self.arrvehicls.addObject(vehicleInfo)
}
}
}
I am creating a weather app with Swift. So I have retrieved the JSON data and stored it in a dictionary:
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
///////getting URL:
let mainAddress = NSURL(string: "https://...") //for NY
//Now, getting the data syncronously by creating a session object::
let sharedSession = NSURLSession.sharedSession()
let downloadTask: NSURLSessionDownloadTask =
sharedSession.downloadTaskWithURL(mainAddress!, completionHandler: {
(location:NSURL!, response:NSURLResponse!, error: NSError!) -> Void in
//using the if statement to avoid crashing when the URL is wrong.
if error == nil {
//Now, creating a dataObject for the task:
let dataObject = NSData(contentsOfURL: location)
//getting a formated dictionary of the data from URL:
let weatherDictionary: NSDictionary = NSJSONSerialization.JSONObjectWithData(dataObject!, options: nil, error: nil) as NSDictionary //added '!' to NSdata for now
}
})
downloadTask.resume()
I have used a Struct, in a difirent file, in order to organize and initialize the dictionary's data:
import Foundation
import UIKit
import WatchKit
//created the struct just to better organize the data. In the future, if the API keys change, it would be easier to ajust the code, rather than if the data was directly read from the API onto the graph.
struct hourlyData {
///declaring only keys that have Integers as value.
var daylyPop0 : Int
var daylyPop1 : Int
var daylyPop2 : Int
var daylyPop3 : Int
var daylyPop4 : Int
var summaryNowDay : String
var summaryNowNight : String
var iconNow : String
var currentTime: String?
//Initializing the values here. With optional properties:
init(weatherDictionary:NSDictionary){
daylyPop0 = weatherDictionary["daily0_pop"] as Int
daylyPop1 = weatherDictionary["daily1_pop"] as Int
daylyPop2 = weatherDictionary["daily4_pop"] as Int
daylyPop3 = weatherDictionary["daily3_pop"] as Int
daylyPop4 = weatherDictionary["daily2_pop"] as Int
}
Now, I am implementing a chart for it. So I need to access the values from the dictionary in order to implement them on the chart. However, I've been unsuccessfull after many attemps.
The code lets me access the hourlyData struct, but not the weatherDictionary, since it was declared inside the session declaration.
Anyone knows an effective way to do it?
Any tips would be appreciated, thanks.
Try this for asynchronous request:
var request = NSURLRequest(URL: url)
var dict = NSDictionary()
var yourSavedData = hourlyData()
NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue()) { (response, data, error) -> Void in
if data == nil
{
println("Error in connection")
return
}
var error = NSErrorPointer()
dict = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers, error: error) as NSDictionary
if error != nil
{
println(error.debugDescription)
return
}
NSOperationQueue.mainQueue().addOperationWithBlock({ () -> Void in
if let yourDict = dict as? NSDictionary
{
yourSavedData = hourlyData(yourDict!)
}
}
})
Not tested, but should work.
You need to use if let to parse dictionary.
Alright, so after the answers you guys posted I've updated the code a little. This is how the viewDidLoad looks like:
override func viewDidLoad() {
super.viewDidLoad()
///////getting URL:
let url = NSURL(string: "http://..........") //for NY
var request = NSURLRequest(URL: url!)
var dict = NSDictionary()
var yourSavedData = hourlyData(weatherDictionary: NSDictionary())
NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue()) { (response, data, error) -> Void in
if data == nil
{
println("Error in connection")
return
}
var error = NSErrorPointer()
dict = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers, error: error) as! NSDictionary
if error != nil
{
println(error.debugDescription)
return
}
NSOperationQueue.mainQueue().addOperationWithBlock({ () -> Void in
if let yourDict = dict as? NSDictionary
{
yourSavedData = hourlyData(weatherDictionary: yourDict)
}
}
})
And this is how the other swift file with the Struct looks like:
struct hourlyData {
///declaring only keys that have Integers as value.
var daylyPop0 : Int
var daylyPop1 : Int
var daylyPop2 : Int
var daylyPop3 : Int
var daylyPop4 : Int
var summaryNowDay : String
var summaryNowNight : String
var iconNow : String
var currentTime: String?
//Initializing the values here. With optional properties:
init(weatherDictionary:NSDictionary){
daylyPop0 = weatherDictionary["hourly10_pop"] as! Int
daylyPop1 = weatherDictionary["hourly11_pop"] as! Int
daylyPop2 = weatherDictionary["hourly12_pop"] as! Int
daylyPop3 = weatherDictionary["hourly13_pop"] as! Int
daylyPop4 = weatherDictionary["hourly14_pop"] as! Int
summaryNowDay = weatherDictionary["today_day_fcttext_metric"] as! String
summaryNowNight = weatherDictionary["today_night_fcttext_metric"] as! String
iconNow = weatherDictionary["current_icon"] as! String
let currentTimeIntValue = weatherDictionary["forecast_time"] as! Int
currentTime = dateStringFromUnixTime(currentTimeIntValue)
}
//Converting unixTime to a desired style:::used ShortStyle in this case:
func dateStringFromUnixTime(unixTime: Int) -> String{
let timeInSeconds = NSTimeInterval(unixTime)
let weatherDate = NSDate(timeIntervalSince1970: timeInSeconds)
let dateFormatter = NSDateFormatter()
dateFormatter.timeStyle = .ShortStyle
return dateFormatter.stringFromDate(weatherDate)
}
}
Now the code looks fine, and it does not show any error, besides a warning under the 'if let', which says: Conditional cast from 'NSDictionary' to 'NSDictionary' always succeeds.
When I run the simulator, it crashes and displays: fatal error: unexpectedly found nil while unwrapping an Optional value. Highlighting, in green, the code line: daylyPop0 = weatherDictionary["hourly10_pop"] as! Int