views:

575

answers:

2

I have a simple iphone application that paints blocks using a subclass of CALayer and am trying to find the best way to save state or persist the currently created layers.

I tried using Brad Larson's answer from this previous question on storing custom objects in the NSUserDefaults, which worked for persisting my subclass of CALayer, but not it's basic state like geometry and background color, so they were there but did not render on relaunch.

I made my declared instance variables conform to the NSCoding protocol but do not know how to make CALayer's properties do the same without re-declaring all of it's properties. Or is this not the correct/best approach altogether?

Here is the code I'm using to archive the array of layers:

[[NSUserDefaults standardUserDefaults] setObject:[NSKeyedArchiver archivedDataWithRootObject:viewController.view.layer.sublayers] forKey:@"savedArray"];

And here is the code I'm using to reload my layers in -viewDidLoad:

    NSUserDefaults *currentDefaults = [NSUserDefaults standardUserDefaults];

NSData *dataRepresentingSavedArray = [currentDefaults objectForKey:@"savedArray"];

if (dataRepresentingSavedArray != nil) {

 [self restoreStateWithData:dataRepresentingSavedArray];

And in -restoreStateWithData:

    NSArray *savedLayers = [NSKeyedUnarchiver unarchiveObjectWithData:data];

if (savedLayers != nil) {

 for(layer in savedLayers) {

  [self.view.layer addSublayer:layer];

 }

 [spaceView.layer layoutSublayers];  
}
+2  A: 

Cocoa (and Cocoa Touch) are mostly based on a model-view-controller organization. CALayers are in the view tier. This leads to two questions:

  1. What part of your model does your layer present to the user?
  2. How do you make that part of the model persist?

The answers to those questions are your solution.

Nowadays, for a new app, the simplest (certainly most extensible) path to a persistent model is probably Core Data.

Peter Hosey
Peter's point here is key. Focus on the data you want to store, not the CALayer implementation. The CALayer should *display* your data, not *be* your data. When you've created a data class, storing that should be fairly straightforward, and when you load it again, you can easily regenerate a new CALayer to display it.
Rob Napier
Thanks for the answers. I understand conceptually what you are saying, but am not clear how to implement it. I don't have any experience creating a data model for persistence yet.Does this sound right so far:I've created a subclass of NSObject with properties for the data I want to persist to regenerate layers at relaunch: frame, corner radius and background color, and implemented the NSCoding protocol. My app delegate has an array of these model objects. I pass the properties from layer to model object and into the array, then archive. Unarchive and reload into new layers. ??
Joe Ricioppo
You start out right, but then you appear to start stuffing presentation details in your model. I'm not sure what your app is; if it's a graphics app, then maybe “frame” is a legitimate part of the model. Otherwise, I doubt it. If frame rectangles and corner radii and colors are not part of the data that the user is using your application to curate and edit, then they do not belong in the model, and you should leave them to your subclass of CALayer to handle. Your subclass of CALayer should also have one other property, by which you give the layer the model object that it should present.
Peter Hosey
Yes, it's a simple graphics application where the user paints colored blocks. The corner radius is fixed, so I will move that to the subclass as you said. There isn't really any data that the layers represent other than their visual state (geometry and color), which is what I'm trying to persist. So each layer should have an instance of the data object to store it's state? Then at app termination enumerate though the layers passing their data objects to an array of data objects owned and archived by the app delegate?
Joe Ricioppo
I was just enumerating through the layers at app termination and passing the geometry and color properties into model objects, then into an array to be archived. But it's better for the layer to have it's own instance of the model object? I pretty much got it working except UIColor will not encode, archive, then un-archive successfully. Is there a way to archive structures, so that I could just persist the CGColorRef without converting it to a UIColor? Thanks for your help. Objective-C is my first language, which I've picked up from books and sample code, so this is all very helpful.
Joe Ricioppo
The controller owns and creates both the model objects and the view objects (layers), and gives each model object to the view (layer) that will display it. Controller and view (layer) share the same model object, and each retain it.
Peter Hosey
“I pretty much got it working except UIColor will not encode, archive, then un-archive successfully. Is there a way to archive structures, so that I could just persist the CGColorRef without converting it to a UIColor?” CGColor isn't a structure. UIColor's documentation says that it conforms to NSCoding, so whatever archiver and unarchiver you're using should be able to encode it. If that isn't working, you should file a bug in RadarWeb.
Peter Hosey
Ok thanks. I'll file a bug on UIColor. But what is a CGColorRef if it's not a struct? and is there a way to save it?
Joe Ricioppo
It's a Core Foundation object. These are interchangeable with Cocoa objects; you can put them in NSArrays (which are also CFArrays), send them `retain`, `release`, and `autorelease` messages, and pass them to the `%@` formatter of `stringWithFormat:`, `CFStringCreateWithFormat`, and `NSLog`. However, they do not conform to `NSCoding`, so you can't archive them directly.
Peter Hosey
Thanks Peter. I haven't given the CALayer subclass an instance of the model yet, but this is what I have so far which seems like it should work if I was doing it right. http://stackoverflow.com/questions/1182691/archiving-and-unarchiving-results-in-bad-access
Joe Ricioppo
+1  A: 

Just to be precise, according to the Apple docs, the CALayer is actually the model and not the view in the MVC pattern.

"CALayer is the model class for layer-tree objects. It encapsulates the position, size, and transform of a layer, which defines its coordinate system."

The view behind the layer is actually the view part of the pattern. A layer cannot be displayed without a backing view.

It seems to me that it should be a perfectly legitimate candidate for data serialization. Take a look at the KVC Extensions for CALayer. Particularly look at:

- (BOOL)shouldArchiveValueForKey:(NSString *)key
Matt Long
Interesting. CALayer on Mac OS conforms to the NSCoding protocol, but on iPhone OS it does not. This method is also not in the iphone kit.Why would that be?
Joe Ricioppo
This comment is outdated :-). The latest iPhone SDK also supports archiving CALayers.
jarjar