My app involves a user being able to drag specific items such as volume sliders and UIImage views. My goal is to be able to save the locations of the items so that the user can not have to drag the items again after re-launch. Unfortunately, I have seen errors along taking the 'saving CGRect rect = item.frame to NSuserDefaultsorNSKeyedArchiver' route. Any other suggestions?
You could use NSStringFromgCGRect() and CGRectFromString() to store them.
If you're using keyed archiving on iPhone, UIKit specifically supports an addition that looks like this: -encodeCGRect:forKey: and does precisely what you want. See the Apple docs for NSCoder's UIKit additions.
If you're using NSUserDefaults, you don't get the luxury of using an explicit encoder for CGRects, but as some other answers say, you could save them as strings using NSStringFromCGRect() and its reverse.
You would probably encode CGRect using NSData and then store NSData object using NSUserDefaults or NSKeyedArchiver.
I'm not sure what errors exactly are you referring in your question, maybe more explanation needed?
You have to convert the floats in the CGRect to NSNumbers and then put them in a Dictionary.
If you need to store a lot of CGRects, I recommend creating a little lightweight class with the CGRect elements as archivable NSNumber attributes. Create an initializer that takes a CGRect. Then write quick NSCoder protocol methods so the entire object can be archived
That way you can you quickly and easy convert, store and then retrieve the CGRects.
Related
I have a Class for handling my data in my project, and now I need to store the data.
I'd like to use a Plist but I'm a bit unsure of how to start.
My class is pretty simple - 6 pieces of data, flat (no hierarchy).
I want my app to start with no data, so can I assume that I should create the PList programmatically once the User creates their first piece of data? (That is, don't create a .plist file in 'Supporting Files' prior to distribution?)
Then, when the app starts the next time, read the data and create an NSMUtableArray array of Class instances?
To create a property list, all you need to do is use appropriate types (i.e. those that support the property list format: NSData, NSString, NSDictionary, NSNumber, NSDate, NSArray), store them in a single container, and tell the containing object to write itself to a file. To read the data, you can initialize that same type using a path. For example:
// writing some data to a property list
NSString *somePath = ... // replace ... with the path where you want to store the plist file
NSMutableDictionary myDict = [NSMutableDictionary dictionary];
[myDict setObject:#"Caleb" forKey:#"name"];
[myDict setObject:[NSNumber numberWithInt:240] forKey:#"cholesterolOrIQ"];
[myDict writeToFile:somePath atomically:YES];
// reading the file again
NSDictionary *readDict = [NSDictionary dictionaryWithContentsOfFile:somePath];
The simplest way is to simple save an NSArray or NSDictionary to disk. Caleb's answer goes into detail there so I won't repeat it, other than to say you might have to convert a non-compatible object like NSColor to an property list object like NSData. It's up to you to do this each time you save or load your data.
NSKeyedArchiver and NSKeyedUnarchiver give you a little more control over the process, but work pretty much the same way. You provide (or get back) a plist compatible root object (usually an NSDictionary) that contains your data. I recommend creating a dictionary that includes your data structure as well as an arbitrary number (your app's build number is a good choice) to use as a version indicator. This way if you ever update your data model you can easily determine if you need to do anything to convert it to the new version.
If you're putting your own objects into the data file, look into NSCoding. The protocol gives you two methods using NSKeyedArchiver and NSKeyedUnarchiver to save and restore your data. This is by far the most straightforward approach if your data model consists of anything more than a few simple strings and numbers, since you're dealing with your own native objects. In your case, you would have your data class implement NSCoding and use the NSKeyedArchiver and NSKeyedUnarchiver methods to encode your six instance variables. When it's time to save or load, pack the instance of your class into an NSDictionary (along with a versioning number as I mentioned above) and call NSKeyedArchiver's archiveRootObject:toFile:. Your save an load methods deal only with your own data object, which makes things easy for you. The common pitfall to watch out for here is if your custom data object contains other custom object. This is fine, but you have to make sure every object that's going to be saved has its own NSCoding implementation.
Two things you can do:
Use NSUserDefaults:
http://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Foundation/Classes/NSUserDefaults_Class/Reference/Reference.html
The objectForKey method is the one you want to use to store your class. But, as pointed out in the comments, this shouldn't really be used for storing lots of user data; it's best for saving preferences.
For storing more data, you might want to look at Core Data. It's more complex, but should be better suited to your needs. Here's a tutorial on it:
http://mobile.tutsplus.com/tutorials/iphone/iphone-core-data/
Neither of these seems best for your simple application, but I leave this answer up since it gives alternatives for saving data to the iPhone.
I am using KVC to set properties of objects from a plist. I know I can save them as strings <string>{{66, 114}, {64, 64}}</string> and manually convert to structs, but how can I save for example a CGPoint or CGRect in a plist in a way that Cocoa KVC would understand? The Key-Value Coding Programming Guide seems to indicate that I need to save them as NSValues. How?
The Key-Value Coding Programming Guide seems to indicate that I need to save them as NSValues.
No, you need an NSValue to pass a point or rectangle to setValue:forKey:. The KVC docs aren't talking about saving the values anywhere, since that isn't part of KVC. And you can't save an NSValue in a property list, since it isn't one of the property list classes.
So, you need to convert the rectangle to and from one of the property-list data types. I prefer to convert them to and from strings, which you can do with some of the functions in UIKit.
These are actually Core Graphics structures, not UIKit (hence the names CGPoint and CGRect), so you might expect Core Graphics to have functions for this. Indeed it does, but of a different sort: In CGGeometry, Core Graphics provides functions to convert CGPoints and CGRects to and from dictionaries.
Strings or dictionaries: It's your choice. Whichever you choose, they are property lists, so you can store them in your property list output.
On retrieval, convert the dictionary or string to a point or rectangle, and create an NSValue with that to pass to setValue:forKey:. Of course, this works in reverse the other way: When saving, valueForKey: will give you an NSValue, from which you need to extract the point or rectangle to convert to a dictionary or string to save in the plist.
Just a guess: I make an attribute and make it's type "binary". But in the end, how would I use that? I guess there is an NSData behind the scenes? So that attribute actually takes an NSData?
This question has been asked a number of times and the answer is a bit more complex.
When it comes to binary data you should determine how to store it based on the expected size of data you are going to be working with. The rule is:
Less than 100K;store as a binary property in your main table
Less than 1M; store as a binary property in a ancillary table to avoid over fetching
Greater than 1M; store on disk and store its file path in the Core Data table.
In addition, when you are storing images, it is recommended to store them in a standard format such as JPG or PNG. By using the transformable property type you can actually have your subclass give the appearance of accessing the UIImage class when the actual store is a PNG representation. I go into this in detail in the bog post on Cocoa Is My Girlfriend.
Update
The reason behind storing > 1M binary data on disk is because of the cache. The NSPersistentStoreCoordinator will keep a cache of data so that when your app asks for the "next" object it doesn't need to go back out to disk. This cache works really well. However it is small, very small on iOS. If you pull in a big piece of binary data you can easily blow out that entire cache and your entire app suffers greatly.
That's correct, use binary which is represented as a NSdata object, then u can use uiimages imageWithData class method in order to retrieve your images.
Marcus's answer works, but it's not the easy way - and, AFAICT, it's not how Apple "intends" you to do it. In theory, you should be able to just mark the image as "Transformable", and CD would do everything automatically.
That doesn't work because UIImage is "missing" the implementation of NSCoding - Apple's serialization system - and so the default transformer from CD has no idea how to save an image.
The alternative, which requires a little code, but is still simpler than Marcus's approach ... allegedly you run into Apple's bugs. Apple's CoreData implementation (allegedly) has been broken since 2008 - the custom transformers are ignored for any data-store of type "Binary".
Going back to "CD would do everything automatically, if only UIImage were setup right" ... IMHO, UIImage ought to be serializable, even if the "Default" serialization isn't ideal for all cases. If you fix that, then lo and behold ... CoreData starts saving/laoding UIImage instances with zero coding from you.
Here's code to make your UIImage instances all serializable. All you have to do is import this category / copy paste this category into the classes where you want the "upgraded" UIImage version.
#implementation UIImage (MyCategory)
- (void)encodeWithCoder:(NSCoder *)encoder
{
[encoder encodeDataObject:UIImagePNGRepresentation(self)];
}
- (id)initWithCoder:(NSCoder *)decoder
{
NSData *data = [[decoder decodeDataObject] retain];
return [[UIImage alloc] initWithData:data];
}
#end
I feel a little dumbfounded. I know how to store any kind of "plist supporting" data in a preferences file, such as NSArray, NSDictionary, NSString and their CF counterparts. No problem there.
Now I like to store the values of my custom object this way. I had thought that if I implement some kind of serialization/archiving protocol, I could get NSUserDefaults to understand my class just like it understand NSDictionary.
E.g, implementing the NSCoding protocol should give the NSUserDefaults code all that it needs: I give it the key names along with the values as plist compatible types (NSString, mostly in my case). But my encoder doesn't even get invoked. :(
Then I thought that there must be at least some function that generates a NSDictionary from the NSCoding protocol, so that I can then send this dict to store in the prefs. Ideally, there'd by something like the NSKeyedArchiver that I pass any NSCoding compatible object and it gives me a NSDictionary, and vice versa. But that doesn't appeat to exist in Apple's framework.
Do I have to write that really myself? I'd expect this would be a quite common need.
Note: I realize that NSKeyedArchiver generates a binary plist, which I could write as a plist file. But that's not what I want. I want to add the contents of this object to my app's prefs plist file, i.e. I want to store both my object and other prefs data in the plist file. That's what doesn't seem to be possible with the given functions.
I'm currently just adding objects to an NSDictionary then calling NSDictionary's "writeToFile:atomically:" method. I'm mostly adding strings, and an image as NSData, but I believe anything I add to the NSDictionary that implements the NSCoding protocol should get written to the file.
Then later when I call NSDictionary's initWithContentsOfFile everything gets put back in the dictionary as it had been. Would this work for you - letting the dictionary take care of the serialization stuff?
Some kind of serialization available in iPhone OS? Is that practically possible or should I quickly forget about that?
I am making a tiny app that stores some inputs in an NSMutableArray. When the user leaves, the inputs should stay alive until he/she returns to continue adding or removing stuff to/from that array.
When the app quits, there must be some way to store all the stuff in the array in a file. Or must I iterate over it, rip everything out and write it i.e. comma-separated somewhere, then the next time go in again, read the stuff in, and iterate over the lines in the file to make an array with that data? That would be hard like a brick. How to?
The easy way, since you already have an NSArray object is to write the array to disk using
- (BOOL)writeToFile:(NSString *)path atomically:(BOOL)flag
and read it back in with:
- (id)initWithContentsOfFile:(NSString *)aPath
or
+ (id)arrayWithContentsOfFile:(NSString *)aPath
You can also use NSCoder.
You can probably search sof for the right code.
So long as the objects in the array implement NSCoding (NSString and NSValue do; if you're storing your own objects it's relatively straightforward), you can use:
[NSKeyedArchiver archiveRootObject: array toFile: filePath];
to save and:
array = [NSKeyedUnarchiver unarchiveObjectWithFile: filePath];
to load.
You can similarly load/save to NSData.
The iPhone SDK is designed to store data using SQLite tables.
You can use NSPropertyListSeralization, since NSArray is a type of property list. You can find more information here.