TweetFollow Us on Twitter

July 02 Mac OS X

Volume Number: 18 (2002)
Issue Number: 7
Column Tag: Mac OS X

Writing Plug-Ins for Cocoa: iPhoto to PhotoToWeb Exporter Plug-In

by Andrew Stone

After you've been working with Cocoa for a while, you'll come to appreciate the truth behind this statement: "If it's hard to do, then you're not doing it right”. By this I mean any coding solution that involves convoluted logic, going beneath the API or using undocumented methods, is probably not the right approach.

However, sometimes delving into secret API is the fastest way to get something done. Caveat Emptor: your program may break if the underlying methods change! That's why I usually admonish against using private, hidden methods.

This article will explain how to peek into the guts of an Objective C Cocoa application, iPhoto, and write a PlugIn that allows users to export their photos to the Stone Studio digital photography to web application PhotoToWeb®. iPhoto offers only limited web production facilities, whereas PhotoToWeb offers a complete suite of web design functionality, including the ability to create linked multi-level photo websites.

My design objectives were to keep the PlugIn small and very simple, including using the existing PhotoToWeb AppleScript API. This way, there are no code or file format dependencies in the PlugIn. Instead, we simply build a script that asks PhotoToWeb to build a new album with the exported photos. Also, instead of duplicating the user's photos, we just export the full path names since PhotoToWeb can build albums out of any images anywhere on the user's disk or network.


Figure 1: iPhoto loads the PhotoToWeb export PlugIn via Share - Export

Class-Dump Is Your Friend

If you want to see the inner architecture of an Objective C application, you'll need class-dump, which you can download from:

http://www.omnigroup.com/~nygard/Projects/index.html

class-dump is a utility for examining the Objective-C segment of Mach-O files. It generates the @interface and @protocol declarations for classes, categories and protocols.

Class-dump is open source, and has been an ongoing project since the early NeXT (the grandfather of OS X) days. It has had several contributors, including Eric P. Scott, Steve Nygard, James McIlree, Tom Hageman, and Carl Lindberg. It's a unix executable and has no GUI. Running it without arguments gives you the options:

class-dump 2.1.5

Usage: class-dump [-a] [-A] [-e] [-R] [-C regex] [-r] [-s] [-S] executable-file
        -a  show instance variable offsets
        -A  show implementation addresses
        -e  expand structure (and union) definition whenever possible
        -R  recursively expand @protocol <>
        -C  only display classes matching regular expression
        -r  recursively expand frameworks and fixed VM shared libraries
        -s  convert STR to char *
        -S  sort protocols, classes, and methods

You can dump all the classes of an application, or just those matching some pattern. Since we're interested in the Export methods, we'll run it like this:

class-dump -C Export /Applications/iPhoto.app/Contents/MacOSX/iPhoto
which outputs:
@protocol ExportImageProtocol
- directoryPath;
- (void)cancelExportBeforeBeginning;
- (void)cancelExport;
- (void)startExport;
- (void)clickExport;
- (void)disableControls;
- (void)enableControls;
- window;
- getImageDictionaryAtIndex:(unsigned int)fp16;
- getThumbPathAtIndex:(unsigned int)fp16;
- getImagePathAtIndex:(unsigned int)fp16;
- (char)imageIsPortraitAtIndex:(unsigned int)fp16;
- imageSelectionArray;
- (unsigned int)imageCount;
@end
@interface ExportMgr:NSObject <ExportImageProtocol>
{
    ArchiveDocument *mDocument;
    NSMutableArray *mExporters;
    KeyMgr *mSelection;
    ExportController *mExportController;
}
+ exportMgr;
- (void)releasePlugins;
- (void)setExportController:fp12;
- exportController;
- (void)setDocument:fp12;
- document;
- (void)updateDocumentSelection;
- (unsigned int)count;
- recAtIndex:(unsigned int)fp12;
- (void)scanForExporters;
- (unsigned int)imageCount;
- imageSelectionArray;
- (char)imageIsPortraitAtIndex:(unsigned int)fp12;
- getImagePathAtIndex:(unsigned int)fp12;
- getThumbPathAtIndex:(unsigned int)fp12;
- getImageDictionaryAtIndex:(unsigned int)fp12;
- getImageRecAtIndex:(unsigned int)fp12;
- (void)enableControls;
- (void)disableControls;
- window;
- (void)clickExport;
- (void)startExport;
- (void)cancelExport;
- (void)cancelExportBeforeBeginning;
- directoryPath;
- (void)_copySelection:fp12;
@end

So all we need to do to write a PlugIn is implement the methods defined in the ExportImageProtocol, add a few keys to the Info.plist file, and provide an interface file with any special controls. You can open some of the existing PlugIns inside iPhoto by control-clicking iPhoto in Finder, looking inside the Contents/PlugIns folder, and control-clicking one of the Exporter bundles.

Of course, without documentation or source, we'll have to guess at what certain methods do. ExportMgr has a class method "+exportMgr” which provides us with the ExportMgr instance. Reading over the ExportMgr class methods, we see methods for retrieving a set of photos one at a time, and getting information about that photo.

Making PlugIns

To make a loadable bundle (which is exactly what a PlugIn is), launch Project Builder and choose New Project..., select "Bundle”, provide a path, and save. Create a new empty nib in InterfaceBuilder, add a CustomView (we really don't need a window, just a view containing our interface elements), and save it into your bundle project.

We only need one class, "PhotoToWebExport”, but we also need to include the protocol as defined in ExportPluginProtocol, and declare that our object conforms to this protocol:

/* PhotoToWebExport.h */
#import <Cocoa/Cocoa.h>
@protocol ExportPluginProtocol
- (void)cancelExport;
- (void)unlockProgress;
- (void)lockProgress;
- (void *)progress;
- (void)performExport:fp16;
- (void)startExport:fp16;
- defaultDirectory;
- defaultFileName;
- requiredFileType;
- (void)viewWillBeDeactivated;
- (void)viewWillBeActivated;
- lastView;
- firstView;
- settingsView;
- initWithExportImageObj:fp16;
@end
#define IS_NULL(s) (!s || [s isEqualToString:@””])
#define NOT_NULL(s) (s && ![s isEqualToString:@””])
@interface PhotoToWebExport : NSObject <ExportPluginProtocol>
{
    IBOutlet id settingsView;
    IBOutlet id titleField;
    IBOutlet id imageView;
    IBOutlet id actionMatrix;
    IBOutlet id finishView;
    IBOutlet id firstView;
    id exportManager;
    ComponentInstance myComponent;
    NSString *_lastDirectoryPath;
}
- (void)downloadLatest:(id)sender;
@end
#import "PhotoToWebExport.h”
#import <Carbon/Carbon.h>
@implementation PhotoToWebExport
// Designated initializer 
// we'll grab the ExportMgr instance - although you could
// just use the class method + [ExportMgr exportMgr]
- initWithExportImageObj:fp16 {    
    self = [super init];
    exportManager = fp16;
   // we'll use AppleScript to talk to PhotoToWeb:
    myComponent = OpenDefaultComponent(kOSAComponentType, kOSAGenericScriptingComponentSubtype);
    return self;
}
// if there are photos to export, create and run an AppleScript
// to load the photos into a new PhotoToWeb album:
- (void)doExport:fp16 {
    BOOL good = YES;
    if ([exportManager imageCount] > 0) 
        [self runScript:[self scriptCommand]];
    else {
        NSRunAlertPanel(@”No images selected”,@”No images to export”,@”OK”, nil, nil);
        good = NO;
    }
    
    // make panel disappear - might be other way:
    [[exportManager exportController] cancel:self];
   
   // Bring the album to the front so user can make web site or thumbnails etc...
    if (good) [[NSWorkspace sharedWorkspace] openFile:[exportManager directoryPath] 
    withApplication:@”PhotoToWeb”];
}
// this is the guts of the plugin 
// We'll create the AppleScript script necessary to export
// the selected photos, and attempt to grab as much information
// as the users have provided in iPhoto - such as comments, rotation
// crop information, title, etc.
- (NSString *)scriptCommand {
    NSMutableString *s = [NSMutableString stringWithCapacity:2000];
    unsigned i, c = [exportManager imageCount];
    
    // for next time the user runs it, we'll have their preferred directory:
    [_lastDirectoryPath release];
    _lastDirectoryPath = [[[exportManager directoryPath] stringByDeletingLastPathComponent] 
    copyWithZone:[self zone]];
    [s appendString:@”tell application \”PhotoToWeb\”\r”];
    [s appendString:@”\tmake new album at before front album\r”];
    [s appendString:@”\ttell the front album\r”];
// this will prevent the AppleScript from timing out if there is a huge number of photos:
    [s appendString:@”\t\tignoring application responses\r”];
    
    for (i = 0; i < c; i++) {
        NSString *path = [exportManager getImagePathAtIndex:i];
        NSDictionary *dict = [exportManager getImageDictionaryAtIndex:i];
        int rotation = [[dict objectForKey:@”Rotation”] intValue];
        NSString *title = [dict objectForKey:@”Caption”];
        NSString *story = [dict objectForKey:@”Annotation”]; // could be nil
        NSDate *archiveDate = [dict objectForKey:@”ArchiveDate”];
        NSString *date = [[NSCalendarDate dateWithTimeIntervalSinceReferenceDate:
        [archiveDate timeIntervalSinceReferenceDate]] descriptionWithCalendarFormat:@”Archived: 
        %e %B %Y”];
        [s appendString:[NSString stringWithFormat:@”\t\t\tadd photos in \”%@\”\r”, path]];
        if (rotation != 0) {
                [s appendString:[NSString stringWithFormat:@”\t\t\tset the photorotation of last photo 
                to %d\r”, rotation]];
        }
        if (NOT_NULL(title)) 
            [s appendString:[NSString stringWithFormat:@”\t\t\tset the title of last photo to 
            \”%@\”\r”, title]];
        if (NOT_NULL(story)) {
            [s appendString:[NSString stringWithFormat:@”\t\t\tset the text contents of last photo to 
            \”%@\”\r”, NOT_NULL(date)? [NSString stringWithFormat: @”%@ %@”,story,date]:story]];
        } else if (NOT_NULL(date))
            [s appendString:[NSString stringWithFormat:@”\t\t\tset the text contents of last photo to 
            \”%@\”\r”, date]];
    
    }
    [s appendString:@”\t\tend ignoring\r”];
    [s appendString:[NSString stringWithFormat:@”\t\tsave in \”%@\”\r”,[exportManager directoryPath]]];
    [s appendString:@”\tend tell\r”];
    [s appendString:@”end tell\r”];
    return s;
}
- (void)performExport:fp16  {
    // not sure how this differs from startExport but doesn't seem to be needed
}
- (void)startExport:fp16 {
    [self doExport:fp16];
}
// if the plugin has been run before, use the last saved path
// Otherwise choose a standard location:
- (id) defaultDirectory {
    if (NOT_NULL(_lastDirectoryPath)) return _lastDirectoryPath;
   return [NSHomeDirectory() stringByAppendingPathComponent:@”Documents”];
}
// here's a nice way to guarantee uniqueness of the album name
// concatenate the year-month-day-seconds:
- defaultFileName {
    return [NSString stringWithFormat:@”%@-%d”,[[NSCalendarDate calendarDate] 
    descriptionWithCalendarFormat:@”%Y-%m-%d”], [NSDate timeIntervalSinceReferenceDate]];
}
// this is PhotoToWeb's single Album file type:
- requiredFileType {
    return @”album”;
}
- lastView {
   return finishView;
}
- firstView {
   return firstView;
}
- settingsView {
   return settingsView;
}
// An action method to provide user with latest version of PhotoToWeb:
- (void)downloadLatest:(id)sender {
            [[NSWorkspace sharedWorkspace] openURL:[NSURL
            URLWithString:@”http://www.stone.com/NewDownload.html#PHOTO”]];
}
// I can't think of anything to do in these two methods
// In a more complex plugin, you might set default values:
- (void)viewWillBeDeactivated {
}
- (void)viewWillBeActivated {
}
// these next 4 methods are not yet flushed out because I haven't figured
// out exactly what to do with them - if you do, email me!
- (void)cancelExport {
}
- (void)unlockProgress {
}
- (void)lockProgress {
}
- (void *)progress {
    return NULL; //??
}
// The AppleScript compilation methods:
#define CHECK  
//fprintf(stderr,”result code = %d”, ok);
// This converts an AEDesc into a corresponding NSValue.
static id aedesc_to_id(AEDesc *desc)
{
    OSErr ok;
    
    if (desc->descriptorType == typeChar)
    {
        NSMutableData *outBytes;
        NSString *txt;
        
        outBytes = [[NSMutableData alloc] initWithLength:AEGetDescDataSize(desc)];
        ok = AEGetDescData(desc, [outBytes mutableBytes], [outBytes length]);
        CHECK;
    
        txt = [[NSString alloc] initWithData:outBytes encoding:[NSString defaultCStringEncoding]];
        [outBytes release];
        [txt autorelease];
        
        return txt;
    }
    
    if (desc->descriptorType == typeSInt16)
    {
        SInt16 buf;
        
        AEGetDescData(desc, &buf, sizeof(buf));
        
        return [NSNumber numberWithShort:buf];
    }
    
    return [NSString stringWithFormat:@”[unconverted AEDesc, type=\”%c%c%c%c\”]”, 
    ((char *)&(desc->descriptorType))[0], ((char *)&(desc->descriptorType))[1], 
    ((char *)&(desc->descriptorType))[2], ((char *)&(desc->descriptorType))[3]];
}
- (void)runScript:(NSString *)txt
{
    NSData *scriptChars = [txt dataUsingEncoding:[NSString defaultCStringEncoding]];
    AEDesc source, resultText;
    OSAID scriptId, resultId;
    OSErr ok;
    // Convert the source string into an AEDesc of string type.
    ok = AECreateDesc(typeChar, [scriptChars bytes], [scriptChars length], &source);
    CHECK;
    
    // Compile the source into a script.
    scriptId = kOSANullScript;
    ok = OSACompile(myComponent, &source, kOSAModeNull, &scriptId);
    AEDisposeDesc(&source);
    CHECK;
            
    // Execute the script, using defaults for everything.
    resultId = 0;
    ok = OSAExecute(myComponent, scriptId, kOSANullScript, kOSAModeNull, &resultId);
    CHECK;
    
    if (ok == errOSAScriptError) {
        AEDesc ernum, erstr;
        id ernumobj, erstrobj;
        
        // Extract the error number and error message from our scripting component.
        ok = OSAScriptError(myComponent, kOSAErrorNumber, typeShortInteger, &ernum);
        CHECK;
        ok = OSAScriptError(myComponent, kOSAErrorMessage, typeChar, &erstr);
        CHECK;
        
        // Convert them to ObjC types.
        ernumobj = aedesc_to_id(&ernum);
        AEDisposeDesc(&ernum);
        erstrobj = aedesc_to_id(&erstr);
        AEDisposeDesc(&erstr);
        
        txt = [NSString stringWithFormat:@”Error, number=%@, message=%@”, ernumobj, erstrobj];
    } else {
        // If no error, extract the result, and convert it to a string for display
        
        if (resultId != 0) { // apple doesn't mention that this can be 0?
            ok = OSADisplay(myComponent, resultId, typeChar, kOSAModeNull, &resultText);
            CHECK;
    
            txt = aedesc_to_id(&resultText);
            AEDisposeDesc(&resultText);
        } else {
            txt = @”[no value returned]”;
        }
        OSADispose(myComponent, resultId);
    }
    
    ok = OSADispose(myComponent, scriptId);
    CHECK;
}
@end

After you compile your PlugIn, drop it into the PlugIns folder in iPhoto, relaunch iPhoto, choose "Share” and "Export”, and if all is well, you'll see your PlugIn as a tab in the Export TabView window.

Then Apple Released iPhoto 1.1

Before this article went to print, Apple released an update to iPhoto—and indeed, my Exporter bundle broke! First, I looked through the console error messages and saw that my PlugIn wasn't responding to certain messages. Then, I used class-dump to confirm that some protocols had changed.

The first problem was that my PlugIn's main settingsView didn't respond to the ExportPluginBoxProtocol protocol:

@protocol ExportPluginBoxProtocol
- (char)performKeyEquivalent:fp16;
@end

So, by looking at the nib files in the Apple-provided PlugIns, I deduced this simple NSBox subclass:

@interface P2WExportPluginBox:NSBox <ExportPluginBoxProtocol>
{
    id mPlugin;
}
@end
@implementation P2WExportPluginBox
- (char)performKeyEquivalent:fp12 {
}
@end

Drop this source file into the Interface Builder class window to have it read the file. Assign the class "P2WExportPluginBox” to the NSBox containing your PlugIn controls in Interface Builder. Connect the "mPlugin” instance variable to the file's owner. Save, build, and drop into the PlugIns folder. (An interesting sidenote: variable names can reveal a lot. The coder here was probably an old Mac toolbox programmer, as indicated by the lower-case "m”, as in "my”, for the ivar. Objective-C coders conventionally use an underscore to prefix ivars, so the ivar name would have been "_plugin”.)

Second, a few more methods were needed in the main PlugIn class to get it to work again:

@implementation PhotoToWebExport
- (NSString *)name {
    return @”Stone Design's iPhoto to PhotoToWeb Exporter”;
}
- (BOOL)treatSingleSelectionDifferently { return NO; }
...
 

Conclusion

Cocoa, especially the Objective C flavor, is the future of Mac OS X, as evidenced by the new apps coming out of Apple written almost entirely in Cocoa. Dynamic runtime loading comes free, and you can examine the classes and methods in an application using class-dump. Knowledge of Carbon is still very useful. For example, in this PlugIn we use it to compile and execute our AppleScript. It's a good idea to keep an agnostic attitude towards the various Apple API's.

Using the techniques outlined in this article, you should be able to add all sorts of functionality to the ever-increasing number of applications available for Mac OS X.


Andrew Stone

 
AAPL
$97.67
Apple Inc.
+2.95
MSFT
$45.38
Microsoft Corpora
+0.55
GOOG
$597.41
Google Inc.
+2.67

MacTech Search:
Community Search:

Software Updates via MacUpdate

Firefox 31.0 - Fast, safe Web browser. (...
Firefox for Mac offers a fast, safe Web browsing experience. Browse quickly, securely, and effortlessly. With its industry-leading features, Firefox is the choice of Web development professionals... Read more
Little Snitch 3.3.3 - Alerts you to outg...
Little Snitch gives you control over your private outgoing data. Track background activityAs soon as your computer connects to the Internet, applications often have permission to send any... Read more
Thunderbird 31.0 - Email client from Moz...
As of July 2012, Thunderbird has transitioned to a new governance model, with new features being developed by the broader free software and open source community, and security fixes and improvements... Read more
Together 3.2 - Store and organize all of...
Together helps you organize your Mac, giving you the ability to store, edit and preview your files in a single clean, uncluttered interface. Smart storage. With simple drag-and-drop functionality,... Read more
Cyberduck 4.5 - FTP and SFTP browser. (F...
Cyberduck is a robust FTP/FTP-TLS/SFTP browser for the Mac whose lack of visual clutter and cleverly intuitive features make it easy to use. Support for external editors and system technologies such... Read more
Airmail 1.4 - Powerful, minimal email cl...
Airmail is a powerful, minimal mail client.It was designed to retain the same experience with a single or multiple accounts and provide a quick, modern and easy-to-use user experience. Airmail... Read more
Macs Fan Control 1.1.12 - Monitor and co...
Macs Fan Control allows you to monitor and control almost any aspect of your computer's fans, with support for controlling fan speed, temperature sensors pane, menu-bar icon, and autostart with... Read more
A Better Finder Rename 9.37 - File, phot...
A Better Finder Rename is the most complete renaming solution available on the market today. That's why, since 1996, tens of thousands of hobbyists, professionals and businesses depend on A Better... Read more
MacBook Air EFI Firmware Update 2.9 - Fo...
MacBook Air EFI Firmware Update is recommended for MacBook Air (Mid 2011) models. This update addresses an issue where systems may take longer to wake from sleep than expected and fixes a rare issue... Read more
FileZilla 3.9.0.1 - Fast and reliable FT...
FileZilla (ported from Windows) is a fast and reliable FTP client and server with lots of useful features and an intuitive interface.Version 3.9.0.1: MSW: Fix installation issue with locked DLLs... Read more

Latest Forum Discussions

See All

Stronghold 3: The Campaigns Review
Stronghold 3: The Campaigns Review By Jennifer Allen on July 23rd, 2014 Our Rating: :: DULL STRATEGIZINGiPad Only App - Designed for the iPad A cumbersome strategy game, Stronghold 3: The Campaigns has a few too many issues to... | Read more »
Table Tennis Touch on Sale for a Limited...
Table Tennis Touch on Sale for a Limited Time Posted by Jessica Fisher on July 23rd, 2014 [ permalink ] Universal App - Designed for iPhone and iPad | Read more »
Secret Files Tunguska Review
Secret Files Tunguska Review By Jennifer Allen on July 23rd, 2014 Our Rating: :: CONSPIRACY-LITTERED ADVENTURINGUniversal App - Designed for iPhone and iPad Offering traditional adventuring with no fear of in-app purchases, Secret... | Read more »
Celebrate Summer With a Cat in the Hat L...
Celebrate Summer With a Cat in the Hat Learning Library Sale Posted by Ellis Spice on July 22nd, 2014 [ permalink ] Universal App - Designed for iPhone and iPad | Read more »
Dragon Raiders Review
Dragon Raiders Review By Nadia Oxford on July 22nd, 2014 Our Rating: :: RUN, DRAGON, RUNUniversal App - Designed for iPhone and iPad Dragon Raiders is rough and scaly in some parts, but overall it’s an enjoyable level-based running... | Read more »
MyTaskList Review
MyTaskList Review By Jennifer Allen on July 22nd, 2014 Our Rating: :: EFFECTIVE IF PLAINUniversal App - Designed for iPhone and iPad It’s not the most stylish of task management apps, but MyTaskList has all the features you could... | Read more »
FlyCraft Herbie: Crazy Machines Review
FlyCraft Herbie: Crazy Machines Review By Jennifer Allen on July 22nd, 2014 Our Rating: :: TRICKY FLYINGUniversal App - Designed for iPhone and iPad A tough game of careful thrusting and navigation, FlyCraft Herbie: Crazy Machines... | Read more »
MTN Review
MTN Review By Jessica Fisher on July 22nd, 2014 Our Rating: :: ADORABLE, SERENE, AND AMUSINGUniversal App - Designed for iPhone and iPad MTN is an adorable, talking pet mountain that is less game and more zen garden.   | Read more »
Fly High with Ninja UP! Now Available o...
Fly High with Ninja UP! Now Available on the App Store Posted by Jessica Fisher on July 22nd, 2014 [ permalink ] Universal App - Designed for iPhone and iPad | Read more »
Bio Inc. Review
Bio Inc. Review By Nadia Oxford on July 22nd, 2014 Our Rating: :: SICKENING - IN A COMPELLING WAYUniversal App - Designed for iPhone and iPad Bio Inc is about orchestrating the medical destruction of a single person. If that doesn’... | Read more »

Price Scanner via MacPrices.net

MacBook Airs on sale starting at $799, free s...
B&H Photo has the new 2014 MacBook Airs on sale for up to $100 off MSRP for a limited time. Shipping is free, and B&H charges NY sales tax only. They also include free copies of Parallels... Read more
Apple 27″ Thunderbolt Display (refurbished) a...
The Apple Store has Apple Certified Refurbished 27″ Thunderbolt Displays available for $799 including free shipping. That’s $200 off the cost of new models. Read more
WaterField Designs Unveils Cycling Ride Pouch...
High end computer case and bag maker WaterField Designs of San Francisco now enters the cycling market with the introduction of the Cycling Ride Pouch – an upscale toolkit with a scratch-free iPhone... Read more
Kingston Digital Ships Large Capacity Near 1T...
Kingston Digital, Inc., the Flash memory affiliate of Kingston Technology Company, Inc.,has announced its latest addition to the SSDNow V300 series, the V310. The Kingston SSDNow V310 solid-state... Read more
Apple’s Fiscal Third Quarter Results; Record...
Apple has announced financial results for its fiscal 2014 third quarter ended June 28, 2014, racking up quarterly revenue of $37.4 billion and quarterly net profit of $7.7 billion, or $1.28 per... Read more
15-inch 2.0GHz MacBook Pro Retina on sale for...
B&H Photo has the 15″ 2.0GHz Retina MacBook Pro on sale for $1829 including free shipping plus NY sales tax only. Their price is $170 off MSRP. B&H will also include free copies of Parallels... Read more
Apple restocks refurbished Mac minis for up t...
The Apple Store has restocked Apple Certified Refurbished Mac minis for up to $150 off the cost of new models. Apple’s one-year warranty is included with each mini, and shipping is free: - 2.5GHz Mac... Read more
Twelve South HiRise For MacBook – Height-Adju...
If you use your MacBook as a workhorse desktop substitute, as many of us do, a laptop stand combined with an external keyboard and pointing device are pretty much obligatory if you want to avoid... Read more
Why The Mac Was Not Included In The Apple/IBM...
TUAW’s Yoni Heisler cites Fredrick Paul of Network World whoi blogged last week that the Mac’s conspicuous absence from Apple and IBM’s landmark partnership agreement represents a huge squandered... Read more
Save $100 on 13-inch Retina MacBook Pros, plu...
Adorama has 13″ Retina MacBook Pros on sale for $100 off MSRP. Shipping is free, and Adorama charges sales tax in NY & NJ only: - 13″ 2.4GHz/128GB MacBook Pro with Retina Display: $1199 - 13″ 2.... Read more

Jobs Board

Sr Software Lead Engineer, *Apple* Online S...
Sr Software Lead Engineer, Apple Online Store Publishing Systems Keywords: Company: Apple Job Code: E3PCAK8MgYYkw Location (City or ZIP): Santa Clara Status: Full Read more
*Apple* Retail - Multiple Positions (US) - A...
Sales Specialist - Retail Customer Service and Sales Transform Apple Store visitors into loyal Apple customers. When customers enter the store, you're also the Read more
*Apple* Retail - Multiple Positions (US) - A...
Sales Specialist - Retail Customer Service and Sales Transform Apple Store visitors into loyal Apple customers. When customers enter the store, you're also the Read more
Sr *Apple* Engineer - IT - Requisition #: -...
For more information about TIAA-CREF, visit our website . The Apple Engineer will provide engineering and third-level incident support for 300- 500 MacOS desktop/laptop Read more
*Apple* Systems Administrator - DISH (United...
…satellite service provider, and Dish is currently looking for an experienced Apple /Mac Systems Administrator. Apple systems administrator will be responsible for Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.