I've been busy creating a Schedule app for a school project but can't really seem to figure it out. Someone on this site already helped me to filter out data from a JSON link I had, but I can't really make it work. I want to put the Data that I get in my console into a Table view. I get roster info for 5 days, so every cell should have different information. I've watched several youtube videos and looked around SO but its either not really what I need, or I don't understand the way it works.
The code I currently have :
typealias JSONDictionary = [String:Any]
let savedValue = UserDefaults.standard.string(forKey: "LoginUserID")
let url = URL(string: "(url)")
let task = URLSession.shared.dataTask(with: url!) { (data, response, error) in
if error != nil
{
print ("ERROR")
}
else
{
if let content = data
{
do {
if let json = try JSONSerialization.jsonObject(with:data!, options: []) as? JSONDictionary {
if let days = json["Days"] as? [JSONDictionary] {
for day in days {
print(day["DayName"] as! String)
if let lessons = day["Lessons"] as? [JSONDictionary] {
for lesson in lessons {
let classRoom = lesson["Classroom"] as! String
let name = lesson["Name"] as! String
let teacher = lesson["Teacher"] as! String
print(teacher)
}
}
}
}
}
} catch {
print(error)
}
}
}
}
task.resume()
So I need to first of all be able to get info for example just Monday. Then i need to divide a table cell into 7 parts, and then put all the info i get into those parts.
I hope someone can help out because I'm really struggling
You are parsing the data. That's great, but you need a reference to it, i.e. an object. (UPDATE: looking at your data and your parsing, you are going to have some issues. DayName & Lesson are dictionaries inside an array of the dictionary Day. Your parsing isn't quite complete...)
TableViews work best when access an array of objects. To create an array of objects, you'll need to create a class ("Lessons" I'm assuming?). In the class, you'll set up the properties that a Lesson object should have -> Classroom, Name, Teacher. Here is a Class file I made for a simple project.
class DailyWeather: NSObject {
var time : Double = 0.0
var summaryDescription : String = ""
var temperatureMin : Double = 0.0
var temperatureMax : Double = 0.0
var precipProbability : Double = 0.0
override init() {
super.init()
self.time = 0.0
self.summaryDescription = ""
self.temperatureMin = 0.0
self.temperatureMax = 0.0
self.precipProbability = 0.0
}
}
As you can see, I declared all my properties that I'll need in the class. I took an easy route and gave them all values to start, whether they were empty strings or values of 0 if the data type was an Int or Double. Then I initialized them. After that, I was able to create an object, and set it's values when I parsed the JSON data.
if let dailyDict = jsonDictionary["daily"] as? JSONDictionary {
print(dailyDict)
if let dataArray = dailyDict["data"] as? JSONArray {
print(dataArray)
for dataDict in dataArray {
let dailyWeatherData = DailyWeather()
if let summary = dataDict["summary"] as? String {
dailyWeatherData.summaryDescription = summary
} else {
print("I could not parse summary")
} ... <continue to parse and then append the object at the end before the loop begins again>
As you can see, just after I began looping through my Data array, I created an instance of the Class I created above
let dailyWeatherData = DailyWeather()
From there, I could set the properties of that dailyWeatherData object while I parsed it. By placing the object inside the loop, it will create a new object for you every time.
From there, you'll need to append the object at the end of the loop before it starts over again to an array of objects, in my case, an array of DailyWeather objects.
I'm going to assume you're doing everything in the ViewController (which is where most newbies start) so put your object array at the top before ViewDidLoad()
var DailyWeatherArray = [DailyWeather]()
This creates an array that will hold all those objects you're create in the loop while you parse the data. You'll put those objects by appending the object to the array just before the end of the loop.
After that, you can just access the array from the TableViewDelegates and DataSource functions you will be using to populate the tableview. I'll let you figure that part out as I've given you quite a bit to be going on with.
** My code was in an older version of Swift, so there may be a couple of discrepancies...
Related
I decide some JSON and try to typecast it to a dictionary of String: classy and it fails. I have found that often the reason I have trouble doing something is because of a misunderstanding of how Swift works, so here is what I want to happen. Feel free to tell me that I am doing it wrong and if I do it this way all will be wonderful.
I want my data to live between runs of the app so I have to save the data to storage between runs. I have an object, data and associated code, and I have places where changes I make to a copy should reflect back to the original so it is a class. I have a bunch of these objects and most of the time I pick the one I want based on an id that is an integer. An array is not good since it would be a sparse array cause come ids are not used. I came up with a dictionary with a key of the id and data of the structure. I turned the key from an Int to a String, by changing the Int id to a String, cause converting a dictionary to JSON is MUCH easier for a key that is a string. I save the JSON string. When the app starts again I read the string in and convert the JSON string to Any. Then I typecast the result to the desired dictionary. This is where it fails. The cast does not work. In my Googling the samples I found said this should work.
Here is my code:
class Y: Codable, Hashable {
var a: String = "c"
static func ==(lhs: Y, rhs: Y) -> Bool {
return lhs.a == rhs.a
}
func hash(into hasher: inout Hasher) {
hasher.combine(a)
}
}
struct ContentView: View {
var body: some View {
VStack {
Button ("Error") {
var y = Y()
var yDict = [String: Y]()
yDict["0"] = y
do {
let encodedData = try JSONEncoder().encode(yDict)
let jsonString = String(data: encodedData, encoding: .utf8)
let decoded = try JSONSerialization.jsonObject(with: encodedData, options: [])
if let yyDictDec = decoded as? [String:Y] {
print("yDict after decide")
print (yyDictDec)
}
} catch {
print(error.localizedDescription)
}
print("x")
}
}
}
}
In this code the if yyDictDec = is failing, I think, cause the prints after it never happen. I can cast it as [String, Any] but I really need it to be my class.
My problem is in the convert JSON back to the dictionary. I feel I am missing something fairly simple.
Don´t use JSONSerialization use JsonDecoder and decode it to the the type it was before encoding. e.g.:
let decoded = try JSONDecoder().decode([String: Y].self, from: encodedData)
I'm trying to parse JSON with currency rates contains dynamic keys and a dynamic number of values. Output depends on input parameters, such as Base currency and several currencies to compare.
Example of JSON:
{
"USD_AFN": 70.129997,
"USD_AUD": 1.284793,
"USD_BDT": 82.889999,
"USD_BRL": 3.418294,
"USD_KHR": 4004.99952
}
, or:
{
"EUR_CAD": 0.799997
}
Also, I should be able to change Base currency and currencies to compare, and change number of currencies to compare.
I already tried this answer.
What is the optimal way to handle it?
Thanks
Additional info
So, I made the struct without the initializer
struct CurrencyRate: Codable {
var results : [String:Double]
}
and trying to decode it
do { let results = try decoder.decode(CurrencyRate.self, from: dataToDecode) print(results) } catch { print("Error") }
I'm still getting the error.
Eventually, I just need an array of currency rates (values) to populate it in a Table View.
After some experimentation my Playground looks as follows:
import Cocoa
import Foundation
let jsonData = """
{
"USD_AFN": 70.129997,
"USD_AUD": 1.284793,
"USD_BDT": 82.889999,
"USD_BRL": 3.418294,
"USD_KHR": 4004.99952
}
""".data(using: .utf8)!
do {
let obj = try JSONSerialization.jsonObject(with:jsonData, options:[])
print(obj) // this is an NSDictionary
if let dict = obj as? [String:Double] {
print(dict) // This is not "just" a cast ... more than I thought
}
}
struct CurrencyRate: Codable {
var results : [String:Double]
}
// If you use a "results"-key it _must_ be present in your JSON, but it would allow to add methods
let resultsJson = """
{
"results" : {
"USD_AFN": 70.129997,
"USD_AUD": 1.284793,
"USD_BDT": 82.889999,
"USD_BRL": 3.418294,
"USD_KHR": 4004.99952
}
}
""".data(using: .utf8)!
do {
let currencyRate = try JSONDecoder().decode(CurrencyRate.self, from: resultsJson)
print(currencyRate)
}
// this is probably the easiest solution for just reading it
do {
let rates = try JSONDecoder().decode([String:Double].self, from:jsonData)
print(rates)
}
// While you could do the following it does not feel "proper"
typealias CurrencyRatesDict = [String:Double]
extension Dictionary where Key == String, Value == Double {
func conversionRate(from:String, to:String) -> Double {
let key = "\(from)_\(to)"
if let rate = self[key] {
return rate
} else {
return -1.0
}
}
}
do {
let currRates = try JSONDecoder().decode(CurrencyRatesDict.self, from:jsonData)
print(currRates)
print(currRates.conversionRate(from:"USD", to:"AUD"))
}
This taught me a few things. I would not have thought that a NSDictionary (which is produced by JSONSerialization.jsonObject automatically and has no types) converts this easily into a [String:Double], but of course it might fail and you should write some error handling to catch it.
Your CurrencyRate struct would have the advantage to allow easy extensions. Since Dictionaries are structs it is not possible to derive from them. As the last version illustrates it is possible to add a conditional extension to a Dictionary. However this would add your new function to any Dictionary matching the signature which might be acceptable in many cases even though it 'feels' wrong from the design perspective.
As you can see there is a whole bunch of ways to deal with this in Swift. I would suggest you use the Codable protocol and an additional key. Most probably there are "other things" you will want to do with your object.
This is a simple question of how-to. I don't know (and can't figure out on my own) what the correct terminology is for "editing" the data received from the JSON code. Here's my current code:
// Create a url and a session to load it in the background.
let url = URL(string: "http://api.fixer.io/latest")
let task = URLSession.shared.dataTask(with: url!) { (data, response, error) in
if error == nil {
// Try to extract some content from the data.
if let content = data {
do {
// Try to create an array out of the extracted data content.
let jsonResult = try JSONSerialization.jsonObject(with: content, options: JSONSerialization.ReadingOptions.mutableContainers) as AnyObject
// Search for specific objects in the array.
if let rates = jsonResult["rates"] as? NSDictionary {
print("Rates: \(rates)\n\n")
if let currency = rates["USD"] {
// Output of line below is – "Currency: \"1.0893\""
print("Currency: \(currency)")
}
}
} catch {
print("Error deserializing the JSON:\n\(String(describing: error))")
}
}
} else {
print("Error creating the URLSession:\n\(String(describing: error))")
}
}
// Begin using the URLSession to extract the data.
task.resume()
As you can see above, I am getting some data from a url using JSON, and then extracting a specific set of data which I'm titling rates and currency.
However, I can't find anywhere on the web for how to edit data. For example: let's say I want to change the value of currency to be equal to "$230" instead of "1.0893." What is the term/code necessary to go in and change that? Another example would be if I wanted to add another object into the rates Dictionary. What if I wanted to add "MyCurrency: true", or something like that? I'm not knowledgeable of the specific syntax on this. I need help!
EDIT – Yes, I am trying to change the database itself, that way when the information is pulled from it later on, it is updated with the changes I make now. In my above example, I said I could change currency to "$230." Well, I want it to permanently stay that way in the database, so that when I extract its value later, instead of still being "1.0893," it's now the value I changed it to.
Is this term "pushing?" I would like to make changes to the database itself.
Parse the JSON into a custom struct or class, for example
struct Currency {
let abbrev : String
var value : Double
var myCurrency : Bool
var dictionaryRepresentation : [String:Any] {
return ["abbrev" : abbrev, "value" : value, "myCurrency" : myCurrency]
}
}
var currencies = [Currency]()
The abbrev member is a constant (let), the other members are variables.
...
// Try to create an array out of the extracted data content.
let jsonResult = try JSONSerialization.jsonObject(with: content) as! [String:Any]
if let rates = jsonResult["rates"] as? [String:Double] {
for (abbrev, value) in rates {
let myCurrency = abbrev == "USD"
currencies.append(Currency(abbrev: abbrev, value: value, myCurrency: myCurrency))
}
}
The code uses native Dictionary. And .mutableContainers is useless in Swift anyway.
Here I'm trying to get currency values of INR for last 30 dates.
I'm fetching last 30 dates values of INR currency using Alamofire.
//strDates contains all 30 days dates
for i in 0..<strDates.count {
Alamofire.request("http://api.fixer.io/\(strDates[i])?base=USD").responseJSON { response in
if let arr = response.result.value as? [String:AnyObject]
{
let inrc = (arr["rates"]?["INR"] as? Double)!
print(inrc)
self.sValues.append(inc)
print(sValues)
//It prints values here.
}
}
}
print(sValues) //Print nil
setChart(dataPoints: strDates, values: sValues)
How do I use this sValues array outside the Alamofire block.
Here, Actually I'm sending dates & INR values as a parameter to below method.
func setChart(dataPoints: [String], values: [Double]) {
barChartView.noDataText = "You need to provide data for the chart."
for i in 0..<dataPoints.count {
let dataEntry = BarChartDataEntry(x: Double(i), yValues: [values[i]])
dataEntries.append(dataEntry)
}
let chartDataSet = BarChartDataSet(values: dataEntries, label: "INR Rates(₹)/$")
let chartData = BarChartData(dataSet: chartDataSet)
barChartView.data = chartData
barChartView.xAxis.labelPosition = .bottom
barChartView.rightAxis.enabled = false
barChartView.leftAxis.enabled = true
barChartView.data?.setDrawValues(false)
barChartView.leftAxis.granularityEnabled = true
barChartView.leftAxis.granularity = 1.0
barChartView.xAxis.granularityEnabled = true
barChartView.xAxis.granularity = 1.0
barChartView.leftAxis.axisMinimum = 70//65
barChartView.leftAxis.axisMaximum = 60//70
//chartDataSet.colors = [UIColor.cyan, UIColor.green]
}
Are you looking for the chart to refresh when only when ALL of the calls are done? If so you use a pattern like this:
var completedCalls = 0
for i in 0..<strDates.count {
Alamofire.request("http://api.fixer.io/\(strDates[i])?base=USD").responseJSON { response in
if let arr = response.result.value as? [String:AnyObject]
{
completedCalls += 1
let inrc = (arr["rates"]?["INR"] as? Double)!
print(inrc)
self.sValues.append(inc)
print(sValues)
//It prints values here.
if completedCalls = strDates.count {
DispatchQueue.main {
setChart(dataPoints: strDates, values: sValues)
}
}
}
}
}
The idea is to count how many API requests come back and only take action when all of the requests are done (you should check that all of them actually succeeded as well and show an error if any fail).
Your codes cause sValues to print nil because your print(sValues) ran before your request response. This block
if let arr = response.result.value as? [String:AnyObject]
{
let inrc = (arr["rates"]?["INR"] as? Double)!
print(inrc)
self.sValues.append(inc)
print(sValues)
//It prints values here.
}
runs only when your request returns with a value or an error. So any usage of values from your request should also be done here. If you want to use the value to update certain UI element or database, you should also call the method from within this block to ensure that you already retrieved the value from its source.
I do suggest you adopt this mentality when programming any asynchronous codes as you can never expect when your codes running in another thread or the server to return. It is always safer to run you methods that requires the return value in a completion block that runs only when the asynchronous codes have completed.
If you're displaying them in a table, create a strong variable of type Array, or some other data provider (e.g. CoreData, SQLite) and store the results there. In the completion block for AF, set the value of the Array variable (or update your local data provider) to the API result data, then call reloadData() on your table. You'll also need to configure the UITableView delegate and datasource methods.
I am trying to parse data which look:
It looks like each record is sequential.. 0, 1, 2 and then within each record there are lots of key value pairs such as the name or showID.
I want to go into each record and only get certain pairs, for example the name, showID and Date.
Here is my code, I am unsure what should be my modal in for item in loop
in other words, how do I get the specific fields into my empty dictionary array?
let task = NSURLSession.sharedSession().dataTaskWithURL(url) { (data, response, error) -> Void in
if let urlContent = data
{
do
{
var jsonResult:NSDictionary = try NSJSONSerialization.JSONObjectWithData(urlContent, options: NSJSONReadingOptions.MutableContainers) as! NSDictionary
if let items = jsonResult["items"] as! NSArray?
{
var emptyArrayOfDictionary = [[String : AnyObject]]()
for item in 0...jsonResult.count
{
}
}
The idea would be to create a struct (or a class) which contains the properties you need, created with an initializer from the values in your dictionaries.
Let's say you want to make "Show" objects containing the show name and the show ID.
You could create a struct like this:
struct Show {
let name:String
let showID:Int
init?(dictionary: [String:AnyObject]) {
guard let name = dictionary["name"] as? String,
let showID = dictionary["showID"] as? Int else {
return nil
}
self.name = name
self.showID = showID
}
}
Then iterate over your dictionaries and pass each one to the struct initializer, something like this:
let task = NSURLSession.sharedSession().dataTaskWithURL(url) { (data, response, error) -> Void in
if let urlContent = data {
do {
if let jsonResult = try NSJSONSerialization.JSONObjectWithData(urlContent, options: []) as? [String : AnyObject] {
if let items = jsonResult["items"] as? [[String : AnyObject]] {
let shows = items.flatMap { Show(dictionary: $0) }
}
}
} catch {
print(error)
}
}
}
The struct initializer is an Optional one, meaning that if the dictionary does not contain the keys "name" or "showID" it will not create the object and will return nil instead; that's why we're using flatMap to iterate instead of map (because flatMap unwraps the Optionals).
Now you have an array of objects, shows, and you can filter or sort its contents easily with Swift methods like sort, filter, etc.
Each object in the shows array is a Show object and has name and showID properties with the data of your dictionaries.
What flatMap does is create an array of Show objects by iterating (like a loop) over the initial array. On this line:
let shows = items.flatMap { Show(dictionary: $0) }
the $0 represents the current array element. What it means is that for each element in the items array, we take it and create a new Show instance with it, and put the resulting array of objects in the constant shows.
There's also map which is often used, but here the init of our Show struct is an optional init, so it returns an Optional Show, and flatMap knows how to deal with this (it will safely unwrap the optional and ignore the nil ones) where map does not.
If you would like to simplify your son parsing try this Open source https://github.com/SwiftyJSON/SwiftyJSON
With this you access name field of item 0
let userName = json[0]["name"].string