How to archive ivar/property of object in iOS - ios

I know how to use NSCoding in some object.
What I do not like is that:
[NSKeyedArchiver archiveRootObject:self toFile:path]
will save also other data (some stuff regarding the object) except from which is defined in
- (void)encodeWithCoder:(NSCoder *)aCoder
Is there some way to same just one ivar/property of the object, and then restoring it, without saving all other stuff ?
I hope that you can understand my question.
Also, is there some way to make file encrypted ?
I am planing to keep score of game in this way (if there is some other better way please say so).
But I would not like that somebody can change file and in that way fake his score.

Yes, write it explicitly to a file on its own, as data, text, JSON formatted text, ...
NSKeyedArchiver needs the additional information so that it knows what type of class to create. If you don't want that information to be saved then you can't use NSKeyedArchiver.
A simple option is to save as a plist. NSDictionary makes this easy:
NSDictionary *saveDict = #{ #"MyInt" : #( self.intValue ), #"MyBool" : #( self.boolValue ) };
[saveDict writeToFile:... atomically:YES];
but, this has limitations on the data types you want to store. So does JSON.

Related

Is it possible to save an NSManagedObject in NSUserDefaults?

I've having difficulty saving an NSManagedObject in userDefaults and I'd like to know a) should I be trying to do this or is this not an appropriate approach or b) if it is an appropriate approach, how can I get it to work?
I'm writing my app in Swift 2.3 and it has a few user default options, one of which is for a default "lift" (as in weightlifting, e.g. 'bench press', 'clean and jerk', 'incline bench press'). I'm actually converting them from an enum to a Core Data entity because every lift event that the user will be able to keep track of will be one of the available lifts types (for which I'll establish the appropriate relationship).
Here's the extension with the properties:
extension Lift {
#NSManaged var liftName: String
#NSManaged var type: NSSet
}
and the Lift entity with the things Xcode is complaining about:
class Lift: NSManagedObject, NSCoding {
func encodeWithCoder(aCoder: NSCoder) {
aCoder.encodeObject(liftName, forKey: "liftName")
} // Super.init isn't called on all paths before returning from initializer
required init(coder aDecoder: NSCoder) {
// Initialization of immutable variable was never used, etc...
let liftName = aDecoder.decodeObjectForKey("liftName") as! String
}
}
I've dealt with these types of errors before so my real concern is whether or not I'm headed down the wrong path.
I've read numerous threads tonight which have taught me that I'll need to encode an object (but not specifically an NSManagedObject) to save it then unencoded it when retrieving it and that my class must conform to NSCoding and what that protocol requires. But then I've seen threads that say NSManagedObjects should NOT be stored in userDefaults, but I don't know if that's true.
I've spent a few hours on this so before I go further, can/should this be done?
No, you should not store an NSManagedObject in NSUserDefaults. Core Data is an object persistence technology, so it doesn't make sense to try and persist an NSManagedObject in some other format.
There are few alternatives you can use:
You could simply store the name of the lift in your user defaults and then query for this to get the object back from Core Data. This may not work for you if lift names aren't unique
You can add an identifier attribute to your Lift entity and store something like a UUID string in that attribute; You can then store the same string in UserDefaults. This will ensure one object is selected uniquely.
You can add a new boolean attribute to your Lift entity default and again use a query to retrieve it; You would need to ensure you only set the value to true on one lift at a time.
You can use managedObject.objectId.uriRepresentation to get a URL that you can store as a string and then use to retrieve the object later. This is probably the most complex solution and I would suggest you try one of the other options.
No, You cannot do this. You cannot save Core Data objects in User Defaults. It can only save in its DB.
This two are totally two different things. NSUserDefault stores the light pieces of data where NSManagedObject stores the light or heavy amount of data and is very fast than NSUserDefault for storing and retrieving purposes.
NSManagedObject -> NSManagedObject link to coredata.
You should it to store a large list of elements. As far your last question, there is nothing preventing you from using both Core Data and a backend to store your data. In fact, there are frameworks out there to facilitate exactly this.
NSUserDefaults -> NSUserDefaults is a class that allows simple storage of different data types. It is ideal for small bits of information you need to persist between app launches or device restarts. NSUserDefaults is not sufficient and reliable to store and query the huge amount of data. It's suggestable if you'll have a backend (database on the server) to store events and their invitees to persist consistency of user's information (if user logged in back to your app from other app supportive device then he'll get all information he stored).
NSUserDefaults supports the following data types:
NSString, NSNumber, NSDate, NSArray, NSDictionary and NSData
Hope it will help you.
Storable Types in NSUserDefaults.
The NSUserDefaults class acts very much like something called a Property List (aka plist). It may be just a fancy interface for a plist, or it may be more, I’m not entirely sure. Nonetheless, plists are limited in what kind of objects they can store. The six types plists can store are:
NSData
NSString
NSNumber
NSDate
NSArray
NSDictionary
So,you need to use in NSKeyedArchiver.
let ArchvieArr = NSMutableArray()
ArchvieArr.addObject(NSKeyedArchiver.archivedDataWithRootObject(LiftObj)).
NSUserDefaults.standardUserDefaults().setObject(ArchvieArr, forKey: "savedArray")

Can you save Parse information in NSUserDefaults? (IOS)

Basically, i want to save my current Parse user's friends into NSUserDefaults. For some reason it doesn't seem to be saving correctly or it won't let me save them. Is this because the array of objects(friends) that i am returning is to intricate for NSUserDefaults to handle?
I have tried turning the array of objects into NSData and saving it that way but i still haven't yielded any good results.
I would really appreciate if someone could help me out.
Cheers.
You really should not use NSUserDefaults for storing this type of information. NSUserDefaults was created in order to store and retrieve "settings" type information as opposed to data.
As for your question, in order to save custom objects you need to implement these 2 methods:
- (void)encodeWithCoder:(NSCoder *)encoder {
}
- (id)initWithCoder:(NSCoder *)decoder {
}
These methods will essentially tell the application how to store the objects data in a file as well as how to read it back in and create an object from it.
Documentation:
https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/Archiving/Articles/codingobjects.html
However, if you are looking to store these objects locally you should look into Parse's local caching feature. You could also use Core Data to store the objects locally but maintaining 2 instances of the objects may be ugly.
Parse Local Caching:
https://www.parse.com/docs/ios_guide#localdatastore/iOS

How do I persist graphic data in iPhone/iPad?

I am making an iPad app where the user can create graphic content with images and text. I am storing this in memory in an array of custom UIView subclasses. Each of these view subclasses can have any number of subviews containing images or text.
Now I need to save these in the device. As I explore, there seem to be many ways to do this and would like to know what would be the best for this case.
It looks like you are asking for the architectural design of what will end up being a Drawing app. This means that best it's really dependent on you specific use-cases, and cannot be answered completely unless you provide a quite detailed list of requirement.
But in general, I could try to give you some general tips that will have anyway to be integrated with you own specific nitty-gritty implementation.
This description will make some assumptions regarding the basic use cases that an app like this may need:
The user can create an image using multiple tools to achieve the result. These can be anything, from a brush to a textfield and so on
The information regarding which tools have been used to create the picture and how this tools have influenced the picture current look, can be saved in order to allow the user to later on edit the picture
Said this, the main problem is: how to store your drawing state in order to recover it later?
There are indeed many ways to achieve it, but I believe 2 of them are what would be considered "clean and famous enough".
NSKeyedArchiver
This wouldn't be my favourite (difficult to maintain), but if you have to deal with UIView, it's probably gonna be the quickest.
The NSKeyedArchiver is
.. a concrete subclass of NSCoder, provides a way to encode objects
(and scalar values) into an architecture-independent format that can
be stored in a file.
It implements the Memento design pattern and It's the same pattern described in Pro Objective-C Design Patterns, that, incidentally, presents a case study that has many of the most important use-cases matching yours:
A drawing pad allows scribbling with the user’s finger.
[...]
It allows the user to save a scribble.
It allows the user to open a saved scribble.
[...]
It's an app for having a drawing pad, where you can draw lines with your finger.
Yours looks like a simplified version of this, with images and texts instead of the scribble.
So, what's the pro, in this specific case, of using the NSKeyedArchiver? The UIView already implements the NSCoding protocol, the one needed to archive the object. So, for most of the information you need to store (coordinates, frame size, background color ...), you don't have to do anything but... archiving the object.
For any additional attribute on top of the UIView (for instance: the local path of your image, because archiving an UIImageView is really expensive), you can take a look at this article that explains with proper detail what you have to do in order to take advantage of the NSKeyedArchiver to store your object states.
This all boils down to:
implement the NSCoding protocol for each of the tools your drawing app is gonna provide
keep track of the subviews that the user create (images, text...)
when the user hit "save", loop through them, create an archive, and store them to a sensful path. The first component of the path could be the name of the Drawing, the second one the name of the tool and the third an id for each time the tool has been used. Like
// A mountain image
/<path to you Document dir>/Mountains/Image/1
// A sun
/<path to you Document dir>/Mountains/Image/2
// The text "Mountain is awesome"
/<path to you Document dir>/Mountains/Text/1
Then of course you will have to save the list of Drawing names somewhere, either in a plist file or in a NSUserDefault, so to be able to show them to the user in case they want to restore them for editing.
Core data
This is probably the cleanest and more maintainable way to store you object states, but is gonna be a bit tough and cumbersome, in particular if it's the first time you use core data. I'm not gonna dig into Core Data, but I can give you some guidelines of the whole procedure. Basically:
You create a db schema that represents each of the tools your are gonna let the user use. Like: a table for Image, a table for Text and so on
On each table you put the attributes you need to remember (location, text color for "Text", image URL for "Image" and so on)
You create a table for the Drawing that the user create, with a 1-many relationship to the tool tables. This relations represents the object shown in the drawing.
Initialize you drawing canvas and each component according to what's stored in the db
Every time the user hit "save", create or update the proper db tables in order to reflect the current drawing configuration in the storage.
One of the advantages of this approach is that, if one day you want to change a tool component property or add new ones, you can take advantage of schema migrations in order to deliver backward compatibilities with new updates. So the users will still be able to use their old drawings.
And so on and so forth...
These are two of the zilions of possibilities. You could use also use:
NSUSerDefault to store states, that I suggest to avoid. It's gonna be really hard to maintain
Mix of the two aforementioned techniques
If you plan to deliver >= iOS6 only support, you can check this
etc
The two I described are just what I feel are the usual and most discussed way of doing this. You find them in books, tutorials and they let you quite a lot of flexibility for anything you have to do.
If you need more explanatory links, let me know.
As I mentioned in a comment, you might want to look into iOS's state preservation API's. However, if you want to build your own system to do this it'd be pretty simple using some clever categories and dictionaries. Then you can serialize/deserialize your dictionaries using NSKeyedArchiver and NSKeyedUnarchiver.
eg:
#interface UIButton (MyAppCategory)
- (NSDictionary *)viewProperties;
- (void)configureFromProperties: (NSDictionary *) properties;
#end
#implementation UIButton (MyAppCategory)
- (NSDictionary *)viewProperties {
return #{ #"class" : NSStringFromClass([self class]),
#"frame" : [NSValue valueWithRect:self.frame],
#"titleLabelText" : self.titleLabel.text,
// etc...
};
}
- (void)configureFromProperties: (NSDictionary *) properties {
NSValue * value = properties[#"frame"];
if ([value isKindOfClass:[NSValue class]]) {
self.frame = value.rectValue;
}
NSSString * titleLabelText = properties[#"titleLabelText"];
if ([titleLabelText isKindOfClass:[NSString class]]) {
self.titleLabel.text = titleLabelText;
}
}
#end
// replicate the above pattern for other view objects you need to support
#implementation MyViewFactory
- (UIView)recreateViewFromProperties: (NSDictionary *) properties {
NSString * className = properties[#"class"];
if ([className isKindOfClass:[NSString class]]) {
Class viewClass = NSClassFromString(className);
id viewObject = [[viewClass alloc] init];
if ([viewObject respondsToSelector:#selector(configureFromProperties:)]]) {
[viewObject performSelector:#selector(configureFromProperties:) withObject:properties];
return viewObject;
}
}
return nil;
}
// exercise for the reader: iterate your views and use the viewProperties: method to collect your views' configuration info...
#end
If you want to allow for future session editing and loading etc. I would suggest designing a data structure and create a core data model out of it.
Some structure holding the session metadata e.g. sessionID, creationDate, dictionary of key:imageName value:imageFrame (CGRect wrapped in NSValue, use setObjectForKey).
Loading images for the session would work by calling the keys into an array using e.g.[sessionImageDictionary allKeys], iterating through the keys and asynchronously (NSOperationQueue with maxConcurrentOperationCount) loading the image at some Macro path to e.g. the library directory, and appending the key, which is the imageName.
In the same iteration you can set its frame by calling [sessionImageDictionary valueForKey:[arrayOfKeys objectAtIndex:currentIteration]; Converting the previously stored NSValue back to CGRect.
The datastructure all depends on the amount of features you want, but the good thing is it allows for expansion and with core data as the backing store, you could do things like sync between devices, enable multiple sessions for loading and saving like a "My projects" feature. It will help if lets say the user builds up a library of images (all stored in your apps library directory) and then the user uses the same image in the same session or in multiple sessions, only one copy of the image needs to exist, with zero duplicate write outs to disk and the core data object will have the filename stored in the session.
The most important part would be building a correct Core-Data model and writing an extractor that can accept these custom subclasses, strip out the data to create, populate and save an NSManagedObject to the persistent store.
Your best option is to use UIDocument with NSFileWrapper folder. Then you can store all your files in one folder which is saved automatically when the contents change.
Refer to:http://developer.apple.com/library/ios/#documentation/DataManagement/Conceptual/DocumentBasedAppPGiOS/Introduction/Introduction.html#//apple_ref/doc/uid/TP40011149-CH1-SW1

Read plaintext document into NSArray in iOS?

I'm new to iOS but have plenty of experience with c++ and Python. I'm trying to figure out how to read a plaintext file I have on my computer into an NSArray in xcode. In c++ I would do this:
while(istr>>string) myArray.push_back(string);
However, I need to create a local copy to be stored on the iOS device. Is there a way I can package this data so that a local copy of JUST THE ARRAY will be stored on the device? I was thinking of maybe doing something with a JSON serialization or something.
Should I really just suck it up and do this:
NSArray myArray = [[NSArray alloc] initWithObjects: #"myInfo", nil];
I just want a more elegant way to handle this, I guess.
I think maybe you're thinking a little too C about this. In C and C++, strings are arrays of bytes. In ObjC, there's an object for that. It's called NSString, and it's probably what you should be storing plaintext in.
It even has an easy class method to help you out with this if you already have a byte array:
+(id)stringWithCString:(const char *)cString encoding:(NSStringEncoding)enc
See the NSString documentation for more details.
As to storing it on the device, there are solutions that range from the simple (NSUserDefaults) to the complex (Core Data), but pretty much anything will expect plain text be in an NSString.
EDIT:
The title of this question talks about reading the string from the filesystem. First step is to get the bytes of the file into an NSData object. The easy way:
+(id)dataWithContentsOfFile:(NSString *)path
Then make a string out of the data with this initializer of NSString:
-(id)initWithData:(NSData *)data encoding:(NSStringEncoding)encoding
I don't know if this can help you, anyway if you just need to store an array of data to filesystem and deserialize it back to NSArray, an easy way is to use plists.
It is a convenient way to store a small amount of data, without any kind of relationship (there is Core Data for that). The main advantage is that you can store in it NSArray, NSDictionary, NSNumber, NSString, NSDate and NSData (so any kind of binary information) and they get automatically serialized and deserialized through some simple methods.
You can write an NSArray to a file in this way:
- (BOOL)writeToFile:(NSString *)path atomically:(BOOL)flag
and deserialize it back with this:
+ (id)arrayWithContentsOfFile:(NSString *)aPath
If you just want to provide some initial data to your app, and it is for example an array of strings or something similar, you can manually add a plist to your project by going to File->New->File and choosing Resources->Property list, and fill it by hand.
You can read more at https://developer.apple.com/library/ios/#documentation/Cocoa/Conceptual/PropertyLists/Introduction/Introduction.html

iOS - Saving NSMutableArray to iPhone

I'll ask in the form of a hypothetical, which might make it easier for me to explain.
I have a class called Person, and in this has three fields:
NSString *name;
NSDate *dateOfBirth; and
NSMutableArray *friends.
An example object is this:
name = "John Smith"
dateOfBirth = 01/04/1985
friends = "Simon Scott"; "Jennifer Lane"; "Mary Firth"
Once the user has filled the NSMutableArray with the data they want, what would be the best way to save this data to the iPhone? I would anticipate that there could be up to 100 instances of the Person object, and all that will be required is the displaying of this data in a UITableView and giving the user the ability to add and remove entries at their will.
I have seen multiple suggestions on this site, which include NSDictionaries and using the writeToFile method, but before I research one of these, I was hoping someone could point me in the right direction? I would like to ensure that I'll be using the easiest and most appropriate method that's out there.
Many thanks.
Please take a look at the Property List Programming Guide. As long as you stick with a core set of object types for your data, you can write and read your data from a file or URL in one line, like this:
[people writeToURL:someURL atomically:NO];
The types you've mentioned in your question (strings, dates, arrays, dictionaries) can all be written to a property list.
#Achiral,
It really depends on what you want to do with the data and how concerned you are with the flexibility of your code.
However, my recommendation would be to use CoreData to make an SQLlite database and make a 'Person' entity with the properties that you list above. I don't know if you are familiar with CoreData, but it is highly optimized on iOS and is pretty easy to use, since it has a pretty simple 'fill in the blanks' style form for creating the data models. You should also note that CoreData is a well accepted and supported way to store data in an iOS and OS X app.
I hope this helps.

Resources