Friday, October 12, 2012

Supporting Retina Displays

Update (2013-07-17): Updated Info.plist change to use a boolean rather than a string.
Update (2013-01-22): Note that sample code and app requires OS X Lion 10.7.4 or newer

Retina displays are displays that have extremely high pixel resolution.  These displays use more than one pixel to draw information on the screen resulting in much sharper text and other UI elements.  They also allow for higher resolution pictures to fit in a smaller space on the screen. Currently, only Apple offers retina-type displays with their iPad, iPhone, iPod touch and Macbook Pro products.

With a bit of tweaking, your Real Studio Cocoa applications can be made retina-aware so that UI elements and graphics draw at high quality when run on a retina MacBook Pro.

This is a two-step process. First, you need to update the Info.plist in your application bundle to enable retina support.

Second, if your application has graphics, you need to update them in order for them to appear at retina quality. Generally this means that you need to provide much larger graphics and then scale them down to the size you need.  Usually you supply a 2x resolution image and then scale it to fit the available area.  OS X will automatically handle this.

For some graphics, you may find that having separate pre-scaled graphics for retina and non-retina displays work best.  For example, you might want to swap out a 1x image when on non-retina displays, but use a 2x image when on a retina display.  To do this, you need to ask OS X what the scale factor is so that you can determine which image to use.

Enabling Retina Support

To enable retina support, you need to add a special key (above the </dict> entry near the end) to the Info.plist file in the application bundle:

<key>NSHighResolutionCapable</key>
<true/>

This enables retina support for the UI elements, such as buttons and text.

Because you need to edit the plist before the application runs, it can be hard to test this in debug mode.  One solution is to use the “Run Paused” feature.  This will build your application and pause before running it.  You can make the plist changes while it is paused and then continue to run it.  Alternatively, you can use Build Automation to add this key to the plist file after your application is built.

Note: If you do not have a retina MacBook Pro, you can still see how a retina application looks.  One option is to use an iPad with retina display as a extra display for your Mac (using an app such as AirDisplay). Or you can download Quartz Debug from Apple (in their Graphics Tools) and use it to enable HiDPI display modes for your display. Once you do this (select Window->UI Resolution and "Enable HiDPI display modes), extra display modes (with greatly reduced resolution) appear in your display preferences. For example, on my 1920x1200 display, the best HiDPI mode I can select is 960x600.

Retina Graphics

If you just add large (2x) graphics to your projects and scale them down to fit when drawing them, then you automatically get the high resolution graphics used on a retina display.  A non-retina display uses the scaled version.

But using a scaled version can sometimes lead to non-optimal results, so Apple recommends having two separate images included in your application, one for retina displays and one for non-retina displays.

In order to detect if your application is running on a retina display, you can check the BackingScaleFactor function in the Cocoa API (available on OS X 10.7 and newer).  Add this code to a Window:

Function ScalingFactor() As Single
#If TargetCocoa Then
  Declare Function BackingScaleFactor Lib "AppKit" Selector "backingScaleFactor" (target As WindowPtr) As Double
  Return BackingScaleFactor(Self)
#Else
  Return 1
#Endif
End Function

The ScalingFactor is 2 for a Retina MacBook Pro (or other HiDPI modes) and 1 for anything else, but the values could change depending on type of retina display.  You can use this value to determine the images size to use or for other scaling you may need.

Note that this only applies for bitmap graphics that are drawn on the Graphics instance in the Paint event handler.  Vector graphics scale automatically.

If you are manually double-buffering your graphics, you may find that they are not automatically drawn in retina quality.  Rather than double-buffering yourself, you should instead draw directly to the Graphics instance in the Paint event handler.  The DoubleBuffer property of Canvas works with retina displays.


Retina Graphics Sample Project

This sample project includes a built app that runs in retina mode on the retina MacBook Pro. You can see the difference quite readily.
Non-Retina Example

Retina Example




4 comments:

Obleo said...

Great Info, thanks for sharing:)

Obleo said...
This comment has been removed by the author.
Stephen said...

If you want it to display retina during a debug build, add a new Build Automation IDE script and paste this in:

Dim App As String = CurrentBuildLocation + "/""" + CurrentBuildAppName + ".app"""
Call DoShellCommand("/usr/bin/defaults write " + App + "/Contents/Info ""NSHighResolutionCapable"" YES")

Markus Winter said...

On downloading and running the example I get

An exception of class ObjCException was not handled. The application must
shut down.

Exception Message: -[RBNSWindow backingScaleFactor]: unrecognized selector
sent to instance 0x414850

???

Could this be because I'm running on MacOS X 10.6.8?