Save Our Screens 102

How To Write A Mac OS X Screen Saver, Part 2

by David Hill

Introduction

In the last article, we covered some introductory material and put together a basic screen saver. We also
covered some debugging tips that should have helped you get your own projects started. Now that you've got a
simple screen saver up and running and you've got a grasp of the ScreenSaverView class, let's take things a
bit further and add a simple configuration sheet. We'll need to make several modifications to our project.

That's the easy part to explain since it only involves code. We've got all the code in place for our
configuration sheet, but where's the sheet itself? That would be the tricky part. Fire up Interface Builder
and create a new empty NIB. In order to connect our screen saver view class to elements in the NIB and vice
versa, we're going to need to teach IB about our custom class. By default, IB doesn't know about the
ScreenSaverView superclass so we'll start there. Arrange your project window and the NIB window such that you
can see both. Open the ScreenSaverView.h header file in Xcode and drag the header (either the window title
bar proxy or the header icon in the Groups and Files outline) to the NIB window. You should see a green
cursor with a plus in it when the cursor is over the IB window as seen in Figure 1. That's a good sign and it
means that IB will parse the file and absorb the ScreenSaverView class information when you drop the header
file into the IB window.

Figure 1: Teaching Interface Builder
about ScreenSaverView

Now we're ready to teach IB about our custom projectNameView class but there's a simpler way than dragging
header files around and juggling windows. Switch to IB, switch to the Classes tab in the NIB window, and
select Read Files... from the Classes menu. In the resulting dialog, navigate to your project folder, select
your projectNameView.h header, and click Parse. As a final step, we need to tell IB that our NIB will be
owned (this usually means loaded) by our custom class. Switch the NIB file view to Instances and select the
File's Owner. Open the Info window (SHIFT-CMD-I) and select Custom Class (CMD-5) from the popup menu. Find
projectNameView in the list and select it to change the class.

At this point, IB knows about our custom header file and we can start creating the sheet and wiring it up.
The screen saver engine is expecting the configureSheet method to return an NSWindow so let's give it one.
Show the palette in IB and select the Windows button from the toolbar. Drag a normal window into the NIB
document window to add it to your NIB. Switch to the Controls portion of the IB palette and add a switch (a
checkbox for you Carbon people) and two buttons to your window. Change one button to say OK and the other to
say Cancel. Change the switch to say something like Rotate the rectangles but the exact title doesn't matter.
The window should look something like the one in Figure 2.

Figure 2: Our finished configuration window

With our controls in place, we need to make our final connections. The view needs to know about the sheet
in order to close it and the controls need to know how to tell the view they've been clicked. Control-drag
from the File's Owner to the window (either in the Instances view or the titlebar of the window itself) and
set the configureSheet connection. See Figure 3 if you need help. Control-drag from File's Owner to the
checkbox and connect it to the shouldRotateCheckbox outlet. Control-drag from the OK and Cancel buttons to
File's Owner and set the okSheetAction: and cancelSheetAction: connections, respectively. Figure 4 shows the
okSheetAction: connection.

Figure 3: Connecting the configureSheet outlet to our window

Figure 4: Connecting the OK button to okSheetAction:

Now we're getting somewhere. Save this new NIB file as ConfigureSheet into your project directory and IB
will ask if you'd like to add it to your currently open Xcode project. Click Add. Build your screen saver
and try it out. If we're lucky, the saver will still build and work. Open the Desktop and Screen Saver
preferences pane, select your saver, and click the Options button to see your new configuration sheet.
Hopefully it looks a lot like Figure 5. Turn the rotation checkbox off then close the sheet and watch what
happens to the Preview. Looks good, right? Ah, but there's one small problem. Click the Test button and see
for yourself.

Figure 5: The configuration sheet in action

Why is the screen saver still rotating rectangles when we clearly turned off the checkbox in the
configuration sheet? The answer is simple and this is a good excuse to drive home the underlying reason. The
screen saver engine creates a separate instance of the current saver whenever it needs to blank a different
area. The Preview pane gets an instance. The Test run of the saver gets a new, separate instance, and if you
run the saver for real via a hot corner you'll get yet another instance. Those of you with multiple monitors
may have already noticed that the saver draws different things on each monitor which shows that each monitor
also gets its own instance of the screen saver. For the most part, this is a good thing but there are times
when you'd like to share some state between the different instances. User preferences are the most common
case and the ScreenSaver framework provides a convenient solution in the form of the ScreenSaverDefaults class
and its defaultsForModuleWithName: method.

In the next section, we'll use defaultsForModuleWithName: to store and retrieve the current
isRotatingRectangles setting so that each screen saver instance will do the right thing.

Defaults

The NSUserDefaults system provides a very handy way of storing and retrieving user preferences in such a
way that applications (and screen savers) don't have to care where the defaults are stored. Your code can
just make the appropriate method invocations and the frameworks handle the rest. For screen savers, the
ScreenSaverDefaults class makes things even easier. It provides a single method, defaults ForModuleWithName:,
in addition to the standard methods provided by NSUserDefaults. Note that it is extremely important that your
screen saver use the ScreenSaverDefaults mechanism rather than NSUserDefaults, which is keyed off the
application that is reading and writing defaults. Since screen savers get loaded in by more than one
application, you'll need to use the defaultsForModuleWithName: method of ScreenSaverDefaults to keep your
preferences straight. As you might expect from the name, a screen saver uses defaultsForModuleWithName: to
obtain a ScreenSaverDefaults reference that it can then use to load and store preferences. Let's see how that
works in practice by adding code to handle our one preference value, isRotatingRectangles. First, we need to
make sure that the defaults system knows what our global default for isRotatingRectangles should be (would
that be a default default?) by registering a default dictionary.

4. Replace the assignment to isRotatingRectangles in initWithFrame:isPreview: with:

// find the defaults we should use for this screen saver
ScreenSaverDefaults* screenSaverDefaults =
[self defaults];
// create a dictionary to contain our global default values
NSDictionary* defaultDict = [NSDictionary
dictionaryWithObject: kDefaultsYesString
forKey: kDefaultsIsRotatingRectanglesKey];
// register those global defaults
[screenSaverDefaults registerDefaults: defaultDict];
// now we can read in the current default value knowing
// that we'll always get the user defaults or our global defaults
// if no user defaults have been set
isRotatingRectangles = [screenSaverDefaults
boolForKey: kDefaultsIsRotatingRectanglesKey];

Before we move on, I should make a few comments about this code. Note that the defaultDictionary is
created in an autoreleased state so we don't have to worry about cleaning it up to prevent leaks. As you'll
see later, we also don't have to read the defaults back in right away since we'll be checking the default
value of isRotatingRectangles before drawing each frame. So why is the boolForKey: call here? I included it
for several reasons, not the least of which was reinforcement of the defaults idiom. It also makes sure that
we've got the correct value of isRotatingRectangles from the very start in case we forget to check it later in
the code. For example, the configuration sheet code sets the value of the checkbox based on the current value
of isRotatingRectangles. If we forget to load the appropriate value before bringing up the sheet, the
checkbox will be out of sync with the current default value and users will get confused. As a final comment
on the initWithFrame:isPreview: code, note that we've now got two string key values: kDefaultsModuleName that
represents the name of the module in the defaults system and kDefaultsIsRotatingRectanglesKey that we'll need
to use whenever we want to access our default value. What else needs to change in order to correctly handle
the default value? Well, as I mentioned above we'll be checking the default value in the drawRect: method so
that we know whether or not to rotate the rectangles.

5. Add this code before if ( isRotatingRectangles ) in the drawRect: method of projectNameView.m:

Checking here makes sure that if anybody changes the default value, even while we're running, we'll get the
correct value and change our drawing style in mid-stream. One other important concept for user defaults that
can be shared between different instances is that you need to update the defaults whenever the user changes a
value. In our sample, the only place that the user gets to change anything is in the configuration sheet so
we'll need to add a bit of code there.

6. Add the following code to the okSheetAction: method before we close the sheet in the projectNameView.m file:

// write out the current default so other instances of the saver pick up the change, too.
[[self defaults] setBool: isRotatingRectangles
forKey: kDefaultsIsRotatingRectanglesKey];
// update the disk so that the screen saver engine will pick up the correct values
[[self defaults] synchronize];

This code takes the latest version of the isRotatingRectangles variable and stores it in the screen saver's
defaults. At the end there is a call to synchronize, but why? The synchronize call is there so that the true
screen saver mode (invoked via the hot corner or system idle state) picks up the correct default value even if
the System Preferences application is still running. This is technically just an implementation detail, but
it turns out that while the Preview and Test modes share the in-memory defaults, due to the fact that they're
both instantiated from the System Preferences process, there is a separate process that handles the full
invocation of the screen saver module when your system has been idle for too long or you move the mouse into
the Activate Screen Saver hot corner. The screen saver instance loaded by this ScreenSaverEngine application
can only retrieve the latest defaults that have been written to disk and for screen saver modules that only
seems to happen at two times: when the user quits the System Preferences application and when the screen saver
explicitly calls synchronize.

For the purposes of this article, I've used a very generic scheme for the configuration sheet and its
defaults. In this scheme the defaults are only updated and synchronized once the user is done changing all of
the values. No changes are made to any defaults or screen saver state variables while the options sheet is
open and the defaults only get updated once per invocation of the configuration sheet rather than on each
control change. This style of configuration sheet also gives the user the option of canceling any changes
they've made to the settings. The configuration sheet is one way to interact with and control a screen saver
but screen savers can also react to a set of user input events much like any normal application. You can use
these events to enable and disable effects, toggle status displays, and even turn your screen saver into a
game! (Anybody remember Lunatic Fringe?) In the next section, we'll take a look at the methods you'll need
to override to catch these events and handle them in your screen saver.

Handling Events

The view system in Cocoa provides a rich set of user input events that we can tap into while a screen saver
is running. The implementation in the ScreenSaverView superclass uses most of the events as a signal to wake
up the screen saver so the first thing we need to do is override that behavior in our subclass to prevent the
saver from waking prematurely. Note: don't override all of the events or you won't be able to wake the screen
saver at all!

1. Override the key handling code by adding the following implementations of methods inherited from the
NSResponder class to projectView.m:

These methods simply pass all keyDown and keyUp events up to the superclass for it to handle. If, for
example, we don't want keyDown events to wake the screen saver we can change the implementation of keyDown: by
commenting out the call to super. However, we'll want to handle some keys ourselves and let the superclass
handle others.

1. In that case, we can change the keyDown: code to look more like this:

// handle any necessary keyDown events here and pass the rest on to the superclass
if ( [[theEvent charactersIgnoringModifiers]
isEqualTo: @"g"] ) {
// the user pressed the "g" key so draw the next 50 rectangles in grayscale
grayscaleRectanglesLeft += 50;
// note that the "g" key will no longer wake up the screen saver
} else {
[super keyDown: theEvent];
}

We'll also need to add code to declare grayscaleRectanglesLeft and check for its value while drawing so
make the following additional changes.

2. Add the declaration for grayscaleRectanglesLeft to projectNameView.h:

If you run the new version of the screen saver, you'll find that while all the other keys still wake up the
saver, the "g" key causes the saver to draw a series of rectangles in shades of gray instead of the usual
random colors. You can further modify keyDown: to intercept and handle other keys to do whatever you like but
there are some useful keys, most notably the function and arrow keys, that don't have simple character
equivalents. The trick is knowing how to interpret the result of charactersIgnoringModifiers for these keys.
The short answer is, you don't. The longer answer is that you'll need to extract the unichar value of the
character before comparing it to one of the constants like NSUpArrowFunctionKey. Change the implementation of
keyDown: again to look like the following code to see how to handle arrow keys as well.

Note that we're still handling "g" and passing other non-arrow events on to the superclass. With these
changes, the arrow keys now control how wide and tall (or narrow and short) the random rectangles tend to be.
Your screen saver might also need to handle the modifier key event which covers SHIFT, COMMAND, OPTION, and
CONTROL. Unlike the other keys, the modifier keys don't send a keyDown: message. Instead, your screen saver
needs to override the flagsChanged: method in your projectNameView.m to catch modifier key events.

With those small changes, your screen saver should now temporarily reverse the sense of the
isRotatingRectangles flag as long as you hold down the OPTION key. The only other events that you're likely to
want to capture are mouse events and they're just as easy. NSResponder provides a series of methods you can
override for mouse movement, dragging, clicks, and even scroll wheel activity. Depending on your
requirements, you might not need to trap and deal with every event right when it happens. If your screen
saver merely adjusts its drawing based on the current mouse position, you may be able to simply query the
system inside drawRect: using the NSEvent class method mouseLocation which returns the current mouse position
in global coordinates.

Note: watch out for multiple monitors and Preview mode here. Don't assume that the mouse position will
remain within the bounds of your view or even the main display. Users with multiple monitors will be
disappointed if your screen saver fails or misbehaves just because they've plugged in another display.

Different Drawing APIs

Up until this point, we've been using the drawing functionality provided by Quartz 2D and built into Cocoa.
While NSBezierPath does provide for easy, accessible drawing code, there are times when you need to do more
that draw lines, rectangles, curves, etc. This is often the case if you already possess some drawing code
that you're merely trying to wrap up in a screen saver. The first step beyond NSBezierPath is to use NSImage
to load and draw images during drawRect:, perhaps to provide a backdrop or for use as sprites in your
animation. NSImage is very simple to use and provides support for many common image types. In a typical
screen saver, you'll store your images in the Resources folder within the screen saver bundle and load them in
with code similar to the following:

In a real screen saver, you'd most likely want to load any images you need in startAnimation and keep them
around until stopAnimation or even projectNameView's dealloc method since it is very inefficient to load the
image in for every frame. You may even want to provide an accessor method like the following that handles
loading an image the first time it is needed.

As you move beyond Cocoa, you may require some bit of functionality from Quartz 2D that Cocoa doesn't
expose. Not to worry. Cocoa makes it easy to drop down and call Quartz 2D directly. When the system calls
your screen saver's drawRect: method, the drawing environment is already set up for you. You can draw
immediately with Cocoa as we've seen or you can ask Cocoa for the CGContextRef that you'll need to make Quartz
2D drawing calls. The code to retrieve the context looks like this:

This code draws a large red circle with a blue outline in the middle of the screen as you can see from
Figure 6. If you add this code after the rotating rectangle code from our basic screen saver and
isRotatingRectangles is true, you'll notice that the circles don't draw in the middle of the screen any more.
Why not, you ask? Because the Cocoa drawing APIs are layered on top of Quartz 2D, that means that they share
the same drawing environment, including the underlying transformation matrix. When the Cocoa code uses
NSAffineTransform to rotate the drawing of the rectangle, that rotation also applies to any subsequent Quartz
2D code. The moral of the story is to pay attention to any changes you make to the Cocoa or Quartz 2D drawing
state since one affects the other.

Summary

Figure 6: Saving the screen with Quartz 2D

Well, that brings us to the end of our series on screen savers. In this article we've added a
configuration sheet to our screen saver, stored and reloaded the resulting default value, learned how to
handle keyboard and mouse events, and investigated NSImage and Quartz 2D. Now you've got the information in
hand to go out and write some really cool screen savers.

David Hill is a freelance writer living in College Station, Texas. In a former life, he worked
in Apple's Developer Technical Support group helping developers print, draw, and write games. In his free time
he dabbles in screen savers and other esoteric topics.

Community Search:

MacTech Search:

Software Updates via MacUpdate

VirtualBox 5.1.30 - x86 virtualization s...

VirtualBox is a family of powerful x86 virtualization products for enterprise as well as home use. Not only is VirtualBox an extremely feature rich, high performance product for enterprise customers... Read more

ScreenFlow 7.1.1 - Create screen recordi...

ScreenFlow is powerful, easy-to-use screencasting software for the Mac. With ScreenFlow you can record the contents of your entire monitor while also capturing your video camera, microphone and your... Read more

Adobe Flash Player 27.0.0.170 - Plug-in...

Adobe Flash Player is a cross-platform, browser-based application runtime that provides uncompromised viewing of expressive applications, content, and videos across browsers and operating systems.... Read more

TotalFinder is a universally acclaimed navigational companion for your Mac. Enhance your Mac's Finder with features so smart and convenient, you won't believe you ever lived without them.
Features... Read more

Backblaze 5.1.0.134 - Online backup serv...

Backblaze is an online backup service designed from the ground-up for the Mac. With unlimited storage available for $5 per month, as well as a free 15-day trial, peace of mind is within reach with... Read more

Postbox 5.0.20 - Powerful and flexible e...

Postbox is a new email application that helps you organize your work life and get stuff done. It has all the elegance and simplicity of Apple Mail, but with more power and flexibility to manage even... Read more

QuarkXPress 2017 is the new version that raises the bar for design and productivity. With non-destructive graphics and image editing directly within your layout, you no longer have to choose between... Read more

Backblaze 5.1.0.134 - Online backup serv...

Backblaze is an online backup service designed from the ground-up for the Mac. With unlimited storage available for $5 per month, as well as a free 15-day trial, peace of mind is within reach with... Read more

Latest Forum Discussions

Color Ballz is an addictive new arcade title from Ketchapp Studios. It takes old school mechanics from games like Brickles and puts a fun twist on it. Your job? To catch balls with a paddle and send them back into a chute to be carried back to... | Read more »

Q&A: A-33 Studio explains why Combat...

When it comes to mobile FPS, it’s often tricky to get the fundamentals right on a platform lacking a physical controller, large display and hefty RAM. With Combat Squad: Project Wednesday, A-33 Studio bravely took on the challenge of making a... | Read more »

Taichi Panda 3: Dragon Hunter guide - ti...

Taichi Panda 3: Dragon Hunter launched this week to players all over the world. It's a beautiful mobile MMORPG that blends elements of Eastern and Western fantasy. It reminds us of a mix between World of Warcraft and Jade Empire.
MMO's can have a... | Read more »

The best new games we played this week -...

Phew. It has been a week, but now it's time to relax, put your feet up, and enjoy some brand new mobile games. It was a bit of slow week, but there's still plenty of new titles to add to your collection. Here are four of our favorites.
[Read... | Read more »

Into the Dead 2 is an endless gunner, of sorts, with a lot of grit and satisfying gunplay behind it. The game looks amazing, and tells an effective story to boot. Plus, it has some quality voice acting behind it to really bring the story to life... | Read more »

Smash Up - The Card Game (Games)

Smash Up - The Card Game 1.0.7
Device: iOS Universal
Category: Games
Price: $4.99, Version: 1.0.7 (iTunes)
Description:
***“It’s a goofy theme with fun art and high replayability, but beneath that veneer of casual play is a great... | Read more »

Dive in to Combat Squad if you’re lookin...

Earlier this year, A-33 Studio made the leap from developing Counter Strike Online to launching its very own FPS for the mobile. Combat Squad: Project Wednesday pits your team of mercs against the world in multiplayer death matches, so if you’re on... | Read more »

The Talos Principle comes to mobile -- h...

Sci-fi puzzler The Talos Principle arrived on the App Store this week. The game, which tells the tale of a newly awokened robot, is a philosophical story of what it means to be human, while the player explores over 100 different areas in a... | Read more »

Bare Bones Software has announced the release and immediate availability of BBEdit 12.0, a significant upgrade to its professional strength text and code editor.
BBEdit 12 introduces a new foundation... Read more

Yale Announces Availability of Apple HomeKit-...

Yale Locks & Hardware has announced that Apple HomeKit support for its Assure Lock family is available this month. The new Yale iM1 Network Module, which provides support for the Apple Home app... Read more

Clearance 2016 13″ MacBook Pros, refurbished,...

Apple has Certified Refurbished 2016 13″ MacBook Pros available starting at $1189. An Apple one-year warranty is included with each model, and shipping is free:
– 13″ 2.9GHz/512GB Touch Bar Gray... Read more

Amazon has the Silver 13″ 3.1GHz/256GB MacBook Pro (MPXX2LL/A) on sale for $1699 including free shipping. Their price is $100 off MSRP.
Read more

12″ MacBook available for $1099 with Apple re...

Apple has Certified Refurbished 2017 12″ Retina MacBooks available for $200-$240 off the cost of new models. Apple will include a standard one-year warranty with each MacBook, and shipping is free.... Read more

MacTech is a registered trademark of Xplain Corporation. Xplain, "The journal of Apple technology", Apple Expo, Explain It, MacDev, MacDev-1, THINK Reference, NetProfessional, Apple Expo, MacTech Central, MacTech Domains, MacNews, MacForge, and the MacTutorMan are trademarks or service marks of Xplain Corporation. Sprocket is a registered trademark of eSprocket Corporation. Other trademarks and copyrights appearing in this printing or software remain the property of their respective holders. Not responsible for typographical errors.

All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.