How to save struct to NSUserDefaults in Swift 2.0 - ios

I have a struct named Jarand I would like to save an array of them to NSUserDefaults. Here is the jar struct code:
struct Jar {
let name: String
let amount: Int
init(name: String, amount: Int){
self.name = name
self.amount = amount
}
}
I belive that I will need to convert this to an NSObject to be able to save it. (Because you can't save a struct directly to NSUserDefaults). My questions are:
How do I convert an array of structs to an NSObject? and How to convert an NSObject back at an array of structs.

This solution is inspired by #Duncan C. I wrote it more familiar way as we do in case Custom Class encoding and decoding.
public struct IRDriver {
public var name: String?
public var amount: Int?
public init() {
}
// Decode
public init(dictionary: Dictionary<String, AnyObject>){
name = dictionary["name"] as? String
amount = dictionary["amount"] as? Int
}
// Encode
public func encode() -> Dictionary<String, AnyObject> {
var dictionary : Dictionary = Dictionary<String, AnyObject>()
dictionary["name"] = name
dictionary["amount"] = amount
return dictionary
}
}

For saving to user defaults you have a couple of options: Have the object conform to NSCoding, or implement methods that convert it to/from an NSDictionary, and save that.
Something like this:
func dictionaryFromJar() -> NSDictionary
{
let dictionary: [AnyObject: AnyObject] = ["name": name, "amount": amount]
return dictionary
}
I think the automatic bridging between Swift dictionaries and NSDictionary would work here, but I'm not positive. My swift is getting a little rusty. :(

Related

Cast from '(key: String, value: [Dates])' to unrelated type 'Dates' always fails

As my objective is to use the MVVM architecture,I have configured my Model class fine, but now having troubles with initialising my ViewModel class. It gives me an error saying "Cast from '(key: String, value: [Any])' to unrelated type 'Dates' always fails". When I run it, it crashes. Any help would much appreciate.
View Model Class as follow
struct JobsViewModel {
private let title : String?
private let imageArray : [Formats]?
private let category : String?
private let shifts : [ShitDetails]?
private let distance : String?
//Dependency injection
init(mainData:Dates) {
self.title = mainData.title
self.imageArray = mainData.client?.photos
self.shifts = mainData.shifts
self.category = mainData.job_category?.description
self.distance = mainData.distance
}
}
Model Class as follow
public struct Schedule: Codable {
public let data : [String:[Dates]]
}
public struct Dates: Codable {
public let title: String?
public let distance: String?
public let client: Images?
public let shifts: [ShitDetails]?
public let job_category:JobCategory?
}
On success of my API call I'm trying to initailize it as bellow and its where it crash.
var jobsViewModel = [JobsViewModel]() //jobsViewModel is a instance variable
Network.shared.retrieveHotelDetails(successBlock: { (results) in
let mainData = results as? Schedule
self.jobsViewModel = mainData?.data.map({return JobsViewModel(mainData: $0 as! Dates)}) ?? []
}
{"2018-06-07": [
{
"someKey": "Test1",
"someKey": "Test1"
}
],
"2018-06-06": [
{
"someKey": "Test1",
"someKey": "Test1"
}
]}
So you're trying to map from that (mainData?.data is a [String:[Dates]] Dictionary) to a "JobsViewModel" Array.
Your main issue is that you essentially try to map from something that contains multiple arrays to one array. If you want to do that, flatMap will be your best option.
Just like this:
self.jobsViewModel = mainData.data.flatMap({ $0.value }).map({ JobsViewModel(mainData: $0 )})
This first "flat maps" all of the Dates Arrays in your dictionary into one large dictionary that contains them all and then creates (via "map") a JobsViewModel for each of them by using each Dates object in the JobsViewModel initializer.

How can I Read/Write array of objects from/to JSON using a Swift class that matches JSON from NewtonSoft (JSON.Net) component?

I'm trying to read a JSON file that I've created using the NewtonSoft JSON.Net parser in another (Windows) program. The JSON was created by the JSON.Net component when it serialized an array of objects. The sample JSON looks like the following (for this example I'm just showing two of the objects):
[{"MaxLength":23,"HasSpecialChars":false,"HasUpperCase":true,"Key":"firstOne"},
{"MaxLength":0,"HasSpecialChars":false,"HasUpperCase":false,"Key":"secondOne"}]
Notice that this is an array of objects in json.
Now I need some Swift code that will read this JSON in and write it out after values are altered in the program.
What I've Tried
I found this SO entry : Reading in a JSON File Using Swift
However, to get an array of objects, that entry uses separate structs that are Codable like the following:
struct ResponseData: Decodable {
var thisNameShowsUpInJson: [SiteKey]
}
That forces the outer array to have it's own name property in the json.
For example, the only way the code at that post works is if my JSON is altered to include an outer object with a name (SiteKey) like the following:
{"thisNameShowsUpInJson": [{"MaxLength":23,"HasSpecialChars":false,"HasUpperCase":true,"Key":"firstOne"},
{"MaxLength":0,"HasSpecialChars":false,"HasUpperCase":false,"Key":"secondOne"}]
}
However, that is not correct for the way that JSON.Net writes an array of objects to a file.
Here's my simple Swift class that I want to serialize and deserialize:
class SiteKey : Codable{
var Key : String
var MaxLength : Int
var HasSpecialChars : Bool
var HasUpperCase : Bool
init(key : String, maxLength : Int,
hasSpecialChars : Bool,
hasUpperCase : Bool){
Key = key;
MaxLength = maxLength;
HasSpecialChars = hasSpecialChars;
HasUpperCase = hasUpperCase;
}
}
I'd like to read the data from a named file and deserialize the data into objects.
Then, I'd like to serialize the in memory objects back out to a file like my example.
Imagine that you have an array of codable objects
var array = [SiteKey]()
then you can simply encode the entire array to Data using JSONEncoder
do {
let encoded = try JSONEncoder().encode(array)
} catch { print(error) }
To decode Data to your array of objects you can use JSONDecoder
do {
array = try JSONDecoder().decode([SiteKey].self, from: encoded)
} catch { print(error) }
My suggestions:
make your class struct instead, then you can remove hard-coded init since you get one for free
name variables with small capital letter and then use coding keys for renaming it while encoding/decoding
struct SiteKey : Codable {
var key : String
var maxLength : Int
var hasSpecialChars : Bool
var hasUpperCase : Bool
enum CodingKeys: String, CodingKey {
case key = "Key"
case maxLength = "MaxLength"
case hasSpecialChars = "HasSpecialChars"
case hasUpperCase = "HasUpperCase"
}
}
I discovered the code I need to use in Swift which allows me to read and write the JSON (array of objects) that is output by JSON.Net.
I've added two methods to my SiteKey object :
func loadJson(filename fileName: String) -> [SiteKey]
func writeJson(filename fileName: String, allSiteKeys : [SiteKey])
The first function takes a string that points to a json file and returns the array of SiteKeys that is in the file.
The second function takes a filename and the array of SiteKey objects and writes them to the file.
Here's the altered SiteKey class with the added functions.
class SiteKey : Codable{
var Key : String
var MaxLength : Int
var HasSpecialChars : Bool
var HasUpperCase : Bool
init(key : String, maxLength : Int,
hasSpecialChars : Bool,
hasUpperCase : Bool){
Key = key;
MaxLength = maxLength;
HasSpecialChars = hasSpecialChars;
HasUpperCase = hasUpperCase;
}
func loadJson(filename fileName: String) -> [SiteKey]? {
if let url = Bundle.main.url(forAuxiliaryExecutable: fileName) {
do {
let data = try Data(contentsOf: url)
let decoder = JSONDecoder()
let allKeys = try decoder.decode([SiteKey].self, from: data)
return allKeys
} catch {
print("error:\(error)")
}
}
return nil
}
func writeJson(filename fileName: String, allSiteKeys : [SiteKey]){
let Data = try? JSONEncoder().encode(allSiteKeys)
let pathAsURL = URL(fileURLWithPath: fileName)
do {
try Data?.write(to: pathAsURL)
}
catch {
print("Failed to write JSON data: \(error.localizedDescription)")
}
}
}
Here's the usage:
let newSiteKey = siteKey.loadJson(filename: "/Users/fakeUser/Documents/Dev/all.json")
When the loadJson method returns the newSiteKey will contain an array of SiteKey class objects that can be iterated through.

How to implement model class for multiple values in swift 3?

Here I am having value in JSON in which for some of multiple key value pairs it returning string and for some it is returning array here in custom attributes array in first dictionary in that value key value pair the data present is different and in the second dictionary value key value pair is different here then how to implement the model class for inside array for different key values ?
struct MediaGallery {
let id : Int
let mediaType : String
let label : Any
let position : Int
let disabled : Any
let file : String
init(dict : [String:Any]) {
self.id = (dict["id"] as? Int)!
self.mediaType = (dict["media_type"] as? String)!
self.label = dict["label"]!
self.position = (dict["position"] as? Int)!
self.disabled = dict["disabled"]!
self.file = (dict["file"] as? String)!
}
}
struct AttributeList {
let label : String
let value : String
let code : String
init(dict : [String:Any]){
self.label = (dict["label"])! as! String
self.value = (dict["value"])! as! String
self.code = (dict["code"])! as! String
}
}
struct DetailsListAttribute {
let attributeCode : String
let value : Any
init?(dict : [String:Any]) {
self.attributeCode = dict["attribute_code"] as! String
print(self.attributeCode)
if let values = dict["value"] as? String {
self.value = values
}
else {
if let arr = dict["value"] as? [[String:Any]]{
var filterArr = [AttributeList]()
for obj in arr {
filterArr.append(AttributeList(dict: obj))
}
self.value = filterArr
} else {
self.value = [AttributeList]()
}
}
}
}
I would suggest please save some time by using this great GIT Library ObjectMapper . it will help you to model your object and convert your model objects (classes and structs) to JSON and vice versa.
I've tried multiple JSON-mapping frameworks that were mentioned in Tj3n comment. They all have pros and cons. Apple suggests you to follow the recommendation given here. Also you should check Codable protocol (swift 4 is required).
Ok I don't have the whole JSON, and it doesn't seem clear to me.
But here is how you can parse and create your model Class easily in Swift with the Codable protocol.
You can read more about it and/or some examples, tutorials : Ultimate Guide.
Briefly, what is the Codable protocol ?
You don't need third party library anymore in order to parse and set the json data to your model class.
You juste have to create your class like the JSON is represented. And according to the key-name, it will create the class, properties and everything for you.
Here is an example with your JSON, I don't know if I understood your JSON formatting, but you got the trick :
struct Response: Codable {
let ca: [CustomAttribute]?
enum CodingKeys: String, CodingKey {
case ca = "custom_attributes"
}
}
struct CustomAttribute: Codable {
let code: String?
let value: [Value]?
struct Value: Codable {
let label: String?
let value: String?
let code: String?
let avg: String? // I don't know how your value array is composed
let count: Int? // I don't know how your value array is composed
}
enum CodingKeys: String, CodingKey {
case code = "attribute_code"
case avg = "avg_rating_percent"
}
}
For me, it looks like something like that.
I don't see the whole JSON, but imagine you have the whole JSON as the Response Struct, it contains several objects, like the CustomAttribute Array for example.
Then you can define the CustomAttribute structure, and add as many properties as the JSON has.
Anyway, you can call it this way :
When you have the response from your API call, you can go :
if let data = response.data {
let decoder = JSONDecoder()
let response = try! decoder.decode(Response.self, from: data)
print("Only printing the Custom Attribute : \(response.ca!)")
}
I decode the whole json data as an Object Response (like my Struct).
And I pass to my response callback, or
this might be late but I think this will helps others
The model class which are varies for frameworks like SwiftyJSON, simple swift class, Gloss or swift codable (Swift 4). you can easily generate model class online with your customization jsoncafe.com

How to use For Loop through keys of NSUserDefault Dictionary

I can't get a value out of this computed property (userList)...is my init set up correctly? I'm trying to build a list that will populate the rows in my table view. (*Edit: I've edited my question with a saveData function, but "cannot invoke setObject" with an argument list of type ([String : User], forKey: String)" –
import Foundation
class DataManager {
static let sharedInstance = DataManager()
var users = [String : User]()
init() {
let userDefaults = NSUserDefaults.standardUserDefaults()
if let var userFromDefaults = userDefaults.objectForKey("userKey") as? [String : User] {
users = userFromDefaults
}
else {
// add default values later
}
}
func saveData() {
let userDefaults = NSUserDefaults.standardUserDefaults()
userDefaults.setObject(users, forKey: "userKey")
}
var userList: [String] {
var list: [String] = []
for name in users.keys {
list.append(name)
}
list.sort(<)
return list
}
My viewcontroller:
import UIKit
class NameViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
var users: [String] = DataManager.sharedInstance.userList
User Struct with a method to convert into json:
import Foundation
struct User {
var name = ""
var stores: [Store] = []
init?(json: [String: AnyObject]) {
if let name = json["name"] as? String,
storesJSON = json["stores"] as? [[String: AnyObject]]
{
self.name = name
self.stores = storesJSON.map { Store(json: $0)! }
} else {
return nil
}
}
init() { }
func toJSON() -> [String: AnyObject] {
return [
"name": name,
"stores": stores.map { $0.toJSON() }
]
}
}
You can grab all keys/Values using (from apple documentation)
dictionaryRepresentation() Returns a dictionary that contains a union
of all key-value pairs in the domains in the search list.
Using this you can loop all the elements in the dictionary
for element in NSUserDefaults.standardUserDefaults().dictionaryRepresentation() {
println(element)
//Do what you need to do here
}
You can also retrieve just the keys:
println(NSUserDefaults.standardUserDefaults().dictionaryRepresentation().keys.array);
or just the values
println(NSUserDefaults.standardUserDefaults().dictionaryRepresentation().values.array);
As Matt pointed out in his comment, you need to save contents to NSUserDefaults before you can read them.
Something else that will prevent your code from working:
NSUserDefaults will only save "property list" objects (NSString, NSData, NSDate, NSNumber, NSArray, or NSDictionary objects or their Swift equivalents.)
If you try to save anything else, or a collection containing anything but those data types, the save fails and you get nil when you try to read it back. Your code shows you trying to save a dictionary where the value for each key is an object of type "User". That's going to fail since "User" is not one of the short list of "property list" object types.
This is not a good user of NSUserDefaults, which is meant to store preferences and other small bits of data between app launches. See the documentation on NSUserDefaults.
You should be managing this dictionary of users in memory when you are displaying data, and if you want to persist it to disk between app launches you should use CoreData or make your User class conform to NSCoding and read/write it with archiving. Here's a nice tutorial.

Can I use a swift dictionary in a subclass of AWSDynamoDBModel?

I have a class that inherits from AWSDynamoDBModel and adheres to AWSDynamoDBModeling protocol. Example:
class ProfileDatabaseModel : AWSDynamoDBModel, AWSDynamoDBModeling {
var userid: String
var firstName: String
var lastName: String
var someOtherStuff: [String: String] // IS THIS OK?
// assume some other details here, like initializations
class func dynamoDBTableName() -> String! {
return "atable"
}
class func hashKeyAttribute() -> String! {
return "userid"
}
}
With a class like this, I can perform the following few lines that update the DynamoDB with the data in an instantiation of this class:
var db = AWSDynamoDBObjectMapper.defaultDynamoDBObjectMapper()
var model = ProfileDatabaseModel()
// fill in model data
let task: BFTask = db.save(model)
My question is: Can I have that dictionary in the object? Will that work with the object mapper? If not as a Swift dictionary, would it work as an NSDictionary? Or, do I need to translate it to a JSON format?
Currently, AWSDynamoDBObjectMapper supports:
NSNumber
NSString
NSData
NSArray of the above three datatypes
NSDictionary is not supported, but we are evaluating how we can support the map datatype in the coming releases.

Resources