TweetFollow Us on Twitter

Table Techniques Taught Tastefully (part 1)

Volume Number: 18 (2002)
Issue Number: 9
Column Tag: Cocoa Development

Table Techniques Taught Tastefully (part 1)

Using NSTableView for Real-World Applications

by Dan Wood

Introduction

One of the most useful and powerful displays any desktop application can have is a table of data. Before Cocoa came along, table display was high on the difficulty scale. Now with NSTableView, simple display of table data is almost trivially easy. With minimal effort, it's possible to get advanced display capabilities and functionality as well, the kind you would expect to find in full-featured "professional" programs.

When you make use of an NSTableView in your program, you control a scrollable grid of rows and columns, each cell containing text or other items. Column resizing and rearranging comes "for free," and text cells can be edited. What an NSTableView is not applicable for is a hierarchical outline (use an NSOutlineView for that), variable-height rows like you would find in an HTML table display, or cell-based display and selection like you would find in a spreadsheet. If you're going that far outside of the base functionality of NSTableView, you may find yourself needing to "roll your own" class, or perhaps start out with a more sophisticated base class. But with a little guidance from this article and perhaps other sources as well, you should be able to use NSTableView for just about any reasonable need.

This is the first of a multiple-part series of articles; the first one starts out with the basics, just to get the reader up to speed on how to use NSTableView, and then dives into a few techniques that I have picked up to add zest to your tables. By the end of this article, you should be familiar with basic techniques of displaying and editing tables along with methods to display more than just plain text and manipulating table column widths. By the end of the series, you will also have in your toolkit more advanced techniques such as alphabetic type-ahead, display of buttons and graphics, sorting, drag and drop, striping alternate rows, and building custom subclasses. You can pick and choose which of these are applicable for your program.


Figure 1: TableTester Application

The Tester Application

I've built a "TableTester" application (See figure 1; downloadable at www.karelia.com/tabletester/) that shows off most of the table features described in this series. The display is a single window with a number of tab views; the table in each tab view shows off one or more features described here.

TableTester is constructed in such a way as to separate the table functionality from the implementation details as much as possible, making each class (for data source, delegate, or subclassing NSTableView) easy to read.

Naming conventions in the code should be noted, however. Being an old PowerPlant developer, I got used to prefixes on variables to distinguish local variables from other types, and I've carried this over into the world of Cocoa. Prefixes m, o, s, g stand for member (instance) variable, outlet (also an instance variable), static variable, and global variable. Parameters passed into functions typically begin with in as well. This way, it's easy to glance at my code and know what kind of variable I'm looking at. For example, oTable refers to an outlet to the NSTableView object used by a delegate; oData is an outlet to the data array. Feel free to adapt or disdain these conventions.

To build up the sample table data, I've stored some test data in TSV (tab-separated value) files, and I've included a category method on NSString, -(NSArray *) arrayFromTSV, to read the file into memory. (The implementation of this method is not listed here, for lack of relevance, but it can be found in the project's source code.)

Controlling a Table

There are three "entry points" that you have to control how your NSTableView will look and act. These are listed in the order of complexity and specialization.

  • The Data Source. Each NSTableView must have an object connected to its dataSource outlet; this connection is usually made through Interface Builder. This is an object that contains methods listed in the NSTableDataSource informal protocol. At the minimum, this is how a table gets its data to display; table editing and drag & drop is handled using this connection as well.

  • The Delegate. An NSTableView can connect to a delegate object (usually the same object as the data source in your code) to notify your code before and after certain events. Using a delegate is optional, but it gives your code much finer control over how a table is displayed and how it responds to user activity. We will begin exploring uses for the delegate in the second half of this article.

  • Subclassing NSTableView. Cocoa objects often do not need to be subclassed to be useful, but if you have any needs that the base class can't handle, there are a number of places you can customize a table's look and behavior in subtle or radical ways. Later in this series, we will showcase some subclasses that you can use for your purposes; these are built in such a way to encourage the Model-View-Controller separation that is made easy by the architecture of Cocoa.

Data Display

The absolute minimum that a table can do is display data, and in order to accomplish this, you must implement two methods in your "data source" class: numberOfRowsInTableView: and tableView: objectValueForTableColumn: row:.

The former method is called whenever the table view needs to know how many rows are in your table. It is invoked quite frequently, so you should be sure to have an efficient implementation. If your data structure that your table accesses is complex and it takes a while just to calculate the size of it, you should probably cache the size and return that cached value in your implementation of this method.

The latter method is called to display the contents of a given row and column in the table, but it only is called for cells that are actually visible. If you have a table with 5000 rows, but only twenty rows (and all three columns) are visible at a time in the enclosing scroll view, then this method will only be called 20 * 3 = 60 times. Your method should still be as efficient as possible for accessing data; if the data cannot be retrieved immediately (for instance, if they must be retrieved over the network), it may make sense to return placeholder values and then refresh the table display after the data arrive.


Figure 2: An Array of Dictionaries

TableTester uses a simple array, each element corresponding to a row in the table, so returning the row count is as simple as retrieving the array count. Each element in the array is a dictionary, so we make a point of setting the identifier of each table column to correspond to the key of the dictionary. This is illustrated in figure 2. Getting the value is then a matter of getting the appropriate dictionary for the given row, and then getting the value from the dictionary based on the column identifier. (If your code only controls one table, then you can ignore the NSTableView parameter; otherwise you could compare with the tables your code manages.) This is a typical approach (but by no means the only one) to populating a table view, useful because it allows table columns to be arranged in any order without being affected by the structure of the underlying data. (See listing 1.)

Listing 1: SimpleSource.m

numberOfRowsInTableView:
Return the number of rows in the entire table by retrieving the data array's count.
- (int)numberOfRowsInTableView:(NSTableView *)inTableView
{                                    
   return [oData count];                  
}                                    

tableView: objectValueForTableColumn: row:
Return the string at the given row and column by fetching the row from the array, and then fetching 
the appropriately keyed string based on the column identifier.

- (id)tableView:(NSTableView *)inTableView      
      objectValueForTableColumn:(NSTableColumn*)inTableColumn
      row:(int)inRowIndex                     
{                                    
   NSDictionary *dict = [oData objectAtIndex:inRowIndex];
   return [dict objectForKey:[inTableColumn identifier]];
}                                    

Editing

If you wish to make the data in your table editable, you need to make sure that the table column is set to be editable from within Interface Builder, and you need to implement tableView: setObjectValue: forTableColumn: row: in your table's data source. NSTableView handles the rest, responding to a double-click in a cell and sending the message to your data source after the value has changed.

TableTester relies on the fact that the data is actually stored in mutable dictionaries, along with the correspondence between column identifiers and dictionary keys mentioned above. See listing 2.

Listing 2: SimpleSource.m

tableView: setObjectValue: forTableColumn: row:
Update the dictionary corresponding to the given row by setting the string keyed by the column 
identifier to the given value.

- (void)tableView:(NSTableView *)inTableView
     setObjectValue:(id)inObject
     forTableColumn:(NSTableColumn *)inTableColumn
     row:(int)inRowIndex
{
   NSMutableDictionary *dict
      = [oData objectAtIndex:inRowIndex];
   [dict setObject:inObject forKey:
      [inTableColumn identifier]];
}

Adding a New Row

How do you let the user add and delete rows? Every application handles the user interface differently: some have little + and - buttons near the table, some have a separate field for entering data with a button to add the contents of the fields into the table; some go for a truly minimalist approach, avoiding the need for additional screen space. Here we show how to add a new row using a small button in the upper right corner of the table view itself (in a space that would otherwise be unused; see figure 3) that programmatically activates the table for editing your new row.


Figure 3: A Button in the Corner

To place a little button in the upper corner of the table, you can use Interface Builder. Create a custom view in your nib, and put a small button (about 12 pixels square) into the view. Then hook up that button to the table view's cornerView outlet. Hook up the button's action to your method to invoke when the button is clicked.

(A note about the corner view: It's tricky to get it to look just right. A more sophisticated approach than connecting to the outlet is to position the button in code as a subview of the default view, which takes care of displaying the gradient. You may need to experiment with the button type in order for it to preserve the background gradient; a "toggle" button seems to do the trick.)

To jump into editing mode, you need to select the row you will be editing, and then invoke editColumn: row: withEvent: select:. This example (listing 3) adds a new empty item to the data array, then starts editing the leftmost column.

Listing 3: TableDelegate.m

doAdd:
Creates an empty row's dictionary and adds it to the end of the data array.  The selection is changed 
to this new last row, and we enter editing mode.

- (IBAction) doAdd:(id)sender
{
   NSMutableDictionary *newRow
      = [NSMutableDictionary dictionary];   // an empty dictionary
   int rowNum = [oData count];
   [oData addObject:newRow];   // Add an empty object to the data array
   [oTable selectRow:rowNum byExtendingSelection:NO];
   [oTable editColumn:0 row:rowNum withEvent:nil
      select:YES];
}

DELETING ROWS

No matter what the user interface, you will need to implement an action method that loops through each selected row using selectedRowEnumerator. Deleting elements from an NSMutableArray is a little tricky, since you can't delete elements from an NSMutableArray from within an enumerator. In our sample (listing 4), we replace each row to delete with the special placeholder [NSNull null] and then delete those values afterwards in a single operation. You can have the delete: action method connected to a button and/or have it respond to the Delete menu. The only trick is that the current version of Interface Builder has the Delete menu item sending the clear: action. If your program has text editing anywhere, and you want the user to ue the Delete menu to clear text, then you will have to reconcile this! Using Interface Builder, you should change the menu item to send delete: to the first responder, so your delete: method will be invoked, and so that the menu item will also function on any editable text you may have in your program, which also implements a delete: method.

In our example code, we have a method implemented in the controller to actually perform the deletion, to encourage separation between the view and the controller. Our delete: method sends the method deleteSelectedRowsInTableView to that controller.

Listing 4: SimpleSource.m

deleteSelectedRowsInTableView:
For each selected row number, replace the data element with the special null placeholder value.  
Afterwards, clear out those values in one operation, and redisplay the data.

- (void) deleteSelectedRowsInTableView:
      (NSTableView *)inTableView
{
   int row = [inTableView selectedRow];   // get the last-selected row
   if (row >= 0)
   {
      // Replace each selected row data with special null placeholder
      NSEnumerator *theEnum
         = [inTableView selectedRowEnumerator];
      id theItem;
      while (nil != (theItem = [theEnum nextObject]) )
      {
         int row = [theItem intValue];
         [oData replaceObjectAtIndex:row withObject:
            [NSNull null]];
      }
      // Now, remove the NSNull placeholders
      [oData removeObjectIdenticalTo:[NSNull null]];
      [inTableView deselectAll:nil];
      [inTableView reloadData];
   }
}

delete:
Respond to the user action (from a button or menu) by passing along the request to the table's data 
source.

- (IBAction) delete:(id)sender
{
   [[oTable dataSource]
      deleteSelectedRowsInTableView:oTable];
}

Selecting Rows

Many tables need to do something when the user selects a row, or perhaps when the user double-clicks on a row. NSTableView, like other views, has the ability to associate an action with a click to invoke a particular method; you would "wire up" an action in Interface Builder using techniques that are (hopefully!) familiar to you by now.

But there is a problem with using the action associated with a table: your code only gets called when the user clicks on a row on the table. If the user is using the keyboard to move up and down the rows of the table, nothing happens. If you want the display to reflect the currently selected row in your table, you need a better solution.

To handle a row selection change even by keyboard, the better approach is to make use of the delegate model. (Plus, more than one object can be watching for this change; an action will only be sent to one controller.) If your NSTableView has an object connected to the delegate outlet, certain messages are sent to that object before, during, or after strategic operations, to give your program finer control over its behavior. So what we want to do is hook up the delegate in our nib file, and implement tableViewSelectionDidChange: in that delegate object. The implementation of this method will handle any change in a row selection, and usually does what you would expect. (In some situations, you might even want to respond to a click with an action method as well as using the delegate method; that's fine too.)

In TableTester, we find a URL to display from the table's underlying data, and populate an NSTextField with that URL. Whenever the user clicks on a new row, or changes the selection using the arrow keys, the URL display changes appropriately.

Listing 5: WrappingDelegate.m

selectedRowURL
Return a URL associated with the selected row in the table (if any) by looking up the appropriate 
dictionary object in the data array, and getting the string associated with the "url" key.

- (NSString *) selectedRowURL
{
   NSString *result = nil;
   int row = [oTable selectedRow];
   if (row >= 0)
   {
      result
         = [[oData objectAtIndex:row] objectForKey:@"url"];
   }
   return result;
}

tableViewSelectionDidChange:
Respond to the delegate message that the selection in the table has changed by fetching the URL 
string associated with the selected row, and putting that string in the text field hooked up to the 
oURLField outlet.

- (void)tableViewSelectionDidChange:
      (NSNotification *)aNotification
{
   [oURLField setObjectValue:[self selectedRowURL]];
}

What about double-clicking? To invoke a method when the user double-clicks on a row, you need to insert a little bit of code somewhere (such as your awakeFromNib method) to connect the target and the "double action" to the appropriate object and method.

    [oTable setTarget:self];
    [oTable setDoubleAction:@selector(openSelectedRow:)];

Controlling Cell Text Display

A table view, to display itself, uses a single cell -- an NSTextFieldCell, for text display -- for each column; this cell is used repeatedly to display each row. With this in mind, we can customize the display of each table column's rows by modifying its associated cell.

In our TableTester program (under the "Wrapping" tab), we set all of the column cells to wrap their text. Additionally, we find the "description" column, and set its font to be a smaller size. A good place to accomplish this is in the awakeFromNib method, which is invoked after the table view has loaded but before it tries to display anything.

Listing 6: WrappingDelegate.m

awakeFromNib
Set all of the table's columns to wrap their text to multiple lines; Permanently make the 
"description" column display with a small font.

- (void)awakeFromNib
{
   NSEnumerator *theEnum
      = [[oTable tableColumns] objectEnumerator];
   NSTableColumn *theCol;
   while (nil != (theCol = [theEnum nextObject]) )
   {
      [[theCol dataCell] setWraps:YES];
   }
   // now change the "description" column's cell
   theCol
      = [oTable tableColumnWithIdentifier:@"description"];
   [[theCol dataCell] setFont:
      [NSFont systemFontOfSize:
         [NSFont smallSystemFontSize]]];
}

But what about if you want to change attributes for a cell depending upon the row you are displaying? Returning the NSString to display in each cell using tableView: objectValueForTableColumn: row: doesn't give you control over a cell's display. (One technique would be to build an NSAttributedString, but you may wish to adjust other cell properties such as the background color.) A solution is to have your code implement the delegate method tableView: willDisplayCell: forTableColumn: row:. This message is sent to your delegate object right before each cell is about to be displayed, and you can act upon it to change attributes of the cell that is reused for each row.

Listing 7: AttributedDelegate.m

tableView: willDisplayCell: forTableColumn: row:
Modify the cell's color and font depending on the publisher and the price.
- (void)tableView:(NSTableView *)inTableView
    willDisplayCell:(id)inCell
     forTableColumn:(NSTableColumn *)inTableColumn
                    row:(int)inRow
{
   NSDictionary *dict = [oData objectAtIndex:inRow];
   // Make the row's text in any column be red if the publisher is Karelia; black otherwise
   BOOL karelia
      = [[dict objectForKey:@"publisher"]
            isEqualToString:@"Karelia"];
   [inCell setTextColor:
      (karelia ? [NSColor redColor] : [NSColor blackColor])];
   // Modify the cell for the "price" column
   if ([[inTableColumn identifier]
            isEqualToString:@"price"])
   {
      if ([[dict objectForKey:@"price"]
         isEqualToString:@"free"])
      {
         // Make the price text bold if it's free
         [inCell setFont:
            [NSFont boldSystemFontOfSize:
               [NSFont systemFontSize]]];
      }
      else
      {
         // Otherwise, just use regular system font
         [inCell setFont:
            [NSFont systemFontOfSize:[NSFont systemFontSize]]];
      }
   }
}

TableTester (under the "Attributed" tab) checks the column that is about to be displayed, and changes attributes of the text cell for certain columns. The listing below causes all "free" software prices to be shown in boldface and all software from Karelia in red. Note that you always have to be able to set the attributes back to their other state; since the cells are reused for each column, setting a cell's style but not setting it back would have strange display results. The result of applying the code from listing 6 and listing 7 is shown here in figure 4.


Figure 4: Attributed and Wrapping Cells

Controlling Table Column Widths

When you resize a window containing a table set to grow as the window grows, your application needs to appropriately resize the table columns. You can check the "autoresizing" flag for the table view in your nib file to cause all columns to proportionally resize as you resize the table; if it's not checked, the last column will grow as the table grows. You can also make use of the maximum and minimum column widths in order to constrain columns to a certain size. But this is often not good enough; what if you want certain columns to shrink and grow in some particular proportions? The solution is to write a little code to resize the columns for you according to your needs.

To respond to resizing table views, you need to respond to the NSViewFrameDidChangeNotification that is sent to the delegate by the scroll view that holds the table view. A good place to set this up is in your awakeFromNib method, as we do here. (Note that in this sample, we also rebuild the table columns immediately so they will start out with the right size.)

When your delegate receives the notification, it should calculate the column sizes based upon the width of the entire table, the columns, and the spacing between the columns. You can set new widths for the columns as appropriate. (TableTester, as an unrealistic example, holds the first column at a constant value and sizes the remaining columns proportionally.)

Listing 8: StretchingDelegate.m

awakeFromNib
Start the table's scrollView listening to the NSViewFrameDidChangeNotification, and set the column 
widths initially.

- (void)awakeFromNib
{
   [[NSNotificationCenter defaultCenter]
      addObserver:self
         selector:@selector(rebuildTable:)
            name:NSViewFrameDidChangeNotification
          object:[oTable enclosingScrollView]];
   [self rebuildTable:nil];      // force an initial rebuild
}

rebuildTable
Set the widths of the table columns.  The first column gets a constant width; the rest of the columns
divide up the remaining space.

- (void) rebuildTable:(NSNotification *)inNotification
{
   NSArray *columns = [oTable tableColumns];
   int numberOfColumns = [columns count];
   float spacingWidth = [oTable intercellSpacing].width;
   float tableWidth = [oTable bounds].size.width;
   float firstColWidth = 200.0;      // constant width for first column
   // Calculate the width of the other table columns by dividing up remaining space
   float remWidth
      = tableWidth - firstColWidth - spacingWidth;
   float colWidth
      = (remWidth / (numberOfColumns-1)) - spacingWidth;
   int i;
   // First, set the first column to a constant value
   [[columns objectAtIndex:0] setWidth:firstColWidth];
   // Now, set the rest of the columns starting at index 1
   for ( i = 1 ; i < numberOfColumns ; i++ )
   {
      NSTableColumn *column = [columns objectAtIndex:i];
      [column setWidth:colWidth];
   }
}

Your table-rebuilding code could even add or delete table columns programmatically, causing the number of columns across to adjust as the size changes. One caveat: NSTableColumn objects created with the [[NSTableColumn alloc] initWithIdentifier:@"foo"] technique will have different display properties than those created in Interface Builder, so you may need to adjust your font heights in code if you use this technique.

Saving Table Column Widths

A capability that comes for free with table views, if you make the effort to turn it on in your code, is saving of column widths and positions in your preferences file for you automatically. (There's a spot to set this in Interface Builder, but as of this writing, it is disabled and nonfunctional.) By providing a name to distinguish one table from another in the preferences file, and turning on the auto-saving feature, any column adjustment is saved and restored for you. The two lines here, placed for example in the awakeFromNib method, are all you need. In TableTester, this feature is demonstrated in the table under the "Attributed" tab.

   [oTable setAutosaveName:@"my table"];
   [oTable setAutosaveTableColumns:YES];

One thing to watch out for if you save your table columns: If you later decide to remove or rename a column, and your users have the table column positions stored in their application defaults, they may run into problems. As NSTableView reads the preferences and builds the table according to those settings, it will "choke" if it finds a column identifier that it no longer exists, causing missing columns or exception to be thrown. So if you are releasing an update to an application where table columns were saved, be sure to keep your table columns around in Interface Builder, and remove or rename them programmatically.

Until We Meet Again

If you've followed along, you should have a pretty good understanding of how to display data in an NSTableView, and a few tricks of the trade for editing its contents, controlling display of the cells, and manipulating the columns. Certainly this is good enough for a basic program. But there are so many more cool things you can do with NSTableView, and this is why there's more to come. Tune in next month for part two, in which we'll cover more options for deleting rows, alphabetic type-ahead, display of non-text cells, sorting, drag and drop, and clipboard support.


Dan Wood once took an introductory Arabic class, but nobody in the room knew what language they were being taught. He likes to buy fruits and vegetables from the farmer's market on Tuesday mornings. He missed the last two days of WWDC this year due to the birth of his son. He is the author of Watson, an application written in Cocoa. Dan thanks Chuck Pisula at Apple for his technical help with this series, and acknowledges online code fragments from John C. Randolph, Stephane Sudre, Ondra Cada, Vince DeMarco, Harry Emmanuel, and others. You can reach him at dwood@karelia.com.

 
AAPL
$102.23
Apple Inc.
+2.47
MSFT
$44.61
Microsoft Corpora
+0.53
GOOG
$520.91
Google Inc.
+0.07

MacTech Search:
Community Search:

Software Updates via MacUpdate

NTFS 12.0.39 - Provides full read and wr...
Paragon NTFS breaks down the barriers between Windows and OS X. Paragon NTFS effectively solves the communication problems between the Mac system and NTFS, providing full read and write access to... Read more
RestoreMeNot 2.0.3 - Disable window rest...
RestoreMeNot provides a simple way to disable the window restoration for individual applications so that you can fine-tune this behavior to suit your needs. Please note that RestoreMeNot is designed... Read more
Macgo Blu-ray Player 2.10.9.1750 - Blu-r...
Macgo Mac Blu-ray Player can bring you the most unforgettable Blu-ray experience on your Mac. Overview Macgo Mac Blu-ray Player can satisfy just about every need you could possibly have in a Blu-ray... Read more
Apple iOS 8.1 - The latest version of Ap...
The latest version of iOS can be downloaded through iTunes. Apple iOS 8 comes with big updates to apps you use every day, like Messages and Photos. A whole new way to share content with your family.... Read more
TechTool Pro 7.0.5 - Hard drive and syst...
TechTool Pro is now 7, and this is the most advanced version of the acclaimed Macintosh troubleshooting utility created in its 20-year history. Micromat has redeveloped TechTool Pro 7 to be fully 64... Read more
PDFKey Pro 4.0.2 - Edit and print passwo...
PDFKey Pro can unlock PDF documents protected for printing and copying when you've forgotten your password. It can now also protect your PDF files with a password to prevent unauthorized access and/... Read more
Yasu 2.9.1 - System maintenance app; per...
Yasu was originally created with System Administrators who service large groups of workstations in mind, Yasu (Yet Another System Utility) was made to do a specific group of maintenance tasks... Read more
Hazel 3.3 - Create rules for organizing...
Hazel is your personal housekeeper, organizing and cleaning folders based on rules you define. Hazel can also manage your trash and uninstall your applications. Organize your files using a... Read more
Autopano Giga 3.7 - Stitch multiple imag...
Autopano Giga allows you to stitch 2, 20, or 2,000 images. Version 3.0 integrates impressive new features that will definitely make you adopt Autopano Pro or Autopano Giga: Choose between 9... Read more
MenuMeters 1.8 - CPU, memory, disk, and...
MenuMeters is a set of CPU, memory, disk, and network monitoring tools for Mac OS X. Although there are numerous other programs which do the same thing, none had quite the feature set I was looking... Read more

Latest Forum Discussions

See All

Clips Review
Clips Review By Jennifer Allen on October 21st, 2014 Our Rating: :: CONVENIENT PASTINGUniversal App - Designed for iPhone and iPad Making copying and pasting more powerful than usual, Clips is a great way to move stuff around.   | Read more »
MonSense Review
MonSense Review By Jennifer Allen on October 21st, 2014 Our Rating: :: ORGANIZED FINANCESiPhone App - Designed for the iPhone, compatible with the iPad Organize your finances with the quick and easy to use, MonSense.   | Read more »
This Week at 148Apps: October 13-17, 201...
Expert App Reviewers   So little time and so very many apps. What’s a poor iPhone/iPad lover to do? Fortunately, 148Apps is here to give you the rundown on the latest and greatest releases. And we even have a tremendous back catalog of reviews; just... | Read more »
Angry Birds Transformers Review
Angry Birds Transformers Review By Jennifer Allen on October 20th, 2014 Our Rating: :: TRANSFORMED BIRDSUniversal App - Designed for iPhone and iPad Transformed in a way you wouldn’t expect, Angry Birds Transformers is a quite... | Read more »
GAMEVIL Announces the Upcoming Launch of...
GAMEVIL Announces the Upcoming Launch of Mark of the Dragon Posted by Jessica Fisher on October 20th, 2014 [ permalink ] Mark of the Dragon, by GAMEVIL, put | Read more »
Interview With the Angry Birds Transform...
Angry Birds Transformers recently transformed and rolled out worldwide. This run-and-gun title is a hit with young Transformers fans, but the ample references to classic Transformers fandom has also earned it a place in the hearts of long-time... | Read more »
Hail to the King: Deathbat Review
Hail to the King: Deathbat Review By Rob Thomas on October 20th, 2014 Our Rating: :: SO FAR AWAYUniversal App - Designed for iPhone and iPad Hail to the King: Deathbat may feel like “Coming Home” for Avenged Sevenfold’s faithful,... | Read more »
Find Free Food on Campus with Ypay
Find Free Food on Campus with Ypay Posted by Jessica Fisher on October 20th, 2014 [ permalink ] iPhone App - Designed for the iPhone, compatible with the iPad | Read more »
Strung Along Review
Strung Along Review By Jordan Minor on October 20th, 2014 Our Rating: :: GOT NO STRINGSUniversal App - Designed for iPhone and iPad A cool gimmick and a great art style keep Strung Along from completely falling apart.   | Read more »
P2P file transferring app Send Anywhere...
File sharing services like Dropbox have security issues. Email attachments can be problematic when it comes to sharing large files. USB dongles don’t fit into your phone. Send Anywhere, a peer-to-peer file transferring application, solves all of... | Read more »

Price Scanner via MacPrices.net

Strong iPhone, Mac And App Store Sales Drive...
Apple on Monday announced financial results for its fiscal 2014 fourth quarter ended September 27, 2014. The Company posted quarterly revenue of $42.1 billion and quarterly net profit of $8.5 billion... Read more
Apple Posts How-To For OS X Recovery
OS X 10.7 Lion and later include OS X Recovery. This feature includes all of the tools you need to reinstall OS X, repair your disk, and even restore from a Time Machine backup. OS X Recovery... Read more
Mac OS X Versions (Builds) Supported By Vario...
Apple Support has posted a handy resource explaining which Mac OS X versions (builds) originally shipped with or are available for your computer via retail discs, downloads, or Software Update. Apple... Read more
Deals on 2011 13-inch MacBook Airs, from $649
Daily Steals has the Mid-2011 13″ 1.7GHz i5 MacBook Air (4GB/128GB) available for $699 with a 90 day warranty. The Mid-2011 13″ 1.7GHz i5 MacBook Air (4GB/128GB SSD) is available for $649 at Other... Read more
2013 15-inch 2.0GHz Retina MacBook Pro availa...
B&H Photo has leftover previous-generation 15″ 2.0GHz Retina MacBook Pros now available for $1599 including free shipping plus NY sales tax only. Their price is $400 off original MSRP. B&H... Read more
Updated iPad Prices
We’ve updated our iPad Air Price Tracker and our iPad mini Price Tracker with the latest information on prices and availability from Apple and other resellers, including the new iPad Air 2 and the... Read more
Apple Pay Available to Millions of Visa Cardh...
Visa Inc. brings secure, convenient payments to iPad Air 2 and iPad mini 3as well as iPhone 6 and 6 Plus. Starting October 20th, eligible Visa cardholders in the U.S. will be able to use Apple Pay,... Read more
Textkraft Pocket – the missing TextEdit for i...
infovole GmbH has announced the release and immediate availability of Textkraft Pocket 1.0, a professional text editor and note taking app for Apple’s iPhone. In March 2014 rumors were all about... Read more
C Spire to offer iPad Air 2 and iPad mini 3,...
C Spire on Friday announced that it will offer iPad Air 2 and iPad mini 3, both with Wi-Fi + Cellular, on its 4G+ LTE network in the coming weeks. C Spire will offer the new iPads with a range of... Read more
Belkin Announces Full Line of Keyboards and C...
Belkin International has unveiled a new lineup of keyboard cases and accessories for Apple’s newest iPads, featuring three QODE keyboards and a collection of thin, lightweight folios for both the... Read more

Jobs Board

Project Manager / Business Analyst, WW *Appl...
…a senior project manager / business analyst to work within our Worldwide Apple Fulfillment Operations and the Business Process Re-engineering team. This role will work Read more
*Apple* Retail - Multiple Positions (US) - A...
Job Description: Sales Specialist - Retail Customer Service and Sales Transform Apple Store visitors into loyal Apple customers. When customers enter the store, Read more
Position Opening at *Apple* - Apple (United...
…customers purchase our products, you're the one who helps them get more out of their new Apple technology. Your day in the Apple Store is filled with a range of Read more
Position Opening at *Apple* - Apple (United...
**Job Summary** At the Apple Store, you connect business professionals and entrepreneurs with the tools they need in order to put Apple solutions to work in their Read more
Position Opening at *Apple* - Apple (United...
**Job Summary** The Apple Store is a retail environment like no other - uniquely focused on delivering amazing customer experiences. As an Expert, you introduce people Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.