Vincent Gable’s Blog

June 3, 2008

AppleScript is the Uncanny Valley

Filed under: Design,MacOSX,Programming,Quotes,Usability | , , ,
― Vincent Gable on June 3, 2008

A interesting theory:

I think this “like English but not quite” aspect of AppleScript is the Uncanny Valley of programming languages. Because AppleScript looks like English it is easy to fall into the trap of believing it has the flexibility of English. When that mental model fails its more unsettling than when you screw up the syntax in a regular programming language because your mental model isn’t making unwarranted assumptions.

Mark Reid

May 31, 2008

Messages to Nowhere

Filed under: Bug Bite,Cocoa,iPhone,MacOSX,Objective-C,Programming | , , ,
― Vincent Gable on May 31, 2008

If you send a message to (call a method of) an object that is nil (NULL) in Objective-C, nothing happens, and the result of the message is nil (aka 0, aka 0.0, aka NO aka false). At least most of the time. There is an important exception if sizeof(return_type) > sizeof(void*); then the return-value is undefined under PowerPC/iPhone, and thus all Macs for the next several years. So watch out if you are using a return value that is a struct, double, long double, or long long.

The fully story:

In Objective-C, it is valid to send a message to a nil object. The Objective-C runtime assumes that the return value of a message sent to a nil object is nil, as long as the message returns an object or any integer scalar of size less than or equal to sizeof(void*).
On Intel-based Macintosh computers, messages to a nil object always return 0.0 for methods whose return type is float, double, long double, or long long. Methods whose return value is a struct, as defined by the Mac OS X ABI Function Call Guide to be returned in registers, will return 0.0 for every field in the data structure. Other struct data types will not be filled with zeros. This is also true under Rosetta. On PowerPC Macintosh computers, the behavior is undefined.

I was recently bitten by this exceptional behavior. I was using an NSRange struct describing a substring; but the string was nil, so the substring was garbage. But only on a PPC machine! Even running under Rosetta wouldn’t have reproduced the bug on my MacBook Pro.Undefined values can be a hard bug to detect, because they may be reasonable values when tests are run.

Code running in the iPhone simulator will return all-zero stucts when messaging nil, but the same code running on an actual device will return undefined structs. Be aware that testing in the simulator isn’t enough to catch these bugs.

A few simple guidelines can help you avoid my misfortune:

  • Be especially careful using of any objective-C method that returns a double, struct, or long long
  • Don’t write methods that return a double, struct, orlong long. Return an object instead of a struct; an NSNumber* or float instead of a double or long long. If you must return a dangerous data type, then see if you can avoid it. There really isn’t a good reason to return a struct, except for efficiency. And when micro-optimizations like that matter, it makes more sense to write that procedure in straight-C, which avoids the overhead of Objective-C message-passing, and solves the undefined-return-value problem.
  • But if you absolutely must return a dangerous data type, then return it in a parameter.
    Bad:
    - (double) crazyMath;
    Good:
    - (void) crazyMathResult:(double*)result;.

I love Objective-C’s “nil messaging” behavior, even though it is rough around the edges. It’s usefulness is beyond the scope of this article, but it can simplify your code if you don’t return a data-type that is larger then sizeof(void*). With time, when the intel-style return behavior can be universally relied on, things will be even better.

May 28, 2008

The Minimum Screen Size You Must Support for Mac OS X Is 800×600

Filed under: Accessibility,Bug Bite,Design,MacOSX,Programming,Quotes,Usability |
― Vincent Gable on May 28, 2008

Mac OS X can run on systems with a screen size as small as 800 x 600 … Unless you know that your users will be using a specific display size, it is best to optimize your applications for display at 1024 x 768 pixels. … Design your user interface for a resolution of at least 800 x 600.

According to Apple’s Human Interface Guidelines (retrieved 2010-04-21).

May 25, 2008

Objects that Won’t Hide

Filed under: Bug Bite,Cocoa,Interface Builder,MacOSX,Objective-C,Programming |
― Vincent Gable on May 25, 2008

NOTE: Although this specific Bug Bite is about NSTextView, and the “hidden” property, the same underlying issue applies to other interface-objects (NSTableView, etc.), and different properties, like size.

Problem

If you send a setHidden:YES message to an NSTextView, and it’s text disappears, but the view itself (white box) stays visible here’s the problem, and the solution.

It turns out that if you created the NSTextView by dragging it off the pallet in Interface Builder, then it’s not an NSTextView. It’s an NSTextView wrapped inside an NSClipView inside an NSScrollView. The NSScrollView is what puts up the scroll-bars if the NSTextView gets really big; the NSClipView helps make the scrolling work.

So if text is your IBOutlet to your NSTextView, then when you say [text setHidden:YES];, the NSTextView is hidden, but the the total package won’t disappear, unless you hide the NSScrollView as well.

Solutions

You can send the message to NSScrollView containing text, like so:
   [[text enclosingScrollView] setHidden:YES];.
This will hide everything inside the NSScrollView, including text.

Another solution is to create just an NSTextView in Interface Builder. To do this, put an NSView in your interface (it’s called a “Custom View”,in the Interface Builder objects pallet). Then select it, bring up the object inspector (cmd-shift-i), choose the “custom class” from the category menu at the top, and select NSTextView from the list of subclasses. This puts an NSTextView in your interface, without the surrounding clip and scroll views. Unfortunately, it also means you can’t configure it in Interface Builder, beyond resizing it. That’s why I’m not partial to this approach, although I have used it.

Thanks to ZachR for suggesting enclosingScrollView.

May 14, 2008

NSAlert + Sheets + Threads = Inexplicable Bugs

Filed under: Bug Bite,Cocoa,Interface Builder,MacOSX,Objective-C,Programming | , ,
― Vincent Gable on May 14, 2008

UPDATED 2008-12-26: in general, all AppKit code should be called on the main thread.

Problem:
When using an NSAlert to display a sheet in a multi-threaded application, unexpected badness can happen.

I was using
beginSheetModalForWindow:modalDelegate:didEndSelector:contextInfo:
To display an NSAlert as a sheet.

But when the sheet appeared, the window it was attached to disappeared and got into some weird broken state where it would appear iff the application was not frontmost.

Fortunately, I remembered having encountered weirdness with NSAlert sheets before. The symptoms were different (previously the alert didn’t have focus), but the same solution still worked.

Solution: make sure the message to display the sheet is sent by the main thread. To do this, put the call to beginSheetModalForWindow:modalDelegate:didEndSelector:contextInfo: inside another method, showMyAlert, then use performSelectorOnMainThread:withObject:waitUntilDone: to make sure showMyAlert is called on the main thread.

Work around use runModal to display the alert as a modal dialog instead of a sheet. runModal Does not appear to have any problems when called from other threads.

Just like last time:

The whole incident feels funny to me. I suspect there may be some deeper issue at work that I am not aware of. When I have time to investigate further I shall update this post. Unfortunately I don’t have time to look into ‘solved’ bugs today.

UPDATED 2008-12-26: in general, all AppKit code should be called on the main thread.

May 13, 2008

Fixing Blurry Images

Filed under: Bug Bite,MacOSX,Programming
― Vincent Gable on May 13, 2008

I had an issue today where an image (A .png at 72 DPI with transparency, inside an NSImageView — if that makes any difference) in a window was blurry. The image was perfectly sharp in Interface Builder’s library panel, but when I placed it in a window, it turned blurry. Maddening!

A (mysterious) workaround: put the image where you want it. Then move it, then hit undo. When it snaps back to where you want it, it should be sharp.

I have absolutely no idea why this worked, or why the images were blurred by Interface Builder/XCode in the first place. (And yes, the images were blurry when the application was built and run. It wasn’t just a display bug in Interface Builder). It was purely coincidence that I stumbled across this “solution”.

May 4, 2008

Getting Mac OS X Version Information

Filed under: MacOSX,Objective-C,Programming,Sample Code,UNIX | , ,
― Vincent Gable on May 4, 2008

Cocoa

For a human-readable string, use [[NSProcessInfo processInfo] operatingSystemVersionString]. It looks like “Version 10.5 (Build 9A581)”, but that format might change in the future. NSProcessInfo.h explicitly warns against using it for parsing. It will always be human-readable though.

For machine-friendly numbers, use the Gestalt function, with one of the selectors:
gestaltSystemVersionMajor (in 10.4.17 this would be the decimal value 10)
gestaltSystemVersionMinor (in 10.4.17 this would be the decimal value 4)
gestaltSystemVersionBugFix (in 10.4.17 this would be the decimal value 17)
Do not use gestaltSystemVersion unless your code needs to run on OS X 10.2 or earlier. It’s a legacy function that can’t report minor/bugfix versions > 9; meaning it can’t distinguish between 10.4.9 and 10.4.11. The CocoaDev wiki has example code that works in 10.0 if you need to go that route.

Here’s a simpler example of using Gestalt:

- (BOOL) usingLeopard {
  long minorVersion, majorVersion;
  Gestalt(gestaltSystemVersionMajor, &majorVersion);
  Gestalt(gestaltSystemVersionMinor, &minorVersion);
  return majorVersion == 10 && minorVersion == 5;
}

Scripts

For scripts and command-line work, there is the sw_vers command.
$ sw_vers
ProductName: Mac OS X
ProductVersion: 10.5
BuildVersion: 9A581
$ sw_vers -productName
Mac OS X
$ sw_vers -productVersion
10.5
$ sw_vers -buildVersion
9A581

Java

Check the value of:

System.getProperty("os.name");
System.getProperty("os.version");

Fallback

Finally, if you must, you can parse /System/Library/CoreServices/SystemVersion.plist — but I don’t like the idea of doing that. There’s a chance that the file could have been corrupted or maliciously altered. It seems safer, and less complicated, to directly ask the operating system it’s version.

Minimum OS Requirements

To enforce a minimum OS requirement for your application, you can set the LSMinimumSystemVersion key in the Info.plist file. Big Nerd Ranch has a more robust, user-friendly, but complicated solution for older legacy systems.

April 23, 2008

Printing a FourCharCode

Filed under: Bug Bite,C++,Cocoa,MacOSX,Programming,Sample Code,Tips | , , , ,
― Vincent Gable on April 23, 2008

A lot of Macintosh APIs take or return a 32-bit value that is supposed to be interpreted as a four character long string. (Anything using one of the types FourCharCode, OSStatus, OSType, ResType, ScriptCode, UInt32, CodecType probably does this.) The idea is that the value 1952999795 tells you less, and is harder to remember, then 'this'.

It’s a good idea, unfortunately there isn’t a simple way to print integers as character strings. So you have to write your own code to print them out correctly, and since it has to print correctly on both big endian and little endian machines this is tricker then it should be. I’ve done it incorrectly before, but more importantly so has Apple’s own sample code!

Here are the best solutions I’ve found. Please let me know if you have a better way, or if you find any bugs. (The code is public-domain, so use it any way you please.)

FourCharCode to NSString:
use NSString *NSFileTypeForHFSTypeCode(OSType code);
Results are developer-readable and look like: @"'this'", but Apple says “The format of the string is a private implementation detail”, so don’t write code that depends on the format.

FourCharCode to a C-string (char*):
use the C-macro

#define FourCC2Str(code) (char[5]){(code >> 24) & 0xFF, (code >> 16) & 0xFF, (code >> 8) & 0xFF, code & 0xFF, 0}
(requires -std=c99; credit to Joachim Bengtsson). Note that the resulting C-string is statically allocated on the stack, not dynamically allocated on the heap; do not return it from a function.
If you want a value on the heap, try:


void fourByteCodeString (UInt32 code, char* str){
  sprintf(str, "'%c%c%c%c'",
    (code >> 24) & 0xFF, (code >> 16) & 0xFF,
    (code >> 8) & 0xFF, code & 0xFF);
}

In GDB, you can print a number as characters like so:

(gdb) print/T 1936746868
$4 = 'spit'

(via Borkware’s GDB tips)

If you do end up rolling your own FourCharCode printer (and I don’t recommend it!), then be sure to use arithmetic/shifting to extract character values from the integer. You can not rely on the layout of the integer in memory, because it will change on different machines. Even if you are certain that you’re code will only run on an x86 machine, it’s still a bad idea. It limits the robustness and reusability of your code. It sets you up for an unexpected bug if you ever do port your code. And things change unexpectedly in this business.

Printing a FourCharCode is harder then it should be. Experienced developers who should know better have been bitten by it (and so have I). The best solution is probably a %format for printf/NSLog that prints integers as character strings. Unfortunately, it doesn’t look like we’ll be seeing that anytime soon.

April 11, 2008

NSWindow setResizable:

Filed under: Cocoa,MacOSX,Objective-C,Programming,Sample Code | ,
― Vincent Gable on April 11, 2008

-[NSWindow setResizable:(BOOL)] does not exist. But, here is a way to get that effect as long as the window was resizable when created. (I don’t know of a way to make a window resizeable if it was first created unresizeable).

The basic idea is to show/hide the resizing controls, and implement delegate functions that prevent the window from being resized when the resizing controls are hidden.

//show or hide hide the window's resizing controls:
[[window standardWindowButton:NSWindowZoomButton] setHidden:!resizable];
[window setShowsResizeIndicator:resizable];

Note that setShowsResizeIndicator: only shows/hides the resize-indicator in the lower-right of the window. It will not make the window non-resizeable.

To keep the window from being resized when showsResizeIndicator is false, implement the delegate methods:

- (NSSize)windowWillResize:(NSWindow *) window toSize:(NSSize)newSize
{
	if([window showsResizeIndicator])
		return newSize; //resize happens
	else
		return [window frame].size; //no change
}

- (BOOL)windowShouldZoom:(NSWindow *)window toFrame:(NSRect)newFrame
{
	//let the zoom happen iff showsResizeIndicator is YES
	return [window showsResizeIndicator];
}

No Delegate Hack

Finally, there is a hack that avoids using delegate methods, although I don’t recommend it.

[[window standardWindowButton:NSWindowZoomButton] setHidden:!resizable];
[window setShowsResizeIndicator:resizable];
CGFloat resizeIncrements = resizable ? 1 : MAXFLOAT;
[window setResizeIncrements:NSMakeSize(resizeIncrements, resizeIncrements)];

setResizeIncrements “Restricts the user’s ability to resize the receiver so the width and height change by multiples of width and height increments.” So setting it to MAXFLOAT x MAXFLOAT means that the user can’t resize the window, because they won’t have a big enough screen. Note that this won’t disable the “zoom” function, although the user probably couldn’t use it with the zoom button hidden.

This approach uses slightly fewer lines of code, but it is more brittle and indirect, since it depends on the screen-size, not if the window is (visibly) resizeable. I wouldn’t use it over delegates. But here it is.

March 31, 2008

Resolution Independence

Filed under: Accessibility,Design,MacOSX,Programming,Research,Usability | , , ,
― Vincent Gable on March 31, 2008

Displays are getting sharper all the time. Sharpness is very important, because things don’t just look better on high-resolution displays; they are measurably faster to use.

according to Jakob Nielsen:

Low-resolution monitors (including all computer screens until now) have poor readability: people read about 25% slower from computer screens than from printed paper. Experimental 300 (PPI) displays (costing $30,000 (in 1998)) have been measured to have the same reading speed as print, so we will get better screens in the future.

The resolution of a display is measured in PPI, Pixels Per Inch, the number of pixels used to draw a one inch long, one pixel thick, line. A higher PPI means smaller pixels, and clearer images. You can calculate the PPI of your screen by

PPI ≅ sqrt(pixels_across^2 + pixels_down^2) / diagonal_inches

I am writing this article on an iMac with a 24″ screen, showing 1920 x 1200 pixels, so my PPI ≅ sqrt(1920*1920+1200*1200)/24 ≅ 93 PPI.

The resolution of a printer is measured in DPI, or Dots Per Inch. Unfortunately, DPI is often used as another term for PPI. That is technically inaccurate, but even Microsoft does it. SPI, Scans Per Inch, is another term that is used instead of PPI, but fortunately is correct when describing digital displays.

Bitmapped images are smaller on high-resolution displays, because the pixels the picture is made of are smaller. This can be a big problem for interface elements.

For example, today Apple’s interface guidelines say “The standard Help button is 20 pixels in diameter and should be placed at least 12 pixels from other interface elements.” On my iMac, a 20-pixel wide circle is about 0.21″ in diameter — roughly 1/3rd the diameter of a Dime. help_button_today.png But on a 300 PPI display, it would be 0.07″ in diameter — roughly as thick as three pieces of mechanical pencil lead help_button_300ppi.png. As you can see, it’s way too small to comfortably click!

Just scaling bitmapped images, so a 20-pixel wide picture is always drawn 0.21″ wide, isn’t a good work around. It’s just emulating a low-definition, low-readability, display on a more expensive high-definition display. Another problem is that it looks terrible when everything else on the system is crystal clear.

help_button_300_scaled_up.png

It gets more ugly when the scaling-factor is higher, say for a display that matched the print fidelity of the tawdry magazines you see in the grocery check-out isle.

A resolution independent interface can be usably, and pleasantly, displayed on high-definition or standard-definition screens. There are two ways to build a resolution-independent display.

You can use vector images, which can be resized without problems. SVG and PDF are two popular formats. This sounds like an elegant “design once, show anywhere” solution. But in practice today building a vector image of the same quality as a bitmapped image can be very difficult, especially with complex images. This will change in time. Today designers create bitmap images by embellishing a vector image with bitmap-effects. As vector-imiging tools improve, they will be able to perform equivalent effects, but in a resolution-independent way.

Alternately, your app can carry-around a set of bitmapped images and draw the one that most-closly matches the current resolution. If you only have to support a handful of well-definied resolutions, this might be the best choice with today’s tools. The only caveat is that all signs point to vector-images being the future. Loading one vector image is also easier the selecting one of many bitmapped images to load. (And theoretically a vector image won’t have to be tweaked if we suddenly get one million PPI displays).

Already Solved

I think it’s important to note that Fonts have been facing an even more severe resolution-independence problem for decades, and have mostly solved it. The same font can be rendered well on a 1200 DPI printer, and a 72 PPI display. That’s a much bigger resolution disparity then a computer interface will ever have to deal with, because by the time a screen close to the resolution of a 1200 DPI printer is common, nobody will be using 72 PPI displays anymore (they aren’t even sold today).

Early fonts were bitmap fonts — a collection of bitmaps of what the font should look like at a particular scaling. But today vector fonts have replaced bitmapped fonts on the desktop, and even the iPhone. Vector fonts let us do amazing things with scaling (font specific stuff starts at 1:18). Vector fonts have been such an unqualified success, that it’s hard to imagine vector images not replacing bitmapped images for interface elements.

When Will It Happen

Jeff Atwood “did some research to document how far we’ve come in display resolution over the last twenty years.”


Year

Model

Resolution
Size DPI
1984
Original Macintosh
512 x 342 9″ (8.5″) 72
1984

IBM PC AT

640 x 350 13″ (12.3″) 60
1994
Apple Multiple Scan 17 Display
1024 x 768 17″ (16.1″) 80
2004

Apple Cinema HD display

2560 x 1600 30″ 100

I used the Tag studios Monitor DPI calculator to arrive at the DPI numbers in the above table. I couldn’t quite figure out what the actual displayable area of those early CRT monitors were, so I estimated about 5% non-displayable area based on the diagonal measurement.

Regardless, it’s sobering to consider that the resolution of computer displays has increased by less than a factor of two over the last twenty years. Sure, displays have gotten larger– much larger– but actual display resolution in terms of pixels per inch has only gone up by a factor of about 1.6.

I can’t think of any other piece of computer hardware that has improved so little since 1984.

It certainly doesn’t not look like we will get 300 PPI displays anytime soon! At the present rate, it will be decades before a $2000 display matches the clarity of a $20 printer.

However today’s smarphones and small-devices have much sharper screens then computers. The first generation iPhone is 160 PPI, the Kindle is 167 PPI. Now these are much smaller screens then full-sized computers use, and I don’t know how cost-effective it is to scale the screens up from 3.5″ to 30″. But I do know that significantly higher resolution displays are being produced in increasing volume today for smartphone. So we may be fortunate enough to have, say 180 PPI, displays many years sooner then Atwood’s data suggests.

UPDATE 2009-02-11: Wikipedia’s list of devices by PPI lists some very high PPI devices. The Sony Ericsson X1
phone has broken the 300 PPI “readability threshold”
. The Fujitsu Lifebook U820 has a 270 PPI display. But the screen is only slightly bigger then the Osborne 1 “portable” computer of 1981. Still, if history is any indicator, once better technology is actually sold, it catches on very quickly.

« Newer PostsOlder Posts »

Powered by WordPress