Convert a custom object to Data to be saved in NSUserDefauts
The issue there. is that you are mixing up Codable with NSCoding. To use NSKeyedArchiver's archivedData method you need to have a class that conforms to NSCoding. In you case you have a structure that conforms to Codable so you need to use JSONEncoder encode method. Note: Your equatable method declaration was wrong:
struct BallPark: Codable, Equatable {
static func == (lhs: Self, rhs: Self) -> Bool {
return lhs.id == rhs.id
}
let id: String
let name: String?
let location: VenueCoordinates?
let mapEnabled: Bool?
let facilityStatus: String?
}
struct VenueCoordinates: Codable {
let latitude: String?
let longitude: String?
var lat: Double? {
guard let latitud = latitude else { return nil}
return Double(latitud)
}
var lon: Double? {
guard let longitude = longitude else { return nil}
return Double(longitude)
}
}
let bestBallParkToSave = BallPark.init(id: "1", name: "Steve", location: .init(latitude: "20.0", longitude: "40.0"), mapEnabled: true, facilityStatus: "a status")
do {
let encodedBestBallPark = try JSONEncoder().encode(bestBallParkToSave)
UserDefaults.standard.set(encodedBestBallPark, forKey: "favoriteBallPark")
if let ballParkData = UserDefaults.standard.data(forKey: "favoriteBallPark") {
let loadedBallPark = try JSONDecoder().decode(BallPark.self, from: ballParkData)
print(loadedBallPark) // BallPark(id: "1", name: Optional("Steve"), location: Optional(VenueCoordinates(latitude: Optional("20.0"), longitude: Optional("40.0"))), mapEnabled: Optional(true), facilityStatus: Optional("a status"))
}
} catch {
print(error)
}
You can also make your life easier extending UserDefaults and create customs encoding and decoding methods:
extension UserDefaults {
func decodeObject<T: Decodable>(forKey defaultName: String, using decoder: JSONDecoder = .init()) throws -> T {
try decoder.decode(T.self, from: data(forKey: defaultName) ?? .init())
}
func encode<T: Encodable>(_ value: T, forKey defaultName: String, using encoder: JSONEncoder = .init()) throws {
try set(encoder.encode(value), forKey: defaultName)
}
}
Usage:
let bestBallParkToSave = BallPark(id: "1", name: "Steve", location: .init(latitude: "20.0", longitude: "40.0"), mapEnabled: true, facilityStatus: "a status")
do {
try UserDefaults.standard.encode(bestBallParkToSave, forKey: "favoriteBallPark")
let loadedBallPark: BallPark = try UserDefaults.standard.decodeObject(forKey: "favoriteBallPark")
print(loadedBallPark) // BallPark(id: "1", name: Optional("Steve"), location: Optional(VenueCoordinates(latitude: Optional("20.0"), longitude: Optional("40.0"))), mapEnabled: Optional(true), facilityStatus: Optional("a status"))
} catch {
print(error)
}
Save custom objects into NSUserDefaults
Actually, you will need to archive the custom object into NSData
then save it to user defaults and retrieve it from user defaults and unarchive it again.
You can archive it like this
let teams = [Team(id: 1, name: "team1", shortname: "t1"), Team(id: 2, name: "team2", shortname: "t2")]
var userDefaults = UserDefaults.standard
let encodedData: Data = NSKeyedArchiver.archivedData(withRootObject: teams)
userDefaults.set(encodedData, forKey: "teams")
and unarchive it like this
let decoded = userDefaults.data(forKey: "teams")
let decodedTeams = NSKeyedUnarchiver.unarchiveObject(with: decoded) as! [Team]
But if you just did that you will get
.Team encodeWithCoder:]: unrecognized selector sent to instance
You will have to make Team conform to NSCoding just like this
class Team: NSObject, NSCoding {
var id: Int
var name: String
var shortname: String
init(id: Int, name: String, shortname: String) {
self.id = id
self.name = name
self.shortname = shortname
}
required convenience init(coder aDecoder: NSCoder) {
let id = aDecoder.decodeInteger(forKey: "id")
let name = aDecoder.decodeObject(forKey: "name") as! String
let shortname = aDecoder.decodeObject(forKey: "shortname") as! String
self.init(id: id, name: name, shortname: shortname)
}
func encode(with aCoder: NSCoder) {
aCoder.encode(id, forKey: "id")
aCoder.encode(name, forKey: "name")
aCoder.encode(shortname, forKey: "shortname")
}
}
how can store custom objects in NSUserDefaults
First you create custom class Like below.
CustomObject.h
#import <Foundation/Foundation.h>
@interface CustomObject : NSObject<NSCoding>
@property(strong,nonatomic)NSString *name;
@end
CustomObject.m
#import "CustomObject.h"
@implementation CustomObject
- (void)encodeWithCoder:(NSCoder *)encoder {
//Encode properties, other class variables, etc
[encoder encodeObject:self.name forKey:@"name"];
}
- (id)initWithCoder:(NSCoder *)decoder {
if((self = [super init])) {
//decode properties, other class vars
self.name = [decoder decodeObjectForKey:@"name"];
}
return self;
}
Then create CustomObject class object and store in NSUserDefaults
Stored your object like this
CustomObject *object =[CustomObject new];
object.name = @"test";
NSMutableArray *arr = [[NSMutableArray alloc]init];
[arr addObject:object];
NSData *encodedObject = [NSKeyedArchiver archivedDataWithRootObject:arr];
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:encodedObject forKey:@"storeObject"];
[defaults synchronize];
get custom object from NSUserDefaults like this
NSData *storedEncodedObject = [defaults objectForKey:@"storeObject"];
NSArray *arrStoreObject = [NSKeyedUnarchiver unarchiveObjectWithData:storedEncodedObject];
for (int i=0; i<arrStoreObject.count; i++)
{
CustomObject *storedObject = [arrStoreObject objectAtIndex:i];
NSLog(@"%@",storedObject.name);
}
Swift saving and retrieving custom object from UserDefaults
Swift 4 or later
You can once again save/test your values in a Playground
UserDefaults need to be tested in a real project. Note: No need to force synchronize. If you want to test the coding/decoding in a playground you can save the data to a plist file in the document directory using the keyed archiver. You need also to fix some issues in your class:
class Person: NSObject, NSCoding {
let name: String
let age: Int
init(name: String, age: Int) {
self.name = name
self.age = age
}
required init(coder decoder: NSCoder) {
self.name = decoder.decodeObject(forKey: "name") as? String ?? ""
self.age = decoder.decodeInteger(forKey: "age")
}
func encode(with coder: NSCoder) {
coder.encode(name, forKey: "name")
coder.encode(age, forKey: "age")
}
}
Testing:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
do {
// setting a value for a key
let newPerson = Person(name: "Joe", age: 10)
var people = [Person]()
people.append(newPerson)
let encodedData = try NSKeyedArchiver.archivedData(withRootObject: people, requiringSecureCoding: false)
UserDefaults.standard.set(encodedData, forKey: "people")
// retrieving a value for a key
if let data = UserDefaults.standard.data(forKey: "people"),
let myPeopleList = try NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data) as? [Person] {
myPeopleList.forEach({print($0.name, $0.age)}) // Joe 10
}
} catch {
print(error)
}
}
}
How to store custom objects in NSUserDefaults
On your Player class, implement the following two methods (substituting calls to encodeObject with something relevant to your own object):
- (void)encodeWithCoder:(NSCoder *)encoder {
//Encode properties, other class variables, etc
[encoder encodeObject:self.question forKey:@"question"];
[encoder encodeObject:self.categoryName forKey:@"category"];
[encoder encodeObject:self.subCategoryName forKey:@"subcategory"];
}
- (id)initWithCoder:(NSCoder *)decoder {
if((self = [super init])) {
//decode properties, other class vars
self.question = [decoder decodeObjectForKey:@"question"];
self.categoryName = [decoder decodeObjectForKey:@"category"];
self.subCategoryName = [decoder decodeObjectForKey:@"subcategory"];
}
return self;
}
Reading and writing from NSUserDefaults
:
- (void)saveCustomObject:(MyObject *)object key:(NSString *)key {
NSData *encodedObject = [NSKeyedArchiver archivedDataWithRootObject:object];
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:encodedObject forKey:key];
[defaults synchronize];
}
- (MyObject *)loadCustomObjectWithKey:(NSString *)key {
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSData *encodedObject = [defaults objectForKey:key];
MyObject *object = [NSKeyedUnarchiver unarchiveObjectWithData:encodedObject];
return object;
}
Code shamelessly borrowed from: saving class in nsuserdefaults
How can I store an custom class as Data
Define a custom object which conforms to Codable
. A simple example:
struct MyObject: Codable {
var something: String
}
And when you want to save it to UserDefaults, just encode
it:
do {
let encoded = try JSONEncoder().encode(MyObject(something: "String"))
UserDefaults.standard.set(encoded, forKey: "kSavedObject")
} catch {
print(error)
}
If you want to retrieve it, you can use decode
:
if let data = UserDefaults.standard.data(forKey: "kSavedObject") {
do {
let myRetrievedObject = try JSONDecoder().decode(MyObject.self, from: data)
} catch {
print(error)
}
}
How to save a multi-leveled custom object to NSUserDefault's?
All the objects (Instructor, Class, and Student) need to adopt and implement the NSCoding protocol. See this post for an implentation. As @uchuugaka said though, it sounds like NSUserDefaults isn't the place to store this data.
You could still serialize the data to disk, but just write it to a separate file. Apple's Archives and Serializations Programming Guide is good reading to learn the basics of this method.
If the object graph you want to save is going to be more complex and you require searching and querying the object graph, e.g. finding the number of students with studentMoney >= 50 with lastName beginning with 'A', then you may want to look into Core Data. However, be aware that Core Data is an advanced framework so it does have a sizable learning curve.
Related Topics
How Constant Is the Firebase Anonymous Id
Drag Rotate a Node Around a Fixed Point
Convert Character to Integer in Swift
Swift Error Handling for Methods That Do Not Throw
How to Write a Function That Will Unwrap a Generic Property in Swift Assuming It Is an Optional Type
How to Add Kerning to a Textfield in Swiftui
Swift Parsing Attribute Name for Given Elementname
How to Split an Int to Its Individual Digits
How to Create a Window with Transparent Background with Swift on Osx
Getting Country Name from Country Code
In Swiftui, Where Are the Control Events, I.E. Scrollviewdidscroll to Detect the Bottom of List Data
Difference Between String Interpolation and String Initializer in Swift
Self' Captured by a Closure Before All Members Were Initialized
How to Create an Array of Functions
Segue from a Slpagingviewswift Vc and Dismiss the Destination Vc to Return
How to Set the Alpha of an Uiimage in Swift Programmatically