How to store multiple back dates in a Swift Struct? - ios

I would like to store the previous 4 days closing event in an individual struct so that i can make reference to them later on in the program. How would you go about storing the the closing event for each 4 days after sorting them from the JSON API.
The code below has sorted the previous 4 days but i am unable to figure how to store each day to use them separately
class DailyViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let jsonUrlString = "https://www.alphavantage.co/query?function=TIME_SERIES_DAILY&symbol=MSFT&apikey=demo"
let urlObj = URL(string: jsonUrlString)
URLSession.shared.dataTask(with: urlObj!) {(data, response, error) in
guard let data = data else { return }
do {
let forex = try JSONDecoder().decode(Root.self, from: data)
let sortedKeys = forex.timeSeriesDaily.keys.sorted(by: >)
let requestedKeys = sortedKeys.prefix(4)
var requestedPrices = [String:Forex]()
requestedKeys.forEach{ requestedPrices[$0] = forex.timeSeriesDaily[$0] }
print(requestedPrices)
print()
} catch {
print(error)
}
}.resume()
}
struct Root: Codable {
let metaData: [String: String]
let timeSeriesDaily: [String:Forex]
enum CodingKeys: String, CodingKey {
case timeSeriesDaily = "Time Series (Daily)"
case metaData = "Meta Data"
}
}
struct Forex: Codable {
let open, high, low, close: String
enum CodingKeys: String, CodingKey {
case open = "1. open"
case high = "2. high"
case low = "3. low"
case close = "4. close"
}
}
}

One way is to create a struct with four properties for this and add a specific init that takes an array
struct LastFour {
var close1: String
var close2: String
var close3: String
var close4: String
init?(_ closes: [String]) {
guard closes.count >= 4 else {
return nil
}
close1 = closes[0]
close2 = closes[1]
close3 = closes[2]
close4 = closes[3]
}
}
and then use map when initialising the struct from the dictionary
let lastFour = LastFour(requestedPrices.values.map {$0.close})
Note that the init is optional and returns nil in case the array is to short, another option could be to throw an error for instance.
Maybe a more flexible solution would be to use an array internally in the struct and then access the data via a method or perhaps computed properties
struct LastFour {
private var closeEvents: [String]
func close(at index: Int) -> String {
}
}
This would of course require similar code for init and checking the correct size but it would be easier to change if more or less elements are needed

My suggestion is to create another struct with the date and the close price
struct CloseData {
let date, price : String
}
and populate it
do {
let forex = try JSONDecoder().decode(Root.self, from: data)
let sortedKeys = forex.timeSeriesDaily.keys.sorted(by: >)
let requestedKeys = sortedKeys.prefix(4)
let requestedPrices = requestedKeys.map{ CloseData(date: $0, price: forex.timeSeriesDaily[$0]!.close) }
The result is an array of CloseData items

Related

How to archive data in swift?

I am trying to archive data and want to store it in userdefault but app getting crash.
Also tried this
let encodedData = try NSKeyedArchiver.archivedData(withRootObject: selectedPoductDetails, requiringSecureCoding: false)
selectedPoductDetails is dict of type [String: SelectedProductDetail]
import Foundation
class SelectedProductDetail {
let product: String
var amount: Double
var title: String
init(product: String, amount: Double, title: String ) {
self.product = product
self.amount = amount
self.title = title
}
}
May i know why its not working and possible solution for same?
For this case you can use UserDefaults
struct ProductDetail: Codable {
//...
}
let encoder = JSONEncoder()
let selectedProductDetails = ProductDetail()
// Set
if let data = try? encoder.encode(selectedProductDetails) {
UserDefaults.standard.set(data, forKey: "selectedProductDetails")
}
// Get
if let selectedProductDetailsData = UserDefaults.standard.object(forKey: "selectedProductDetails") as? Data {
let selectedProductDetails = try? JSONDecoder().decode(ProductDetail.self, from: selectedProductDetailsData)
}
As mentioned in the comments to use NSKeyedArchiver the class must adopt NSSecureCoding and implement the two required methods.
The types in your class are JSON compatible, so adopt Codable and archive the data with JSONEncoder (or PropertyListEncoder). You could even use a struct and delete the init method
struct SelectedProductDetail: Codable {
let product: String
var amount: Double
var title: String
}
var productDetails = [String: SelectedProductDetail]()
// populate the dictionary
do {
let data = try JSONEncoder().encode(productDetails)
UserDefaults.standard.set(data, forKey: "productDetails")
} catch {
print(error)
}
And load it
do {
guard let data = UserDefaults.standard.data(forKey: "productDetails") else { return }
productDetails = try JSONDecoder().decode([String: SelectedProductDetail].self, from: data)
} catch {
print(error)
}
Note:
UserDefaults is the wrong place for user data. It's better to save the data in the Documents folder

save arrays of json in firebase with my swift 4 app

I have an app that shows 4 options. Everyday you click on one or more of the options. Right now, it's storing in the firebase database like an array of string, where every string is one of the options. Like this
override func addSelection(selection: String) {
self.quitPlan.medications.append(selection)
}
var medications: [String] {
get {
return document[Properties.medications.rawValue] as? [String] ?? []
}
set {
document[Properties.medications.rawValue] = newValue
}
}
But I actually want an array of jsons, with the option and the option. I have try:
override func addSelection(selection: String) {
let medicationSelected = Medication(medication: selection, date: Date())
self.quitPlan.medications.append(medicationSelected)
}
var medications: [Medication] {
get {
return document[Properties.medications.rawValue] as? [Medication] ?? []
}
set {
document[Properties.medications.rawValue] = newValue
}
}
struct Medication {
let medication: String
let date: Date
}
But it's not working, I'm getting 'FIRInvalidArgumentException', reason: 'Unsupported type: __SwiftValue'
You can do something like this:
struct Medication {
let medication: String
let date: Date
private let divider = "|"
func toString() -> String {
return midication + divider + date.toString()
}
func from(_ string: String) -> Medication {
let arr = string.split(divider)
let medication = arr[0]
let date = // TODO: Date from string arr[1]
return Medication(medication: medication, date: date)
}}
and
self.quitPlan.medications.append(medicationSelected.toString())
Firebase cannot save custom Swift structs.
A possible solution is to encode the array of Medication to a JSON string.
struct Medication : Codable {
let medication: String
let date: Date
}
In the database change the type from an array of string to single string
var medications: [Medication] {
get {
guard let medicationJSON = document[Properties.medications.rawValue] as? String,
let data = medicationJSON.data(using: .utf8),
let medi = try? JSONDecoder().decode([Medication].self, from: data) else { return [] }
return medi
}
set {
let medicationData = try! JSONEncoder().encode(newValue)
document[Properties.medications.rawValue] = String(data: medicationData, encoding: .utf8)!
}
}
Firestore can save a Swift struct to a collection, there is a module for this.
First, you should include the module:
import FirebaseFirestoreSwift
Then, just do:
db.collection("yourCollectionName").document(from: yourSwiftObject)
It will be converted to be saved in your Firestore collection.

how to add Json value into model Array to display into tableview in swift

I'm using the tableview to display the Two Json value but the problem is I cant add value into model struct to displaying into tableview using two Api's. i want to show percentage value in one of the cell label and
here is my json
[
{
"Percentage": 99.792098999,
}
]
my second json value
{
"Categories": [
"Developer",
"ios "
],
"Tags": [
{
"Value": "kishore",
"Key": "Name"
},
{
"Value": "2",
"Key": "office"
},
]
}
and i need show the Categories value in Categories label in tableview
value and key on tableview
here is my Struct
struct info: Decodable {
let Categories: String?
let Tags: String?
let Value: String?
let Key: String?
var Name: String?
let percentage: Double?
}
here its my code
var List = [info]()
do {
let json = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers)
print(json as Any)
guard let jsonArray = json as? [[String: Any]] else {
return
}
print(jsonArray)
for dic in jsonArray{
guard let per = dic["percentage"] as? Double else { return }
print(per)
}
and second json
if let array = json["Tags"] as? [[String: String]] {
for dict in array {
let key = dict["Key"]
let value = dict["Value"]
switch key {
case "office":
case "Name":
default:
break;
}
}
here is my cell for row indexpath
cell.Categories.text = list[indexpath.row].percentage
cell.Name.text = list[indexpath.row].name
cell.office.text = list[indexpath.row].office
Please use Swift 4 Codable protocol to decode the value from JSON.
//1.0 Create your structures and make it conform to Codable Protocol
struct Tags: Codable{
var Key: String
var Value: String
}
struct Sample: Codable{
var Categories: [String]
var Tags: [Tags]
}
In your method, perform below steps:
//2.0 Get your json data from your API. In example below, i am reading from a JSON file named "Sample.json"
if let path = Bundle.main.path(forResource: "Sample", ofType: "json") {
do {
let jsonData = try Data(contentsOf: URL(fileURLWithPath: path), options: .mappedIfSafe)
do {
//3.0 use JSONDecoder's decode method to decode JSON to your model.
let sample = try JSONDecoder().decode(Sample.self, from: jsonData)
//4.0 User the "sample" model to do your stuff. Example, printing some values
print("Sample.Category = \(sample.Categories)")
print("Sample.Name = \(sample.Tags[0].Value)")
print("Sample.Office = \(sample.Tags[1].Value)")
} catch let error {
print("Error = \(error)")
}
} catch {
// handle error
}
}
I prefer to use Codable all the time with JSON even for simpler types so for percentage I would do
struct ItemElement: Decodable {
let percentage: Double
enum CodingKeys: String, CodingKey {
case percentage = "Percentage"
}
}
and we need to keep these values in a separate array, declared as a class property
let percentageList: [Double]()
and json encoding would then be
let decoder = JSONDecoder()
do {
let result = try decoder.decode([ItemElement].self, from: data)
percentageList = result.map { item.percentage }
} catch {
print(error)
}
Similar for the second part
struct Item: Decodable {
let categories: [String]
let tags: [Tag]
enum CodingKeys: String, CodingKey {
case categories = "Categories"
case tags = "Tags"
}
}
struct Tag: Decodable {
let value, key: String
enum CodingKeys: String, CodingKey {
case value = "Value"
case key = "Key"
}
}
use a dictionary for the result, again as a class property
var values = [String: String]()
and the decoding
let decoder = JSONDecoder()
do {
let result = try decoder.decode(Item.self, from: data)
for item in result.tags {
values[item.key] = values.item.value
}
} catch {
print(error)
}
and then in the cell for row code
cell.Categories.text = percentageList[indexpath.row].percentage
cell.Name.text = values["name"]
cell.office.text = values["office"]
Note that this last code looks very strange since you don't have an array of name/office values judging by your json. Maybe you have simplified it some way but the code above is the best I can do with the information given even if it possibly wrong

Nested Json data fetch and append problem using swiftyjson library swift

Getting data append problem in nested json by using swiftjson library swift.
I have created two struct but getting an error while appending the final list. Error in getting when appending data. Have i created struct well.
My struct
struct GistModel {
var comments : Int!
var commentsUrl : String!
var descriptionField : String!
var owner : Owner!
}
struct Owner{
var login : String!
}
JSON DATA result:
{
url: "https://api.github.com/gists/7e624eed62b3a317541791d719dcacf2",
forks_url: "https://api.github.com/gists/7e624eed62b3a317541791d719dcacf2/forks",
commits_url: "https://api.github.com/gists/7e624eed62b3a317541791d719dcacf2/commits",
id: "7e624eed62b3a317541791d719dcacf2",
node_id: "MDQ6R2lzdDdlNjI0ZWVkNjJiM2EzMTc1NDE3OTFkNzE5ZGNhY2Yy",
git_pull_url: "https://gist.github.com/7e624eed62b3a317541791d719dcacf2.git",
git_push_url: "https://gist.github.com/7e624eed62b3a317541791d719dcacf2.git",
html_url: "https://gist.github.com/7e624eed62b3a317541791d719dcacf2",
files: 
 {
GistTest2: 
 {
filename: "GistTest2",
type: "text/plain",
language: null,
raw_url: "https://gist.githubusercontent.com/MasamMahmood/7e624eed62b3a317541791d719dcacf2/raw/7302f0d923e9e08b0e502ad9df762a1b2aa072e1/GistTest2",
size: 29
}
},
public: true,
created_at: "2019-02-01T18:41:39Z",
updated_at: "2019-02-01T19:01:16Z",
description: "Gist Test 2",
comments: 0,
user: null,
comments_url: "https://api.github.com/gists/7e624eed62b3a317541791d719dcacf2/comments",
owner: 
 {
login: "MasamMahmood",
id: 36441313,
node_id: "MDQ6VXNlcjM2NDQxMzEz",
avatar_url: "https://avatars3.githubusercontent.com/u/36441313?v=4",
gravatar_id: "",
url: "https://api.github.com/users/MasamMahmood",
html_url: "https://github.com/MasamMahmood",
followers_url: "https://api.github.com/users/MasamMahmood/followers",
following_url: "https://api.github.com/users/MasamMahmood/following{/other_user}",
gists_url: "https://api.github.com/users/MasamMahmood/gists{/gist_id}",
starred_url: "https://api.github.com/users/MasamMahmood/starred{/owner}{/repo}",
subscriptions_url: "https://api.github.com/users/MasamMahmood/subscriptions",
organizations_url: "https://api.github.com/users/MasamMahmood/orgs",
repos_url: "https://api.github.com/users/MasamMahmood/repos",
events_url: "https://api.github.com/users/MasamMahmood/events{/privacy}",
received_events_url: "https://api.github.com/users/MasamMahmood/received_events",
type: "User",
site_admin: false
},
truncated: false
}
Swift:
switch response.result{
case .success(let value):
let json = JSON(value)
print(json)
for subJson in json.arrayValue {
let comm = subJson["comments"].intValue
let commurl = subJson["comments_url"].stringValue
let desc = subJson["description"].string
//let age = subJson["owner"]["login"].string
for item in subJson{
let login = subJson["owner"]["login"].string
// do something
}
let user = GistModel(comments: comm, commentsUrl: commurl, descriptionField: desc, login: login)//, owner: login)
self.DataList.append(user)
print(user)
}
I am newbie Getting error on append the list. "Use of unresolved identifier 'login'".
If you are willing to move to standard json handling using Codable then this will work. First let structs implement Decodable
struct GistModel: Decodable {
let comments: Int
let commentsUrl: String
let description: String //Changed the name here
let owner: Owner
}
struct Owner: Decodable {
let login: String
}
And encoding is done like this
do {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let result = try decoder.decode([GistModel].self, from: data)
print(result[0].owner.login)
print(result[0].comments)
print(result[0].commentsUrl)
} catch {
print(error)
}

How to sum a particular key in an Array Object Swift 4?

This response from API:
{
"status": 200,
"message": "Success",
"data": {
"result": [
{
"name": "Anything",
"response": [
{
"name": "XYZ",
"prize": "1.86"
},
{
"name": "ABCD",
"prize": "9.86"
}
]
}
],
"overall": "XYZ"
}
}
How, can I sum the prize in the response as I have to show it in the header of Table. I did this.
var prizeArr = [Int]()
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView?
{
let header = tableView.dequeueReusableHeaderFooterView(withIdentifier: cellReuseIdentifier) as! OverallHeaderCell
let countArr = winnerArr![section]["response"] as? Array<Dictionary<String,Any>>
prizeArr = (countArr!.compactMap{ $0["prize"] })
print(prizeArr)
header.textL[0].text = winnerArr![section]["name"] as? String
header.textL[3].text = "\(String(describing: countArr!.count))"
return header
}
I am trying to store the prize in the prizeArr and then I will sum it up. But, it prints nil, but when I write it in the terminal it gives the value individually. How, can I sum the prize in this ArrayObject?
Also, when I use prizeArr = [String](), then it works and store the prize in the Array, but for Int why is it not working?
First, as #Benhamine pointed out you should start with a clean architecture and map your JSON into a type safe class.
Step 1: Codable Types
Lets define a structure that represents our JSON so we can better consume it in our App. JSON is something we never want to pass around our App. Lets rather pass around something well-defined and documented so we have no need to do any force unwraps and crash our App.
struct JSONResponse: Codable {
enum CodingKeys: String, CodingKey {
case data
}
let data: Data
}
extension JSONResponse {
struct Data: Codable {
enum CodingKeys: String, CodingKey {
case results = "result"
}
let results: [Result]
}
}
extension JSONResponse.Data {
struct Result: Codable {
let name: String
let winners: [Winner]
enum CodingKeys: String, CodingKey {
case winners = "response"
case name
}
}
}
extension JSONResponse.Data.Result {
struct Winner: Codable {
let name: String
let prize: String
}
}
Step 2: Parsing
Parsing using Codable is super simple. The code below will show how we convert it into JSON and also how one could go about getting the sum of the float values.
do {
let o: JSONResponse = try JSONDecoder().decode(JSONResponse.self, from: jsonData)
let floatValues = o.data.results.flatMap({ $0.winners }).compactMap({ Float($0.prize) })
floatValues.reduce(0, +)
print(floatValues)
} catch let e {
print(e)
}
Step 3: Integrate
We now have the building blocks we need to get this information so let's hook it up to your code by starting with what we want our code to look like.
/// We store our main data type for easy reference
var resultsBySection: [Result]
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView?
{
let header = tableView.dequeueReusableHeaderFooterView(withIdentifier: cellReuseIdentifier) as! OverallHeaderCell
let currentResults = resultsBySection[section]
let prizeTotals = currentResults.flatMap({ $0.winners }).compactMap({ Float($0.prize) })
let totalPriceMoney = prizeTotals.reduce(0, +)
header.textL[0].text = currentResults.name
header.textL[3].text = "\(String(describing: totalPriceMoney))"
return header
}
Notice how in the above code I do not do any JSON decoding in cell dequeueing. Ideally that should be done when we retrieve our JSON and convert it to our types.
Step 4: Refactor
An essential piece of any code experience should contain some reflection into the code we have written and consider how it could be refactored.
In our example above we could probably hardcode the totals onto the controller or create a custom data structure that will do this for us when we parse JSON. Do we always want to manually calculate the totals if we always need this total? We could have a custom function to do the calculation or just do it in our JSON decoding logic.
Anyways, the idea is that we should always look at what we are writing and question how it could be improved
Try this Codable solution to reduce the prizes together:
struct Winners: Codable {
let status: Int
let message: String
let data: DataClass
}
struct DataClass: Codable {
let result: [Result]
let overall: String
}
struct Result: Codable {
let name: String
let response: [Response]
}
class Response: Codable {
let name: String
let prize: Double
init(name: String, prize: Double) {
self.name = name
self.prize = prize
}
enum CodingKeys: String, CodingKey {
case name
case prize
}
enum SerializationError: Error {
case missing(String)
case invalid(String, Any)
}
public required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
name = try container.decode(String.self, forKey: .name)
let prizeString = try container.decode(String.self, forKey: .prize)
guard let prize = Double(prizeString) else {
throw SerializationError.invalid("prize", prizeString)
}
self.prize = prize
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(name, forKey: .name)
try container.encode("\(prize)", forKey: .prize)
}
}
func testing() {
let winners = try! JSONDecoder().decode(Winners.self, from: jsonData)
let sum = winners.data.result[section].response
.map({ $0.prize })
.reduce(0, +)
print(sum)
}
let sum = winnerArr?[section]["response"].reduce(0, { x, y in
x + y["prize"]
}) ?? 0
As an aside, I would recommend parsing the response and turning it into usable objects rather than dealing with the raw response : https://developer.apple.com/swift/blog/?id=37
Thanks to all of your guys, who helped. I am a newbie and not familiar with Codable. Thanks for introducing me to the same. I will try to understand it and use it for future purposes.
So, coming back to the answer:
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView?
{
self.prizeArr.removeAll()
let header = tableView.dequeueReusableHeaderFooterView(withIdentifier: cellReuseIdentifier) as! OverallHeaderCell
let resArr = self.winnerArr![section]["response"]! as? Array<Dictionary<String,Any>>
for prize in resArr!
{
let doubleStr = prize["prize"] as? NSString
self.prizeArr.append((doubleStr?.doubleValue)!)
}
let sumedArr = prizeArr.reduce(0, +)
let countArr = winnerArr![section]["response"] as? Array<Dictionary<String,Any>>
header.textL[0].text = winnerArr![section]["name"] as? String
header.textL[1].text = "$\(sumedArr)"
header.textL[3].text = "\(String(describing: countArr!.count))"
return header
}
Here I am converting each string to a double and appending it to the prizeArr and then, finally, summing the entire array to get the desired result. But, this is not the ideal way to do it. This answer is for all the newbies like me and please learn Codables.

Resources