Views: Post Processed
Guy English March 30, 2008
Visual Cues
If your application presents data to the user then there are some valuable tools you’ve been leaving on the table because they have traditionally been difficult to achieve with computer graphical interfaces. Mac OS X has some great graphics technology that changes the game - and I don’t mean Core Animation.
Core Image - Not Just For Images Anymore
Core Image is incredible. The amount of complexity that it abstracts and offers in a straight forward interface is staggering. I may be a little more appreciative of it since I’ve had occasion to do a lot of low level graphics programming against OpenGL or DirectX or whatever directly - Core Image exposes, with a minimum of fuss, all the best parts of playing with a modern pixel pipeline.
The basics of Core Image are dead simple - you provide a recipe for how you’d like your image cooked and it’ll give you a result. The recipes are in the form of a few simple objects strung together with some settings tweaked. If you’ve played with a modern Mac OS X image editor like Acorn (from Flying Meat) what you see in the filters window (developed by Rogue Sheep) is pretty much how the underlying implementation works. (Yes, I did just pimp friends.) So if you’ve got a basic grasp of stringing filters together in a graphics program then you’re in good shape to do so in your own application.
“But I don’t write a graphics app!”
Well, yes, in fact, you do. A Mac OS X desktop application uses a graphical interface to communicate information to the user. One of the wonderful things about graphics is that you can communicate lots of information very quickly. Even better than that is that the information doesn’t need to be accurate. That sounds preposterous - why on Earth would you want to show the user inaccurate information. Well, clearly, you don’t - but you don’t need to show exact information either - your job is to provide an environment with enough graphical cues so that the user can interact quickly and effectively with whatever data you’re presenting to them. If you’re not sold let’s look at scroll bars - they shrink in proportion with the size of the displayed information versus the size of the total amount of information. They’re pretty close to being accurate but not really - there’s a minimum size on that scroll bar thumb so it breaks down for huge amounts of data - and the other thing is, really, you don’t care - you get the idea but a pixel here or there doesn’t matter to you. It’s the same for Dock icons bouncing on load or even the classic lying progress meter - none of these are precise but they allow you, the human, to infer information quickly.
Humans are damn good at picking out important visual information rapidly. We’re good at it because otherwise 6,000 years ago we’d all have been eaten by Dinosaurs. We have some built in clues that can help us distinguish relevance - objects outside our focus are more blurry and those brightly colored draw our attention more than those which are muted and subdued. These cues are used daily by every sighted person to make sense of the vast amount of visual clutter we’re bombarded with.
“Where are you going with this?”
Core Image can be leveraged to provide your users with richer visual cues to help them more quickly grasp the information you are displaying. The graphics architecture of Mac OS X allows us to draw into different contexts - once we’ve got our data in a graphics context we can operate on it graphically as if it were an image. We’re going to use this ability to do some fancy post-process effects to our views that will add visual cues to the data represented. I’ll use an NSTableView subclass to demonstrate but it’s important to recognize that this technique is applicable to any view - and may even work better with your own custom views.
Post Processed Table Views
The idea is simple - we’ll present a typical table view to the user but allow for various ‘focusing’ effects to be applied to it. For each item in the table we’ll provide a degree indicating how much of the effect to apply. For a search results table, for example, we can blur results more the less relevant they are. Or if we’re looking at time based data we could apply a sepia tone to older entries - a common visual cue indicating that an item has aged.
Implementation
We need three things to make this work: the table as it would have appeared without any post processing, a set of floats indicating how much we’d like the effect applied, and a mask image we’ll use to graphically represent those floats. A good way to go about this on Mac OS X is to use CGLayers - they’re fast, efficient, and will be cached on the graphics card when possible. The process is reasonably simple: if we’re applying a post process effect then allocate the viewLayer which we’ll render the base NSTableView into; then allocate the viewMaskLayer in which we’ll draw the various shades of gray representing how much to apply the effect. Since the effect is multiplicative 1 (or White) represents a full application of the effect while 0 (or Black) represents not applying the effect at all.
So far, so simple. The next step isn’t much harder either. First - set the current graphics context to the viewLayer and ask our superclass to draw. That way we’ll capture what it would have drawn in our layer context rather than having it sent to the screen. Next we’ll need our table view data source to be able to provide us with per-item blending information. To achieve this we set our mask layer as the current graphics context then call -(CGFloat)tableView:(NSTableView*) tv postProcessFactorForRow:(NSInteger)row on our data source. We take the result, turn it into a shade of gray and fill the row rect with the color. Once we’ve iterated over the visible rows we now have a mask image which contains bands of gray each indicating how much to apply our filter.
Finally we apply the post process effect - we’ve got our source view and our mask and we just use a CIBlendWithMask filter to combine the two. The effect is up to you really - and it’s irrelevant to this approach. The code provided shows a sepia tone, a blur, a contrast and provides for a way to set custom effects. You can download the source code here, play around and see what works for you. Remember - we happen to be using a table view here but this technique is generally applicable - if your view presents a set of data then you can use this technique. This technique also works on Tiger - in fact the code just recently got Objective-C 2.0-ized but this was originally all written against Tiger without Core Animation in mind. If you want to use this on Tiger, go wild - you’ll just need to reverse the ObjC2.0 @properties and data source @protocol a bit but it’s dead easy.
Also - I’m not sure if I’ve made this explicit: if there’s code I’ve put up on my blog then it’s yours to use as you wish. That said, if you’re making an awesome UI for your Nuclear Armageddon machine I’d prefer you look elsewhere - Nuclear is so passé.
Update
Shamed by Gus for not having any visuals here’s a screen shot of a test app I just built. It shows a listing of the files on the Desktop - files of the same type as the selection are not blurred, files of a similar type (as determined by a UTI check) are blurred a little and totally dissimilar files are blurred even more. The effect is overstated for the sake of the example, it’s the technique which is really of interest.

And here’s a movie of it in action: KBPostProcessTableView.mov
I also updated the code and included the test app and a project. Download it here.
Filed Under Cocoa | 4 Comments
Collection Extensions
Guy English February 26, 2008
Brown is Ugly
Well it’s been a long, long time since I posted anything here. If you’re reading this it’s either because we knock back drinks together at Mac events or you’re someone I should be buying a drink for at a Mac event. The basic idea I initially had for this blog was that I’d post a series of Weird & Wonderful Cocoa tricks. There’s tons of great Cocoa content out there ranging from introductory to pretty advanced but it seemed like there was a hole in the Semi-Abusive segment. I’ve had a number of ideas I’ve wanted to put up but with the pressures of the oppressive corporate world (I work at a subsidiary of an umbrella company held by a Rogue Amoeba interest. We’re hiring.) and various other things on my plate nothing ever gets written. The thing about the Weird & Wonderful is that while it’s easy to know if something is weird it’s something else to know if it is indeed Wonderful and it’s something even more else to be able to explain why it’s Wonderful. So in the interest of trying to keep my feed from turning brown in NetNewsWire I think I’ll start to mix it up a little. That said, let’s get Weird & Wonderful.
Loopy Problems
You’re too smart to be writing yet another loop. Nine times out of ten it’s mostly boiler plate - the operation you’re doing can be easily explained in a couple of words but expressing it in code is cumbersome. Even with Objective-C 2.0’s new for in iteration loops are still basically boring beasts. Let’s see what we can do to avoid them.
At the mega-giant corporation where I slave under the ever watchful eye of a hawkish manager we often need to iterate over Employees to create the endless reports required by our strict oversight committee. We’d like to be able to say, “Give me the names of all Employees who’s computers are idle for more than 5 minutes”. Here’s how we’d do it in Cocoa:
NSMutableArray *namesOfIdleEmployees = [NSMutableArray array];
for ( Employee *employee in allEmployees )
{
if ( employee.idleTime > 5 )
{
[namesOfIdleEmployees addObject: employee.name];
}
}
Now, sure, that doesn’t look like much code and it’s not really arduous to write but you’ve got better things to do with your time than crank out code like that. Like make more money for your overlords. They love that.
So what are we actually doing in that loop? How would we express it in words? Maybe “collect the names of employees who are idle for more than five minutes”. That sounds like a nice concise order, one you could pretty easily turn into a method on the collection. But if you did that in this case there’s a billion other cases you’d have to do too. And that gets more tiring that just doing the loops when you need them. It would be nice if we could just ask the collection though. Kind of like how we can ask for a @distinctUnionOfObjects. However, to do that we’d need to add our own array operators or override valueForKeyPath: or something weird.
Weird & Wonderful
NSArray *names = [allEmployees valueForKeyPath: @"[collect].{idleTime>5}.name"];
Kind of nifty, isn’t it? What we’re doing here is saying we want to filter based on idle time and any objects that pass the filter we collect their name. Easy as pie.
To do this you do indeed have to override valueForKeyPath: - there’s no way to add your own @style array operators. That’s ok though because once we’ve overridden valueForKeyPath: we can get fancy with how we handle things and add some pretty powerful functionality.
method calls
NSArray *results = [myCollection valueForKeyPath: @"[collect].name"];Method calls go between []’s. In this case since there’s no parameter given an implicit parameter is assumed. The result of this expression is basically:
[myCollection collect: @"name"]- the collect method iterates over the objects in the collection and gathers all the values for the key ‘name’.You can have more complicated method calls:
NSArray *results = [myCollection valueForKeyPath: @"[collect].name.[componentsSeparatedByString: ' ']"];The result of this expression is that
componentsSeparatedByString: @" "will be called for the value of ‘name’ in each object in the collection. The resulting array of components will then be gathered by the ‘collect’ call. The end result is an array of subarrays containing the words of the name.inline predicates
NSArray *results = [myCollection valueForKeyPath: @"[collect].{salary>100}.jobTitle"];Predicates can be specified between {}’s. The predicate string is used to create an NSPredicate which is then used to evaluate the object. If the object matches the predicate then it returns the value of the remainder of the keypath otherwise it returns nil. In this case we use the predicate to filter the collection based on a salary. Each object is checked if it’s salary property is greater than 100. If it is then the value of it’s jobTitle property is returned. If it’s not nil is returned. Collect gathers the results ignoring any that are nil. The end effect of this is that you’ll get an array of all the job titles where salary is more than 100.
inline value transformers
NSArray *results = [myCollection valueForKeyPath: @"[collect].<NSUnarchiveFromDataTransformerName>.imageData"];You may specify the name of a value transformer between <>’s. The value transformer is handed the value of the remainder of the keypath. In this case we use the unarchive from data value transformer and hand it some imageData. The result of the transformer is then collected by ‘collect’. The resulting array would contain unarchived NSImage instances. You may specify any of the build in value transformers by their constants or you can use your own value transformers names.
Some Examples
NSArray *waitsAlbumCovers =
[myRecordCollection valueForKeyPath:
@"[collect].{artist like 'Tom Waits'}.<NSUnarchiveFromDataTransformerName>.albumCoverImageData"];
waitsAlbumCovers now contains NSImage instances for each of the albums in my collection where ‘Tom Waits’ is the artist.
NSString *albumsTitles =
[myRecordCollection valueForKeyPath:
@"[concatenate: * withSeparator: ', '].{artist=='Tom Waits'}.albumTitle"];
albumTitles contains a string of all Tom Wait’s album titles separated with ‘, ‘. This example shows the use of a special place holder symbol. The ‘*’ expands during evaluation to the remainder of the keypath. In this case the resulting call on the collection would look like: [myRecordCollection concatentate: @"{artist=='Tom Waits'}.albumTitle" withSeparator: @", "]; The concatenate:withSeparator: method would then iterate over the contents of the collection and concatenate the value of
{artist like 'Tom Waits'}.albumTitle placing the separator in between.
Implementation
The code for all this is actually surprisingly small. It’s one file, one class which isn’t actually really used and a couple of categories. Objective-C & Cocoa are just ridiculously awesome at times.
First up we need to override valueForKeyPath:. That’s totally possible to do - we make our own NSObject subclass then inherit everything from that and we’re good to go. Except that’s a lame solution because then we’d not be able to use these calls on stock Cocoa collections. What we want is to replace NSObject’s implementation with our own. On Leopard there’s a new way of doing that: class_replaceMethod. We use that early in the life time of the process (in main.m before you actually kick off the AppKit) and we’re good to go.
The code for our version of valueForKeyPath: is also pretty basic. We’re given a “key path” which is a string with components separated by ‘.’ characters. We just use componentsSeparatedByString: to grab each component then iterate over each one. If we detect that the component starts with one of our special characters ( [, { or < ) then we handle it. Otherwise we just recurse down the component list.
collect:, concatenate: and other methods need to be implemented by each of the collection classes you’d want to call them on. Rather than code them for each kind of collection I hung them off NSObject in a category and, better, I also made NSObject be able to act as a collection by implementing NSFastEnumeration for it. So now this is valid code:
NSString *exampleString = @"Example string";
for ( NSString *string in exampleString ) NSLog( @"%@", string );
That’ll print out “Example string”. As far as I’m concerned single instances should behave as collections of 1 item. Adding this to NSObject doesn’t effect NSArray, NSSet, or NSDictionary enumerations at all. The real benefit of NSObject being a collection is that this, sort of contrived example, works:
NSString *jobTitle =
[person valueForKeyPath: @"[collect].{hasNotBeedFiredYet==YES}.jobTitle"];
You’ll get nil or the jobTitle depending on the person not being canned yet.
The best way to check out the implementation though is to grab the source code. It should be pointed out that this code should really be taken as a proof of concept. There’s so much room for optimization and general clean up it’s not even funny. It does, however, work, which is always nice, and it shows off what you can do with this kind of approach. Also as a word of warning: I’ve yet to use this in any sizable project. It may also be a terribly bad idea for some reason that’s escaped me but will no doubt be pointed out in the first comment.
All the warnings out of the way I think what you can do with this stuff is pretty powerful and will save a ton of boring loop iteration code. It lets you more easily express what you want rather than the iterative steps needed to get it done. And that’s always a good thing.
Filed Under Cocoa | 11 Comments
Bug Responder
Guy English March 11, 2007
When the user takes an action in a Cocoa app the framework looks for the first object in the responder chain that can handle that action. Basically each object in the responder chain is asked if it implements the given action selector and if not passes the buck on to the next object in the chain. This works great since you can put code to handle user actions in the higher levels of your applications controller classes without having to worry about calling into them yourself from button handlers. The trade off here is that the further removed from the action you put the code the less contextual information you may have with which to make your decisions.
Generally this isn’t a problem. Your NSWindowController will handle an action for the relevant document or you handle it higher up for the application in general. Things become a little more complicated though when you have view controllers. These are very much like window controllers except they are responsible for only a sub-portion of the view hierarchy. As an example consider a view controller in charge of a date picker. The date picker may have several subviews, one for the day, month, year, maybe some more for the time. The idea of a view controller is that it can create a view hierarchy and manage it in a way thats opaque to the rest of the app. The date picker is a trivial example - you can imagine view controllers that manage a complex amount of state and touch their model area in a non-trivial way.
Often you’ll want to give the user the ability to report bugs in your software, sort of like what Safari does with it’s Bug Button. Ideally we’d click the bug button or chose an item from a menu and we’d get a nice bug report with contextual information as to what the user was looking at. There were a couple of ways to approach this. First is to add code in each controller that’ll handle a ‘reportBug:’ action, gather it’s information and send it to the centralized bug reporting class. This fails though because if I implement ‘reportBug:’ in my date picker then I lose the context in which I was trying to pick a date - and the bug report loses some of it’s usefulness. If I implement ‘reportBug:’ in my window controller then I can know the big picture but I don’t know what’s going on with the view controllers below me - they may have pertinent information that hasn’t been committed to the model layer yet. So it looks like we’re screwed either way.
My solution to the problem was to implement my ‘reportBug:’ action at the highest level in my application. It looks like this:
- (IBAction) reportBug: (id) sender
{
KBBugReportController *bugReport = [KBBugReportController sharedBugReportController];
[bugReport gatherBugReportInformation];
[bugReport sendBugReport];
}
Nice and simple, isn’t it? Except how can ‘gatherBugReportInformation’ work? The trick is we walk the responder chain ourselves and ask each responder to contribute some context information. By the time we’ve walked the entire chain we’ll have travelled from the most specific information to the most general.
Here’s what that’d look like:
- (void) gatherBugReportInformation
{
KBCoalescingDictionary *bugReportInformation = [KBCoalescingDictionary dictionary];
NSResponder *responder = [[NSApp mainWindow] firstResponder];
while ( responder != nil )
{
if ( [responder respondsToSelector: @selector( submitBugReportInformation: )] )
{
[(id)responder submitBugReportInformation: bugReportInformation];
}
responder = [responder nextResponder];
}
NSArray *titles = [bugReportInformation objectsForKey: kKBBugReportTitleKey];
[self setTitle: [titles componentsJoinedByString: @" "]];
NSArray *contents = [bugReportInformation objectsForKey: kKBBugReportContentKey];
[self setContent: [contents componentsJoinedByString: @" "]];
[bugReportInformation setObject: [NSDate date] forKey: kKBBugReportTimeStampKey];
[self setBugReportInformation: bugReportInformation];
}
The method I use here is that responders implement an informal protocol or one method - ’submitBugReportInformation:’. The method takes one argument a special NSMutableDictionary into which they can write whatever bug report info they want. I’ve got a few special keys defined for the bug report title, contents and time stamp. If you send the bug report in an email the title and contents come in handy. There is one trick here and that’s using a coalescing dictionary. All that does is that when it gets asked to set and object for a key that already exists it instead makes and array and sticks both objects into the key slot. Asking it objectForKey will return the last object set and asking it for objectsForKey (note the ’s’) will return the array of all objects set for that key. This makes it easy to just write whatever you want into it without worrying about blowing away other objects data.
By the end of ‘gatherBugReportInformation’ we’ve travelled the chain and given each responder the opportunity to help us out with some context info. All we need to do now is send our dictionary off to our server somehow. We can either upload it with an HTTP POST request or send it off in an email. Either way I suggest letting the user have a look at what you’re going to send; you can get quite a backlash for not being polite about this kind of thing.
Filed Under Cocoa | 2 Comments
Shader Source Code
Guy English March 3, 2007
I’ve posted the source code to a basic implementation of the Cocoa Shaders I talked about here. There’s code for conditional shaders, shader lists, clip shaders, and affine transform shaders. As far as shaders that actually draw there’s a solid color shader and one for drawing an image. The image shader lets you tweak the compositing operation, source rect, and set a drawing scale.
Included is a sample app to show you how you could use this stuff in practice. The app has a custom view which draws using a shader. The demo shader will change its background from blue to red when the view is clicked. Its implemented using only the simple shaders provided.
Fancier gradient fill shaders, CoreImage based shaders and that kind of thing are left, for now, as an exercise for the reader. I mentioned it in the original post but if you’re looking to do gradients you’d do yourself a favour by looking at CTGradient.
Filed Under Cocoa | Leave a Comment
Cocoa Shaders
Guy English February 23, 2007
Keeping up with the Joneses UI trends on Mac OS X can take a fair amount of programming and artistic effort. With the HIG becoming antiquated and new implicit design guidelines become more prevalent we find that Cocoa doesn’t provide us with au-currant controls right out of the box. So we’re left with either rolling our own or making do.
One of the first things I do when I download an app is poke around its resources. Often you’ll find all manner of bitmap images that lay out portions of the apps controls. Left sides, middles, right sides. Sometimes circles where the center pixel will be stretched for the length of the control. Apple’s applications are often stuffed full with these custom little images but it’s not just them, I’m sure that many of your favorite apps have similar resources.
“Well so what” you may ask, “We want nice looking controls, the cost of downloading a few extra images is next to nothing, and we can tweak the images at change the look of the controls, it’s no big deal”. And then Apple keeps talking about resolution independence and you curse as you have your artist (or yourself) laboriously pound out multi-rep tiffs containing all the DPIs you need. And your download bloats. But whatever, it’s the 21st Century and my binaries and tiffs are fat and it’s all good. Then a user complains that the left side of the “SomethingCool” button in your app is all distorted and you need to go and check the tiffs and see that one of the reps is screwy and fix it. These little pains are common to development but, often, we can avoid them.
Will Shipley wrote this in a post about implementing a gradient table view:
Which leads me to an important rule: In general, if I can replace an image with code, I do so. This is not something that’s intuitive, and it took me many years to decide that this is the best policy. Code is easier to change and understand than images are.
I read that and thought, “Shipley, my boy, you’re just so wrong”. A large chunk of my career has been spent writing video games. If you’ve ever looked around a video game there’s resources that control pretty much everything. One of the guiding design philosophies of modern game engine design is to push as many decisions out of the code and into the hands of the designer as possible. Given this approach the current resource heavy Mac app development makes perfect sense. Until you consider the context. On the games I worked on artists could outnumber the programmers 5 to 1. Apple, who’s apps are littered with resources, has the budget to employ however many the artists it needs. Indie Mac Shop, Inc does not. Indie Mac Shop has, likely, one maybe two programmers and some contact with a part time artist who may well be on contract. Perhaps they may do the artwork themselves, taking time away from their coding. Either way art, in the indie dev world, is harder to come by than code. Shipley, of course, is speaking from the perspective of a programmer who’s career has been spent at a relatively small shop and his advice is spot on for that environment. So turns out he’s onto something. Let’s see if I can save a little face by extending the concept he puts forward.
The Setup
Drawing involves two things: the resources used by graphical elements and the logic required to arrange these elements the right way. Resources include the colors, images and perhaps font information used in the element. The logic dictates in what order and where we employ these resources. If we call our resources “data” and our logic “code” it becomes clear that we can encapsulate our drawing away into a nice, reusable and, importantly, polymorphic class.
The Shader
In 3D graphics a “shader” is used to define and control how a surface is rendered. The basic principle is that for each point on a surface inputs are given to the shader and it returns the final color for that point. Typical shaders involve looking up a pixel from a texture map, modifing that pixels color based upon light intensity and direction, and perhaps adding a brighting color for a specular highlight (shiny!). This method has a valuable trait: the renderer doesn’t know or care what the shader is actually doing. All it does is offer the shader a ton of information (point on the surface, direction to the light, ambient light conditions, stuff like that) and the shader does its thing and spits out an answer. Internally the shader may be using a texture map or it may be doing some fancy math to fake a marble texture. It just doesn’t matter, the result is the same.
It’s a good idea; let’s steal it.
Thanks to the flexibility of Objective-C and Cocoa we can apply this approach to our drawing and build an elegant encapsulation. Our recipe involves Key Value Coding and NSPredicates.
A Cocoa Shader
Our first stab at a Cocoa shader might be as simple as:
@interface KBShader : NSObject
{
}
- (void) drawInRect: (NSRect) rect;
@end
We can subclass that and add NSImages or whatever and when we need to draw we’ll just call drawInRect: on our instance. Simple, but it doesn’t quite meet the flexibility we’re aiming for. Ideally we want a way to pass information into the shader so it can make desicisions based on the environment its being used in. Like knowing the ambient light level in a room we’d like to know if our button is disabled or not. So we change our drawing method to look like this:
- (void) drawInRect: (NSRect) rect input: (id) input;
We pass input as an id rather than an NSDictionary. A dictionary would have been great and could easilly have encapsulated any state information we’d like to send to the shader. Its shortcoming, however, is that we’d need to maintain its state and make sure it matched the state of the element we’re trying to draw. That’s just error prone and boring code to write, so we won’t do it. Instead we leverage our nifty Cocooa Key Value Coding methods.
Using KVC we can treat any object as if it were a dictionary. Even better, by using key paths we can traverse the objects its accessors return and dig deep into its guts. So it’s a great fit for our shader - lot’s of environment information available to us in an easy to get to way. Using KVC we could write a shader implementation like this:
- (void) drawInRect: (NSRect) rect input: (id) input
{
if ( [[input valueForKey: @"isEnabled"] boolValue] == YES )
{
[[NSColor redColor] set];
}
else
{
[[NSColor blueColor] set];
}
NSRectFill( rect );
}
Now that’s pretty decent. We can change the way we draw based upon the state of the object we’re supposed to be drawing. We can, of course, query a lot more attributes than just isEnabled.
Naming Shaders
I always liked how you could ask NSImage for an image just by calling imageNamed:. Shaders deserve the same treatment. It’s pretty easy - add a name ivar to the shader and some class methods to register and deregister a shader with a given name and you’re done. Then you can just say: [view setShader: [KBShader shaderNamed: @”CurrentEnVogueGlossEffect”]]; and you’re good.
Cocoa Shader Tree
There’s still room for improvement though. We don’t neccessarily want our shaders to be tied to one control. We’d like a ‘WhiteGloss’ shader, a ‘GrayGloss’ shader and maybe a ‘FlatWhite’ shader or something. And we’d really like to be able to say: “Button, when you’re enabled use the ‘WhiteGloss’ shader, when you’re pressed use ‘GrayGloss’”. We can do that by nesting some if’s and refering the call to other shaders but there’s a better way.
What we want is to have our shaders to be able to decide if they want to draw or not based on what’s been input. Cocoa has a nice way of giving us YES or NO answers based on a complex chain of tests - NSPredicate. Let’s use it for something better than some mabmy-pamby CoreData query - we’ll use it to draw something shiny.
(Just as a disclaimer don’t take this code literally - I’m not going to write the init’s and dealloc because it’s boiler plate and distracting).
@interface KBConditionalShader : KBShader
{
NSPredicate *_predicate;
KBShader *_yesShader;
KBShader *_noShader;
}
- (id) initWithPredicate: (NSPredicate*) predicate
ifShader: (KBShader*) yesShader
elseShader: (KBShader*) noShader;
@end
@implementation KBConditionalShader
- (void) drawInRect: (NSRect) rect input: (id) input
{
if ( [_predicate evaluateWithObject: input] == YES )
{
[_yesShader drawInRect: rect input: input];
}
else
{
[_noShader drawInRect: rect input: input];
}
}
@end
Now we’ve got a shader that doesn’t actually draw - it does control flow based on a test of the input. Thats kind of cool but so what? Let’s expand on that control flow idea.
@interface KBShaderList : KBShader
{
NSMutableArray *_shaders;
}
- (id) initWithShaders: (NSArray*) shaders;
@end
@implementation KBShaderList
- (void) drawInRect: (NSRect) rect input: (id) input
{
unsigned currentShaderIndex, numberOfShaders;
numberOfShaders = [_shaders count];
for ( currentShaderIndex = 0; currentShaderIndex < numberOfShaders; currentShaderIndex++ )
{
[[_shaders objectAtIndex: currentShaderIndex] drawInRect: rect input: input];
}
}
@end
Now we can string shaders together. Why on earth would we want this? Well let’s say we want a nice gradient background with an image in the top left hand corner. We can reuse our gradient background shader and our draw an image shader and just chain them together. And we’ve got a conditional shader too…
KBConditionalShader *conditionalBackground =
[KBConditionalShader shaderWithPredicate: [NSPredicate predicateWithFormat: @"(isEnabled==YES)"]
ifShader: [KBShader shaderNamed: kKBBlueGradientShader]
elseShader: [KBShader shaderNamed: kKBGrayGradientShader]];
KBShaderList *shader = [KBShaderList shaderWithShaders:
[NSArray arrayWithObjects: conditionalBackground,
[KBShader shaderNamed: kImageBadgeShader],
nil]];
The resulting shader will conditionally change the background from blue to gray if the input object is enabled or not. Regardless of state it will draw an image badge. Conditional shaders and shader lists can be nested as much as you’d like so you can make some pretty rediculously complicated shaders if you really want.
Benefits
Shaders offer some real benefits over bundling up the drawing code with what ever element you’re drawing. First the obvious one - they consolodate the drawing code into one place and it can be shared easily between code that needs them.
Secondly is abstraction. The shader draws, that’s what it does. If you’re using CTGradient today and want to switch that to something else in the future you change it in your gradient shader and you’re done. You can even use different gradient shader implementations depending on platform if you want. Uh, you know, if that was something that’d make sense.
Thirdly they offer optimization and profiling opportunities. You can instrument your shader and discover that each time it’s asked to draw its in the same size rect. With a shader you can cache the results and just draw the cached bitmap instead.
And there’s an immense flexibility behind the shader model. Once you can set a shader for a view it’s realatively easy to write a shader picker that can dynamically change the shader. No, really, honest, I’m not talking about skinning even though it’s a natural fit, but it’s a valuable tool to have during development so you can quickly experiment with different looks. Or consider a ‘debug’ shader that when asked to draw dumps a bunch of control state to the console.
And, saving a big one for last, it’s a big step towards making Resolution Independence a far lesser burden. Some resources are inescapable (and for what it’s worth - multi res Tiff files are what the Apple boys recommend) but many of the images I see when I’m poking around could be replaced by shaders. When Apple changes some random UI element and you want to keep up it’s easier to pull out the color picker and change some RGB values in a shader than it is to ask an artist to whip something up in Photoshop. Your code will scale and the artist will be stuck doing x number of reps. Be kind to artists, they’ve got better things to do.
Filed Under Cocoa | 11 Comments