How do I save state with CALayers? - cocoa

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];
}

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

Cocoa (and Cocoa Touch) are mostly based on a model-view-controller organization. CALayers are in the view tier. This leads to two questions:
What part of your model does your layer present to the user?
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.

Related

NSView performance of wantsLayer

If I create a blank Mac XCode project and layout 500 simple NSView objects side by side in the main window it loads pretty damn fast. If I set wantsLayer=YES on each subview, performance dramatically drops, by several seconds. Why is this the case conceptually? It seems that layers would be faster not slower than regular old NSViews.
You're giving the system more work to do by layer-backing so many views. Layer-backing allows graphic acceleration (for drawing) but it adds a bit of overhead to things like layout, not to mention just creating them and putting them on screen. If used properly, it's not really much of a problem.
Typically, if you had so many "things" to manage on screen at once, you'd have one layer-backed hosting view that manages its own tree of sublayers. "But what about view-based table views?" you ask. Trickery, trickery, I say! Table views don't actually keep all the cell views they manage around; they efficiently reuse them, keeping around only enough to represent what's on screen and/or animating around.
So I'd say this isn't really a problem since it's not a particularly good approach to throw 500+ layer-backed views up for layout and drawing to begin with. :-)
as of 2021, Joshua Nozzi's answer is just the half of the truth.
When you want to utilise that much layers, you should make use of the power of CALayers with Sublayers instead of using NSViews over and over again, each with its own NSCell and possible CALayer backed as you forced it to with -wantsLayer:.
There is nothing wrong with 500 sublayers. Sublayers allow you to be fast when properly structured.
If you want to speed up even more, turn off autoanimation by force on each Layer that does not need it. Yes a lot examples out there show that you can turn off Autoanimation each time you call some property to be changed, slowing down your drawing even more, because it involves even more object messaging.
Keep in mind the following example turns off Auto-animated Keys by their Key name and kicks out the whole action and does not apply onto all Sublayers on purpose. If your structure of Sublayers of Sublayers need it you could call this on according ParentLayer to wipe out the CPU eating Autoanimation. Its much better to let the autoanimation do its job on the layers you actually want it to. Which turns around Apples default paradigm to animate everything. If most action keys are turned off be aware that your layers do show up kind of like a state machine. In the example here #"frame" animations are not wiped out..
void turnOffAutoAnimationsForLayerAndSublayers(CALayer* layer) {
NSNull *no = [NSNull null]; //(id)kCFNull
NSDictionary *dict = [NSDictionary dictionaryWithObjects:#[no,no,no,no,no,no,no,no]
forKeys:#[#"bounds",#"position",#"contents",#"hidden",#"backgroundColor",#"foregroundColor",#"borderWidth",#"borderColor"]];
for (CALayer *layertochange in layer.sublayers ) {
if (layertochange.sublayers.count) {
for (CALayer *sublayertochange in layertochange.sublayers) {
sublayertochange.actions = dict;
}
}
layertochange.actions = dict;
}
}
and use it in your CALayer backed NSView -init like..
CALayer *layer = [CALayer layer];
layer.frame = self.frame;
//...backgroundColor, contents, etc etc..
// here you could even do a for loop adding as much sublayers you want..
for (int y=0; y<100; y++) {
CALayer *sub = [CALayer layer];
//...frame, backgroundColor, contents, of sublayers etc etc..
layer.frame = CGRectMake(0, 0, 10, y*10);
[layer addSublayer:sub];
}
// structure is ready, add it to the base layer
self.layer = layer;
// now tell NSView you really want to make use this layer structure
self.wantsLayer = YES;
// turn off AutoAnimations
turnOffAutoAnimationsForLayerAndSublayers(self.layer);
For layers that are basically all the same there is even a specialised Subclass called CAReplicatorLayer. Allowing you to add even more with less internal draw calls.
You should also know that layers that are hidden are not calculated at all (meaning drawing here). You can still change properties even on hidden Layers. Unhide them, when needed, not earlier. So in example a custom CATextLayer that will change its string property to lets say #"", aka nothing, will still be drawn. But if you Subclass CATextLayer and change its string implementation to lets say..
#interface YourTextAutoHideLayer : CATextLayer
-(void)setString:(id _Nullable )string;
#end
#implementation YourTextAutoHideLayer
-(void)setString:(id _Nullable)string {
self.hidden = [string isEqual:#""];
super.string = string;
}
#end
you speed up drawing even more. A) because less object messaging to hide the layer that has no text anyway and B) once hidden it is not part of the internal draw calls effective speeding up your main CALayers drawing.
On NSTableViews that are NSCell based you usually never use CALayers. There is no need to, NSTableViews manage the visible Cells on purpose to keep in example scrolling smoothly. And still they (NSTableViewCells) have a reuse-mechanism to speed things up. You will very likely not re-invent a tableview with CALayers anyway. But in case you do reinvent, there is a CAScrollLayer class too.
And if that is not enough speed it is worth thinking about a MetalView utilising the power of GPUs.
Edit: code your CALayers not in -drawRect:. -drawRect: is called anytime something changes, in example frame/position on screen or bounds etc.. so try to avoid coding CALayers in there. You can set [self setNeedsLayout:YES]; & [self setNeedsDisplay:YES]; on purpose for a good reason, the reason is you want to avoid too much drawing for basically no change at all. -drawRect: in example was/is such method that is supposed to handle a backing with its own context to draw in. As CALayers have their very own mechanism you can let the drawRect block blank, doing nothing in there, maybe even erasing the method at all if you dont need it.
CALayers are not part of the auto layout system unless you code in -layout.. so again, keep CALayer drawing outside such and you are good. and pssst a lot CALayers properties can be changed even outside the main thread, some definitely not like layer.string.

group fetch / data updates (class structure design)

What would be the best design for the following scenario?
I have a class that manages a bunch of NSManagedObjects. Inserting, deleting, fetching, etc. A viewController uses this object as the dataSource for a tableView. Thus every time the managed objects change (added, deleted or altered), the tableview has to reloadData().
To ensure that my class has the correct list of objects, it should fetch() the managedObjects after every delete or insert and notify any observers that its contents have changed.
So far this is all working nicely. However I would to limit the number of fetch() operations. Like NSView only draws once even though you called setNeedsDisplay multiple times. What is the best approach to do something similar to this?
It's kind of similar to a NSArrayController, but my class performs more functions in the backend while NSArrayController is more for binding views to the backend.
You should look for NSManagedObjectContextObjectsDidChangeNotification, posted by NSManagedObjectContext
The notification is posted during processPendingChanges, after the changes have been processed, but before it is safe to call save: again (if you try, you will generate an infinite loop).
The notification object is the managed object context. The userInfo dictionary contains the following keys: NSInsertedObjectsKey, NSUpdatedObjectsKey, and NSDeletedObjectsKey.
core data coalesce the changes for you, so it's already quite optimized.
Anyway, depending on what you want to do, a better option could be to subclass NSArrayController, probably overriding the - (NSArray *)arrangeObjects:(NSArray *)objects method, e.g:
- (NSArray *)arrangeObjects:(NSArray *)objects
{
NSArray *a1 = [self mayBeYouWantToPreprocessFetchedObjects:objects];
NSArray *a2 = [super arrangeObjects:a1]; // this performs filtering, etc
NSArray *a3 = [self mayBeYouWantToPostProcessArrangedObjects:a2];
// [self doWhatYouWantWithArrangedObjects:a3]; // e.g. trigger a reloadData if you're not using bindings
// or probably better : performOnMainThread: a method that will use arrangedObjects :
[self performSelectorOnMainThread:#selector(dataWasReloaded) withObject:nil waitUntilDone:NO];
return a3;
}
Doing so, you would get for free
all core data handling, including optimising the number of fetch (you can expect/hope NSArrayController is well optimised, and won't rearrange object when it's not necessary)
possibility to bind to model source like NSArray or NSSet in addition to core data (could be f.i. the arrangedObjects of another NSArrayController)
possibility to bind a NSTableView to your controller
all NSArrayController features, e.g. predicate filtering
I'm using such technique to provide data source to a NSOutlineView (partly because I have some specific processing on the fetched object, and also because NSTreeController is very limited), being still able to bind a NSTableView and have a flat view of my data

iOS layout; I'm not getting it

Well, "not getting it" is too harsh; I've got it working in for what for me is a logical setup, but it does not seem to be what iOS deems logical. So I'm not getting something.
Suppose I've got an app that shows two pieces of information; a date and a table. According to the MVC approach I've got three MVC at work here, one for the date, one for the table and one that takes both these MCVs and makes it into a screen, wiring them up.
The master MVC knows how/where it wants to layout the two sub MVC's. Each detail MVC only takes care of its own childeren within the bounds that were specified by the master MVC. Something like:
- (void)loadView {
MVC* mvc1 = [[MVC1 alloc] initwithFrame:...]
[self.view addSubview:mvc1.view];
MVC* mvc2 = [[MVC2 alloc] initwithFrame:...]
[self.view addSubview:mvc2.view];
}
If the above is logical (which is it for me) then I would expect any MVC class to have a constructor "initWithFrame". But an MVC does not, only view have this.
Why?
How would one correctly layout nested MVCs? (Naturally I do not have just these two, but the detail MVCs have sub MVCs again.)
Thanks all for replying. I will study the links that were provided.
Let me try to explain my issue one more time, hopefully to making it more clear. Do note that I already figured out that my view does not match iOS's, since I do not like where my code is going.
Yes, I'm calling a UIViewController an "MVC", since it for me at the moment implements all aspects of a MVC; it has controller code and an embedded view, plus the controller usually also holds and provides the data (all TableView examples implement it like this).
MVC can be present on many levels; basically a UITextField could (should?) be a MVC; there is a view, but also controller logic involved that you do not want to mix with other code. Encapsulation. For example: Java's Swing JTextField has a MVC. So does a JTable, JList, ... Multiple MVC patterns nested in other MVC's to build a whole screen.
This what I expect when some platform says it uses the MVC pattern. So When I coded the table, I created a MVC and only send the loadData message with a date as the parameter to it. It needs to take care of the rest itself. I have a Detail MVC that can slide in; I then tell it the object it needs to show and it needs to take care of the rest itself. Encapsulation.
So I have a lot of UIViewControllers with embedded UIViews. And that is not the way to do it...
One more potential link is the great talk from WWDC 2010 on MVC.
http://developer.apple.com/videos/wwdc/2010/
It is Session 116 - Model-View-Controllr for iPhone OS
The session is chock full of practical advice on how MVC really works, what makes it tick, why it's good. But it also has a lot of intro stuff to help folks new to the concept to wrap their heads around it.
If I understand your sentence on Java's Swing classes above are you talking about the anonymous classes that respond to events? If so those are not "MVC's", they are what is termed 'Observers', when they observe an event from the view they take some action (usually send a message to a controller). Cocoa Touch uses the Target/Action paradigm (and delegation) to achieve this.
I'd also strongly suggest you take Matthew and Stephen's advice and write a bunch of code. If you don't build that base of intuition, asking the right question (which is most of what is needed to get a good answer) is very difficult.
I really think the WWDC 2010 talk will help.
Good Luck!
If I understand your question -- and I may not, see my comments on it -- I think you're applying the MVC design pattern far too granularly. Most commonly in the setup you describe you'll have a single Model, a single Controller, and multiple Views that are grouped/combined, as in a .xib file.
In Cocoa Touch terms you'd have one UIView that contains a UILabel with the date and a UITableView for your table. These are your Views.
You'll certainly have a Model for the table data, likely an array of data. Your date data might be from its own model if it's a date retrieved from something or calculated or whatever, something entirely separate from the array of data. If it's instead associated with the array data -- they're both pulling from a database, or the date is calculated from the array data, or what have you -- then you have a single Model.
If the data is all coming from a single Model then a single Controller is likely fine. Even if the data is coming from more than one source/Model you likely only need/want one controller in this setup. The UITableView will have a UITableViewController, and that same controller can take care of providing your date as well.
To sum, the Model View Controller design pattern doesn't call for having a bunch of nested sets of models, views, and controllers. They could be, and sufficiently complex projects may call for it. Broadly, though, you'll have a controller that's associated with a model and one or more views, and that set of objects works together to provide a piece of functionality.
Tbee,
I'll post a tiny code example here, since it seems you're not really getting it.
#interface MyView : UIView
#property (retain) IBOutlet UIButton *button1;
#property (retain) IBOutlet UIButton *button2;
#property (assign) bool myData;
-(IBAction) doButton1:(id)sender;
-(IBAction) doButton2:(id)sender;
#end;
#implementation MyView
#synthesize button1 = _button1;
#synthesize button2 = _button2;
#synthesize myData = _myData;
// I'm leaving out the initWithNib, viewDidLoad, etc.
- (IBAction) doButton1:(id)sender
{
// do something as a result of clicking button1
_myData = YES;
}
- (IBAction) doButton2:(id)sender
{
// do something as a result of clicking button2
_myData = NO;
}
#end
Connect those up in InterfaceBuilder, and you've got a working "MVC." You don't need a completely new UIViewController for each button. The one for the View takes care of it.
UITableView and it's associated Views are more complex, and may require an additional UIViewController to help encapsulate. I really don't suggest starting out by using them, but this is a good tutorial here. It's got a lot of images which will show you how to connect things up in IB and the like. It's old, so your XCode may not look like the images, but it helps.
Thanks for the links, I'll look into them.
So far I've rewritten most of my application to using views instead of viewcontrollers (except the toplevel one) and it starts to match up with the API calls that are available like layoutSubviews. What I find disturbing that I need to do this now:
[tableDataSource loadData:date];
[tableView reloadData];
Where in my previous setup all I did was:
[tableViewController loadData:date];
But apparently that is the way to do it. One thing is unclear to me ATM. Since I construct and layout the view in loadView in my AppViewController, how do they get relayouted if the orientation changes. The VC does not have a layoutSubviews, so I should use the didRotateFromInterfaceOrientation and reposition the subviews from there?
BTW, I'm not mixing registering anonymous inner classes as listeners (observers). I'm very experienced with writing Swing components and JavaFX controls. And that probably is the culprit, in Java(FX) every component has a view and a controller (not always a model).

How to use NSWindowDidExposeNotification

I am trying to update another windows when the one becomes visible. So I found the NSWindowDidExposeNotification and tried to work with it, so I wrote in my awakeFromNib:
// MyClass.m
- (void)awakeFromNib {
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
[nc addObserver:self
selector:#selector(mentionsWindowDidExpose:)
name:NSWindowDidExposeNotification
object:nil];
}
and implemented the method
// MyClass.h
- (void)mentionsWindowDidExpose:(id)sender;
// MyClass.m
- (void)mentionsWindowDidExpose:(id)sender {
NSLog(#"test");
}
But it never gets called which is odd. What do I do wrong here?
Generally speaking, you would set up your controller as the window's delegate in order to receive these notifications, like so:
// MyClass.m
- (void)awakeFromNib {
// note: this step can also be done in IB by dragging a connection
// from the window's "delegate" property to your `MyClass` object
[window setDelegate:self];
}
- (void)windowDidExpose:(NSNotification *)notification {
NSLog(#"test");
}
Although, after reading here and here, windowDidExpose may not be your best bet. I would recommend trying the windowDidBecomeKey delegate method instead. That one is posted whenever your window gains "focus" (starts responding to user input) which may be the right time to show your second window.
Update: (in response to comments)
Apple's documentation (quoted below) indicates that NSWindowDidExposeNotification is only valid for nonretained windows, which, according to the posts that I linked above, are quite uncommon.
NSWindowDidExposeNotification
Posted whenever a portion of a nonretained NSWindow object is exposed, whether by being ordered in front of other windows or by other windows being removed from in front of it.
The notification object is the NSWindow object that has been exposed. The userInfo dictionary contains ... the rectangle that has been exposed.
On a higher level, NSNotification objects are simply packages of data that get passed around between Cocoa classes and NSNotificationCenter objects. NSNotificationCenter objects are controllers that manage these packages of data and send them out to observers as required. There is usually no need to trap notifications directly. You can simply use KVC/KVO or pre-defined delegates in your classes and Cocoa handles all of the dirty details behind the scenes.
See Notification Programming Topics and Key Value Coding Programming Guide if you want to know more.

How should a custom view update a model object?

This is a Cocoa n00b question - I've been programming GUI applications for years in other environments, but now I would like to understand what is "idiomatic Cocoa" for the following trivialized situation:
I have a simple custom NSView that allows the user to draw simple shapes within it. Its drawRect implementation is like this:
- (void)drawRect:(NSRect)rect
{
// Draw a white background.
[[NSColor whiteColor] set];
NSRect bounds = [self bounds];
[NSBezierPath fillRect:bounds];
[[NSColor blackColor] set];
// 'shapes' is a NSMutableArray instance variable
// whose elements are NSValues, each wrapping an NSRect.
for (NSValue *value in shapes)
{
NSRect someRect;
[value getValue:&someRect];
[self drawShapeForRect:someRect];
}
// In addition to drawing the shapes in the 'shapes'
// array, we draw the shape based on the user's
// current drag interaction.
[self drawShapeForRect:[self dragRect]];
}
You can see how simple this code is: the shapes array instance variable acts as the model that the drawRect method uses to draw the shapes. New NSRects are added to shapes every time the user performs a mouse-down/drag/mouse-up sequence, which I've also implemented in this custom view. Here's my question:
If this were a "real" Cocoa application, what would be the idiomatic way for my custom view to update its model?
In other words, how should the custom view notify the controller that another shape needs to be added to the list of shapes? Right now, the view tracks shapes in its own NSMutableArray, which is fine as an implementation detail, but I do not want to expose this array as part of my custom view's public API. Furthermore, I would want to put error-checking, save/load, and undo code in a centralized place like the controller rather than have it littered all over my custom views. In my past experience with other GUI programming environments, models are managed by an object in my controller layer, and the view doesn't generally update them directly - rather, the view communicates when something happens, by dispatching an event, or by calling a method on a controller it has a reference to, or using some similarly-decoupled approach.
My gut feeling is that idiomatic Cocoa code would expose a delegate property on my custom view, and then wire the MyDocument controller object (or another controller-layer object hanging off of the document controller) to the view, as its delegate, in the xib file. Then the view can call some methods like shapeAdded:(NSRect)shape on the delegate. But it seems like there are any number of other ways to do this, such as having the controller pass a reference to a model object (the list of shapes) directly to the custom view (feels wrong), or having the view dispatch a notification that the controller would listen to (feels unwieldy), and then the controller updates the model.
Having a delegate is a cromulent way to do this. The other way would be to expose an NSArray binding on the view, and bind it to an array controller's arrangedObjects binding, then bind the array controller's content binding to whatever owns the real array holding the model objects. You can then add other views on the same array controller, such as a list of objects in the active layer.
This being a custom view, you'll need to either create an IBPlugin to expose the binding in IB, or bind it programmatically by sending the view a bind:toObject:withKeyPath:options: message.
There is a very good example xcode project in your /Developer/Examples/AppKit/Sketch directory which is a more advanced version of what you are doing, but pertinent nonetheless. It has great examples of using bindings between controller and view that will shed light on the "right" way to do things. This example doesn't use IB Plugins so you'll get to see the manual calls to bind and the observe methods that are implemented.
there are several similarities between your code and an NSTableView, so I would look at maybe using a data source (similar to your delegate) or even perhaps bindings.

Resources