TweetFollow Us on Twitter

Save Our Screens 102

Volume Number: 21 (2005)
Issue Number: 7
Column Tag: Programming

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.

1. Change the code in projectNameView.h to this:

   #import <ScreenSaver/ScreenSaver.h>
   #define kConfigSheetNIB @"ConfigureSheet"

   @interface projectNameView : ScreenSaverView {
      IBOutlet NSWindow* configureSheet;
      IBOutlet id shouldRotateCheckbox;
      BOOL isRotatingRectangles;
      BOOL mDrawBackground;
   }
   - (IBAction) cancelSheetAction: (id) sender;
   - (IBAction) okSheetAction: (id) sender;
   @end

2. Change the hasConfigureSheet method to return YES.

3. Change the code in the configureSheet method to this:

if ( configureSheet == nil ) {
      [NSBundle loadNibNamed: kConfigSheetNIB owner: self];
   }
   [shouldRotateCheckbox setState: isRotatingRectangles];
   return configureSheet;

4. Conditionalize the rotation code in drawRect: like so:

   if ( isRotatingRectangles ) {
      NSAffineTransform* rotation =
                  [NSAffineTransform transform];
      float degrees = SSRandomFloatBetween( 0.0, 360.0 );
      [rotation rotateByDegrees: degrees];
      [rotation concat];
   }

5. Add the following line to initWithFrame:isPreview: before returning self:

isRotatingRectangles = YES;

6. Add a cancelSheetAction: method that should look like:

   - (IBAction) cancelSheetAction: (id) sender {
      //  close the sheet without saving the settings
      [NSApp endSheet: configureSheet];
   }

7. Add an okSheetAction: method that should look like:

   - (IBAction) okSheetAction: (id) sender {
      //  record the settings in the configuration sheet
      isRotatingRectangles = [shouldRotateCheckbox state];
      [NSApp endSheet: configureSheet];
   }

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.

1. Add the following define statements to projectNameView.h:

#define kDefaultsModuleName
                  @"projectName_Random_Rectangles"
   #define kDefaultsIsRotatingRectanglesKey
                  @"isRotatingRectanglesDefault"
   #define kDefaultsYesString            @"YES"

2. Declare a new helper method in projectNameView.h:

   - (ScreenSaverDefaults*) defaults;

3. Add the new helper method definition to projectNameView.m:

   - (ScreenSaverDefaults*) defaults {
      //  there's no need to cache this value so we'll make a handy accessor
      return [ScreenSaverDefaults defaultsForModuleWithName:
                  kDefaultsModuleName];
   }

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:

isRotatingRectangles = [[self defaults]
                  boolForKey: kDefaultsIsRotatingRectanglesKey];

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:

   - (void)keyDown:(NSEvent *)theEvent {
      //  handle any necessary keyDown events here and pass the rest on to the superclass
      [super keyDown: theEvent];
   }
   - (void)keyUp:(NSEvent *)theEvent {
      //  handle any necessary keyUp events here and pass the rest on to the superclass
      [super keyUp: theEvent];
   }

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:

   int grayscaleRectanglesLeft;

3. Give grayscaleRectanglesLeft an initial value in initWithFrame:isPreview:

grayscaleRectanglesLeft = 0;

4. Change the color setup in drawRect: from this:

float red = SSRandomFloatBetween( 0.0, 1.0 );
      float green = SSRandomFloatBetween( 0.0, 1.0 );
      float blue = SSRandomFloatBetween( 0.0, 1.0 );
      float alpha = SSRandomFloatBetween( 0.0, 1.0 );
      [[NSColor colorWithDeviceRed: red
                  green: green blue: blue alpha: alpha] set];
   to this:

   if ( grayscaleRectanglesLeft ) {
         float white = SSRandomFloatBetween( 0.0, 1.0 );
         float alpha = SSRandomFloatBetween( 0.0, 1.0 );

         [[NSColor colorWithDeviceWhite: white
                  alpha: alpha] set];
         grayscaleRectanglesLeft--;
      } else {
         float red = SSRandomFloatBetween( 0.0, 1.0 );
         float green = SSRandomFloatBetween( 0.0, 1.0 );
         float blue = SSRandomFloatBetween( 0.0, 1.0 );
         float alpha = SSRandomFloatBetween( 0.0, 1.0 );
         [[NSColor colorWithDeviceRed: red
                  green: green blue: blue alpha: alpha] set];
      }

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.

1. Change keyDown: to look like this:

   NSString* eventCharacters =
         [theEvent charactersIgnoringModifiers];
   unichar firstCharacter =
         [eventCharacters characterAtIndex: 0];
   //  handle any necessary keyDown events here and pass the rest on to the superclass
   if ( [eventCharacters isEqualTo: @"g"] ) {
      //  the user pressed the "g" key so draw the next 50 rectangles in grayscale
      grayscaleRectanglesLeft += 50;
   } else if ( firstCharacter == NSUpArrowFunctionKey ) {
      //  the user pressed the up arrow so make the rectangles taller
      rectangleHeightMultiplier *= 2;
   } else if ( firstCharacter == NSDownArrowFunctionKey ) {
      //  the user pressed the down arrow so make the rectangles shorter
      rectangleHeightMultiplier /= 2;
   } else if ( firstCharacter == NSRightArrowFunctionKey ) {
      //  the user pressed the right arrow so make the rectangles wider
      rectangleWidthMultiplier *= 2;
   } else if ( firstCharacter == NSLeftArrowFunctionKey ) {
      //  the user pressed the left arrow so make the rectangles narrower
      rectangleWidthMultiplier /= 2;
   } else {
      [super keyDown: theEvent];
   }

2. Add the declarations to projectNameView.h

   float rectangleHeightMultiplier;
   float rectangleWidthMultiplier;

3. Add some initial values to initWithFrame:isPreview: in projectNameView.m

//  set the starting multipliers to 1.0
   rectangleHeightMultiplier = 1.0;
   rectangleWidthMultiplier = 1.0;

4. Change the rectToFill line in drawRect: to this:

NSRect rectToFill =
         NSMakeRect( startingX, startingY,
               width * rectangleWidthMultiplier,
               height * rectangleHeightMultiplier );

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.

1. Add the following method to projectNameView.m

   - (void)flagsChanged:(NSEvent *)theEvent {
      //  toggle the current sense of the isRotatingRectangles flag
      //  while the user is holding down the OPTION key
      if ( [theEvent modifierFlags] & NSAlternateKeyMask ) {
         reverseSenseOfIsRotatingRectangles = YES;
      } else {
         reverseSenseOfIsRotatingRectangles = NO;
      }
   }

2. Add the following declaration to the projectNameView.h file:

BOOL reverseSenseOfIsRotatingRectangles;

3. Set the initial value in initWithFrame:isPreview:

//  start out with isRotatingRectangles meaning what it says
   reverseSenseOfIsRotatingRectangles = NO;

4. Check the value in drawRect: by replacing the rotate code with:

BOOL rotateThisRectangle = isRotatingRectangles;
   if ( reverseSenseOfIsRotatingRectangles ) {
      rotateThisRectangle = !rotateThisRectangle;
   }
   if ( rotateThisRectangle ) {
      NSAffineTransform* rotation =
            [NSAffineTransform transform];
      float degrees = SSRandomFloatBetween( 0.0, 360.0 );
      [rotation rotateByDegrees: degrees];
      [rotation concat];
   }

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:

   NSBundle* saverBundle =
            [NSBundle bundleForClass: [self class]];
   NSString* imagePath = [saverBundle
            pathForResource: @"test image" ofType: @"JPG"];
   NSImage* image =
         [[NSImage alloc] initWithContentsOfFile: imagePath];

Drawing the image such that it fills the entire view is simply a matter of calling the following NSImage method:

   NSSize imageSize = [image size];
   NSRect imageRect = NSMakeRect( 0, 0,
         imageSize.width, imageSize.height );
   [image drawInRect: viewBounds fromRect: imageRect
         operation: NSCompositeCopy fraction: 1.0];
   [image release];

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.

   - (NSImage*) backgroundImage {
      if ( backgroundImage == nil ) {
         NSBundle* saverBundle =
               [NSBundle bundleForClass: [self class]];
         NSString* imagePath = [saverBundle
               pathForResource: @"test image" ofType: @"JPG"];
         backgroundImage = [[NSImage alloc]
               initWithContentsOfFile: imagePath];
      }
      return backgroundImage;
   }

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:

   CGContextRef context =
         [[NSGraphicsContext currentContext] graphicsPort];

Add that line and the following code to the end of drawRect: to draw with Quartz 2D:

   CGContextSetRGBFillColor( context, 1.0, 0.0, 0.0, 1.0 );
   CGContextSetRGBStrokeColor(context, 0.0, 0.0, 1.0, 1.0 );
   CGContextBeginPath( context );
   CGContextAddArc( context, NSMidX( viewBounds ),
         NSMidY( viewBounds ), NSHeight( viewBounds ) / 4.0,
         0.0, 2 * 3.14159, 0 );
   CGContextClosePath( context );
   CGContextDrawPath( context, kCGPathFillStroke );
   CGContextFlush( context );

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.

 
AAPL
$106.95
Apple Inc.
-0.39
MSFT
$46.09
Microsoft Corpora
-0.54
GOOG
$550.68
Google Inc.
+1.35

MacTech Search:
Community Search:

Software Updates via MacUpdate

Cocktail 8.0.1 - General maintenance and...
Cocktail is a general purpose utility for OS X that lets you clean, repair and optimize your Mac. It is a powerful digital toolset that helps hundreds of thousands of Mac users around the world get... Read more
LibreOffice 4.3.3.2 - Free Open Source o...
LibreOffice is an office suite (word processor, spreadsheet, presentations, drawing tool) compatible with other major office suites. The Document Foundation is coordinating development and... Read more
VMware Fusion 7.0.1 - Run Windows apps a...
VMware Fusion allows you to create a Virtual Machine on your Mac and run Windows (including Windows 8.1) and Windows software on your Mac. Run your favorite Windows applications alongside Mac... Read more
OneNote 15.3.2 - Free digital notebook f...
OneNote is your very own digital notebook. With OneNote, you can capture that flash of genius, that moment of inspiration, or that list of errands that's too important to forget. Whether you're at... Read more
Audio Hijack Pro 2.11.4 - Record and enh...
Audio Hijack Pro drastically changes the way you use audio on your computer, giving you the freedom to listen to audio when you want and how you want. Record and enhance any audio with Audio Hijack... Read more
Iridient Developer 3.0.0 beta 3 - Powerf...
Iridient Developer (was RAW Developer) is a powerful image conversion application designed specifically for OS X. Iridient Developer gives advanced photographers total control over every aspect of... Read more
TextWrangler 4.5.11 - Free general purpo...
TextWrangler is the powerful general purpose text editor, and Unix and server administrator's tool. Oh, and also, like the best things in life, it's free. TextWrangler is the "little brother" to... Read more
NeoFinder 6.6 - Catalog your external me...
NeoFinder (formerly CDFinder) rapidly organizes your data, either on external or internal disks, or any other volumes. It catalogs all your data, so you stay in control of your data archive or disk... Read more
Chromium 38.0.2125.111 - Fast and stable...
Chromium is an open-source browser project that aims to build a safer, faster, and more stable way for all Internet users to experience the web. FreeSMUG-Free OpenSource Mac User Group build is... Read more
Default Folder X 4.6.11 - Enhances Open...
Default Folder X attaches a toolbar to the right side of the Open and Save dialogs in any OS X-native application. The toolbar gives you fast access to various folders and commands. You just click... Read more

Latest Forum Discussions

See All

SAS: Zombie Assault 4 Review
SAS: Zombie Assault 4 Review By Jennifer Allen on October 30th, 2014 Our Rating: :: FLAWED SHOOTERUniversal App - Designed for iPhone and iPad Shoot everything that moves in this fun, if flawed, twin-stick shooter.   | Read more »
Naailde the Witch Review
Naailde the Witch Review By Amy Solomon on October 30th, 2014 Our Rating: :: PITCH-PERFECT STORYTELLINGUniversal App - Designed for iPhone and iPad Marvelous storytelling, narration, and moving illustrations make this storybook... | Read more »
1st & Goal Review
1st & Goal Review By Andrew Fisher on October 30th, 2014 Our Rating: :: FOR THE D&D LOVING QBUniversal App - Designed for iPhone and iPad 1st & Goal is a board gamer’s football game, a football fan’s board game, and... | Read more »
French Developer Pated Unveils Seashine
French Developer Pated Unveils Seashine Posted by Ellis Spice on October 30th, 2014 [ permalink ] French one-man studio Pated has unveiled Seashine, “a poetic journey into the abyss.” Players take on the role of a jellyfish strugglin | Read more »
Agents of Storm: Tips, Tricks, and Strat...
Calling all agents: Would you like to see what we thought of this rather pretty base builder? Check out our Agents of Storm review! Have you downloaded Agents of Storm, been bowled over by the graphics, and aren’t quite sure what to do next? Never... | Read more »
Any.DO 2.0 Hopes to Help Manage Producti...
Any.DO 2.0 Hopes to Help Manage Productivity Posted by Ellis Spice on October 30th, 2014 [ permalink ] iPhone App - Designed for the iPhone, compatible with the iPad | Read more »
Base Busters Review
Base Busters Review By Jennifer Allen on October 30th, 2014 Our Rating: :: FUN BUT RESTRICTED MIXUniversal App - Designed for iPhone and iPad Mixing up two forms of tower defense gaming and collectible cards, Base Busters is a fun... | Read more »
Sumptus - Personal Expense Tracker in Yo...
Sumptus - Personal Expense Tracker in Your Pocket 1.0 Device: iOS iPhone Category: Finance Price: $3.99, Version: 1.0 (iTunes) Description: Sumptus - a personal finance assistant in your pocket. It’s an ultimate expense tracker... | Read more »
Meat Factory (Games)
Meat Factory 0.1 Device: iOS Universal Category: Games Price: $.99, Version: 0.1 (iTunes) Description: Meat factory harks back to the old days of the game and watch. Easy to pick up and play, but hard to score big. With cute visuals... | Read more »
Medford Asylum: Paranormal Case - Hidden...
Medford Asylum: Paranormal Case - Hidden Object Adventure (Full) 1.0 Device: iOS Universal Category: Games Price: $4.99, Version: 1.0 (iTunes) Description: Uncover the mystery of Medford City asylum! | Read more »

Price Scanner via MacPrices.net

Apple Regains Momentum As Windows Stutters An...
The latest smartphone sales data from Kantar Worldpanel ComTech, for the three months to March 2014, shows Apple performing strongly in the first quarter of the year, with sales bouncing back in... Read more
Worldwide Smartphone Shipments Increase 25.2%...
New smartphone releases and an increased emphasis on emerging markets drove global smartphone shipments above 300 million units for the second consecutive quarter, according to preliminary data from... Read more
Apple now offering refurbished 2014 15-inch M...
The Apple Store is now offering Apple Certified Refurbished 2014 15″ Retina MacBook Pros for up to $400 off the cost of new models. An Apple one-year warranty is included with each model, and... Read more
Apple drops prices on refurbished 2013 Retina...
The Apple Store has dropped prices on 2013 Apple Certified Refurbished 13″ and 15″ Retina MacBook Pros, with Retina models now available starting at $999. Apple’s one-year warranty is standard, and... Read more
New 2.8GHz Mac mini on sale for $949, save $5...
Abt Electronics has the new 2.8GHz Mac mini in stock and on sale for $949.05 including free shipping. Their price is $50 off MSRP, and it’s the lowest price available for this model from any reseller... Read more
Sale! 3.7GHz Quad Core Mac Pro available for...
 B&H Photo has the 3.7GHz Quad Core Mac Pro on sale for $2649 including free shipping plus NY sales tax only. Their price is $350 off MSRP, and it’s the lowest price for this model from any... Read more
Mujjo Steps Up The Game With Refined Touchscr...
Netherlands based Mujjo have just launched their Refined Touchscreen Gloves, stepping up their game. The gloves feature a updated elegant design that takes these knitted gloves to the next level. A... Read more
Sale! Preorder the new 27-inch 5K iMac for $2...
 Abt Electronics has the new 27″ 3.5GHz 5K iMac on sale and available for preorder for $2374.05 including free shipping. Their price is $125 off MSRP, and it’s the lowest price available for this... Read more
Simplex Solutions Inc. Brings Secure Web Surf...
New York based Simplex Solutions Inc. has announced the release and immediate availability of Private Browser 1.0, its revolutionary new secure web browser developed for iPhone, iPad and iPod touch... Read more
Save up to $180 off MSRP with an Apple refurb...
The Apple Store has Apple Certified Refurbished 2014 MacBook Airs available for up to $180 off the cost of new models. An Apple one-year warranty is included with each MacBook, and shipping is free.... Read more

Jobs Board

*Apple* Solutions Consultant (ASC) - Apple I...
…important role that the ASC serves is that of providing an excellent Apple Customer Experience. Responsibilities include: * Promoting Apple products and solutions Read more
*Apple* Solutions Consultant (ASC) - Apple I...
…important role that the ASC serves is that of providing an excellent Apple Customer Experience. Responsibilities include: * Promoting Apple products and solutions Read more
*Apple* Solutions Consultant (ASC) - Apple I...
…important role that the ASC serves is that of providing an excellent Apple Customer Experience. Responsibilities include: * Promoting Apple products and solutions Read more
*Apple* Solutions Consultant - Apple Inc. (U...
…important role that the ASC serves is that of providing an excellent Apple Customer Experience. Responsibilities include: * Promoting Apple products and solutions Read more
*Apple* Solutions Consultant - Apple Inc. (U...
…important role that the ASC serves is that of providing an excellent Apple Customer Experience. Responsibilities include: * Promoting Apple products and solutions Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.