For some reason the JSON object from parsing doesnt update after network calls to and api we built. I check the endpoint and now for a fact it updates right away. I have a timer being called every 10 sec to make the call but the parsed json doesnt update until after a minute or so. I have tried putting it on the main thread and that still doesnt work. Here is my code:
#objc func getLaunches() {
let simulator = UserDefaults.standard.string(forKey: self.launchSimulator)
if(simulator == self.password){
print("they are the same")
}
guard let launchUrl = URL(string: launchesURL) else {
return
}
let request = URLRequest(url: launchUrl)
DispatchQueue.main.async { [weak self] in
let task = URLSession.shared.dataTask(with: request, completionHandler: {
(data, response, error) -> Void in
if let error = error {
print(error)
return
}
// Parse JSON data
if let data = data {
self?.launches.removeAll()
self?.launches = (self!.parseJsonData(data: data))
let nextlaunch = self?.launches[0]
// Reload table view
self?.hours = nextlaunch?.time
self?.yearMonth = nextlaunch?.date
var fulltime = self?.yearMonth
fulltime!.insert("-", at: fulltime!.index(fulltime!.startIndex, offsetBy: 4))
fulltime!.insert("-", at: fulltime!.index(fulltime!.startIndex, offsetBy: 7))
fulltime = fulltime! + " "
fulltime = fulltime! + self!.hours
let fullFormatter = DateFormatter()
fullFormatter.dateFormat = "YYYY-MM-dd HH:mm"
fullFormatter.timeZone = TimeZone(abbreviation: "EST")
self?.launchDate = fullFormatter.date(from: fulltime!)
self?.getCountdown()
}
})
task.resume()
}
}
//parse launch info from json to dictionary into launches object
func parseJsonData(data: Data) -> [NextLaunch] {
var launches = [NextLaunch]()
do {
let jsonResult = try JSONSerialization.jsonObject(with: data, options:
JSONSerialization.ReadingOptions.allowFragments) as? NSDictionary
let jsonLaunches = jsonResult?["launches"] as! [NSDictionary]
for jsonLaunch in jsonLaunches {
let launch = NextLaunch()
launch.date = jsonLaunch["date"] as! String
launch.time = jsonLaunch["time"] as! String
if(launch.time == ""){
launch.time = "00:00"
}
launch.mission = jsonLaunch["mission"] as! String
launch.launchpad = jsonLaunch["launch_pad"] as! String
launch.image = jsonLaunch["image"] as! String
launch.delay = jsonLaunch["delayed"] as! String
//show delay image if it is delayed
if(launch.delay == "1"){
self.delayed()
}else{
self.notDelayed()
}
launches.append(launch)
}
} catch {
print(error)
}
return launches
}
You need
DispatchQueue.main.async {
self?.getCountdown()
}
As the response of URLSession.shared.dataTask(with: occurs in a background thread
Related
I am trying to add values from what
was printed in the json shown by my code
to specific arrays. For example I want
the lat / lng /name from the JSon file and
add it to an array. How would I go about doing this
? I am new to swift and would appreciate the help.
My Json code is in the comments! Thanks
func getBurritosInArea() {
let keyword:String = "burrito"
let urlString =
"https://maps.googleapis.com/maps/api/place/nearbysearch/json?location=\(latitude),\(longitude)&&fields=formatted_address,name,rating&radius=1500&type=restaurant&keyword=(\apikey)"
guard let request = URL(string:urlString) else { return }
let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
guard let data = data, error == nil,
let json = try? JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.mutableContainers),
let results = json as? [String: Any] else { //error handling
return
}
if let placeResults = results["results"] as? [[String: Any]]{
for placeResult in results{
print(placeResult)
}
}
}
}
}
json code:
viewport = {
northeast = {
lat = "40.74642527989272";
lng = "-73.97449797010727"; };
southwest = {
lat = "40.74372562010728";
lng = "-73.97719762989271"; };
};
};
icon = "maps.gstatic.com/mapfiles/place_api/icons/restaurant-71.png"; id = c884c332c5cc1578d0c1d4d7f4d4ad451358c9e6;
name = "Baby Bo's Cantina"; "opening_hours" = { "open_now" = 1; };
Hi Please try below code,
First Create an Array Named aryDict,
func getBurritosInArea()
{
guard let url = URL(string: "your url") else {return}
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
guard let dataResponse = data,
error == nil else {
print(error?.localizedDescription ?? "Response Error")
return }
do{
//here dataResponse received from a network request
let jsonResponse = try JSONSerialization.jsonObject(with:
dataResponse, options: []) as? NSArray
// print(jsonResponse?.value(forKey: STR_NAME) as Any )
let array = NSArray.init(array: jsonResponse ?? [])
var aryDict = NSMutableArray.init(array: array)
// self.tblView.reloadData()
print(aryDict)
} catch let parsingError {
print("Error", parsingError)
}
}
task.resume()
}
Guys I need assistance on swift 4.0
i have a simple function that calls a json and fills those vars with values.
self.getDataFromServer()
after 'self.getDataFromServer()' this will fill in the arrays with retrieved data. This was working great on the previous version of swift (swift 3).
This is the code after the self.getDataFromServer() and getting index out of range, because data isn't populated (PS: This was working on swift 3.0)
var totalAmount = [String]()
var remainingAmount = [String]()
self.getDataFromServer()
let newTotal = Int(totalAmount[0])! + Int(remainingAmount[0])!
let newRemaining = String(newTotal)
updateDailyData(newRemainingAmount: newRemaining, id: userID[0])
I'm getting error on 'newTotal' saying index out of range. Please Help.
I noticed that on swift 4, I'm facing this issue whenever i'm calling JSON.
The JSON Function is as below:
func getDataFromServer() {
let dateOfToday = Date()
let strDateOfToday = convertToString(myDate: dateOfToday)
let postString = "ANYTHING HERE";
let myUrl = URL(string: "ANYTHING HERE")
var request = URLRequest(url:myUrl!)
request.httpMethod = "POST"// Compose a query string
request.httpBody = postString.data(using: String.Encoding.utf8);
//Start the task
let task = URLSession.shared.dataTask(with: request) {
(data: Data?, response: URLResponse?, error: Error?) in
if error != nil
{
print("error=\(String(describing: error))")
return
}
let values = try! JSONSerialization.jsonObject(with: data! as Data, options: JSONSerialization.ReadingOptions.allowFragments) as! NSArray
let success = ((values.value(forKey: "success") as? [String])! as NSArray) as! [String]
if (success[0] == "1")
{
self.totalAmount = ((values.value(forKey: "totalAmount") as? [String])! as NSArray) as! [String]
self.remainingAmount = ((values.value(forKey: "remainingAmount") as? [String])! as NSArray) as! [String]
}
}
//Let's convert response sent from a server side script to a NSDictionary object:
task.resume()
}
Here is your perfect working solution using Closure
Swift 4
self.getDataFromServer { (arrTotalAmount, arrRemainingAmount) in
if arrTotalAmount.count > 0, arrRemainingAmount.count > 0 {
// Your code here
let newTotal = Int(totalAmount[0])! + Int(remainingAmount[0])!
let newRemaining = String(newTotal)
updateDailyData(newRemainingAmount: newRemaining, id: userID[0])
}
}
func getDataFromServer(completion: #escaping (_ arrTotalAmount: [String], _ arrRemainingAmount: [String]) -> Void) {
let dateOfToday = Date()
let strDateOfToday = convertToString(myDate: dateOfToday)
let postString = "ANYTHING HERE";
let myUrl = URL(string: "ANYTHING HERE")
var request = URLRequest(url:myUrl!)
request.httpMethod = "POST"// Compose a query string
request.httpBody = postString.data(using: String.Encoding.utf8);
//Start the task
let task = URLSession.shared.dataTask(with: request) { (data: Data?, response: URLResponse?, error: Error?) in
if error != nil
{
print("error=\(String(describing: error))")
return
}
let values = try! JSONSerialization.jsonObject(with: data! as Data, options: JSONSerialization.ReadingOptions.allowFragments) as! NSArray
let success = ((values.value(forKey: "success") as? [String])! as NSArray) as! [String]
if (success[0] == "1")
{
let totalAmount = ((values.value(forKey: "totalAmount") as? [String])! as NSArray) as! [String]
let remaininAmount = ((values.value(forKey: "remainingAmount") as? [String])! as NSArray) as! [String]
completion(totalAmount, remaininAmount)
}
}
//Let's convert response sent from a server side script to a NSDictionary object:
task.resume()
}
I can't really imagine this working in Swift 3, since the effects of your getDataFromServer function are asynchronous. More precisely, the function returns before the code inside the dataTask is executed.
Try something like this to execute code on the caller side after the dataTask has succeeded:
func getDataFromServer(onDone: () -> Void) {
...
let task = URLSession.shared.dataTask(with: request) { ... in
...
onDone()
}
task.resume()
}
And to call it:
var totalAmount = [String]()
var remainingAmount = [String]()
self.getDataFromServer {
let newTotal = Int(totalAmount[0])! + Int(remainingAmount[0])!
let newRemaining = String(newTotal)
updateDailyData(newRemainingAmount: newRemaining, id: userID[0])
}
Good morning, I have this block of code that I am using to get exchange rates from an API JSON file. I use this function to convert a result to different currencies. I am trying to add functionality to store the data so, when offline, it still shows a result with the last downloaded JSON file data.
I was thinking maybe use NSUserdefault? but not too sure where...
I am already using this to check if online or not.
if Reachability.isConnectedToNetwork(){
// connected do something}
else{
// do something else }
Thank you for any pointer and any kind of help that could send me in the right direction.
func exchangeRatefunc (mycurrency: String, mapaye: Double) {
let apiEndPoint = "https://api.fixer.io/latest?base=USD"
guard let url = NSURL(string: apiEndPoint) else {
self.infoLabel.text = NSLocalizedString("Error getting exchange rates.", comment: "")
print("Url is not valid")
return
}
let urlRequest = NSURLRequest(url: url as URL)
let session = URLSession.shared
let task = session.dataTask(with: urlRequest as URLRequest) { (data, response, error) in
guard error == nil else {
print(error?.localizedDescription ?? 0)
return
}
guard let data = data else {
print("No data received")
self.infoLabel.text = NSLocalizedString("You appear to be offline!", comment: "")
return
}
do {
guard let exchangeDict = try JSONSerialization.jsonObject(with: data, options: []) as? [String: AnyObject] else {
print("Could not convert JSON to dictionary")
self.infoLabel.text = NSLocalizedString("No conversion Available.", comment: "")
return
}
print(exchangeDict.values)
if let rates = exchangeDict["rates"] {
let currencies = [mycurrency]
for currency in currencies {
if let rate = rates[currency] as? Double {
self.exchangeRates.append("1 USD = \(rate) " + currency)
let myprefcurdisplay = mapaye * rate
OperationQueue.main.addOperation({
self.myPreferedCurrencyPayLabel.updateWithText("\(myprefcurdisplay.roundTo(places: 2)) \(currency)")
if let date = exchangeDict["date"] as? String {
self.infoLabel.text = "\(currency)/USD" + NSLocalizedString(" rates updated on" + " \(date)", comment: "")
}
})
}
}
}
}
catch {
print("Error trying to convert JSON to dictionary")
}
}
task.resume()
}
I have a network connection with reads the data using JSON and gives a callback;
executeRequestURL(requestURL: url, taskCallback: {(status, resp) -> Void in
if (status == true) {
if let results = resp as? NSDictionary {
print ("\(results.count) results found")
let list = results.allValues.first as! NSArray
print (list)
}
} else {
print ("Error -- \(resp)")
}
})
This calls;
private class func executeRequestURL(requestURL: NSURL, taskCallback: #escaping (Bool, AnyObject?) -> ()) {
print ("Attempting URL -- \(requestURL)")
let request: NSURLRequest = NSURLRequest(url: requestURL as URL, cachePolicy: .reloadIgnoringLocalAndRemoteCacheData, timeoutInterval: kAPI_TIMEOUT)
let session: URLSession = URLSession.shared
let task = session.dataTask(with: request as URLRequest, completionHandler: {
(data, response, error) in
guard error == nil else {
print(error)
return
}
guard let data = data else {
print("Data is empty")
return
}
let json = try! JSONSerialization.jsonObject(with: data, options: [])
//print(json)
if let response = response as? HTTPURLResponse , 200...299 ~= response.statusCode {
taskCallback(true, json as AnyObject?)
} else {
taskCallback(false, json as AnyObject?)
}
})
task.resume()
}
The problem I have is that I want to read the results into a dictionary, loop through it and create objects.
For now, I will put my code in the executeRequestURL just to ensure it works, but I intend to seperate this code away for the required entity.
Question:
How do I read the resp as a dictionary?
Thanks
Sample response follows;
{
"objects": [
{
"uid": "coll_20ce39424470457c925f823fc150b3d4",
"title": "Popular",
"temp_image": "",
"body": "",
"active": true,
"slug": "popular",
"created": "2014-10-25T12:45:54+00:00",
"modified": "2014-10-25T12:45:54.159000+00:00",
"ends_on": "2100-01-01T00:00:00+00:00",
}
]
}
As the JSON is a dictionary, return a dictionary ([String:Any]) from the callback. In Swift 3 AnyObject has become Any. The strong type system of Swift encourages to be always as specific as possible.
Do a better error handling! You should return an error rather than just false.
The code uses the new Swift 3 structs URL and URLRequest
private class func executeRequestURL(requestURL: URL, taskCallback: #escaping (Bool, [String:Any]?) -> ()) {
print ("Attempting URL -- \(requestURL)")
let request = URLRequest(url: requestURL, cachePolicy: .reloadIgnoringLocalAndRemoteCacheData, timeoutInterval: kAPI_TIMEOUT)
let session = URLSession.shared
let task = session.dataTask(with: request, completionHandler: {
(data, response, error) in
guard error == nil else {
print(error)
taskCallback(false, nil)
return
}
guard let data = data else {
print("Data is empty") // <- this will never be reached. If there is no error,
taskCallback(false, nil) // data is always non-nil.
return
}
if let response = response as? HTTPURLResponse , 200...299 ~= response.statusCode {
let json = try! JSONSerialization.jsonObject(with: data, options: []) as! [String:Any]
taskCallback(true, json)
} else {
taskCallback(false, nil)
}
})
task.resume()
}
The JSON result contains a dictionary with one key objects which contains an array of dictionaries. JSON collection types are very easy to distinguish: {} is dictionary, [] is array.
To map the JSON to objects create a struct
struct Item {
var uid : String
var title : String
var tempImage : String
var body : String
var active : Bool
var slug : String
var created : String
var modified : String
var endOn : String
}
and an array
var items = [Item]()
Then map the dictionaries to Item
if let objects = json["objects"] as? [[String:Any]] {
for object in objects {
let uid = object["uid"] as! String
var title = object["title"] as! String
var tempImage = object["temp_image"] as! String
var body = object["body"] as! String
var active = object["active"] as! Bool
var slug = object["slug"] as! String
var created = object["created"] as! String
var modified = object["modified"] as! String
var endOn = object["end_on"] as! String
let item = Item(uid: uid, title: title, tempImage:tempImage, body: body, active: active, slug: slug, created: created, modified: modified, endOn: endOn)
items.append(item)
}
The JSON values seem to come from a database which includes always all fields so the forced unwrapped values are safe.
I've done it like so:
func getHttpData(urlAddress : String)
{
// Asynchronous Http call to your api url, using NSURLSession:
guard let url = URL(string: urlAddress) else
{
print("Url conversion issue.")
return
}
URLSession.shared.dataTask(with: url, completionHandler: { (data, response, error) -> Void in
// Check if data was received successfully
if error == nil && data != nil {
do {
// Convert NSData to Dictionary where keys are of type String, and values are of any type
let json = try JSONSerialization.jsonObject(with: data!, options: JSONSerialization.ReadingOptions.mutableContainers) as! [String:AnyObject]
// Call whatever function you want to do with your dictionary
useMyDictionary(dictionary: json)
} catch {
print(error)
// Something went wrong
}
}
else if error != nil
{
print(error)
}
}).resume()
}
There are many other ways but I like to do it using ObjectMapper. it looks cleaner to me. So just create a new Swift file, import ObjectMapper and write below code.
class yourDataModel: Mappable {
// MARK: - Constants & Variables
var myObjects: [yourDataModel]
required init?(_ map: Map) {
myObjects = []
}
func mapping(map: Map) {
myObjects <- map["objects"]
}
}
class YourCustomObjects: Mappable {
// MARK: - Constants & Variables
var userId:String
var title:String
var tempimage:String
var body:String
var active:Bool
var slug : String
var createdDate:String
var modifiedDate:String
var endDate:String
// MARK: - init
required init?(_ map: Map) {
userId = ""
title = ""
tempimage = ""
body = ""
active = false
slug = ""
createdDate = ""
modifiedDate = ""
endDate = ""
}
func mapping(map: Map) {
userId <- map["uid"]
title <- map["title"]
tempimage <- map["temp_image"]
body <- map["body"]
active <- map["active"]
slug <- map["slug"]
createdDate <- map["created"]
modifiedDate <- map["modified"]
endDate <- map["ends_on"]
}
}
Basically its your model class, now you just have to pass it your result in JSON which will be an AnyObject hopefully, and it will give you an array containing all your "objects" in it. You can use it like below
if let data = Mapper<yourDataModel>().map(resp){
print(data)
}
Try this, and let me know if you face any difficulty.
let task = session.dataTaskWithURL(url!, completionHandler: {
data, response, error -> Void in
if (error != nil) {
println(error)
} else {
let jsonresult = NSJSONSerialization.JSONObjectWithData(data, options:NSJSONReadingOptions.MutableContainers, error: nil) as NSDictionary
var dummyfeed:AnyObject
//println(jsonresult)
for var i = 0; i < jsonresult["feed"]!.count; i++ {
self.feeds.append([String:String]())
dummyfeed = jsonresult["feed"]![i] as NSDictionary
self.feeds[i]["id"] = dummyfeed["id"] as? String
self.feeds[i]["name"] = dummyfeed["name"] as? String
self.feeds[i]["status"] = dummyfeed["status"] as? String
self.feeds[i]["profilePic"] = dummyfeed["profilePic"] as? String
self.feeds[i]["timeStamp"] = dummyfeed["timeStamp"] as? String
self.feeds[i]["url"] = dummyfeed["url"] as? String
}
}
})
task.resume()
So Feeds is a global variable, so that I display the picture of each entry in Feeds on a table view. But it's calling asynchronously println(self.feeds) inside the task variable and println(feeds) outside of the task variable are differnent. How do I make it synchronously?
Do not make it run synchronously. Run it asynchronously, but then synchronize the interaction with feeds. The simplest way to achieve that it to dispatch the updating of the feeds back to the main queue and reloadData for the table view. This eliminates the possibility that you'll be using it from the main queue while it's mutating in the background, but avoids the horrible UX of doing this network request synchronously:
let task = session.dataTaskWithURL(url!) { data, response, error in
if (error != nil) {
println(error)
} else {
var parseError: NSError?
if let jsonresult = NSJSONSerialization.JSONObjectWithData(data, options:nil, error: nil) as? NSDictionary {
if let receivedFeeds = jsonresult["feed"] as? [[String: AnyObject]] {
dispatch_async(dispatch_get_main_queue()) {
self.feeds = [[String: String]]()
for receivedFeed in receivedFeeds {
var outputFeed = [String : String]()
outputFeed["id"] = receivedFeed["id"] as? String
outputFeed["name"] = receivedFeed["name"] as? String
outputFeed["status"] = receivedFeed["status"] as? String
outputFeed["profilePic"] = receivedFeed["profilePic"] as? String
outputFeed["timeStamp"] = receivedFeed["timeStamp"] as? String
outputFeed["url"] = receivedFeed["url"] as? String
self.feeds.append(outputFeed)
}
self.tableView.reloadData()
}
} else {
println("did not find `feed`")
}
} else {
println("problem parsing JSON: \(parseError)")
}
}
}
task.resume()
That should be a little more robust handling errors and employs asynchronous pattern of letting request run asynchronously, but dispatch updating of model object and UI back to the main thread.
let task = session.dataTaskWithURL(url!, completionHandler: {
data, response, error -> Void in
if (error != nil) {
println(error)
} else {
let jsonresult = NSJSONSerialization.JSONObjectWithData(data, options:NSJSONReadingOptions.MutableContainers, error: nil) as NSDictionary
var dummyfeed:AnyObject
//println(jsonresult)
for var i = 0; i < jsonresult["feed"]!.count; i++ {
self.feeds.append([String:String]())
dummyfeed = jsonresult["feed"]![i] as NSDictionary
dispatch_async(dispatch_get_main_queue()) {
self.feeds[i]["id"] = dummyfeed["id"] as? String
self.feeds[i]["name"] = dummyfeed["name"] as? String
self.feeds[i]["status"] = dummyfeed["status"] as? String
self.feeds[i]["profilePic"] = dummyfeed["profilePic"] as? String
self.feeds[i]["timeStamp"] = dummyfeed["timeStamp"] as? String
self.feeds[i]["url"] = dummyfeed["url"] as? String
}
}
self.tableView.reloadData()
}
})
task.resume()
Hey Rob, I did what I think you tell me to do, and feeds is still empty :(
I have same problem my code is was working fine, but now, using dataTaskWithURL it didn't return any data, or even error. I think issue is iOS 8.2 I upgraded.