I am using a tuple of arrays in one of my files and want to start saving it in UserDefaults. Here is how I am storing my current data:
typealias savemytuple = (Name: String, NameID: String, BookID: String, Picture: NSData)
var savefun = [savemytuple]()
I have researched and found I may need to do something with the following for saving:
NSKeyedArchiver.archivedDataWithRootObject
And this for retrieving:
NSKeyedUnarchiver.unarchiveObjectWithData
Not sure if this way will work as my tuple has multiple data types. Any idea on how to do this?
The values in UserDefaults must be property lists, so you need to convert each tuple to a property list. A Data is a property list, and there are several ways to convert things to Data.
One way is to stop using a tuple and use a struct instead. If all of the struct's stored properties conform to Codable, then you can ask Swift to generate everything needed to make the struct itself conform to Codable just by declaring the conformance. Both String and Data conform to Codable.
Once the struct is Codable, you can convert one of them, or even an array of them, into a property list via JSONEncoder or PropertyListEncoder:
import Foundation
struct Bookie: Codable {
var name: String
var nameId: String
var bookId: String
var picture: Data
}
let bookies = [
Bookie(name: "mbro12", nameId: "id1", bookId: "b1", picture: Data()),
Bookie(name: "mayoff", nameId: "id2", bookId: "b2", picture: Data())
]
let bookiesData = try! PropertyListEncoder().encode(bookies)
UserDefaults.standard.set(bookiesData, forKey: "bookies")
let fetchedData = UserDefaults.standard.data(forKey: "bookies")!
let fetchedBookies = try! PropertyListDecoder().decode([Bookie].self, from: fetchedData)
print(fetchedBookies)
You have to convert the tuples into Dictionary to save it, not really a proper way, rather make your tuple into a custom object, then conform NSCoding protocol for a proper way to convert between data and object.
You don't really need NSKeyedArchiver.archivedDataWithRootObject, just simply convert the tuple into a Dictionary and have another function to convert back to the tuple type.
Related
I (a complete rookie) am currently trying to create my first iOS app - a currency table/converter for the currency of my country, Ukrainian Hryvna. I have created a TableView that I am going to fill with data from JSON file from the following link:
Tap here
The file itself has Array root. Like that:
[
{
"r030":36,"txt":"Австралійський долар","rate":21.334,"cc":"AUD","exchangedate":"23.12.2020"
}
,{
"r030":124,"txt":"Канадський долар","rate":21.9334,"cc":"CAD","exchangedate":"23.12.2020"
}
,{
"r030":156,"txt":"Юань Женьміньбі","rate":4.3192,"cc":"CNY","exchangedate":"23.12.2020"
}
,{
"r030":191,"txt":"Куна","rate":4.5833,"cc":"HRK","exchangedate":"23.12.2020"
}]
I want to get a create a Dictionary out of this file using just two values: [cc: rate] and then fill my TableView with this data. I don't care for other values.
Something like that:
["AUD": 21.334, "CAD": 21.9334]
Should I use some other data type to store this data? A Struct representing a currency and then make an Array of currency Structs, perhaps?
How do I get this file from that URL and make such a Dictionary/Struct array/...?
Thank you so much in advance :)
Make a struct
struct Currency : Decodable {
let name : String
let rate : Double
private enum CodingKeys : String, CodingKey { case name = "cc", rate }
}
Then load the data with URLSession and decode the JSON array to [Currency] with JSONDecoder (there are zillions of examples of both).
How can I decode the following using swift Decodable? I'm only interested in the extract value
{
"batchcomplete":"",
"query":{
"normalized":[ ],
"pages":{
"434325":{ //can be any number!
"pageid":434325,
"ns":0,
"title":"asdfasdfsa",
"extract":""
I'm attempting the following:
struct Entry: Decodable {
let batchcomplete: String
let query: Query
struct Query: Decodable {
let normalized: [String]
let pages: Page
struct Page: Decodable {
let pageid: Entry // I think this is going to be an issue
struct Entry: Decodable {
let title: String
let extract: String
}
}
}
}
I'm trying to retrieve the extract like this:
print(entry.query.pages.first.extract)
Is there a way to use .first for this ?
I'm only every going to get maximum one page so ideally I would just grab the first element.
Your decoding code is ok up until `Query type.
So what should be used instead:
struct Query: Decodable {
let normalized: [String]
let pages: [String: Page]
struct Page: Decodable {
let pageid: Int
let title: String
let extract: String
}
}
So, key points:
If you don't know what keys would be there, use [String: <SomeDecodableType>] because any JSON Object can be mapped to the [String: <Decodable>].
You don't need to declare Page.Entry, just put all the fields to the Page
To get extract instead of entry.query.pages.first.extract you will have to use entry.query.pages.first?.value.extract (extract of first random page, because [String: Page] is unsorted) or entry.query.pages.sorted(by: sorter).first?.value.extract (where sorter is some function, that takes two key-value pairs and returns true if first one should go before second one in order) to get first using some order.
Example:
import Foundation
class Player: Codable {
let name: String
init(name: String) {
self.name = name
}
}
class Team: Codable {
let players: [Player]
let capitan: Player
init(players: [Player], capitan: Player) {
self.players = players
self.capitan = capitan
}
}
let player1 = Player(name: "p1")
let player2 = Player(name: "p2")
let team = Team(players: [player1, player2], capitan: player1)
print(team.players[0] === team.capitan) // true
let encoder = JSONEncoder()
let data = try encoder.encode(team)
let decoder = JSONDecoder()
let team2 = try decoder.decode(Team.self, from: data)
print(team2.players[0] === team2.capitan) // false
Output:
true
false
How to use Codable protocol with reference types?
The behavior may change in the future?
https://github.com/apple/swift-evolution/blob/master/proposals/0167-swift-encoders.md
This is fundamental to JSON, not Codable. JSON is an encoding of values. It does not keep track of references; it's not part of the format. If you want to keep track of references, you would have to store that in the JSON yourself, by encoding objectIdentifier. You'd then need to join up things that have the same identifier. But that's beyond the scope of Codable, since the underlying format doesn't have the concept. You'd need to create some other format (perhaps that used JSON as its underlying value format, in the way that NSKeyedArchiver uses property lists as its underlying value format).
JSON isn't an object graph storage format; as I said it's a value encoding. If you want an object graph, that's what NSKeyedArchiver is for. If you want a non-Cocoa solution, you would need to implement a new Encoder and Decoder that implemented the same basic idea as the archivers. That isn't what JSONEncoder is for. But I'd use NSKeyedArchiver if possible; it's designed for this.
Note that SE-0167 says that there is a new encodeCodable method on NSKeyedArchiver that is supposed to let you intermix Codable types with NSCoding types (and allow Swift types to participate in NSKeyedArchiver), but I don't see it in Xcode 9 beta 1.
It seems like the encoder currently does not respect references and instead saves a separate instance of the same object each time it is referenced, which is why === fails. You can change it to == and implement Equatable for your classes which should fix this, or as the comment on the question states, use structs.
I am trying to append value to dictionary which I have declared like this.
var setHomIconDict:[(iconImg: UIImage, Operator: String, oprCode: String, oprMerchCode:String)]!
I am not sure its dictionary though. I am learning swift and I found a Intresting code on net. Anyway I am confused on how will I append values to this. I tried
setHomIconDict.append((iconImg:img!, Operator: "Strin", oprCode: "Hello", oprMerchCode: "Huja"))
But i get this error : fatal error: unexpectedly found nil while unwrapping an Optional value . here is the image
Can anyone suggest an appropriate way.
setHomIconDict is not Dictionary it is an Array of tuple and you are getting this crash because you haven't initialized the setHomIconDict object, you have just declared its type. So initialized it with its declaration.
var setHomIconDict:[(iconImg: UIImage, Operator: String, oprCode: String, oprMerchCode:String)] = []
Note: Instead of creating array of tuple better solution is to create one custom model Class with the property that you want to set/access, then create array of that custom Class objects and use it.
You are probably better off creating a struct to hold your values. Then your array would be an array of that struct.
struct homIcon {
var iconImg: UIImage
var Operator: String
var oprCode: String
var oprMerchCode:String
}
var setHomIconArray:[homIcon] = []
let newHomIcon = homIcon(iconImg: img!, Operator: "Strin", oprCode: "Hello", oprMerchCode: "Huja")
setHomIconArray.append(newHomIcon)
As Nirav says, the syntax you are using creates an array of tuples. If you want a dictionary get rid of the parentheses.
You want to define key/value pairs using this syntax:
var aDict = [key:value, key:value, key:value]
Then you can add new key/value pairs using:
aDict[newKey] = newValue
Is your goal to create and use a dictionary, or is it your goal to user and understand the structure that you found on the net?
Ok so I'm able to parse my JSON to my Model struct which looks like this:
JSON:
{
"base":"CHF",
"date":"2017-02-09",
"rates":{
"AUD":1.3086,
"BGN":1.8326,
"BRL":3.123,
"CAD":1.3133,
"CNY":6.879,
"CZK":25.32,
"DKK":6.9665,
"GBP":0.79732,
"HKD":7.7729,
"HRK":6.9992,
"HUF":289.31,
"IDR":13280.0,
"ILS":3.7553,
"INR":66.867,
"JPY":112.48,
"KRW":1146.2,
"MXN":20.482,
"MYR":4.4473,
"NOK":8.3265,
"NZD":1.3871,
"PHP":50.008,
"PLN":4.0382,
"RON":4.2115,
"RUB":58.914,
"SEK":8.8863,
"SGD":1.4173,
"THB":35.076,
"TRY":3.7,
"USD":1.0019,
"ZAR":13.435,
"EUR":0.93703
}
}
MODEL:
struct TestStruct {
var base: String
var date: String
var rates: [String: Double]
init(base: String, date: String, rates: [String:Double]) {
self.base = base
self.date = date
self.rates = rates
}
}
But now I have no Idea how I'm going to store my Model in CoreData I know how store the base and date because those are just strings but how can I store a Dictionary or maybe convert it to something because I will need the "rates" Dictionary back from CoreData since I need to know which currency has which exchange rate...
Core Data is so complex that I suggest you search on Google for tutorials. However, I built the proper data model. May it help you.