TweetFollow Us on Twitter

The Road to Code: A Window with a View

Volume Number: 25
Issue Number: 04
Column Tag: The Road to Code

The Road to Code: A Window with a View

Custom NSViews

by Dave Dribin

Introduction

In previous articles, we've talked a little bit about views and controls and worked with plenty of system supplied views. As a refresher, Figure 1 shows the inheritance hierarchy for controls we've used before: NSTextField and NSButton. In this article, we're going to concentrate on writing our own custom views.


Figure 1: Control inheritance hierarchy

Windows, which are instances of NSWindow, contain one or more views, and views are responsible for drawing output as well as accepting user input. The NSResponder class is responsible for handling user events, such as keyboard and mouse events. The NSView class is responsible for drawing to the screen and, by inheritance, can also handle user events. Writing custom views is sometimes necessary if the system provided views are not appropriate. Besides, writing custom views is fun!

View Hierarchy

Views are arranged hierarchically inside a window. Each view can have child views, called subviews, and a single parent view, called a superview. While any view can have subviews, only certain views are designed to have subviews. For example, controls, like NSButton, are not meant to contain subviews, but NSBox is.

Each window has a view that represents the entire window's visible area called the content view. The content view is the root of the view hierarchy. The window in Figure 2 has a view hierarchy as shown in Figure 3.


Figure 2: Window with text field and button


Figure 3: View hierarchy

If you create your user interface in Interface Builder, it will create the view hierarchy for you. You may need to be aware of the view hierarchy when accessing views in code, though, which requires understanding the concepts of the view hierarchy.

View Geometry

Windows represent a two-dimensional rectangular area of the screen. The origin of the coordinate system that represents windows in AppKit, point (0.0, 0.0), is located in the lower-left corner, with the X-axis increasing to the right and the Y-axis increasing upwards. For example, if we have a window that is 200 pixels wide by 100 pixels high, the coordinate system and origin is shown in Figure 4. This can be a point of confusion if you have done graphics programming on other computer systems, where the origin is located in the upper-left corner.


Figure 4: Window geometry

Before we discuss the geometry of views, we need to discuss the various geometric data structures in Cocoa. The Foundation framework defines three basic geometric data structures: NSPoint, NSSize and NSRect. These are C structures, not classes, for performance reasons. The NSPoint structure represents a geometric point with X and Y coordinates, and is defined as:

typedef struct _NSPoint {
    CGFloat x;
    CGFloat y;
} NSPoint;

Note that Foundation also defines its own floating point primitive, CGFloat. The "CG" prefix stands for Core Graphics, the low-level graphics framework on Mac OS X. Prior to Mac OS X 10.5, float was used instead of CGFloat. The reason for the change has to do with the transition to 64-bit, but it isn't really that important for what we are talking about. What is important is to realize that the coordinate system in Mac OS X is based on floating point numbers, not integers.

While coordinates are floating points, and it is possible to have non-integral components, we generally only use integer values when dealing with screen coordinates, as each screen pixel lands on an integer point. If you see some weird drawing artifacts, it may be due to your use of non-integer coordinates. This can happen when doing division, for example. As screen resolution increases, however, points may not match up with integer points, and using non-integer coordinates becomes less of an issue. In the meantime, it's good to check for non-integer values if you have a drawing problem you are trying to solve.

To set or get the individual X and Y coordinates of a point, just access the structure members directly:

    NSPoint point;
    point.x = 10.0;
    point.y = 20.0;

There is also a function, NSMakePoint, to create a point more easily:

    NSPoint point = NSMakePoint(10.0, 20.0);

The NSSize structure represents a width and height and is defined as:

typedef struct _NSSize {
    CGFloat width;
    CGFloat height;
} NSSize;

There is also a function, NSMakeSize, to create a size more easily:

  NSSize size = NSMakeSize(200.0, 100.0);

And finally, the NSRect structure is composed of both an NSPoint and NSSize, as such:

typedef struct _NSRect {
    NSPoint origin;
    NSSize size;
} NSRect;

The origin of a rectangle is in the lower-left corner, again. The function NSMakeRect allows you to create a rectangle more easily:

    NSRect rect = NSMakeRect(0.0, 0.0, 200.0, 100.0);

Remember that you can chain access to structure members, so you could get the width of this rectangle as such:

    CGFloat width = rect.size.width;

With these basic geometric data structures in hand, we can now begin to explore the geometry of views.

NSView Geometry

A view is a rectangular area of a window. Each view has its own relative coordinate system. By default, the origin of a view is in its lower-left corner, too. A view tracks its size and location using two rectangles, the bounds rectangle and the frame rectangle.

The bounds rectangle represents the view's drawable rectangle in its own coordinate system and is retrieved using the bounds method:

    NSView * view = ...;
    NSRect bounds = [view bounds];

The origin of the bounds rectangle is almost always (0.0, 0.0). While you can change the origin, you typically leave it at (0.0, 0.0).

The frame rectangle represents the view's drawable rectangle from the perspective of its superview using the superview's coordinate system and is retrieved using the frame method:

    NSView * view = ...;
    NSRect frame = [view frame];

The size of the bounds and the frame rectangle is almost always the same. You can change the frame to move or resize the view within its superview, but, again, you typically don't need to change it once you set it up in Interface Builder. Figure 5 shows a view inside its superview. If the frame rectangle is at (5.0, 10.0), size (40.0, 20.0), the bounds is at (0.0, 0.0), size (40.0, 20.0).


Figure 5: Frame and bounds

Custom View Drawing

Enough theory. Let's dive into some real code. Create a new Cocoa Application from the Xcode New Project dialog. I'm calling my project CustomView. Now, create a new file, and select Cocoa > Objective-C NSView Subclass from the New File dialog box, as shown in Figure 6. Call the class CustomView.


Figure 6: New view class

This file template automatically subclasses NSView and creates basic implementations of two methods: the initWithFrame: constructor and drawRect:. The drawRect: method is where you do any custom drawing. Change the CustomView.m file to match Listing 1.

Listing 1: Revised CustomView.m

#import "CustomView.h"
@implementation CustomView
- (id)initWithFrame:(NSRect)frame {
    self = [super initWithFrame:frame];
    if (self == nil)
        return nil;
    
    // Initialization code here.
    return self;
}
- (void)drawRect:(NSRect)rect
{
    [[NSColor redColor] set];
    NSRectFill(rect);
}
@end

We're still leaving the constructor empty for now, but I've added two lines to the drawRect: method. The first statement changes the active color to red, and then we fill the entire bounds of the view using the current color. The result is that our entire view should be red. Of course, we need to put this view inside a window to actually test this out, so it's time to switch to Interface Builder.

Open up the MainWindow.xib file. Now find a custom view in the Library palette, as shown in Figure 7.


Figure 7: Custom view in Library

Drag a custom view to your window and place it right in the center, as shown in Figure 8. Also change the autosizing so that the view will expand vertically and horizontally.


Figure 8: Custom view placement

Now, we need to tell Interface Builder that this view is really an instance of our CustomView class. Do this by switching to the Identity pane of the Inspector window and change the Class to be CustomView, as shown in Figure 9.


Figure 9: Setting CustomView class

Save the NIB, switch back to Xcode, and run the application. The view's rectangle should be red, as shown in Figure 10. Resizing the window should also resize the view.


Figure 10: Red custom view

Congratulations! You've completed your first custom view.

Drawing with NSBezierPath

What else can you draw besides a normal rectangle? The NSBezierPath class is a powerful class to draw all sorts of shapes. It has class methods to draw some of pre-defined shapes. Change drawRect: to this:

- (void)drawRect:(NSRect)rect
{
    NSRect bounds = [self bounds];
    NSBezierPath * path;
    [[NSColor redColor] set];
    NSRectFill(bounds);
    
    [[NSColor greenColor] set];
    path = [NSBezierPath bezierPathWithRoundedRect:bounds
                                           xRadius:75.0
                                           yRadius:75.0];
    [path fill];
    
    [[NSColor blueColor] set];
    path = [NSBezierPath bezierPathWithOvalInRect:bounds];
    [path fill];
}

We now draw a red rectangle, followed by a green rectangle with rounded corners, and finally a blue oval. The fill method of NSBezierPath fills the path using the current color, thus the end result is Figure 11.


Figure 11: Other shapes

You can also create custom shapes by building your own NSBezierPath. That's a bit out of scope for this article, but feel free to read up and try out your own shapes. You can also draw images in your view using the NSImage class.

Note that we are currently ignoring the rect argument that's passed into drawRect:. This represents the partial rectangle of your view that needs to be redrawn. If your drawRect: method is very complicated and will take a long time to execute, you can use this argument to speed up your drawing by only drawing the sections of the view that need to be redrawn. Since our drawing is simple, we just draw the entire bounds every time and ignore this argument.

Updating the View

Let's modify our drawRect: to just draw a rounded rectangle, but let's also make the color and corner radius configurable, stored instance variables. Modify CustomView.h to match Listing 2. Oh, and don't forget to enable garbage collection, if you haven't yet done so.

Listing 2: CustomView.h with color and radius

#import <Cocoa/Cocoa.h>
@interface CustomView : NSView
{
    NSColor * _color;
    CGFloat _radius;
}
@property (nonatomic, copy) NSColor * color;
@property (nonatomic) CGFloat radius;
@end

Now ordinarily, we would just use @synthesize to generate our getter and setter methods, but we have one issue. The system does not constantly call drawRect:, as a performance optimization. It only calls drawRect: when it thinks it needs to be redrawn, such as when the view is first shown or resized. However, we need to force our drawRect: to be called whenever the color or radius changes. The easiest way to do this is to provide custom setters.

The nave implementation would be to call drawRect: directly from the setters, but this will not work. The system generally only allows drawing at certain times, so instead, we mark our view as dirty by calling the setNeedsDisplay: method of NSView with a YES argument. The system will then call our drawRect: the next chance it gets. The full implementation is now Listing 3.

Listing 3: CustomView.m with color and radius

#import "CustomView.h"
@implementation CustomView
@synthesize color = _color;
@synthesize radius = _radius;
- (id)initWithFrame:(NSRect)frame
{
    self = [super initWithFrame:frame];
    if (self == nil)
        return nil;
    
    _color = [NSColor redColor];
    _radius = 15.0;
    
    return self;
}
- (void)setColor:(NSColor *)color
{
    _color = [color copy];
    [self setNeedsDisplay:YES];
}
- (void)setRadius:(CGFloat)radius
{
    _radius = radius;
    [self setNeedsDisplay:YES];
}
- (void)drawRect:(NSRect)rect
{
    [_color set];
    
    NSRect bounds = [self bounds];
    NSBezierPath * path;
    path = [NSBezierPath bezierPathWithRoundedRect:bounds
                                           xRadius:_radius
                                           yRadius:_radius];
    [path fill];
}
@end

Our custom view is now all set up with a configurable color and radius. We just need to update our user interface to allow the user to choose the radius and color. This means we also need a controller class. We could use Cocoa bindings, but I'll show the more explicit method using a custom controller.

Create a new NSObject subclass and call it AppDelegate. For the header file, we need to add an outlet to our view, along with two actions to set the color and radius, as shown in Listing 4.

Listing 4: AppDelegate.h

#import <Cocoa/Cocoa.h>
@class CustomView;
@interface AppDelegate : NSObject
{
    CustomView * _customView;
}
@property (nonatomic) IBOutlet CustomView * customView;
- (IBAction)setRadius:(id)sender;
- (IBAction)setColor:(id)sender;
@end

The implementation is fairly straightforward. We just take the appropriate values from the sending control and update the custom view accordingly, as shown in Listing 5.

Listing 5: AppDelegate.m

#import "AppDelegate.h"
#import "CustomView.h"
@implementation AppDelegate
@synthesize customView = _customView;
- (IBAction)setRadius:(id)sender
{
    CGFloat radius = [sender doubleValue];
    _customView.radius = radius;
}
- (IBAction)setColor:(id)sender
{
    NSColor * color = [sender color];
    _customView.color = color;
}
@end

Now, build the project to ensure you have no compile errors, and switch back to Interface Builder to modify the user interface and hookup our outlets and actions. Make the window a bit taller so we can add some controls at the bottom. Add a label, a slider, and a color well, as shown in Figure 12. For the slider, set the minimum, maximum, and current value to 0.0, 100.0, and 15.0, respectively. Also make sure to check the Continuous box so that we update the view in real time.


Figure 12: Added controls

Create an instance of the AppDelegate class and set it up to be the delegate of NSApplication. Set the customView outlet to the view in the window, the slider's action to be setRadius:, and the color well's action to setColor:.

Save the NIB, and switch back to Xcode. Everything should be hooked up, and you should be able to run the application. Play around with moving the slider and changing the color. Your updates should take effect immediately. If you do not see the color and corner radius updates, make sure that Continuous is checked for both the slider and color well in Interface Builder and check your connections.

To see the effect of the needsDisplay flag, comment out the calls to setNeedsDisplay: and rerun the application. You should see updates only occur when you resize the window.

Handling User Events

So far, we have only covered how custom views can draw their contents, but views can also accept user input, either from the mouse or keyboard. We are going to extend our view to draw a green circle wherever the user clicks their mouse. To implement this, we need to keep track of the circle's center point, so add an instance variable and property of type NSPoint, as shown in Listing 6.

Listing 6: CustomView.h with circle center point

#import <Cocoa/Cocoa.h>
@interface CustomView : NSView
{
    NSColor * _color;
    CGFloat _radius;
    NSPoint _circleCenter;
}
@property (nonatomic, copy) NSColor * color;
@property (nonatomic) CGFloat radius;
@property (nonatomic) NSPoint circleCenter;
@end

Now, set the center point to be (50.0, 50.0) in the constructor and implement a custom setter that sets the needsDisplay flag, just as we did for the color and radius. Finally, update the drawRect: method to draw a green circle using the same radius as the rectangle corners. To draw a circle, we just need to draw an oval within a square. I've expanded out the circle's rectangle calculation to hopefully make this clearer:

- (void)drawRect:(NSRect)rect
{
    [_color set];
    
    NSRect bounds = [self bounds];
    NSBezierPath * path;
    path = [NSBezierPath bezierPathWithRoundedRect:bounds
                                           xRadius:_radius
                                           yRadius:_radius];
    [path fill];
    
    // Draw a green circle
    [[NSColor greenColor] set];
    NSRect circleRect;
    circleRect.origin.x = _circleCenter.x - _radius;
    circleRect.origin.y = _circleCenter.y - _radius;
    circleRect.size.width = _radius * 2.0;
    circleRect.size.height = _radius * 2.0;
    path =  [NSBezierPath bezierPathWithOvalInRect:circleRect];
    [path fill];
}

Handling mouse events is quite easy. Since NSView inherits from NSResponder, we just need to override a few methods. Let's start simple and handle mouse down events:

- (void)mouseDown:(NSEvent *)event { NSPoint locationInWindow = [event locationInWindow]; NSPoint locationInView = [self convertPoint:locationInWindow fromView:nil]; self.circleCenter = locationInView; }

The mouseDown: method gets called when the mouse button is pushed down. The argument to this method is of type NSEvent and encapsulates all information about the current event. Not all methods of NSEvent are relevant to all types of events, but some methods of interest for mouse events are:

- (NSPoint)locationInWindow;

This method returns an NSPoint where the mouse was pressed down.

- (NSInteger)clickCount;

This method returns 1 for a single-click, 2 for double-click, and 3 for a triple-click.

We're going to use the locationInWindow to change the circle's center point. The tricky part is that we don't want the point in the window's coordinate system; we want it in our view's coordinate system. The convertPoint:fromView: method on NSView does this coordinate system conversion for us. If you pass in nil to the fromView: argument, it converts from the window's coordinate system. Once we get the location, we can use our setter to set the new center point. This, in turn, marks the view as needing redisplay.

If you run the application now, you should see the green circle move whenever the mouse is clicked. However, if you drag the mouse around, you'll notice the circle only moves to the starting point. I'd like to have the circle track the mouse when dragged.

The mouseDown: method only gets called when the mouse button is pushed down. There are separate event methods for mouse dragging and mouse up events. To ensure our center point tracks the mouse in all cases, we should implement these methods, too. Since the implementation for all three methods is the same, I've pulled it out into its own method:

- (void)setCircleCenterToEventLocation:(NSEvent *)event
{
    NSPoint locationInWindow = [event locationInWindow];
    NSPoint locationInView = [self convertPoint:locationInWindow
                                       fromView:nil];
    self.circleCenter = locationInView;
}
- (void)mouseDown:(NSEvent *)event
{
    [self setCircleCenterToEventLocation:event];
}
- (void)mouseDragged:(NSEvent *)event
{
    [self setCircleCenterToEventLocation:event];
}
- (void)mouseUp:(NSEvent *)event
{
    [self setCircleCenterToEventLocation:event];
}

With these methods implemented, re-run the application and bask in the glory. You've now got a fully interactive view using custom drawing. A sample run is shown in Figure 13. The full code for CustomView is shown in Listing 7, in case you have trouble getting it to work. The final project is available for download on the MacTech website, as well.


Figure 13: Green circle tracks mouse

Listing 7: CustomView.m, final

#import "CustomView.h"
@implementation CustomView
@synthesize color = _color;
@synthesize radius = _radius;
@synthesize circleCenter = _circleCenter;
- (id)initWithFrame:(NSRect)frame
{
    self = [super initWithFrame:frame];
    if (self == nil)
        return nil;
    
    _color = [NSColor redColor];
    _radius = 15.0;
    _circleCenter = NSMakePoint(50.0, 50.0);
    
    return self;
}
#pragma mark -
#pragma mark Accessors
- (void)setColor:(NSColor *)color
{
    _color = [color copy];
    [self setNeedsDisplay:YES];
}
- (void)setRadius:(CGFloat)radius
{
    _radius = radius;
    [self setNeedsDisplay:YES];
}
- (void)setCircleCenter:(NSPoint)circleCenter
{
    _circleCenter = circleCenter;
    [self setNeedsDisplay:YES];
}
#pragma mark -
#pragma mark Drawing
- (void)drawRect:(NSRect)rect
{
    [_color set];
    
    NSRect bounds = [self bounds];
    NSBezierPath * path;
    path = [NSBezierPath bezierPathWithRoundedRect:bounds
                                           xRadius:_radius
                                           yRadius:_radius];
    [path fill];
    
    // Draw a green circle
    [[NSColor greenColor] set];
    NSRect circleRect;
    circleRect.origin.x = _circleCenter.x - _radius;
    circleRect.origin.y = _circleCenter.y - _radius;
    circleRect.size.width = _radius * 2.0;
    circleRect.size.height = _radius * 2.0;
    path =  [NSBezierPath bezierPathWithOvalInRect:circleRect];
    [path fill];
}
#pragma mark -
#pragma mark Events
- (void)setCircleCenterToEventLocation:(NSEvent *)event
{
    NSPoint locationInWindow = [event locationInWindow];
    NSPoint locationInView = [self convertPoint:locationInWindow
                                       fromView:nil];
    self.circleCenter = locationInView;
}
- (void)mouseDown:(NSEvent *)event
{
    [self setCircleCenterToEventLocation:event];
}
- (void)mouseDragged:(NSEvent *)event
{
    [self setCircleCenterToEventLocation:event];
}
- (void)mouseUp:(NSEvent *)event
{
    [self setCircleCenterToEventLocation:event];
}
@end

Conclusion

The Cocoa view and responder classes make writing custom views fairly easy. All you have to do is subclass NSView, implement a few methods, and add your custom view to a window in Interface Builder. The rest is up to your imagination.


Dave Dribin has been writing professional software for over eleven years. After five years programming embedded C in the telecom industry and a brief stint riding the Internet bubble, he decided to venture out on his own. Since 2001, he has been providing independent consulting services, and in 2006, he founded Bit Maki, Inc. Find out more at http://www.bitmaki.com/ and http://www.dribin.org/dave/.

 

Community Search:
MacTech Search:

Software Updates via MacUpdate

EtreCheck 3.1.5 - For troubleshooting yo...
EtreCheck is an app that displays the important details of your system configuration and allow you to copy that information to the Clipboard. It is meant to be used with Apple Support Communities to... Read more
WALTR 2 2.0.8 - $39.95
WALTR 2 helps you wirelessly drag-and-drop any music, ringtones, videos, PDF, and ePub files onto your iPhone, iPad, or iPod without iTunes. It is the second major version of Softorino's critically-... Read more
Carbon Copy Cloner 4.1.12 - Easy-to-use...
Carbon Copy Cloner backups are better than ordinary backups. Suppose the unthinkable happens while you're under deadline to finish a project: your Mac is unresponsive and all you hear is an ominous,... Read more
Dropbox 16.3.27 - Cloud backup and synch...
Dropbox is an application that creates a special Finder folder that automatically syncs online and between your computers. It allows you to both backup files and keep them up-to-date between systems... Read more
Microsoft OneNote 15.29 - Free digital n...
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
Spotify 1.0.44.100. - Stream music, crea...
Spotify is a streaming music service that gives you on-demand access to millions of songs. Whether you like driving rock, silky R&B, or grandiose classical music, Spotify's massive catalogue puts... Read more
SpamSieve 2.9.27 - Robust spam filter fo...
SpamSieve is a robust spam filter for major email clients that uses powerful Bayesian spam filtering. SpamSieve understands what your spam looks like in order to block it all, but also learns what... Read more
VueScan 9.5.62 - Scanner software with a...
VueScan is a scanning program that works with most high-quality flatbed and film scanners to produce scans that have excellent color fidelity and color balance. VueScan is easy to use, and has... Read more
Fantastical 2.3.2 - Create calendar even...
Fantastical 2 is the Mac calendar you'll actually enjoy using. Creating an event with Fantastical is quick, easy, and fun: Open Fantastical with a single click or keystroke Type in your event... Read more
PCalc 4.4.4 - Full-featured scientific c...
PCalc is a full-featured, scriptable scientific calculator with support for hexadecimal, octal, and binary calculations, as well as an RPN mode, programmable functions, and an extensive set of unit... Read more

Latest Forum Discussions

See All

Track Santa with these three festive app...
Christmas is fast approaching and that means it's time to prepare for Santa's yearly pilgrimage around the globe. Christmas Eve is an exciting time as parents help their kids get ready to welcome Santa. You've got the cookies and milk all planned... | Read more »
Galaxy on Fire 3 and four other fantasti...
Galaxy on Fire 3 - Manticore brings the series back for another round of daring space battles. It's familiar territory for folks who are familiar with the franchise. If you've beaten the game and are looking to broaden your horizons, might we... | Read more »
The best apps for your holiday gift exch...
What's that, you say? You still haven't started your holiday shopping? Don't beat yourself up over it -- a lot of people have been putting it off, too. It's become easier and easier to procrastinate gift shopping thanks to a number of apps that... | Read more »
Toca Hair Salon 3 (Education)
Toca Hair Salon 3 1.0 Device: iOS Universal Category: Education Price: $2.99, Version: 1.0 (iTunes) Description: | Read more »
Winter comes to Darkwood as Seekers Note...
MyTona, based in the chilly Siberian city of Yakutsk, has brought a little festive fun to its hidden object game Seekers Notes: Hidden Mystery. The Christmas update introduces some new inhabitants to players, and with them a chance to win plenty of... | Read more »
Bully: Anniversary Edition (Games)
Bully: Anniversary Edition 1.03.1 Device: iOS Universal Category: Games Price: $6.99, Version: 1.03.1 (iTunes) Description: *** PLEASE NOTE: This game is officially supported on the following devices: iPhone 5 and newer, iPod Touch... | Read more »
PINE GROVE (Games)
PINE GROVE 1.0 Device: iOS Universal Category: Games Price: $1.99, Version: 1.0 (iTunes) Description: A pine grove where there are no footsteps of people due to continuous missing cases. The case is still unsolved and nothing has... | Read more »
Niantic teases new Pokémon announcement...
After rumors started swirling yesterday, it turns out there is an official Pokémon GO update on its way. We’ll find out what’s in store for us and our growing Pokémon collections tomorrow during the Starbucks event, but Niantic will be revealing... | Read more »
3 reasons why Nicki Minaj: The Empire is...
Nicki Minaj is as business-savvy as she is musically talented and she’s proved that by launching her own game. Designed by Glu, purveyors of other fine celebrity games like cult favorite Kim Kardashian: Hollywood, Nicki Minaj: The Empire launched... | Read more »
Clash of Clans is getting its own animat...
Riding on its unending wave of fame and success, Clash of Clans is getting an animated web series based on its Clash-A-Rama animated shorts.As opposed to the current shorts' 60 second run time, the new and improved Clash-A-Rama will be comprised of... | Read more »

Price Scanner via MacPrices.net

Never Settle for Low Performing Wifi With iOS...
AppYogi Software has announced the release of WiFi Signal Strength Status App 1.0, the company’s new utility developed exclusively for macOS. WiFi Signal Strength Status App features a unique, single... Read more
New 2016 13-inch Touch Bar MacBook Pros in st...
B&H Photo has stock of new 2016 Apple 13″ Touch Bar MacBook Pro models, each including free shipping plus NY sales tax only: - 13″ 2.9GHz/512GB Touch Bar MacBook Pro Space Gray: $1999 - 13″ 2.... Read more
New 2016 15″ Touch Bar MacBook Pros in stock...
B&H Photo has new 2016 Apple 15″ Touch Bar MacBook Pro models in stock today including free shipping plus NY sales tax only: - 15″ 2.7GHz Touch Bar MacBook Pro Space Gray: $2799 - 15″ 2.7GHz... Read more
DietSensor App Targeting Diabetes and Obesity...
DietSensor, Inc., a developer of smart food and nutrition applications designed to fight diabetes and obesity and help improve overall fitness, has announced the launch of its DietSensor app for... Read more
Holiday 2016 13-inch 2.0GHz MacBook Pro sales...
B&H has the non-Touch Bar 13″ MacBook Pros in stock today for $50-$100 off MSRP. Shipping is free, and B&H charges NY sales tax only: - 13″ 2.0GHz MacBook Pro Space Gray (MLL42LL/A): $1449 $... Read more
Holiday sale: Apple TVs for $51-$40 off MSRP,...
Best Buy has dropped their price on the 64GB Apple TV to $159.99 including free shipping. That’s $40 off MSRP. 32GB Apple TVs are on sale right now for $98 on Sams Club’s online store. That’s $51 off... Read more
12-inch Retina MacBooks, Apple refurbished, n...
Apple has restocked a full line of Certified Refurbished 2016 12″ Retina MacBooks, now available for $200-$260 off MSRP. Refurbished 2015 models are available starting at $929. Apple will include a... Read more
Holiday sale: 12-inch Retina MacBook for $100...
B&H has 12″ Retina MacBooks on sale for $100 off MSRP as part of their Holiday sale. Shipping is free, and B&H charges NY sales tax only: - 12″ 1.1GHz Space Gray Retina MacBook: $1199 $100... Read more
Apple refurbished 13-inch MacBook Airs availa...
Apple has Certified Refurbished 13″ MacBook Airs available starting at $849. An Apple one-year warranty is included with each MacBook, and shipping is free: - 13″ 1.6GHz/8GB/128GB MacBook Air: $849 $... Read more
Apple refurbished iMacs available for up to $...
Apple has Certified Refurbished 2015 21″ & 27″ iMacs available for up to $350 off MSRP. Apple’s one-year warranty is standard, and shipping is free. The following models are available: - 21″ 3.... Read more

Jobs Board

*Apple* Retail - Multiple Positions - Apple,...
Job Description: Sales Specialist - Retail Customer Service and Sales Transform Apple Store visitors into loyal Apple customers. When customers enter the store, Read more
US- *Apple* Store Leader Program - Apple (Un...
…Summary Learn and grow as you explore the art of leadership at the Apple Store. You'll master our retail business inside and out through training, hands-on Read more
Automotive Detailer - *Apple* Used Autos -...
We are currently conductinginterviews and will be accepting applications for a part-time detailer. Apple Used Autos is a great place to work andstart a career. We Read more
*Apple* Retail - Multiple Positions - Apple,...
Job Description: Sales Specialist - Retail Customer Service and Sales Transform Apple Store visitors into loyal Apple customers. When customers enter the store, Read more
*Apple* Retail - Multiple Positions- Trumbul...
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
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.