How to retrieve data from NSUserDefaults? - ios

currently, I try make an music playlist application. I try to save and retrieve playlist data from NSUserDefaults. I have no problem when saving the data but I got an error when retrieving data.
I got:
Terminating app due to uncaught exception 'MPMediaItemCollectionInitException', reason: '-init is not supported, use -initWithItems:
I'm following the answer from Play iPod playlist retrieved from a saved persistentid list but I try to write it in swift.
here is my save function:
func savePlaylist(var mediaItemCollection: MPMediaItemCollection){
var items: NSArray = mediaItemCollection.items
var listToSave: NSMutableArray = NSMutableArray()
for song in items{
var persistentId: AnyObject! = song.valueForProperty(MPMediaItemPropertyPersistentID)
listToSave.addObject(persistentId)
}
var data: NSData = NSKeyedArchiver.archivedDataWithRootObject(listToSave)
NSUserDefaults.standardUserDefaults().setObject(data, forKey: "songsList")
NSUserDefaults.standardUserDefaults().synchronize()
}
and here is my retrieve function:
func setupMusic(){
if NSUserDefaults.standardUserDefaults().objectForKey("songsList") != nil{
var theList: NSMutableArray = NSMutableArray()
var data: NSData = (NSUserDefaults.standardUserDefaults().objectForKey("songsList") as? NSData)!
var decodedData: NSArray = (NSKeyedUnarchiver.unarchiveObjectWithData(data) as? NSArray)!
theList.addObjectsFromArray(decodedData as [AnyObject])
var allTheSongs: NSMutableArray = NSMutableArray()
for var i = 0; i < theList.count; i++ {
var songQuery = MPMediaQuery.songsQuery()
var songs: NSArray = songQuery.items
allTheSongs.addObject(songs)
}
var currentQueue: MPMediaItemCollection = MPMediaItemCollection()
myMusicPlayer?.setQueueWithItemCollection(currentQueue)
}else{
println("fail!!!!!!!!!!!!")
}
}
How can I fix it?

Here is the issue var currentQueue: MPMediaItemCollection = MPMediaItemCollection()
You must init with items
As per the documentation by apple.
init(items:)
Designated Initializer
Initializes a media item collection with an array of media items.
Declaration
Swift
init!(items items: [AnyObject]!)
Parameters
items
The array of items you are assigning to the media item collection.
You have to init with items like
var currentQueue: MPMediaItemCollection = MPMediaItemCollection(items:itemsArray)

I am giving you a very simple way to any kind of object and retrieve from NSUserDefault .
You don't need to use explicitly archive and unarchive .
Just create a category of NSUserDefaults as follow
#import <Foundation/Foundation.h>
#interface NSUserDefaults (AKSNSUserDefaults)
- (void)saveCustomObject:(id<NSCoding>)object
key:(NSString *)key;
- (id<NSCoding>)loadCustomObjectWithKey:(NSString *)key;
#end
#implementation NSUserDefaults (AKSNSUserDefaults)
- (void)saveCustomObject:(id<NSCoding>)object
key:(NSString *)key {
NSData *encodedObject = [NSKeyedArchiver archivedDataWithRootObject:object];
[self setObject:encodedObject forKey:key];
[self synchronize];
}
- (id)loadCustomObjectWithKey:(NSString *)key {
NSData *encodedObject = [self objectForKey:key];
id object = [NSKeyedUnarchiver unarchiveObjectWithData:encodedObject];
return object;
}
#end
and than your NSUserDefaults is ready to store any object . but one thing is need to use NSCoder in your object model .
for example :
#import <Foundation/Foundation.h>
#interface AKSClassA : NSObject<NSCoding>
// declared some property
#property (nonatomic,assign) NSInteger userID;
#property (nonatomic,strong) NSString *userName;
#end
#import "AKSClassA.h"
#implementation AKSClassA
#synthesize userID;
#synthesize userName;
-(id)initWithCoder:(NSCoder *)aDecoder{
if (self = [super init]) {
self. userID = [aDecoder decodeIntegerForKey:#"userID"];
self. userName = [aDecoder decodeObjectForKey:#"userName"];
}
return self;
}
-(void)encodeWithCoder:(NSCoder *)aCoder{
[aCoder encodeInteger:userID forKey:#"userID"];
[aCoder encodeObject:userName forKey:#"userName"];
}
#end
// in some class if you are using classA object member to store and retrieve with value example below
// to save data in NSUserDefaults
AKSClassA *obj =[array1 objectAtIndex:index];
[[NSUserDefaults standardUserDefaults] saveCustomObject:button key:#"userData"];
// to retrieve data
AKSClassA *obj1 = (AKSClassA*)[[NSUserDefaults standardUserDefaults]loadCustomObjectWithKey:#"userData"];
[titleLabel setText:obj1.userName];

Related

how can store custom objects in NSUserDefaults

I apologize for duplicate Question . I am new in ios . I want to store custom objects in userdefaults. I am using objective-c .
Thanks in advance
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);
}
In Swift 3
// set a value for key
let encodedData = NSKeyedArchiver.archivedData(withRootObject: #YOUR OBJECT#)
UserDefaults.standard.set(encodedData, forKey: #YOUR KEY#)
// retrieving a value for a key
if let data = UserDefaults.standard.data(forKey: #YOUR KEY#),
let obj = NSKeyedUnarchiver.unarchiveObject(with: data) as? #YOUR OBJECT# {
} else {
print("There is an issue")
}

How could I encode an Objective C object instance as a string?

I know there are various ways of archiving objects in objective-c, but is there some way which I can use to turn an object in Objective-C into a string which I can store in a txt file (amongst other data) and then extract from the file to recreate the same object instance again efficiently?
Thanks in advance
It's not recommended to do this.
Why don't you save the object into NSDefaults and extract if from there whenever you want? It's very simple and efficient.
Here is an example.
Let's say you have a User object defined as below
User.h
#interface User : NSObject
#property (nonatomic, strong) NSNumber *id;
#property (nonatomic, strong) NSString *first_name;
#property (nonatomic, strong) NSString *last_name;
#end
User.m
#import "User.h"
#implementation User
-(void)encodeWithCoder:(NSCoder *)encoder {
[encoder encodeObject:id forKey:#"id"];
[encoder encodeObject:first_name forKey:#"first_name"];
[encoder encodeObject:last_name forKey:#"last_name"];
}
-(id)initWithCoder:(NSCoder *)decoder {
if (self = [super init]) {
self.id = [decoder decodeObjectForKey:#"id"];
self.first_name = [decoder decodeObjectForKey:#"first_name"];
self.last_name = [decoder decodeObjectForKey:#"last_name"];
}
}
- (id)initWith:(User *)obj {
if (self = [super init]) {
self.id = obj.id;
self.first_name = obj.first_name;
self.last_name = obj.last_name;
}
}
#end
Whenever you want to save the object, you can do something like that
//Save the user object
User *user = [[User alloc] init];
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:user];
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:data forKey:#"YOUR_KEY"];
[defaults synchronize];
//Get the user object
NSData *data = [[NSUserDefaults standardUserDefaults] objectForKey:#"YOUR_KEY"];
User *user = [[User alloc] initWith:[NSKeyedUnarchiver unarchiveObjectWithData:data]];

How to save and retrieve NSMutableDictionary in NSUserDefaults? [duplicate]

Alright, so I've been doing some poking around, and I realize my problem, but I don't know how to fix it. I have made a custom class to hold some data. I make objects for this class, and I need to them to last between sessions. Before I was putting all my information in NSUserDefaults, but this isn't working.
-[NSUserDefaults setObject:forKey:]: Attempt to insert non-property value '<Player: 0x3b0cc90>' of class 'Player'.
That is the error message I get when I put my custom class, "Player", in the NSUserDefaults. Now, I've read up that apparently NSUserDefaults only stores some types of information. So how an I get my objects into NSUSerDefaults?
I read that there should be a way to to "encode" my custom object and then put it in, but I'm not sure how to implement it, help would be appreciated! Thank you!
****EDIT****
Alright, so I worked with the code given below (Thank you!), but I'm still having some issues. Basically, the code crashes now and I'm not sure why, because it doesn't give any errors. Perhaps I'm missing something basic and I'm just too tired, but we'll see. Here is the implementation of my Custom class, "Player":
#interface Player : NSObject {
NSString *name;
NSNumber *life;
//Log of player's life
}
//Getting functions, return the info
- (NSString *)name;
- (int)life;
- (id)init;
//These are the setters
- (void)setName:(NSString *)input; //string
- (void)setLife:(NSNumber *)input; //number
#end
Implementation File:
#import "Player.h"
#implementation Player
- (id)init {
if (self = [super init]) {
[self setName:#"Player Name"];
[self setLife:[NSNumber numberWithInt:20]];
[self setPsnCounters:[NSNumber numberWithInt:0]];
}
return self;
}
- (NSString *)name {return name;}
- (int)life {return [life intValue];}
- (void)setName:(NSString *)input {
[input retain];
if (name != nil) {
[name release];
}
name = input;
}
- (void)setLife:(NSNumber *)input {
[input retain];
if (life != nil) {
[life release];
}
life = input;
}
/* This code has been added to support encoding and decoding my objecst */
-(void)encodeWithCoder:(NSCoder *)encoder
{
//Encode the properties of the object
[encoder encodeObject:self.name forKey:#"name"];
[encoder encodeObject:self.life forKey:#"life"];
}
-(id)initWithCoder:(NSCoder *)decoder
{
self = [super init];
if ( self != nil )
{
//decode the properties
self.name = [decoder decodeObjectForKey:#"name"];
self.life = [decoder decodeObjectForKey:#"life"];
}
return self;
}
-(void)dealloc {
[name release];
[life release];
[super dealloc];
}
#end
So that's my class, pretty straight forward, I know it works in making my objects. So here is the relevant parts of the AppDelegate file (where I call the encryption and decrypt functions):
#class MainViewController;
#interface MagicApp201AppDelegate : NSObject <UIApplicationDelegate> {
UIWindow *window;
MainViewController *mainViewController;
}
#property (nonatomic, retain) IBOutlet UIWindow *window;
#property (nonatomic, retain) MainViewController *mainViewController;
-(void)saveCustomObject:(Player *)obj;
-(Player *)loadCustomObjectWithKey:(NSString*)key;
#end
And then the important parts of the implementation file:
#import "MagicApp201AppDelegate.h"
#import "MainViewController.h"
#import "Player.h"
#implementation MagicApp201AppDelegate
#synthesize window;
#synthesize mainViewController;
- (void)applicationDidFinishLaunching:(UIApplication *)application {
NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
//First check to see if some things exist
int startup = [prefs integerForKey:#"appHasLaunched"];
if (startup == nil) {
//Make the single player
Player *singlePlayer = [[Player alloc] init];
NSLog([[NSString alloc] initWithFormat:#"%#\n%d\n%d",[singlePlayer name], [singlePlayer life], [singlePlayer psnCounters]]); // test
//Encode the single player so it can be stored in UserDefaults
id test = [MagicApp201AppDelegate new];
[test saveCustomObject:singlePlayer];
[test release];
}
[prefs synchronize];
}
-(void)saveCustomObject:(Player *)object
{
NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
NSData *myEncodedObject = [NSKeyedArchiver archivedDataWithRootObject:object];
[prefs setObject:myEncodedObject forKey:#"testing"];
}
-(Player *)loadCustomObjectWithKey:(NSString*)key
{
NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
NSData *myEncodedObject = [prefs objectForKey:key ];
Player *obj = (Player *)[NSKeyedUnarchiver unarchiveObjectWithData: myEncodedObject];
return obj;
}
Eeee, sorry about all the code. Just trying to help. Basically, the app will launch and then crash immediatly. I've narrowed it down to the encryption part of the app, that's where it crashes, so I'm doing something wrong but I'm not sure what. Help would be appreciated again, thank you!
(I haven't gotten around to decrypting yet, as I haven't gotten encrypting working yet.)
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
Swift 4 introduced the Codable protocol which does all the magic for these kinds of tasks. Just conform your custom struct/class to it:
struct Player: Codable {
let name: String
let life: Double
}
And for storing in the Defaults you can use the PropertyListEncoder/Decoder:
let player = Player(name: "Jim", life: 3.14)
UserDefaults.standard.set(try! PropertyListEncoder().encode(player), forKey: kPlayerDefaultsKey)
let storedObject: Data = UserDefaults.standard.object(forKey: kPlayerDefaultsKey) as! Data
let storedPlayer: Player = try! PropertyListDecoder().decode(Player.self, from: storedObject)
It will work like that for arrays and other container classes of such objects too:
try! PropertyListDecoder().decode([Player].self, from: storedArray)
I create a library RMMapper (https://github.com/roomorama/RMMapper) to help save custom object into NSUserDefaults easier and more convenient, because implementing encodeWithCoder and initWithCoder is super boring!
To mark a class as archivable, just use: #import "NSObject+RMArchivable.h"
To save a custom object into NSUserDefaults:
#import "NSUserDefaults+RMSaveCustomObject.h"
NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
[defaults rm_setCustomObject:user forKey:#"SAVED_DATA"];
To get custom obj from NSUserDefaults:
user = [defaults rm_customObjectForKey:#"SAVED_DATA"];
If anybody is looking for a swift version:
1) Create a custom class for your data
class customData: NSObject, NSCoding {
let name : String
let url : String
let desc : String
init(tuple : (String,String,String)){
self.name = tuple.0
self.url = tuple.1
self.desc = tuple.2
}
func getName() -> String {
return name
}
func getURL() -> String{
return url
}
func getDescription() -> String {
return desc
}
func getTuple() -> (String,String,String) {
return (self.name,self.url,self.desc)
}
required init(coder aDecoder: NSCoder) {
self.name = aDecoder.decodeObjectForKey("name") as! String
self.url = aDecoder.decodeObjectForKey("url") as! String
self.desc = aDecoder.decodeObjectForKey("desc") as! String
}
func encodeWithCoder(aCoder: NSCoder) {
aCoder.encodeObject(self.name, forKey: "name")
aCoder.encodeObject(self.url, forKey: "url")
aCoder.encodeObject(self.desc, forKey: "desc")
}
}
2) To save data use following function:
func saveData()
{
let data = NSKeyedArchiver.archivedDataWithRootObject(custom)
let defaults = NSUserDefaults.standardUserDefaults()
defaults.setObject(data, forKey:"customArray" )
}
3) To retrieve:
if let data = NSUserDefaults.standardUserDefaults().objectForKey("customArray") as? NSData
{
custom = NSKeyedUnarchiver.unarchiveObjectWithData(data) as! [customData]
}
Note: Here I am saving and retrieving an array of the custom class objects.
Taking #chrissr's answer and running with it, this code can be implemented into a nice category on NSUserDefaults to save and retrieve custom objects:
#interface NSUserDefaults (NSUserDefaultsExtensions)
- (void)saveCustomObject:(id<NSCoding>)object
key:(NSString *)key;
- (id<NSCoding>)loadCustomObjectWithKey:(NSString *)key;
#end
#implementation NSUserDefaults (NSUserDefaultsExtensions)
- (void)saveCustomObject:(id<NSCoding>)object
key:(NSString *)key {
NSData *encodedObject = [NSKeyedArchiver archivedDataWithRootObject:object];
[self setObject:encodedObject forKey:key];
[self synchronize];
}
- (id<NSCoding>)loadCustomObjectWithKey:(NSString *)key {
NSData *encodedObject = [self objectForKey:key];
id<NSCoding> object = [NSKeyedUnarchiver unarchiveObjectWithData:encodedObject];
return object;
}
#end
Usage:
[[NSUserDefaults standardUserDefaults] saveCustomObject:myObject key:#"myKey"];
Swift 3
class MyObject: NSObject, NSCoding {
let name : String
let url : String
let desc : String
init(tuple : (String,String,String)){
self.name = tuple.0
self.url = tuple.1
self.desc = tuple.2
}
func getName() -> String {
return name
}
func getURL() -> String{
return url
}
func getDescription() -> String {
return desc
}
func getTuple() -> (String, String, String) {
return (self.name,self.url,self.desc)
}
required init(coder aDecoder: NSCoder) {
self.name = aDecoder.decodeObject(forKey: "name") as? String ?? ""
self.url = aDecoder.decodeObject(forKey: "url") as? String ?? ""
self.desc = aDecoder.decodeObject(forKey: "desc") as? String ?? ""
}
func encode(with aCoder: NSCoder) {
aCoder.encode(self.name, forKey: "name")
aCoder.encode(self.url, forKey: "url")
aCoder.encode(self.desc, forKey: "desc")
}
}
to store and retrieve:
func save() {
let data = NSKeyedArchiver.archivedData(withRootObject: object)
UserDefaults.standard.set(data, forKey:"customData" )
}
func get() -> MyObject? {
guard let data = UserDefaults.standard.object(forKey: "customData") as? Data else { return nil }
return NSKeyedUnarchiver.unarchiveObject(with: data) as? MyObject
}
Synchronize the data/object that you have saved into NSUserDefaults
-(void)saveCustomObject:(Player *)object
{
NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
NSData *myEncodedObject = [NSKeyedArchiver archivedDataWithRootObject:object];
[prefs setObject:myEncodedObject forKey:#"testing"];
[prefs synchronize];
}
Hope this will help you. Thanks

NSCoding data not saved when returning to the app?

I don't understand why previousArrays returns (null), I would like to save a class containing a bezier path and its color.
The code : (after touchesEnded is called, a path is created and saved in memory. When I come back to the app with initWithCoder, previousArrays is (null) ) :
-(id)initWithCoder:(NSCoder *)aDecoder{
if ( !(self=[super initWithCoder:aDecoder])) return nil;
CGContextRef context = UIGraphicsGetCurrentContext();
NSArray *previousArrays = [SaveData loadDict];
NSLog(#"previousArrays : %#", previousArrays);//***HERE*** : return (null)
for ( id object in previousArrays){
//for ( NSDictionary*dict in previousArrays){
NSLog(#"obj %#", [[object class] description]);
//...
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event{
UIBezierPath *bezierPath = [UIBezierPath bezierPathWithCGPath:myPath];//nscoding compliant
DataForPath *firstPath = [[DataForPath alloc] init];
firstPath.path = bezierPath;
firstPath.colorInArray = #(currentColor);
NSLog(#"touchEnded, firstPath : %#", firstPath);
NSDictionary *dict = #{#"firstPath":firstPath};
[SaveData saveDict:dict];
}
#implementation SaveData
static NSString* kMyData = #"data1";
+ (void) saveDict:(NSDictionary*) dict{
NSLog(#"saving data...");
//retrieve previous data
NSData *previousData = [[NSUserDefaults standardUserDefaults] objectForKey:kMyData];
NSMutableArray *previousArray = [[NSKeyedUnarchiver unarchiveObjectWithData:previousData] mutableCopy];
[previousArray addObject:dict];
//add new data, and save
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:previousArray];
[[NSUserDefaults standardUserDefaults] setObject:data forKey:kMyData];
//NSLog(#"%#", [[NSUserDefaults standardUserDefaults] dictionaryRepresentation]);
}
+(NSArray*) loadDict {
NSLog(#"loading data...");
NSData *data = [[NSUserDefaults standardUserDefaults] objectForKey:kMyData];
NSArray *array = [NSKeyedUnarchiver unarchiveObjectWithData:data];
return array;
}
#import <Foundation/Foundation.h>
#import UIKit;
#interface DataForPath : NSObject <NSCoding>
#property (nonatomic, strong) UIBezierPath* path;
#property (nonatomic, strong) NSNumber *colorInArray;
#end
#implementation DataForPath
- (id)initWithCoder:(NSCoder *)decoder{
if ( !(self = [super init]) ) return nil;
self.path = [decoder decodeObjectForKey:#"path"];
self.colorInArray = [decoder decodeObjectForKey:#"colorInArray"];
return self;
}
- (void)encodeWithCoder:(NSCoder *)encoder{
[encoder encodeObject:self.path forKey:#"path"];
[encoder encodeObject:self.colorInArray forKey:#"colorInArray"];
}
Also, is there a way to see what is in the NSUserDefault, such as a "prettyjson" format? -dictionaryRepresentation returns some figures like that <124d1f>
Did you checked saveDict method for previousArray == nil ?, Because before you saved something you have nothing in userDefaults
I had to use this : if ( previousArray == nil ) ...
NSMutableArray *previousArray = [[NSKeyedUnarchiver unarchiveObjectWithData:previousData] mutableCopy];
if ( previousArray == nil ) previousArray = [[NSMutableArray alloc] init];
Not enough information.
Objects can be converted to and from data if they conform to the NSCoding protocol. You have to write code for both the encodeWithCoder and initWithCoder methods that saves all your object's properties.
If you are un-archiving an object that you saved with encodeWithCoder and one of it's properties is not loading then there is most likely a problem with your encodeWithCoder method.
Post the header for the class you're trying to encode, all of the encodeWithCoder and initWithCoder methods, and information about the properties of your object that you are encoding.
Also post the code that converts your object to data and saves it, and provide a description of when/how it is saved.

How to store custom objects in NSUserDefaults

Alright, so I've been doing some poking around, and I realize my problem, but I don't know how to fix it. I have made a custom class to hold some data. I make objects for this class, and I need to them to last between sessions. Before I was putting all my information in NSUserDefaults, but this isn't working.
-[NSUserDefaults setObject:forKey:]: Attempt to insert non-property value '<Player: 0x3b0cc90>' of class 'Player'.
That is the error message I get when I put my custom class, "Player", in the NSUserDefaults. Now, I've read up that apparently NSUserDefaults only stores some types of information. So how an I get my objects into NSUSerDefaults?
I read that there should be a way to to "encode" my custom object and then put it in, but I'm not sure how to implement it, help would be appreciated! Thank you!
****EDIT****
Alright, so I worked with the code given below (Thank you!), but I'm still having some issues. Basically, the code crashes now and I'm not sure why, because it doesn't give any errors. Perhaps I'm missing something basic and I'm just too tired, but we'll see. Here is the implementation of my Custom class, "Player":
#interface Player : NSObject {
NSString *name;
NSNumber *life;
//Log of player's life
}
//Getting functions, return the info
- (NSString *)name;
- (int)life;
- (id)init;
//These are the setters
- (void)setName:(NSString *)input; //string
- (void)setLife:(NSNumber *)input; //number
#end
Implementation File:
#import "Player.h"
#implementation Player
- (id)init {
if (self = [super init]) {
[self setName:#"Player Name"];
[self setLife:[NSNumber numberWithInt:20]];
[self setPsnCounters:[NSNumber numberWithInt:0]];
}
return self;
}
- (NSString *)name {return name;}
- (int)life {return [life intValue];}
- (void)setName:(NSString *)input {
[input retain];
if (name != nil) {
[name release];
}
name = input;
}
- (void)setLife:(NSNumber *)input {
[input retain];
if (life != nil) {
[life release];
}
life = input;
}
/* This code has been added to support encoding and decoding my objecst */
-(void)encodeWithCoder:(NSCoder *)encoder
{
//Encode the properties of the object
[encoder encodeObject:self.name forKey:#"name"];
[encoder encodeObject:self.life forKey:#"life"];
}
-(id)initWithCoder:(NSCoder *)decoder
{
self = [super init];
if ( self != nil )
{
//decode the properties
self.name = [decoder decodeObjectForKey:#"name"];
self.life = [decoder decodeObjectForKey:#"life"];
}
return self;
}
-(void)dealloc {
[name release];
[life release];
[super dealloc];
}
#end
So that's my class, pretty straight forward, I know it works in making my objects. So here is the relevant parts of the AppDelegate file (where I call the encryption and decrypt functions):
#class MainViewController;
#interface MagicApp201AppDelegate : NSObject <UIApplicationDelegate> {
UIWindow *window;
MainViewController *mainViewController;
}
#property (nonatomic, retain) IBOutlet UIWindow *window;
#property (nonatomic, retain) MainViewController *mainViewController;
-(void)saveCustomObject:(Player *)obj;
-(Player *)loadCustomObjectWithKey:(NSString*)key;
#end
And then the important parts of the implementation file:
#import "MagicApp201AppDelegate.h"
#import "MainViewController.h"
#import "Player.h"
#implementation MagicApp201AppDelegate
#synthesize window;
#synthesize mainViewController;
- (void)applicationDidFinishLaunching:(UIApplication *)application {
NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
//First check to see if some things exist
int startup = [prefs integerForKey:#"appHasLaunched"];
if (startup == nil) {
//Make the single player
Player *singlePlayer = [[Player alloc] init];
NSLog([[NSString alloc] initWithFormat:#"%#\n%d\n%d",[singlePlayer name], [singlePlayer life], [singlePlayer psnCounters]]); // test
//Encode the single player so it can be stored in UserDefaults
id test = [MagicApp201AppDelegate new];
[test saveCustomObject:singlePlayer];
[test release];
}
[prefs synchronize];
}
-(void)saveCustomObject:(Player *)object
{
NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
NSData *myEncodedObject = [NSKeyedArchiver archivedDataWithRootObject:object];
[prefs setObject:myEncodedObject forKey:#"testing"];
}
-(Player *)loadCustomObjectWithKey:(NSString*)key
{
NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
NSData *myEncodedObject = [prefs objectForKey:key ];
Player *obj = (Player *)[NSKeyedUnarchiver unarchiveObjectWithData: myEncodedObject];
return obj;
}
Eeee, sorry about all the code. Just trying to help. Basically, the app will launch and then crash immediatly. I've narrowed it down to the encryption part of the app, that's where it crashes, so I'm doing something wrong but I'm not sure what. Help would be appreciated again, thank you!
(I haven't gotten around to decrypting yet, as I haven't gotten encrypting working yet.)
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
Swift 4 introduced the Codable protocol which does all the magic for these kinds of tasks. Just conform your custom struct/class to it:
struct Player: Codable {
let name: String
let life: Double
}
And for storing in the Defaults you can use the PropertyListEncoder/Decoder:
let player = Player(name: "Jim", life: 3.14)
UserDefaults.standard.set(try! PropertyListEncoder().encode(player), forKey: kPlayerDefaultsKey)
let storedObject: Data = UserDefaults.standard.object(forKey: kPlayerDefaultsKey) as! Data
let storedPlayer: Player = try! PropertyListDecoder().decode(Player.self, from: storedObject)
It will work like that for arrays and other container classes of such objects too:
try! PropertyListDecoder().decode([Player].self, from: storedArray)
I create a library RMMapper (https://github.com/roomorama/RMMapper) to help save custom object into NSUserDefaults easier and more convenient, because implementing encodeWithCoder and initWithCoder is super boring!
To mark a class as archivable, just use: #import "NSObject+RMArchivable.h"
To save a custom object into NSUserDefaults:
#import "NSUserDefaults+RMSaveCustomObject.h"
NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
[defaults rm_setCustomObject:user forKey:#"SAVED_DATA"];
To get custom obj from NSUserDefaults:
user = [defaults rm_customObjectForKey:#"SAVED_DATA"];
If anybody is looking for a swift version:
1) Create a custom class for your data
class customData: NSObject, NSCoding {
let name : String
let url : String
let desc : String
init(tuple : (String,String,String)){
self.name = tuple.0
self.url = tuple.1
self.desc = tuple.2
}
func getName() -> String {
return name
}
func getURL() -> String{
return url
}
func getDescription() -> String {
return desc
}
func getTuple() -> (String,String,String) {
return (self.name,self.url,self.desc)
}
required init(coder aDecoder: NSCoder) {
self.name = aDecoder.decodeObjectForKey("name") as! String
self.url = aDecoder.decodeObjectForKey("url") as! String
self.desc = aDecoder.decodeObjectForKey("desc") as! String
}
func encodeWithCoder(aCoder: NSCoder) {
aCoder.encodeObject(self.name, forKey: "name")
aCoder.encodeObject(self.url, forKey: "url")
aCoder.encodeObject(self.desc, forKey: "desc")
}
}
2) To save data use following function:
func saveData()
{
let data = NSKeyedArchiver.archivedDataWithRootObject(custom)
let defaults = NSUserDefaults.standardUserDefaults()
defaults.setObject(data, forKey:"customArray" )
}
3) To retrieve:
if let data = NSUserDefaults.standardUserDefaults().objectForKey("customArray") as? NSData
{
custom = NSKeyedUnarchiver.unarchiveObjectWithData(data) as! [customData]
}
Note: Here I am saving and retrieving an array of the custom class objects.
Taking #chrissr's answer and running with it, this code can be implemented into a nice category on NSUserDefaults to save and retrieve custom objects:
#interface NSUserDefaults (NSUserDefaultsExtensions)
- (void)saveCustomObject:(id<NSCoding>)object
key:(NSString *)key;
- (id<NSCoding>)loadCustomObjectWithKey:(NSString *)key;
#end
#implementation NSUserDefaults (NSUserDefaultsExtensions)
- (void)saveCustomObject:(id<NSCoding>)object
key:(NSString *)key {
NSData *encodedObject = [NSKeyedArchiver archivedDataWithRootObject:object];
[self setObject:encodedObject forKey:key];
[self synchronize];
}
- (id<NSCoding>)loadCustomObjectWithKey:(NSString *)key {
NSData *encodedObject = [self objectForKey:key];
id<NSCoding> object = [NSKeyedUnarchiver unarchiveObjectWithData:encodedObject];
return object;
}
#end
Usage:
[[NSUserDefaults standardUserDefaults] saveCustomObject:myObject key:#"myKey"];
Swift 3
class MyObject: NSObject, NSCoding {
let name : String
let url : String
let desc : String
init(tuple : (String,String,String)){
self.name = tuple.0
self.url = tuple.1
self.desc = tuple.2
}
func getName() -> String {
return name
}
func getURL() -> String{
return url
}
func getDescription() -> String {
return desc
}
func getTuple() -> (String, String, String) {
return (self.name,self.url,self.desc)
}
required init(coder aDecoder: NSCoder) {
self.name = aDecoder.decodeObject(forKey: "name") as? String ?? ""
self.url = aDecoder.decodeObject(forKey: "url") as? String ?? ""
self.desc = aDecoder.decodeObject(forKey: "desc") as? String ?? ""
}
func encode(with aCoder: NSCoder) {
aCoder.encode(self.name, forKey: "name")
aCoder.encode(self.url, forKey: "url")
aCoder.encode(self.desc, forKey: "desc")
}
}
to store and retrieve:
func save() {
let data = NSKeyedArchiver.archivedData(withRootObject: object)
UserDefaults.standard.set(data, forKey:"customData" )
}
func get() -> MyObject? {
guard let data = UserDefaults.standard.object(forKey: "customData") as? Data else { return nil }
return NSKeyedUnarchiver.unarchiveObject(with: data) as? MyObject
}
Synchronize the data/object that you have saved into NSUserDefaults
-(void)saveCustomObject:(Player *)object
{
NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
NSData *myEncodedObject = [NSKeyedArchiver archivedDataWithRootObject:object];
[prefs setObject:myEncodedObject forKey:#"testing"];
[prefs synchronize];
}
Hope this will help you. Thanks

Resources