PATH Documentation > Release Notes
Mac OS X Leopard Developer Release Notes
Cocoa Application Framework
The Cocoa Application Framework (also referred to as the Application Kit, or AppKit) is one of the core Cocoa frameworks. It provides functionality and associated APIs for applications, including objects for graphical user interfaces (GUIs), event-handling mechanisms, application services, and drawing and image composition facilities.
This document describes the changes in AppKit since Mac OS X release 10.4. Changes in these notes since the WWDC 2007 seed of Leopard are highlighted (search for "WWDC 2007").
Note that there is more information on many of these changes in Apple's developer documentation. In some cases header files may also have additional details.
For AppKit release notes for 10.4 (aka Tiger) and earlier, please refer to the older release notes for AppKit.
Feedback
For feedback and comments about AppKit and Foundation APIs and features, you can email cocoa-feedback@group.apple.com. The intent of this address is to provide developers with a direct channel to the engineering team and the technology managers responsible for these technologies. We will read your input, and will try to acknowledge it, but we might not always have time for significant replies. Please use this address for feedback and comments on the APIs and features (for instance "please add Cocoa wrapper classes for such-and-such" or "I need API to do this and that"), but not for support type questions (for instance, "I can't install OS X" or "How does one create a new bundle in Xcode?") or comments in other areas (for instance, "why is the dock centered in Aqua?").
New features and significant changes in Leopard
The following are some the new features in AppKit, Foundation, and related areas. Note that the below table of contents is not a complete list of everything that is in this document, but it should provide a general idea of the important areas covered.
AppKit (these are described in this file):
- Support for 64-Bit Cocoa Applications
- Objective-C 2.0
- Interface Builder 3.0
- Animation Support
- NSView enhancements
- New class for managing views: NSViewController
- New class for grid-based animated layout of array of objects: NSCollectionView (originally known as NSGridView)
- Resolution Independence Improvements
- New controls for editing rules and predicates: NSRuleEditor, NSPredicateEditor
- New class for managing tracking areas: NSTrackingArea
- New class for gradient support: NSGradient
- New controller class for managing dictionary contents: NSDictionaryController
- NSTreeController improvements; new class for representing individual nodes in trees: NSTreeNode
- NSArrayController improvements
- Advanced searching, icon mode, and other features in open/save panels
- New control for displaying file system (or virtual) paths: NSPathControl
- NSMenu custom view support and other enhancements
- Print and page layout panel improvements
- Support for Uniform Type Identifiers (UTIs) in NSDocument, open/save panel, and a number of other classes
- Metadata preservation during NSDocument saving
- Support for Open Document and Open XML document formats in the text system
- Performance improvements in the text system ("non-contiguous layout")
- Changes in NSInputManager's input manager bundle loading
- NSTextView enhancements such as find panel improvements, temporary find indicator, smart quote and link support, list enhancements, etc
- Grammar checking support
- Numerous NSTableView and NSOutlineView enhancements
- NSDatePicker improvements
- NSSound API additions
- NSSpeechSynthesizer enhancements
- NSWindow changes and new window appearance, including support for Heads Up Display (HUD) window style
- Support for accessory view in NSAlert
- New class for representing dock tiles: NSDockTile
- NSSplitView enhancements
- Support for text and image effects in cells
- Standard images in NSImage
- NSBitmapImageRep enhancements
- New properties on NSBox
- NSToolbar enhancements
- Accessibility improvements
- Other enhancements and fixes
Foundation (these are described in the Foundation release notes):
Backward Compatibility
One backward compatibility mechanism that is occasionally used in the frameworks is to check for the version of the SDK an application was built against, and if an older SDK, modify the behavior to be more compatible. This is done in cases where bad incompatibility problems are predicted or discovered; and most of these are listed below in these notes.
Typically we detect where an application was built by looking at the version of the system frameworks the application was linked against. Thus, as a result of relinking your application on Leopard or against Leopard SDK, you might notice different behaviors, some of which might cause incompatibilities. In these cases because the application is being rebuilt, we expect you to address these issues at the same time as well. For this reason, if you are doing a small incremental update of your application to address a few bugs, it's usually best to continue building on the same build environment and libraries used originally, or against the original SDKs.
In some cases, we provide defaults (preferences) settings which can be used to get the old or new behavior, independent of what system an application was built against. Often these preferences are provided for debugging purposes only; in some cases the preferences can be used to globally modify the behavior of an application by registering the values (do it somewhere very early, with -[NSUserDefaults registerDefaults]).
Marking new APIs in headers
New APIs in headers are marked with the constructs:
#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5
...
#endif
or
- (void)aNewMethod AVAILABLE_MAC_OS_X_VERSION_10_5_AND_LATER;
The basic definitions for these come from AvailabilityMacros.h, a standard system header. This is included from Cocoa.h, AppKit.h, and Foundation.h imports.
If you do nothing, MAC_OS_X_VERSION_MAX_ALLOWED is assumed to be MAC_OS_X_VERSION_10_5.
Runtime Version Check
There are several ways to check for new features provided by the Cocoa frameworks at runtime. One is to look for a given new class or method dynamically, and not use it if not there. Another is to use the global variable NSAppKitVersionNumber (or, in Foundation, NSFoundationVersionNumber):
APPKIT_EXTERN double NSAppKitVersionNumber;
#define NSAppKitVersionNumber10_0 577
#define NSAppKitVersionNumber10_1 620
#define NSAppKitVersionNumber10_2 663
#define NSAppKitVersionNumber10_3 743
#define NSAppKitVersionNumber10_4 824
One typical use of this is to floor() the value, and check against the values provided in NSApplication.h:
if (floor(NSAppKitVersionNumber) <= NSAppKitVersionNumber10_0) {
/* On a 10.0.x or earlier system */
} else if (floor(NSAppKitVersionNumber) <= NSAppKitVersionNumber10_1) {
/* On a 10.1 - 10.1.x system */
} else if (floor(NSAppKitVersionNumber) <= NSAppKitVersionNumber10_2) {
/* On a 10.2 - 10.2.x system */
} else if (floor(NSAppKitVersionNumber) <= NSAppKitVersionNumber10_3) {
/* On a 10.3 - 10.3.x system */
} else if (floor(NSAppKitVersionNumber) <= NSAppKitVersionNumber10_4) {
/* On a 10.4 - 10.4.x system */
} else {
/* Leopard or later system */
}
Special cases or situations for version checking are also discussed in the release notes as appropriate. For instance some individual headers may also declare the versions numbers for NSAppKitVersionNumber where some bug fix or functionality is available in a given update, for example:
#define NSAppKitVersionWithSuchAndSuchBadBugFix 582.1
64-Bit
Leopard contains 64-bit versions of many system frameworks, enabling building and running many Cocoa apps as 64-bit.
There are some API changes in Cocoa to accomodate and enable 64-bit. Please refer to the Foundation release notes for more information on 64-bit for Cocoa.
intValue methods under 64-Bit
As described in the Foundation release notes, we have added NSInteger taking-variants of "intValue" and related methods. For the AppKit, this means the following new methods in both NSCell and NSControl:
- (NSInteger)integerValue;
- (void)setIntegerValue:(NSInteger)val;
- (void)takeIntegerValueFrom:(id)sender;
The existing intValue and related methods continue to take the native int type, which is 32-bit on both 32 and 64-bit platforms. These are among the very few methods remaining in Cocoa that take native int or unsigned int, rather than NSInteger or NSUInteger.
NSOpenGL and 64-Bit
The AppKit API declarations in NSOpenGL.h have been changed to use standard OpenGL types (GLint, GLsizei, GLenum, and GLbitfield) in place of explicit C "int" and "long" types, to help facilitate the transition to 64-bit. The effective sizes and signedness of the parameters and return values remain the same as before for 32-bit development.
APIs removed in 64-Bit AppKit
A number of classes have been removed in 64-bit version of AppKit:
- NSMenuView - You can use NSView customizations on NSMenuItem
- NSMovieView - Use QTKit
- NSMovie - Available with very limited functionality (see here); use QTKit
- NSQuickDrawView - QuickDraw is not supported in 64-bit
- NSSimpleHorizontalTypesetter - Deprecated for a while now, use the new NSTypesetter facilities
In addition, a number of previously deprecated individual APIs have also been removed from the 64-bit AppKit, including the old Display PostScript related symbols exported solely for binary compatibility under 32-bit.
Objective-C 2.0 (Updated since WWDC 2007 Seed)
Leopard includes Objective-C 2.0, which brings a number of new features to the language and runtime: Fast enumeration, garbage collection, class extensions, properties, method and class attributes, and optional methods on protocols.
There are a number of additional runtime features available in 64-bit only: Stricter access control on private instance variables and classes, "non-fragile" instance variables, and C++ compatible, "zero-cost" exceptions (which are very cheap to set up, but expensive to throw).
It is important to note that all of these features are Leopard-only. Please refer to the Objective-C 2.0 documentation for full details on these features.
Note that many Objective-C APIs in Leopard have not yet adopted these features. But in future releases, you are likely to see:
- @property used to declare properties
- Delegate declarations switched from informal protocols (categories on NSObject) to formal protocols, with optional methods
- Increased restricted visibility of private symbols in the runtime
The last point is especially important, since it would prevent applications using these symbols from launching in future releases. As always: Please do not access undeclared APIs in your applications. This includes undeclared classes, methods, and instance variables. If you have a strong need for a private API and have no workaround, please let us know.
Interface Builder 3.0 (Updated since WWDC 2007 Seed)
Leopard developer tools includes a new version of Interface Builder (IB). IB 3.0 has a redesigned interface, a much improved integration with Xcode, and ability to access many more AppKit features in your nib files than before.
IB 3.0 supports three file formats. The 2.x file format is same file format that's been in use in Interface Builder previously. It can be edited by IB 2.0, and is deployable on earlier versions of Mac OS X. The 2.x file format does not support some new features of IB, such as the ability to edit custom cell subclasses and toolbars. The 3.x file format supports all the new features, is also deployable on earlier versions of Mac OS X, but can only be edited with IB 3.0. IB 3.0 also supports a textual, human-readable format called the "xib" format. This format is equivalent to the 3.x format, but it's more SCM-friendly. It is compiled down to a 3.x file at build time.
For editing and building your project on Leopard only, we recommend the xib format, since it provides the best development time experience. If you need to build your project on Tiger, but edit your nibs on Leopard only, then you can use the 3.x format. Finally, if you wish to be able to continue to edit your nib files on Tiger, you can stick to the 2.x format. In all these cases, the files can be deployed on Tiger, but some features supported by IB 3.0 may not work on the earlier systems. IB has facilities to warn you in those cases.
Core Animation Layer Tree Rendering (Updated since WWDC 2007 Seed)
Leopard AppKit provides the means to render a Core Animation layer tree inside the bounds of any given NSView. Clients need only provide the root CALayer of the layer tree to be displayed, then enable layer tree rendering for the same view:
[view setLayer:rootLayer];
[view setWantsLayer:YES];
AppKit responds by setting up a Core Animation renderer that animates and composites the layer tree on a background thread.
The Core Animation renderer continues its asynchronous operation until layer tree rendering is disabled for the view (via a setWantsLayer: message with a parameter of NO), or the view is hidden or removed from the window. Un-hiding a view, or adding a view that has wantsLayer enabled to a window, resumes layer tree rendering.
To conserve system resources, AppKit also suspends layer tree rendering for the views in a given window when the window is ordered out, and for all the views in a process when the user's login session is switched out via the "Fast User Switching" feature. In such cases, layer tree rendering automatically resumes when the window is ordered back in, or when the user's login session is switched back in, as appropriate.
See the documentation for the QuartzCore framework's new Core Animation API for more information about layer tree creation, capabilities, and use.
New View Animation Facilities, and Layer-Backed View Drawing (Updated since WWDC 2007 Seed)
In addition to supporting rendering of user-defined Core Animation layer trees, Leopard AppKit adopts API paradigms similar to Core Animation's to allow clients to request animation of view and window properties, and also adds a new "layer-backed" mode for rendering and animation of view trees. Use of layer-backed mode is not required in order to use AppKit's new CAAnimation-based API, but does enable the use of additional new visual treatments for views (per-view overall alpha, shadows, and the ability to apply Core Image filters to rendered content), and accelerates animation of nonresizing views for the price of caching their rendered content into per-view CALayers.
In conventional view rendering, a window's view tree is drawn back-to-front into the window's backing store. The drawn view content is thus pre-composited into a single backing store, in such a way that updating any given part of the window requires re-drawing the affected parts of all the views that contribute to the result. In the new "layer-backed" view drawing mode that is now supported in Leopard, each of the views in a layer-backed view subtree has its content drawn to its own layer in an AppKit-created-and-managed CALayer tree. This carries an additional memory cost, of order (4 * pixels wide * pixels high) bytes per view, that should be weighed when considering whether and where to employ layer-backed mode in an application's user interface, but in return it enables already-rendered view content to be moved around more efficiently during animations, since the content only has to be re-compositied instead of being re-rendered from scratch.
Most of the standard views and controls that AppKit and Mac OS X's other Cocoa frameworks provide are able to function in layer-backed mode in Leopard, with the exception of certain specialized views such as WebKit WebViews and Quartz Composer QCViews, whose use in layer-backed mode is not presently supported.
Layer Size Limits and Tiled Layers (New since WWDC 2007 Seed)
One inherent limitation of rendering view content into a Core Animation layer is that the size of ordinary CALayers is constrained by the maximum OpenGL texture size supported by the host system's graphics hardware. On most current graphics hardware the effective limit is 2046x2046 pixels, beyond which size layer creation will fail. Care should therefore be taken to insure that layer-backed views do not exceed this size limit.
To get around this limitation for potentially larger document views, AppKit employs CATiledLayers to serve as the backing layers for the document views of NSScrollViews. This specialized layer type caches its content in a grid of "tiles" (of default size 256x256 pixels) that are drawn as they become visible, and can be garbage-collected when they go out of view. From the user's perspective, the tiles are added asynchronously as they are revealed during scrolling. The visual appearance of the tile addition can be minimized by enabling the "drawsBackground" property for the enclosing NSScrollView (or, equivalently, its NSClipView), and choosing a background color that most closely matches the document view content being drawn.
Note that when using layer-backed mode for an NSScrollView's document view, it's necessary for the enclosing NSScrollView, or at least its NSClipView ("content view"), to also be layer-backed in order for scrolling to function correctly.
Using Layer-Backed Views (Updated since WWDC 2007 Seed)
A quick-start guide for beginning to experiment with this functionality:
1. Choose a view that will serve as the root view for Core Animation-based view rendering, and switch on Core Animation-based rendering for that view (and implicitly its descendants) by doing [theRootView setWantsLayer:YES];
2. Properties of views and windows for which auto-animation is supported can be animated by messaging through the target object's (view's or window's) "animator" proxy:
[[someDescendantOfTheRootView animator] setFrame:newFrame];
To specify a duration in place of the global default of 0.25 seconds, enclose such messages in an NSAnimationContext that specifies the duration for animation:
[NSAnimationContext beginGrouping];
[[NSAnimationContext currentContext] setDuration:0.25];
[[someDescendantOfTheRootView animator] setFrame:newFrame];
[NSAnimationContext endGrouping];
Basic default animation parameters are provided for the following NSView and NSWindow properties, such that they will animate automatically when assigned a new target value via the view or window's animator:
for NSView: alphaValue, frame, frameOrigin, frameSize, frameRotation, frameCenterRotation, bounds, boundsOrigin, boundsSize, backgroundFilters, contentFilters, compositingFilter, shadow
for NSWindow: alphaValue, frame
Some further notes regarding the new animation and visual property additions to NSView follow, in the "NSAnimationContext", "new NSView properties", and "NSAnimation additions" sections.
NSAnimationContext
NSAnimationContext is a new class in Leopard. NSAnimationContexts are analogous to Core Animation's CATransactions, and are also somewhat similar in overall concept to NSGraphicsContexts. Each thread maintains its own stack of nestable NSAnimationContext instances, with each new instance initialized as a copy of the instance below it (so, inheriting its current properties). Currently, an NSAnimationContext exists for the sole purpose of holding the default duration to be used for "animator"-proxy-initiated animations.
Each thread starts with a current NSAnimationContext whose default duration is 0.25 seconds (the same default value used by Core Animation), meaning that value-set operations for animatable object properties that go through "animator" proxies will animate with that duration by default. To animate with a different duration, a section of code would begin a new NSAnimationContext with the desired duration, perform the desired value-set operations through "animator" proxy or proxies, then close the context when done:
[NSAnimationContext beginGrouping];
[[NSAnimationContext currentContext] setDuration:1.0]; // Animate enclosed operations with a duration of 1 sec
[[aView animator] setFrame:newFrame];
[NSAnimationContext endGrouping];
NSAnimationContexts can be nested, allowing a given block of code to initiate animations using its own specified duration without affecting animations initiated by surrounding code.
[NSAnimationContext beginGrouping];
[[NSAnimationContext currentContext] setDuration:1.0]; // Animate enclosed operations with a duration of 1 sec
[[aView animator] setFrame:newFrame];
...
[NSAnimationContext beginGrouping];
[[NSAnimationContext currentContext] setDuration:0.5]; // Animate alpha fades with half-second duration
[[aView animator] setAlphaValue:0.75];
[[bView animator] setAlphaValue:0.75];
[NSAnimationContext endGrouping];
...
[[bView animator] setFrame:secondFrame]; // Will animate with a duration of 1 sec
[NSAnimationContext endGrouping];
Since an "animator" proxy can be handed off to code that expects an ordinary object of the kind the proxy targets (presently, an NSView or NSWindow), it might in rare circumstances be necessary to suppress animation for code that does not explicitly go through "animator" proxy objects. This can be accomplished using an animation context with a duration of zero:
[NSAnimationContext beginGrouping];
[[NSAnimationContext currentContext] setDuration:0.0]; // Makes value-set operations take effect immediately
[aViewOrMaybeAnAnimator setFrame:newFrame];
[NSAnimationContext endGrouping];
New NSView properties (Updated since WWDC 2007 Seed)
Leopard AppKit adds some new properties to NSViews, which are described below with their corresponding accessor methods.
- (void)setWantsLayer:(BOOL)flag;
- (BOOL)wantsLayer;
The "wantsLayer" property determines whether a view and its descendants should be composited and animated using a Core Animation layer tree, enabling the use of advanced animation and compositing effects. Defaults to NO. Setting this property to YES for the rootmost view for which Core Animation-based compositing is desired is all that's needed to activate Core Animation-based view buffering, compositing, and animation for a given view subtree. The view subtree is then said to be "layer-backed", since each view is given a corresponding Core Animation layer that serves as its backing store.
- (CALayer *)layer;
The -layer method returns the view's corresponding AppKit-created-and-managed CALayer, if the view is layer-backed. Callers may use the returned pointer to message the layer directly, as a means of accessing features that aren't re-exported as NSView properties. May return nil for a view that's currently marked as layer-hosted, if AppKit hasn't yet displayed the view for the first time and thus created the view's layer. For most ordinary usage of animating views' frames and content and applying effects, awareness of and direct access to views' underlying layers is unlikely to be needed, as AppKit will be able to manage them automatically.
- (void)setLayer:(CALayer *)newLayer;
The -setLayer: method sets a given CALayer to be a view's backing layer. This causes the view to dissociate from its previously assigned layer (if any), removing that layer from its surrounding layer tree and releasing the view's reference to the layer. The new layer takes on the old layer's position in the layer tree (or is simply added to the layer tree in the appropriate place, if it isn't replacing an existing layer). A view retains its layer, but AppKit maintains only a weak reference from the layer back to the view. This method manages both associations.
-setLayer: is published to allow for the usage where a developer has an arbitrary layer tree that's not tied to a view subtree and isn't automatically managed by AppKit, and wants to render that in a view (see "Core Animation Layer Tree Rendering").
- (void)setAlphaValue:(CGFloat)viewAlpha;
- (CGFloat)alphaValue;
Sets the overall opacity value with which the view and its descendants are composited into their superview (analogous to a window's alphaValue). Defaults to 1.0. This setting may be varied independently of the class' return value for -isOpaque, and the implementation of the latter needn't take the view's alphaValue into account, since AppKit consults both values when necessary. A view's alphaValue will affect both Core Animation-based and conventional view compositing.
- (NSShadow *)shadow;
- (void)setShadow:(NSShadow *)shadow;
Sets an optional shadow to be drawn behind the view subtree. Defaults to nil. This setting only has an effect for Core Animation-based view compositing. Note that, although Core Animation's shadow model uses the same parameters as a Quartz shadow, the rendered results may differ from those achieved using Quartz shadow rendering. NSShadow is used here merely as an appropriate Cocoa encapsulation for the identical set of shadow parameters.
The following three pairs of accessor methods can be used to apply arbitrary Core Image filter effects for views that are backed by CALayers. As specified by Core Animation, the conceptual model used to apply filter operations and combine their results is:
maskop(mask, compositeop(layerop(layer), backgroundop(background)), background)
- (CIFilter *)compositingFilter;
- (void)setCompositingFilter:(CIFilter *)filter;
Sets a CIFilter that will be used to composite the view subtree over its (possibly filtered) background. Defaults to nil, which implies that source-over compositing should be used. This setting only has an effect for Core Animation-based view compositing.
- (NSArray *)contentFilters;
- (void)setContentFilters:(NSArray *)filters;
Allows the view's content to be filtered through an optional chain of CIFilters before being composited into the render destination. The supplied array of filters needn't be connected to one another, as they will be connected in series automatically by Core Animation. Defaults to nil. This setting only has an effect for Core Animation-based view compositing.
- (NSArray *)backgroundFilters;
- (void)setBackgroundFilters:(NSArray *)filters;
Allows the background behind the view's subtree to be filtered through an optional chain of CIFilters before the view subtree is composited into it. The supplied array of filters needn't be connected to one another, as they will be connected in series automatically by Core Animation. Defaults to nil. This setting only has an effect for Core Animation-based view compositing.
Layer-Backed Views and Out-of-Band Drawing
For the ordinary case of views drawn into a shared window backing store, it has historically been possible to draw into the window area that a view occupies at will, by locking focus on the view, drawing, and unlocking focus:
[view lockFocus];
/* Perform some drawing. */
[view unlockFocus];
This was sometimes used to replace some animated content in response to a timer callback, for example.
Due to the inherently different buffering semantics for layer tree based rendering, this technique cannot be used by layer-backed views. Instead, you should perform all needed drawing in -drawRect:, and use -setNeedsDisplay: and/or -setNeedsDisplayInRect: to prompt updates as needed. This is generally a preferred technique anyway, since it avoids potential inconsistencies with -drawRect:-based drawing, allows other views to contribute potentially necessary content to the affected window area, and enables AppKit to coalesce updates for greater efficiency.
Avoiding Synchronization Issues with Layer-Backed View Animations (New since WWDC 2007 Seed)
When a view is resized, the content that it draws can respond in potentially arbitrary ways. For this reason, AppKit's "animator" proxy based animation API asks resizing views to redraw their content at each step along the way, to insure correct results.
When used with layer-backed views, this can lead to synchronization problems where a single view can appear to "jitter" when its frameOrigin and frameSize are simultaneously animated, or where gaps between adjacent animating views fluctuate, because each view's frameOrigin move is being animated on a background thread by Core Animation. Such synchronization problems can be remedied by insuring that the view frame animations are initiated using a -setFrame: message to each view's animator, rather than going through -setFrameOrigin: and/or -setFrameSize: separately. This cues AppKit to animate both the frameOrigin and frameSize changes itself, so that the results will stay in sync throughout the animation.
Note that AppKit-driven animations are timer-based, and thus require normal periodic runloop servicing to occur in order for them to advance. For best results, try to avoid having lengthy operations block the runloop while AppKit-driven animations are in flight.
Layer-backed NSImageView optimization (New since WWDC 2007 Seed)
For complex views, animations that cause the frame size to change often result in sub-optimal animation performance. When the view's size changes, it must redrawn for each frame of the animation, which often lowers framerates. NSImageView is a good example of this, since rendering images can be expensive. NSImageView has been optimized in certain cases for resizing animations. The following is a set of conditions that must be met in order for the optimization to take effect:
• The image view must be uneditable and have a frame style of NSImageFrameNone.
• The image view must contain AppKit's standard NSImageCell and the view's drawRect: method must not be overridden.
• The view must be layer-backed and must be using the layer that AppKit creates for it (meaning you may not provide your own layer with -setLayer:).
• The "best" image rep (according to -bestRepresentationForDevice:) for the view's image must be an NSBitmapImageRep, NSCachedImageRep, NSPICTImageRep, or NSCGImageRep.
• The view's imageScaling must be NSImageScaleProportionallyUpOrDown with centered imageAlignment or NSImageScaleAxesIndependently or NSNSImageScaleNone with any imageAlignment. Note that the default imageScaling is NSImageScaleProportionallyDown which does not allow the optimization to take effect.
Shifting "needsDisplay" Rectangles in a View
The following method enables shifting of the receiving view's dirty rects in a single operation. The effect of this method is to get all of the receiving view's dirty rectangles, clear all dirty rectangles in the intresection of the specified clipRect and the view's bounds, shift the retrieved rectangles by the given "delta" offsets, clip the result to the intersection of clipRect and the view's bounds, and add the resultant rectangles back to the view.
- (void)translateRectsNeedingDisplayInRect:(NSRect)clipRect by:(NSSize)delta;
This method should rarely be needed, but may be useful to clients that implement their own copy-on-scroll logic.
Pixel Alignment and Transforming View Coordinates To and From "Base" Space (Updated since WWDC 2007 Seed)
In Leopard, NSView provides a new set of methods that should be used when performing pixel-alignment of view content. They provide the means to transform geometry to and from a "base" coordinate space that is pixel-aligned with the backing store into which the view is being drawn:
- (NSRect)convertRectToBase:(NSRect)aRect;
- (NSPoint)convertPointToBase:(NSPoint)aPoint;
- (NSSize)convertSizeToBase:(NSSize)aSize;
- (NSRect)convertRectFromBase:(NSRect)aRect;
- (NSPoint)convertPointFromBase:(NSPoint)aPoint;
- (NSSize)convertSizeFromBase:(NSSize)aSize;
For conventional view rendering, in which a view hierarchy is drawn flattened into a window backing store, this "base" space is the same as the coordinate system of the window, and the results of using these new methods are the same as converting geometry to and from view "nil" using the existing -covert[Rect/Point/Size]:[to/from]View: methods.
Views that are rendered into backing layers in a Core Animation layer tree, however, have their own individual backing stores, which may be aligned such that window space is not necessarily the appropriate coordinate system in which to perform pixel alignment calculations.
These new coordinate transform methods provide a way to abstract view content drawing code from the details of particular backing store configurations, and always achieve correct pixel alignment without having to special-case for layer-backed vs. conventional view rendering mode. Regardless of the underlying details of how view content is being buffered, converting to base space puts one in the native device coordinate system, in which integralizing coordinates produces pixel alignment of geometry.
When using layer-backed views at a user interface scale factor other than 1.0, note that the dimensions of a view and the dimensions of its corresponding backing layer will vary according to the scale factor, since CALayer bounds are always expressed in pixels, while NSView dimensions remain expressed in points. Most clients of layer-backed views will not have a need to perform operations directly in layer space, but for those that do it's important to use the preceding methods to convert geometric quantities between view space and layer ("base") space when appropriate.
Responding to View Hiding/Unhiding
Hiding or un-hiding a given view using the -setHidden: API affects the effective visibility of all of its descendant views. Leopard adds two new NSView methods that clients can override, if desired, to have their custom view classes respond to becoming effectively hidden or unhidden vis-à-vis the -setHidden: API:
- (void)viewDidHide;
- (void)viewDidUnhide;
A view will receive a "viewDidHide" message when its "isHiddenOrHasHiddenAncestor" state goes from NO to YES. This can happen when the view or an ancestor is marked as hidden, or when the view or an ancestor is spliced into a new view hierarchy.)
A view will receive a "viewDidUnhide" message when its "isHiddenOrHasHiddenAncestor" state goes from YES to NO. (This can happen when the view or an ancestor is marked as not hidden, or when the view or an ancestor is removed from its containing view hierarchy.)
Performing Just-Before-Drawing View Activity
NSView has a new "-viewWillDraw" API method in 10.5 that can be overridden to perform any last-minute activity that might be desired at the outset of a view hierarchy "-display..." operation.
- (void)viewWillDraw;
Most often, the activity to be performed at this time consists of some combination of view layout (assigning new frame sizes and/or positions to views) and marking additional view areas as needing display (typically as the result of performing layout of non-view content, such as text glyphs, graphics, or web content). The desired effect is to perform such computations on demand, deferred until their results are about to actually be needed, allowing for the same kind of update coalescing performance benefits that we get with the deferred display mechanism itself, rather than forcing content layout to be performed immediately when the content is established or deferred until a subsequent drawing pass.
At the outset of recursive display of part or all of a view hierarchy, which is always initiated by one of NSView's eight public "-display..." methods, AppKit recurses down the view hierarchy, sending a -viewWillDraw message to each of the views that may be involved in the display operation.
NSView's default implementation of this method is itself the mechanism for the recursion, and allows overriders the flexibility to head-recurse or tail-recurse as best suits the needs of the operations they have to perform. Conceptually, NSView's implementation looks like:
@implementation NSView
- (void)viewWillDraw {
if (any descendant of self overrides "-viewWillDraw") {
for (each subview that intersects the window area being drawn in back-to-front order) {
[subview viewWillDraw];
}
}
}
@end
So an override of this method could do:
- (void)viewWillDraw {
/* Perform some operations before recursing for descendants. */
/* Now recurse to handle all our descendants. Overrides must call up to super like this. */
[super viewWillDraw];
/* Perform some operations that might depend on descendants already having had a chance to update. */
}
During the -viewWillDraw recursion, sending of -setNeedsDisplay: and -setNeedsDisplayInRect: messages to views in the hierarchy that's about to be drawn is valid and supported, and will affect AppKit's assessment of the total area to be rendered in that drawing pass.
If desired, an implementation of -viewWillDraw can use NSView's existing -getRectsBeingDrawn:count: method to obtain a list of rectangles that bound the affected area, enabling it to restrict its efforts to that area.
Setting a View's Subviews
NSView has a new -setSubviews: API method in 10.5 that can be used to reorder a view's subviews, remove existing subviews, and/or add new subviews all via a single API entry point:
- (void)setSubviews:(NSArray *)newSubviews;
With this single method, one can:
- reorder the receiver's existing subviews
- add or remove subviews from the receiver
- potentially replace all of the receiver's previous subviews with a whole new set of views
- potentially remove all of the receiver's previous subviews, leaving it with none (think "[aView setSubviews:[NSArray array]]")
The views in the "newSubviews" array can by any combination of existing subviews of the receiver, subviews of other views, or views that currently have no superview. If "newSubviews" is nil, or contains any duplicate entries, -setSubviews: throws an invalid argument exception. (Prior to the WWDC 2007 Leopard seed, -setSubviews: would also raise an exception if any of the new subviews was already a subview of some view. This is now allowed, and simply results in the views being removed from their previous superviews before being added as subviews of the receiver.)
Given a valid "newSubviews" parameter, -setSubviews: performs whatever sorting of the subviews array, subview addition, and subview removal activity is necessary to leave the receiver with the requested new array of subviews. Thus, any member of "newSubviews" that isn't already a subview of the receiver is added. Any member of the view's existing "subviews" array that isn't in "newSubviews" is removed. And any views that are in both "subviews" and "newSubviews" is simply moved in the subviews array as needed, without being removed and re-added. The -setSubviews: method also marks affected view/window areas as needing display to reflect the new z-ordering.
Views, Focus Rings, and Drawing Performance
To help guarantee correct redraw of focus rings, AppKit may automatically draw additional parts of a window beyond those that application code marked as needing display. It may do this for the first responder view in the application's key window, if that first responder view's focusRingType property is set to a value other than NSFocusRingTypeNone. Any view that can become first responder, but doesn't draw a focus ring, should have its focusRingType set to NSFocusRingTypeNone to avoid unnecessary additional redraw.
NSAnimatablePropertyContainer protocol (Updated since WWDC 2007 Seed)
NSAnimatablePropertyContainer is a new protocol in Leopard, currently adopted by NSView and NSWindow. The methods in NSAnimatablePropertyContainer are as follows:
- (id)animator;
The -animator method returns a proxy object for the receiver that can be used to initiate implied animation of property changes. An object's "animator" should be treated as if it was the object itself, and may be passed to any code that accepts the object as a parameter. Sending of KVC-compliant "set" messages to the proxy will trigger animation for automatically animated properties of its target object, if the active NSAnimationContext in the current thread has a duration value greater than zero, and an animation to use for the property key is found by the -animationForKey: search mechanism defined below. An object's automatically animated properties are those for which [theObject animationForKey:] finds and returns an CAAnimation instead of nil, often because [[theObject class] defaultAnimationForKey:] specifies a default animation for the key.
It's perfectly valid to set a new value for a property for which an animation is currently in progress; this simply sets a new target value for that property, with animation to the new target proceeding from whatever current value the property has reached. An in-flight property animation can be stopped by setting a new value for the property with 0.0 as the surrounding NSAnimationContext's duration value.
For the common specific case of animating views:
Initiating animation via an "animator" proxy object works under both Core Animation-based and conventional view compositing. The primary difference under Core Animation-based compositing is that for intrinsic geometric properties such as the view's "frame," all animation is handled at the Core Animation level, meaning that the view's property will be immediately set to the desired target value, and the view won't see the successive intermediate values. The animation effect in such cases is purely visual and exists only in Core Animation's "render tree" backend. In contrast, under conventional (non-layer-backed) view compositing, view property animations are executed at the AppKit level, and during the course of those animations views will receive value-set operations for successive intermediate values. This is also true for animation of all developer-defined properties, under both layer-backed and conventional view compositing and animation.
- (NSDictionary *)animations;
- (void)setAnimations:(NSDictionary *)dict;
An animatable property container's optional "animations" dictionary maps NSString keys to CAAnimation values. When an occurrence matching the key fires for the view, -animationForKey: first looks in this dictionary for an animation to execute in response.
- (id)animationForKey:(NSString *)key;
When the occurrence specified by "key" fires for an object, -animationForKey: is consulted to find the animation, if any, that should be performed in response. Like its Core Animation counterpart, -[CALayer actionForKeyPath:], this method is a funnel point that defines the standard order in which the search for an animation proceeds, and is not one that clients would typically need to override. This method first checks the receiver's "animations" dictionary, then falls back to +defaultAnimationForKey: for the receiver's class.
+ (id)defaultAnimationForKey:(NSString *)key;
As described above, -animationForKey: next consults the class method +defaultAnimationForKey: when its search of an instance's "animations" dictionary doesn't turn up an animation to use for a given property change.
NSView's +defaultAnimationForKey: method returns a default animation that should be performed when the occurrence specified by "key" fires for a view, where "key" typically names a property whose value is being changed. For each of NSView's own animatable properties, NSView's implementation returns a suitable default CAAnimation to be used. For all other keys this method returns nil.
A developer implementing a custom view subclass can enable automatic animation of the subclass' added properties by overriding this method, and having it return the desired default CAAnimation to use for each of the property keys of interest. The override should defer to super for any keys it doesn't specifically handle, facilitating inheritance of default animation specifications.
@implementation MyView
+ (id)defaultAnimationForKey:(NSString *)key {
if ([key isEqualToString:@"borderColor"]) {
// By default, animate border color changes with simple linear interpolation to the new color value.
return [CABasicAnimation animation];
} else {
// Defer to super's implementation for any keys we don't specifically handle.
return [super defaultAnimationForKeyKey:key];
}
}
@end
NSCollectionView
A new view class has been added to facilitate interesting animations: NSCollectionView. You can set or bind a collection view's content to an array of objects. For each object, the collection view will create an NSCollectionViewItem, which in turn manages a view that is used to display the values of its "represented object." All views automatically create a layout to fit all items into its content and animates them if the content changes (for example, if the content is reordered, it will slide the items into the new positions).
Usually collection view items are created from a "prototype" which is set as the "itemPrototype" outlet in Interface Builder. For the view of the collection view item, you can use standard controls/views to form a "compound" view. For example, you can group an NSImageView and an NSTextField in an NSBox to form a unit that displays images and names for it. To set the view used by a collection view item, you typically use the "view" outlet.
To populate a collection view item's view with values from the represented object, you will typically create bindings from the view (or any of the subviews) to the "representedObject" of the collection view item (example: you could bind the value binding of text field to the key "representedObject.name" of the collection view item). Alternatively, you could subclass NSCollectionViewItem and make it the data source or delegate of one of the views.
Note that in early Leopard seeds (including WWDC 2006) NSCollectionView was known as NSGridView, and NSCollectionViewItem was NSLayoutItem. As foreshadowed in the release notes, these classes have changed; however, the changes are limited strictly to the two class and several method name changes:
- layoutView in NSLayoutItem has become collectionView
- minGridSize, maxGridSize, and corresponding setter methods have become minItemSize, maxItemSize, setMinItemSize:, and setMaxItemSize:
- newLayoutItemForRepresentedObject: is now newItemForRepresentedObject:
- layoutItemPrototype and setLayoutItemPrototype: are now itemPrototype and setItemPrototype:
The old names have been removed in the final version of Leopard.
Resolution Independence (New since WWDC 2007 Seed)
On Leopard, resolution independence (aka "HiDPI") is a developer feature. You can use QuartzDebug in /Developer/Applications/Performance Tools to set the user space scale factor to 1.25, 1.5, 2.0, or 3.0, then launch your application. The user interface of your application will be scaled by the user space scale factor.
Most standard controls are now drawn in high resolution, which allows them to appear crisp when running with user space scaling enabled.
In addition a number of standard images are now available in high-resolution, enabling graphics to appear crisp when scaled. Names of these images are declared in NSImage.h. Note that it's important to use these images only for the purpose indicated by the name, since the actual graphic may change in future releases. See the NSImage section for more info.
There are some known issues with the non-integral scale factors of 1.25 and 1.5.
- Most drawing should occur on integral pixel boundaries, but views are not automatically constrained to fall on pixel boundaries
- Image tiling also looks most correct if the tiled image is integral-pixel sized and adjusted as needed to avoid pixel cracks
- Tracking rects on non-integral boundaries may generate a mouseEntered: event while the mouse is still slightly outside the fractional bounds, or a mouseExited: while the mouse is still slightly inside the fractional bounds.
- Some controls on non-pixel boundaries may have jagged edges if the end caps do not vertically or horizontally align with the fill
- Text may jitter when scrolling if the scrollView is on a non-pixel boundary
You can use -[NSView centerScanRect:] to position a view or a rect within a view on pixel boundaries. On Tiger, this method used NSIntegralRect, which expanded the given rectangle outward as needed to land on integral pixel values. On Leopard, this method is size preserving, which results in better layout behavior when applied to adjacent rectangles. This change applies only to applications linked on Leopard or later for compatibility reasons. Alternatively, you can convert to window coordinates using -[NSView convertRectToBase:], round the result to integral values with rounding rules that suit your needs, then convert back to view coordinates using -[NSView convertRectFromBase:].
Because the scaling from points to pixels is non-integral when the user space scale factor is non-integral, you need to be sure not to use floor, ceil, or round on coordinates expressed in points. This rounding would be likely to result in non-integral pixel values, which would lead to the problems listed above.
NSOpenGL and resolution independence (Updated since WWDC 2007 Seed)
Applications and frameworks that are striving to become resolution-independent can encounter problems with OpenGL usage, due to the fact that many OpenGL API functions, such as glViewport(), expect their parameters in pixel units. Code that is accustomed to running under a user interface scale factor of 1.0 may contain latent errors where view dimensions were incorrectly treated as if they were in pixel units. For scale factors other than 1.0, the distinction between view space units and device (pixel) units becomes much more important to properly observe.
A common usage pattern in Cocoa-based OpenGL applications, for example, has been to pass a view's bounds dimensions directly to glViewport(), which expects to receive its parameters in pixel units:
- (void)reshape {
NSSize bounds = [self bounds];
// This is technically INCORRECT, because bounds is not expressed in pixel units.
glViewport(0, 0, bounds.size.width, bounds.size.height);
}
To help ease the transition to resolution independence for applications that use this common code pattern, Leopard AppKit automatically configures the bounds of any view that has an associated NSOpenGLContext (thus, NSOpenGLViews, as well as ordinary NSViews that are drawn into using an NSOpenGLContext) so that the bounds are expressed in pixel units, according to the current user interface scale factor. So for example, if an application has an NSOpenGLView whose frame size is 100x100 points, and that application is run at a user interface scale factor of 1.25, the NSOpenGLView's frame will remain 100x100 points, but its bounds will be reported as 125x125. That enables commonly used code constructs such as the above -reshape method to function correctly without code changes.
While this automatic workaround may suffice to provide compatibility for many applications, the ideal solution is for OpenGL client code to perform correct unit conversions where needed. For example, the above -reshape method would be more correctly written as:
- (void)reshape {
// Convert up to window space, which is in pixel units.
NSSize boundsInPixelUnits = [self convertRect:[self bounds] toView:nil];
// Now the result is glViewport()-compatible.
glViewport(0, 0, boundsInPixelUnits.size.width, boundsInPixelUnits.size.height);
}
Code that targets Mac OS 10.5 and later can use the -convertRectToBase: method instead of converting to view nil, which has the advantage of correctly producing a result in pixel units regardless of whether the view is layer-backed. (If the view is layer-backed, -convertRectToBase: converts to the coordinate space of the layer's backing store, instead of to the window's coordinate space.)
Text rendering and resolution independence
When rendering with non-integral scale factor, we recommend you use the antialiased text rendering mode. Since the glyph origin might not be pixel-aligned at layout time, rendering non-antialiased text doesn't produce optimal quality.
NSRuleEditor
NSRuleEditor is a new AppKit control introduced in Leopard. NSRuleEditor allows the user to configure "rules," similar to the rules in Mail or in the Finder search window, by selecting from popups, manipulating custom views, and adding, deleting, or rearranging rows.
NSRuleEditor:
- Supports nesting and non-nesting modes
- Gets the available popups/views from its delegate
- Is bindings-compatible
- Supports automatic generation of NSPredicates
- Supports flexible localization through a strings file or dictionary
Please refer to NSRuleEditor.h and documentation for more info on this new class.
NSPredicateEditor
NSPredicateEditor is a subclass of NSRuleEditor specialized for working with predicates. Unlike NSRuleEditor, NSPredicateEditor does not depend on its delegate to populate its rows (and does not call the populating delegate methods). Instead, its rows are populated from its objectValue, which is an NSPredicate.
NSPredicateEditor relies on instances of a new class, NSPredicateEditorRowTemplate, which is responsible for mapping back and forth between the displayed view values and various predicates.
NSPredicateEditor exposes one property, rowTemplates, which is an array of NSPredicateEditorRowTemplates.
- (void)setRowTemplates:(NSArray *)rowTemplates;
- (NSArray *)rowTemplates;
Developers will typically configure NSPredicateEditor with some NSPredicateEditorRowTemplates, either programmatically or in Interface Builder, and then set and get NSPredicates on the NSPredicateEditor. Changes to the predicate are announced with the usual target/action mechanism.
NSMenu
New menu customization APIs
Leopard now allows applications to set a custom view for a menu item, via the following new NSMenuItem methods:
- (void)setView:(NSView *)view;
- (NSView *)view;
The custom view takes over all aspects of the menu item's drawing. Mouse event processing is handled normally for the view, including mouse down, mouse up, mouse moved, mouse entered, mouse exited, mouse dragged, and scroll wheel events. In non-sticky tracking mode (manipulating menus with the mouse button held down), the view will receive mouseDragged: events. See the header file NSMenuItem.h for more information about custom menu item views.
Animation in menu item views
Views in menu items can be animated with the usual mechanisms, such as calling - [NSView setNeedsDisplay:] or - [NSView display] in a repeating timer. But be aware that menu tracking occurs with the run loop running in NSEventTrackingRunLoopMode, so any timers you add must be set to fire while in that mode.
New delegate methods and notifications
Menus will now notify their delegates when they are opened or closed, and when they highlight or unhighlight items during menu tracking. See NSMenu.h for more information about these new delegate methods.
Removal of NSMenuItem protocol
All uses of id <NSMenuItem> have been removed from AppKit in Leopard. Applications should switch to the NSMenuItem class.
NSMenuItem attributed title truncation
In versions of Mac OS X prior to Leopard, NSMenuItems with excessively long attributed titles would be clipped against the edge of the menu. In Leopard, NSMenuItem attributed titles will be truncated by default using the "NSLineBreakByTruncatingMiddle" truncation style. You may override this by specifying a different line break mode for the NSParagraphStyle attached to the attributed title string.
validateItem: no longer called
Historically, AppKit has called the validateItem: method to validate menu items, if validateMenuItem: and validateUserInterfaceItem: were not implemented. This behavior was never documented and it is now disabled for applications built on Leopard. Applications that rely on on validateItem: should switch to validateMenuItem: or validateUserInterfaceItem:.
validateMenuItem: only called on matching key equivalents
Before Leopard, typing a key equivalent would cause AppKit to attempt to validate every menu item in the menu (via validateMenuItem:). As an optimization, AppKit will now only validate the item with the matching key equivalent. Applications should use NSMenu's delegate methods to lazily populate a menu.
Command key = treated like +
As a special case, if no menu item claims ⌘= as a key equivalent, then typing ⌘= will trigger any ⌘+ key equivalent, if the pressed = key is shared with the + key. For example, on a US keyboard layout, pressing the "=" key adjacent to the "-" key on the number row will trigger any ⌘+ key equivalent, but the "=" key above the keypad will not. If the keyboard layout does not have any "+" and "=" characters on the same key, this does not apply.
Key equivalent uniqueing
AppKit tries to ensure that multiple menu items do not have the same key equivalent, because otherwise the user might accidentally invoke one while expecting to invoke the other. This is called key equivalent uniqueing. In Tiger, popup button menus were uniqued with the main menu (and in Leopard, this has been extended to also cover segmented controls). However, menu items that are thought to be aliases of one another can now share the same key equivalent, in Leopard. (Menu items are considered to be an aliases if they have the same action). Thus, in Leopard, you can have "Bold" in a popup button and also in the main menu, and both will show ⌘B as the key equivalent.
Disabled key equivalents passed through
Prior to Leopard, key equivalents corresponding to disabled menu items would be ignored. In Leopard, your application now has a chance to handle these. For example, a key equivalent for control-K on a disabled menu item will no longer block the emacs shortcut in an NSTextView, in Leopard.
ß support in key equivalents
The German sharfes s character (ß) is now supported as a key equivalent.
Menu Item Key Equivalents in non-Roman Scripts (New since WWDC 2007 Seed)
In Tiger and earlier, key equivalents were incorrectly interpreted as follows: Characters were extracted as MacRoman, and the resulting values were interpreted as if they were in the application encoding. This was not a problem in apps running with the MacRoman character encoding, but would cause problems in other cases. (The default character encoding of apps often depends on the user's localization choice.)
For example, specifying a key equivalent of the backslash @"\" (U+005C) would result in a yen sign key equivalent (U+00A5) when running under a Japanese localization, because the backslash value in MacRoman matches the yen value in MacJapanese: both are 0x5C in the respective character sets. This was the only way to obtain a yen key equivalent in Tiger.
In Leopard, key equivalents and character sets are handled correctly. To specify a yen key equivalent, specify the Unicode string containing yen (U+00A5).
For binary compatibility, Leopard will revert to Tiger's behavior for applications linked before Leopard. When updating your application for Leopard, you should ensure that, when running on Leopard, your key equivalent string contains the characters you want, instead of the MacRoman characters that match your character's code points in the application encoding.
State images implemented
The NSMenuItem methods setOnStateImage:, setOffStateImage:, and setMixedStateImage: now work as documented.
No CMM plugins in 64-bit
NSMenu does not attempt to load Contextual Menu Manager plugins in 64-bit apps. There is no public interface for creating 64-bit CMM plugins in Leopard.
Pre-existing overrides of -[NSMenuItem isHidden] (New since WWDC 2007 Seed)
During testing, some apps were found to have pre-existing implementations of -[NSMenuItem isHidden] that interfered with the new Leopard API of the same name. For compatibility reasons, AppKit will not call -[NSMenuItem isHidden] on apps built on Tiger or earlier. When you build your app on Leopard, AppKit will call your method and you may see strange behavior, such as menus disappearing. We recommend that you do not override -[NSMenuItem isHidden], and remove any existing overrides at the point that you build on Leopard.
Titles of Menus in the Menubar (New since WWDC 2007 Seed)
Consider the application's main menu, an instance of NSMenu. That menu contains NSMenuItems, such as the File menu item, and the File menu item has a submenu of type NSMenu containing items such as New, Open, etc.
In Tiger, the initial label appearing in the menu bar was taken from the submenu's title. But due to a bug, changes to this menu's title did not affect the menu bar; instead, the parent item's title was used instead. So for example, the title of the File submenu determines the initial label, but to change the the label, you must modify the title of the File item itself.
For compatibility, Leopard preserves this behavior for apps built on Tiger. However, apps built on Leopard will find that the menubar consistently reflects the titles of the submenus. The titles of the items themselves in the main menu are ignored. For maximum compatibility with both Tiger and Leopard, you should set both the title of a main menu item and the title of its submenu to the same thing.
This note only applies to items that are within the NSApplication's mainMenu, and their immediate submenus. The titles of other menus have no effect.
-[NSApplication currentEvent] set when menu tracking ends
For applications linked on or after Leopard, -[NSApplication currentEvent] will reflect the event that caused menu tracking to end during the action of the selected menu item.
representedObject in NSPopUpButtonCell and NSMenuItemCell
In Leopard, NSMenuItemCell and NSPopUpButtonCell forward both setRepresentedObject: and representedObject to their current NSMenuItem. (Prior to Leopard, NSMenuItemCell and NSPopUpButtonCell would forward representedObject, but not setRepresentedObject:)
NSPopUpButtonCell setImagePosition:
NSPopUpButtonCell now respects the following image positions: NSImageOnly, NSImageLeft, NSImageRight, NSImageOverlaps. Prior to Leopard, NSPopUpButtonCell always drew with the NSImageLeft position.
Unbordered NSPopUpButtonCells
The layout of the text in unbordered popup button cells has been modified to more closely match that of NSButtonCell. For applications linked before Leopard, the layout will be preserved in some cases.
NSPopupButton Content Placement Tag binding option
The content bindings for NSPopupButton have a new option, Content Placement Tag. This provides a way of mixing static menu items with dynamically generated menu items in a popup. When this option is set to a non-zero integer value, the content bindings will only put dynamic content in place of the one menu item in the popup whose tag value matches the option value.
The typical use for this is to place some dynamically generated menu items in one part of the popup, and the follow or precede them with a menu separator and a static "action" menu item.
NSStatusItem
For consistency with NSCell and NSControl, -[NSStatusItem sendActionOn:] now returns the old mask.
NSToolbar
NSToolbar - Interface Builder support
Interface Builder 3 supports creating toolbars and toolbar items. However, you may wish to provide additional toolbar items programatically. To accomplish this, connect the delegate outlet of the NSToolbar to your desired delegate, and implement the NSToolbar delegate methods normally. The default and allowed item identifiers will be the union of those created in Interface Builder and those returned by your delegate.
NSToolbar - Show/Hide menu items
In versions of Mac OS X prior to Leopard, interface items, including menu items, that had toggleToolbarShown: set as their action would have their titles changed to "Show Toolbar" or "Hide Toolbar." For applications linked on or after Leopard, AppKit will only flip "Show Toolbar" to "Hide Toolbar" (or their localized variations) and vice versa, as appropriate. The empty title will also be modified. Nonempty titles other than the localized variants of these strings must be managed by your application.
NSToolbar - Key equivalents can match in toolbars
Before Leopard, only NSPopUpButtons in toolbars could respond to key equivalents. In Leopard, AppKit now respects the key equivalents of all controls in (visible) toolbars, not just popup buttons.
NSToolbar - NSToolbarItem default sizes
As of Leopard, if you call setView: on an NSToolbarItem without having called setMinSize: or setMaxSize:, the toolbar item will set its minimum and maximum size equal to the view's frame size.
NSToolbar - View archival
In Tiger, NSToolbar would always "copy" your toolbar item's view when displaying it in the customization palette with an NSKeyedArchiver. In Leopard, NSToolbar will only copy your view if you set the view on two different toolbar items at once. For example, say you allocate exactly one view (perhaps in a nib) and always pass it to -[NSToolbarItem setView:] in your toolbar:itemForItemIdentifier:willBeInsertedIntoToolbar: method. If that view is in an active toolbar item, and you display the customization palette, NSToolbar will copy that view once. If that view is not in an active toolbar item and then you display the customization palette, NSToolbar will not copy the view for the customization palette, but will copy that view at the point the user drags the corresponding toolbar item from the palette into the toolbar.
If you always ensure that you pass fresh views into setView:, then NSToolbar will never copy the view.
NSToolbar - mouseDownCanMoveWindow
Previous to Leopard, only textured and unified windows were draggable by their toolbar. In Leopard, all windows are draggable by their toolbars. Apps that placed custom views in toolbars and did not override isOpaque or mouseDownCanMoveWindow may find that their views unexpectedly become draggable in addition to responding to clicks. To prevent this, the mouseDownCanMoveWindow is considered to return NO if all of the following are true:
- The app was linked on Tiger or earlier.
- The view is in a toolbar item.
- The view does not override mouseDownCanMoveWindow.
- The window is not textured or unified title/toolbar.
NSSound
NSSound has additional features on Leopard and later. These include the ability to get the duration of a sound, change the volume of a sound, set and get the playback location within the sound, and cause a sound to loop. See the documentation or comments within the NSSound.h header for more about these new APIs.
In Tiger, NSSound would willingly create a "sound" for any valid Quicktime file, including images. In Leopard, NSSound's init methods will now return nil for files that do not have a sound track.
NSSpeechSynthesizer (New since WWDC 2007 Seed)
NSSpeechSynthesizer now provides complete access to the full power of Apple's synthesizer, including full access to third-party synthesizers compatible with Apple's Speech Synthesis framework. Developers can now get and set individual synthesizer properties, pause and continue speech, stop speech at specific boundaries, conveniently set rate and volume properties, specify external dictionaries and receive additional callbacks for errors and synchronization events. Cocoa developers that formerly needed to use the Carbon Speech Synthesis API to gain access to certain synthesizer features should now be able to use the NSSpeechSynthesizer class instead.
Also new for Leopard is Alex, an English voice that leverages advanced Apple technology to deliver natural intonation even at very fast speaking rates.
NSViewController (Updated since WWDC 2007 Seed)
A new class, NSViewController, has been added to the AppKit in Mac OS 10.5. It serves roughly the same purpose as NSWindowController, but for views instead of windows. It:
• Does the same sort of memory management of top-level objects that NSWindowController does, taking the same care to prevent reference cycles when controls are bound to the nib file's owner that NSWindowController began taking in Mac OS 10.4.
• Has a generic "represented object" property, to make it easy for you to establish bindings in the nib to an object that isn't yet known at nib-loading time or readily available to the code that's doing the nib loading.
• Has an implementation of the key-value binding NSEditor informal protocol, so that objects that own view controllers can easily make bound controls in the views commit or discard the changes the user is making.
• Has a "title" property, because we expect this to be used in many situations where the user is given the opportunity to select from several named views.
NSViewController is meant to be very reusable. For example, as described below, NSPageLayout's and NSPrintPanel's -addAccessoryController: methods each take a view controller, and set the represented object to the NSPrintInfo that is to be shown to the user. When the user dismisses a printing panel, NSPageLayout and NSPrintPanel each send NSEditor messages to each accessory view controller to ensure that the user's changes have been committed or discarded properly. The titles of the accessories are gotten from the view controllers and shown to the user in pulldown menus that the user can choose from. If your application needs to dynamically associate relatively complex views with the objects that they present to the user, you might be able to reuse NSViewController in similar ways.
NSViewController is a subclass of NSResponder, but view controllers never make themselves the next responders of the controlled views. It's the responsibility of the object that puts the controlled view in a window to insert the view controller in the responder chain if that would be worthwhile. For example, after getting the view from a view controller and adding it to a superview with -[NSView addSubview:], you can set the subview's next responder to the view controller, and then set view controller's next responder to the superview.
NSPageLayout and NSPrintPanel Accessory View Enhancements (Updated since WWDC 2007 Seed)
NSPageLayout and NSPrintPanel have always had -setAccessoryView: methods that allow you to specify custom panes to be shown in page setup and print panels, respectively. However, there has been no way to specify the corresponding title that should appear in the panel's pop-up menu of panes. There has also been no way to associate summary text that should be shown for the pane's custom print settings in the print panel, or add more than one such pane to a panel. Lastly, with the introduction of built-in preview to NSPrintPanel in Mac OS 10.5 (described in the next section), we must provide a way for you to trigger the redrawing of the preview when the user changes printing parameters that you present to the user in accessory views.
New methods have been added to the NSPageLayout class to take advantage of the new NSViewController class:
- (void)addAccessoryController:(NSViewController *)accessoryController;
- (void)removeAccessoryController:(NSViewController *)accessoryController;
- (NSArray *)accessoryControllers;
When the page setup panel is presented to the user each accessory controller is automatically sent a -setRepresentedObject: message with this object's NSPrintInfo. Each controller is also automatically sent a -title message. If that returns nil the application's short name is used in the popup menu that lets the user choose an accessory view.
Slightly different new methods have also been added to the NSPrintPanel class:
- (void)addAccessoryController:(NSViewController<NSPrintPanelAccessorizing> *)accessoryController;
- (void)removeAccessoryController:(NSViewController<NSPrintPanelAccessorizing> *)accessoryController;
- (NSArray *)accessoryControllers;
These are very similar to their NSPageLayout equivalents, except the accessory controller must also conform to the new NSPrintPanelAccessorizing protocol, which has just two methods:
- (NSArray *)localizedSummaryItems;
Return the text that summarizes the settings that the user has chosen using this print panel accessory view and that should appear in the summary pane of the print panel. It must be an array of dictionaries (not nil), each of which has an NSPrintPanelAccessorySummaryItemNameKey entry and an NSPrintPanelAccessorySummaryItemDescriptionKey entry whose values are strings. A print panel acccessory view must be KVO-compliant for "localizedSummaryItems" because NSPrintPanel observes it to keep what it displays in its Summary view up to date. (In Mac OS 10.5 there is no way for the user to see your accessory view and the Summary view at the same time, but that might not always be true in the future.)
- (NSSet *)keyPathsForValuesAffectingPreview;
Return the key paths for properties whose values affect what is drawn in the print panel's built-in preview. NSPrintPanel observes these key paths and redraws the preview when the values for any of them change. For example, if you write an accessory view that lets the user turn printing of page numbers on and off in the print panel you might provide an implementation of this method that returns a set that includes a string like @"pageNumbering", as in TextEdit's PrintPanelAccessoryController class. This protocol method is optional because it's not necessary if you're not using NSPrintPanel's built-in preview, but if you use preview you almost certainly have to implement this method properly too.
Because of the requirements of this protocol, which are dictated by the realities of providing a good user interface when customizing the print panel, you must always subclass NSViewController when adding a new-style accessory view to a print panel.
The new NSPrintPanelAccessorySummaryItemNameKey and NSPrintPanelAccessorySummaryItemDescriptionKey strings are for use by implementations of NSPrintPanelAccessorizing's -localizedSummaryItems method.
The -setAccessoryView: and -accessoryView methods are now deprecated in both NSPageLayout and NSPrintPanel.
Also deprecated are NSPageLayout's -readPrintInfo and -writePrintInfo methods, and NSPrintPanel's -updateFromPrintInfo and -finalWritePrintInfo methods. They were never very useful. NSViewController provides the functionality those methods were supposed to provide; you can implement an override of the -view method to make your accessory view's UI consistent with the print info to be presented, and overrides of the KVB -commitEditing and -commitEditingWithDelegate:didCommitSelector:contextInfo: methods to make the presented print info consistent with the values showing in your accessory view.
New NSPrintPanel Methods to Control Preview and Which Standard Controls Appear
in Mac OS 10.5 a new preview feature has been added to the AppKit's printing system. The print panel now has a view that shows the user an image of the pages to be printed. It is on by default, but you can turn it off in your application. See the backward binary compatibility note at the end of this section for more information.
In many applications it is not appropriate to present page setup panels to the user, or even include a Page Setup... item in the File menu, but there has been no other easy way to let the user specify page setup parameters to be used when printing. (New UI advice: if your application doesn't persistently store page setup parameters on a per-document basis, or have some mechanism to associate them with whatever other kind of large-scale objects your application may deal with, it probably shouldn't have a page setup panel at all.) Also, some applications need to present print panels that don't include the standard fields for letting the user specify the number of copies or the range of pages to be printed.
So, In Mac OS 10.5, a new enumeration and new methods have been added to the NSPrintPanel class so that you can control previewing and what standard controls appear in the print panel. Please see the header file and documentation for info on these options.
enum {
NSPrintPanelShowsCopies = 0x01,
NSPrintPanelShowsPageRange = 0x02,
NSPrintPanelShowsPaperSize = 0x04,
NSPrintPanelShowsOrientation = 0x08,
NSPrintPanelShowsScaling = 0x10,
NSPrintPanelShowsPageSetupAccessory = 0x100,
NSPrintPanelShowsPreview = 0x20000
};
typedef NSInteger NSPrintPanelOptions;
- (void)setOptions:(NSPrintPanelOptions)options;
- (NSPrintPanelOptions)options;
In Mac OS 10.5 an -options message sent to a freshly-created NSPrintPanel will return (NSPrintPanelShowsCopies | NSPrintPanelShowsPageRange) unless it was created by an NSPrintOperation, in which case it will also return NSPrintPanelShowsPreview. To allow your application to take advantage of controls that may be added by default in future versions of Mac OS X, get the options from the print panel you've just created, turn on and off the flags you care about, and then set the options.
Backward binary compatibility note: The behavior described above applies to applications that are linked against Mac OS 10.5 or later. In any application linked against Mac OS 10.4 or earlier, the NSPrintPanelShowsPreview option is ignored if the application has set a print panel accessory view. Until Mac OS 10.5 there hasn't been any API that would allow you to cause the print preview to be redrawn when the user changes print settings using an accessory view, and if the print preview doesn't redraw then it's showing inaccurate information, and that's worse than no preview at all.
Other NSPrintPanel Enhancements
So that you can change the title of the default button in a print panel from "Print" to something else, two new methods have been added to NSPrintPanel:
- (void)setDefaultButtonTitle:(NSString *)defaultButtonTitle;
- (NSString *)defaultButtonTitle;
The title of the default button in the print panel. You can override the standard button title, "Print," when you're using an NSPrintPanel in such a way that printing isn't actually going to happen when the user presses that button.
So that you can change the help anchor for the question mark button in a print panel to point to something customized for your application, two other new methods have been added to NSPrintPanel:
- (void)setHelpAnchor:(NSString *)helpAnchor;
- (NSString *)helpAnchor;
The HTML help anchor for the print panel. You can override the standard anchor of the print panel's help button.
NSPrintPanel's -runModal method always uses the current NSPrintOperation's NSPrintInfo, so there has been no way to present an application-modal panel that uses any other NSPrintInfo. In Mac OS 10.5 a new method has been added to fix that:
- (NSInteger)runModalWithPrintInfo:(NSPrintInfo *)printInfo;
The default implementation of -runModal now simply invokes [self runModalWithPrintInfo:[[NSPrintOperation currentOperation] printInfo]].
An accessor for the NSPrintInfo that's being presented to the user for editing has also been added:
- (NSPrintInfo *)printInfo;
A simple accessor. Your -beginSheetWithPrintInfo:... delegate can use this so it doesn't have to keep a pointer to the NSPrintInfo elsewhere while waiting for the user to dismiss the print panel.
Access to Underlying Core Printing Objects from NSPrintInfo (Updated since WWDC 2007 Seed)
In previous versions of Mac OS X there has been no public way to get the Core Printing (previously known as "Printing Manager") objects that an NSPrintInfo is actually built on top of. To make it easy for you do to exactly the same things that the PMPrintSettingsGetValue()/PMPrintSettingsSetValue() functions added in Mac OS 10.4 let you do, a new method has been added to NSPrintInfo in Mac OS 10.5. The most immediate problem that this method solves is that there has been no way for your accessory views to put settings in a place where they can be saved and restored by the printing systems presets mechanism:
- (NSMutableDictionary *)printSettings;
The print info's print settings. You can put values in this dictionary to store them in any preset that the user creates while editing this print info with a print panel. Such values must be property list objects. You can also use this dictionary to get values that have been set by other parts of the printing system, like a printer driver's print dialog extension (the same sort of values that are returned by the Carbon Printing Manager's PMPrintSettingsGetValue() function). Other parts of the printing system often use key strings like "com.apple.print.PrintSettings.PMColorSyncProfileID" but dots like those in key strings wouldn't work well with KVC, so those dots are replaced with underscores in keys that appear in this dictionary, as in "com_apple_print_PrintSettings_PMColorSyncProfileID". You should use the same convention when adding entries to this dictionary.
Because there is a substantial amount of functionality exposed by Core Printing API that is not generally applicable enough to be exposed by AppKit API, new methods have been added to NSPrintInfo in Mac OS 10.5 to allow access to the underlying Core Printing objects:
- (void * /* PMPrintSession */)PMPrintSession;
- (void * /* PMPageFormat */)PMPageFormat;
- (void * /* PMPrintSettings */)PMPrintSettings;
Return a Core Printing PMPrintSession, PMPageFormat, or PMPrintSettings object, respectively. The returned object is always consistent with the state of the NSPrintInfo at the moment the method is invoked, but isn't necessarily updated immediately if other NSPrintInfo methods like -setPaperSize: and -setPaperOrientation: are invoked. The returned object will always be valid (in the Core Printing sense). If you set any values in the returned PMPageFormat or PMPrintSettings you should afterward invoke -updateFromPMPageFormat or -updateFromPMPrintSettings, respectively. You don't also have to call PMSessionValidatePageFormat() or PMSessionValidatePrintSettings() if you do that. You should not call PMRelease() for the returned object, except of course to balance any calls of PMRetain() you do.
- (void)updateFromPMPageFormat;
- (void)updateFromPMPrintSettings;
Given that the NSPrintInfo's PMPageFormat or PMPrintSettings has been changed by something other than the NSPrintInfo itself, updates the NSPrintInfo to be consistent.
NSPrintInfo's Key-Value Coding and Key-Value Observing Compliance (New since WWDC 2007 Seed)
Despite what the comment for -[NSPrintInfo printSettings] in <AppKit/NSPrintInfo.h> says, NSPrintInfo's KVC/KVO-compliance is not really complete enough to be useful in Mac OS 10.5. You can add a key-value observer for a key path to an NSPrintInfo but, for example, the observer won't be consistently notified of changes made by the user when the NSPrintInfo is being presented by an NSPrintPanel.
NSPrintOperation Enhancements
In previous versions of Mac OS X the print job title that the printing system uses has been gotten by invoking -[NSView(NSPrinting) printJobTitle:]. Because it is often not programmatically convenient to make such information available to printed views, new methods have been added to NSPrintOperation in Mac OS 10.5, so that the code that creates the print operation can simply set the print job title right away, and not have to leave it somewhere the printed view can find it:
- (void)setJobTitle:(NSString *)jobTitle;
- (NSString *)jobTitle;
If a job title is set it overrides anything that might be gotten by sending the printed view an [NSView(NSPrinting) printJobTitle] message.
In previous versions of Mac OS X the -currentPage method has allowed you to find out exactly which page is being printed during the running of a print operation, but there has been no easy way to determine how many pages the document being printed has. In Mac OS 10.5 a new method has been added to let you do that:
- (NSRange)pageRange;
The first page number might not be 1, depending on what the printed view returned when sent an -[NSView(NSPrinting) knowsPageRange:] message.
Support for Multiple Page Orientations per Print Operation
In Mac OS 10.5 you can now change the orientation of pages while a job is being printed. To do so your printed view must do its own pagination, and indicate so by returning YES when sent -knowsPageRange: messages. Then, when sent -rectForPage: messages, it may change the orientation of the current print operation's print info, and Cocoa will heed the change. For example: [[[NSPrintOperation currentOperation] printInfo] setOrientation:theNewOrientation].
Deprecated NSPrintOperation Methods, and a Bug Fix in -[NSPrintOperation printPanel]
In Mac OS 10.5 the -setAccessoryView: and -accessoryView methods are now also deprecated in NSPrintOperation.
Also deprecated are the -setJobStyleHint: and -jobStyleHint: methods. Instead of sending one of those messages to an NSPrintOperation, just invoke -[NSPrintOperation printPanel] and send them to the returned NSPrintPanel instead. -[NSPrintOperation printPanel] has been updated to create and return a new print panel if none has been set yet. (It would usually just return nil before.)
Bug Fixes in -[NSPrintInfo paperName], -[NSPrintInfo setPaperName:], and -[NSPrinter pageSizeForPaper:]
In Mac OS 10.2 through Mac OS 10.4 there was a bug in -[NSPrintInfo paperName] that would cause it to return incorrect paper names. For example, it might return "na-letter" or "Letter" when it should return "LetterSmall" (remember, the paper "names" returned by -paperName are not for showing to users; use -localizedPaperName to get those). Likewise, -[NSPrintInfo setPaperName:] could set a paper that had the same size as the named paper but a different name. These problems were particularly noticeable when a printer supports multiple "papers" that have the same sizes but different imageable areas. The bug has been fixed in Mac OS 10.5.
In earlier versions of Mac OS X -[NSPrinter pageSizeForPaper:] only worked when passed strings that are paper names, not paper IDs, in the senses established by the Core Printing API. In Mac OS 10.5 this method now always works when passed paper IDs (the sort of string now always returned by -[NSPrintInfo paperName]). For backward binary compatibility it returns the same values that it would have in Mac OS 10.4 if passed a paper name.
Bug Fixes in NSPrintOperation/NSPrintPanel Cancellation
In earlier versions of Mac OS X there was a bug in which a print job would be sent to the printer even though the user had hit the Cancel button in the print progress panel, if rendering of the last page of the job had already begun. This bug has been fixed in Mac OS 10.5.
There was also a bug in which invoking [[[NSPrintOperation currentOperation] printInfo] setJobDisposition:NSPrintCancelJob] from within overrides of NSView(NSPrinting) methods did not prevent the part of the print job that had already been rendered from being sent to the printer. This bug has also been fixed in Mac OS 10.5. You can now programmatically cancel the current print operation in your overrides of any of the NSView(NSPrinting) methods.
In earlier versions of Mac OS X there was no way to distinguish between user cancellation and failure when invoking -[NSPrintOperation runOperationModalForWindow:delegate:didRunSelector:contextInfo:] or -[NSPrintPanel beginSheetWithPrintInfo:modalForWindow:delegate:didEndSelector:contextInfo:]. In Mac OS 10.5 these methods always set the job disposition of the presented print info to NSPrintCancelJob when the user has cancelled the print panel, just as -[NSPrintOperation runOperation] and -[NSPrintPanel runModal] always have. For backward binary compatibility this is only done if the application is linked against Mac OS 10.4 or earlier. In the case of NSPrintOperation the "presented print info" is the one that can be gotten with -[NSPrintOperation printInfo], not the one that was passed in when the NSPrintOperation was created.
There was a bug in which an NSPrintOperation created with a print info whose job disposition was NSPrintCancelJob would never result in output, regardless of what button the user used to dismiss the print panel. This bug has been fixed.
Advice for Developers Working with Printing Margins
The "Printer Margins" in the Custom Page Sizes panel are confusingly named. They are not margins in the NSPrintInfo sense, or in the sense of Microsoft Word, AppleWorks, or any app that lets the user set a document's page margins. They are the widths and heights of the areas of the page that lie outside of the imageable area for the paper size. In other words, the Custom Page Sizes panel is giving you the opportunity to simulate hardware limitations when defining a new paper size. You can use the -[NSPrintInfo imageablePageBounds] method to get the area of page that is surrounded by the printer margins.
Support for Uniform Type Identifiers (UTIs) in NSDocument-Based Applications
If your application requires Mac OS 10.5, it may stop using these legacy Info.plist keys in its CFBundleDocumentTypes entries:
CFBundleTypeExtensions
CFBundleTypeMIMETypes
CFBUndleTypeOSTypes
NSExportableAs
It may instead use the LSItemContentTypes and (new for Mac OS 10.5) NSExportableTypes keys, and declare the corresponding UTIs as appropriate in its Info.plist. Declaration of UTIs in Info.plist files is described by <http://developer.apple.com/macosx/uniformtypeidentifiers.html>. For each CFBundleDocumentTypes entry that has an LSItemContentTypes subentry, sibling subentries with the keys listed above are ignored. (Actually Cocoa's Info.plist parsing has always ignored CFBundleTypeMIMETypes subentries. In Mac OS 10.5 it continues to. LaunchServices pays attention to them though, except when there's an LSItemContentTypes sibling. So, in Mac OS 10.5 Cocoa and LaunchServices follow the same rules about ignoring LSItemContentTypes siblings.)
If your application uses the LSItemContentTypes key it must also use the NSExportableTypes key where applicable. NSExportableTypes entries serve the same purpose as NSExportableAs entries, and are likewise subentries of CFBundleDocumentTypes entries, but their values are arrays of UTIs instead of arrays of old-style free-form type names.
Use of these keys becomes optional:
CFBundleTypeName
CFBundleTypeIconFile
For each CFBundleDocumentTypes entry that has an LSItemContentTypes subentry you can provide subentries using these keys to override the default icon and kind string that would otherwise result from UTI declarations. As has always been the case, any use of CFBundleTypeName should be accompanied by an entry in the application's InfoPlist.strings files.
The meanings of CFBundleTypeRole and NSDocumentClass subentries have not changed in Mac OS 10.5.
For each CFBundleDocumentTypes entry that has an LSItemContentTypes subentry, the UTIs are used as programmatic type names by NSDocument and NSDocumentController, as described below, instead of the value of any CFBundleTypeName subentry that might also be present.
For backward binary compatibility, none of this new behavior takes affect if the application is linked against Mac OS 10.4 or earlier. A CFBundleDocumentTypes entry that has no LSItemContentTypes subentry at all has the same meaning to Cocoa as in Mac OS 10.4.
Debugging tip: In Mac OS 10.5 you can find out what NSDocumentController thinks it's found in your application's Info.plist by executing 'print-object [[NSClassFromString(@"NSDocumentController") sharedDocumentController] _typeDescriptions]' in gdb (using the Xcode Debugger Console, for instance). Do not attempt to invoke or override -_typeDescriptions in your application. It is there just for this debugging purpose, and may disappear at any time.
Shipping tip: If you want to take advantage of UTIs in your application but have it still run on both Mac OS 10.4 and Mac OS 10.5, you can merely add LSItemContentTypes (and NSExportableTypes, where appropriate) subentries to your Info.plist, and then update your overrides of NSDocumentController and NSDocument methods so that they recognize UTIs as type names in addition to the sort of document types names that were always used in Mac OS 10.4. Be careful to add LSItemContentTypes subentries to all of the app's CFBundleDocumentTypes Info.plist entries though, or things could get more complicated. See the description of -typeForContentsOfURL:error:'s new behavior down below for instance.
Support for UTIs in NSDocumentController
In Mac OS 10.5 NSDocumentController supports UTIs. In the following paragraphs an "app-declared" UTI is one that is declared in a well-formed LSItemContentTypes subentry of a well-formed CFBundleDocumentTypes Info.plist entry, in an application that is linked against Mac OS 10.5 or later. A "recognized" UTI is app-declared or conforms to one of the app-declared UTIs.
None of the NSDocumentController methods that were deprecated in Mac OS 10.4 have been updated to handle UTIs properly. You have to stop invoking and overriding deprecated methods to take advantage of UTIs in your application.
-makeUntitledDocumentOfType:error:, -makeDocumentWithContentsOfURL:ofType:error:, and -makeDocumentForURL:withContentsOfURL:ofType:error: all now accept recognized UTIs as type strings, in addition to the sort of document type names that were accepted in Mac OS 10.4. The latter two methods have also been updated to properly handle unrecognized types too. (Because -typeForContentsOfURL:error: might now return an urecognized type.)
-URLsFromRunningOpenPanel now, for each CFBundleDocumentTypes entry that declares an openable document type (as in, has a CFBundleTypeRole of Editor or Viewer, as in Mac OS 10.4) with app-declared UTIs, passes those UTIs into -runModalOpenPanel:forTypes:. For each CFBundleDocumentTypes that declares an openable document without recognized UTIs it still passes file name extensions and encoded HFS file types as in Mac OS 10.4.
-runModalOpenPanel:forTypes: nows accepts all valid UTIs in the array of type strings, in addition to the file name extensions and encoded HFS file types that were accepted in Mac OS 10.4.
-defaultType chooses a CFBundleDocumentTypes entry and considers that document type to be the default type, as in Mac OS 10.4. Once it has made that choice it now returns the first app-declared UTI for that type, if there are any, or a document type name, of the sort returned in Mac OS 10.4, if not. (So the order of elements in the LSItemContentTypes array does matter, just as the order of elements in the CFBundleDocumentTypes array always has.)
-typeForContentsOfURL:error: now uses -[NSWorkspace typeOfFile:error:] to find out the UTI of the file located by the URL. (So it might return a UTI that is not recognized. AppKit's own invocations of it take that fact into account.) For backward binary compatibility it however first tries the same thing that it did in Mac OS 10.4 (invoke -typeFromFileExtension:, possibly twice, passing an HFS file type string for the second invocation) if there are any CFBundleDocumentTypes Info.plist entries that don't have LSItemContentTypes subentries.
-documentClassForType: now accepts any recognized UTI as the type string. As in Mac OS 10.4 it sends a +readableTypes message to each class named in the results of invoking -documentClassNames, and returns the first class whose readable types include one that matches the passed-in type. "Matches" for a UTI means "conforms to," instead of merely "is equal to." (So this method will work even when passed a UTI that is not explicitly declared in the application's Info.plist, but conforms to one that is.)
-displayNameForType: now accepts all valid UTIs as the type string, in addition to the sort of document type names that were accepted in Mac OS 10.4. It returns nil when passed an invalid UTI. For backward binary compatibility it continues to not return nil when passed a non-UTI.
-fileExtensionsFromType: and -typeFromFileExtension: have not changed, but are being deprecated. -fileExtensionsFromType: does not work when passed a UTI. -typeFromFileExtension: only works when passed a file name extension used in a CFBundleDocumentTypes entry that does not have an LSItemContentTypes subentry. In general, if each of the application's CFBundleDocumentTypes Info.plist entries has a valid LSItemContentTypes subentry, and the application doesn't invoke deprecated methods like -fileNamesFromRunningOpenPanel, then these methods will never be invoked from within Cocoa.
Support for UTIs in NSDocument
In Mac OS 10.5 NSDocument's handling of types is consistent with NSDocumentController. Every non-deprecated NSDocument method that takes a type: or ofType: argument now accepts recognized UTIs as type strings, in addition to the sort of document type names that were accepted in Mac OS 10.4. -setFileType: and -fileType: don't actually interpret the file type name in any way, so they work fine with UTIs.
None of the NSDocument methods that were deprecated in Mac OS 10.4 have been updated to handle UTIs properly. You have to stop invoking and overriding deprecated methods to take advantage of UTIs in your application.
+readableTypes, +writableTypes, and -writableTypesForSaveOperation: all now return UTIs for CFBundleDocumentTypes entries with app-declared UTIs, and document types of the sort that were returned in Mac OS 10.4 for those that don't.
+isNativeType: now accepts all valid UTIs as the type string, in addition to the sort of document type names that were accepted in Mac OS 10.4. It will return YES if a passed-in UTI conforms to a recognized UTI for the document class in question, NO otherwise.
A new method has been added to NSDocument, because -[NSDocumentController fileExtensionsFromType:] is being deprecated:
- (NSString *)fileNameExtensionForType:(NSString *)typeName saveOperation:(NSSaveOperationType)saveOperation;
For a specified type, and a particular kind of save operation, return a file name extension that can be appended to a base file name. The default implementation of this method invokes [[NSWorkspace sharedWorkspace] preferredFilenameExtensionForType:typeName] if the type is a UTI or, for backward binary compatibility with Mac OS 10.4 and earlier, invokes [[NSDocumentController sharedDocumentController] fileExtensionsFromType:typeName] and chooses the first file name extension in the returned array if not.
You can override this method to customize the appending of extensions to file names by NSDocument. In Mac OS 10.5 it's only invoked from two places within Cocoa: 1) -autosaveDocumentWithDelegate:didAutosaveSelector:contextInfo: uses this method when creating a new file name for the autosaved contents. 2) -[NSDocument(NSScripting) handleSaveScriptCommand:] uses this method when adding an extension to the file name specified by a script. In all other cases the name of any file being saved will have been fully specified by the user, with the save panel (whether they know it or not).
Support for UTIs in NSPersistentDocument
In Mac OS 10.5 NSPersistentDocument's handling of types is consistent with NSDocumentController's and NSDocument's. Every NSPersistentDocument method that takes a type string argument now accepts recognized UTIs, in addition to the sort of document type names that were accepted in Mac OS 10.4.
Support for UTIs in NSOpenPanel
In Mac OS 10.5 NSOpenPanel supports UTIs. The following methods all now accept all valid UTIs as type strings, in addition to the file name extensions and encoded HFS file types that were accepted in Mac OS 10.4:
-beginSheetForDirectory:file:types:modalForWindow:modalDelegate:didEndSelector:contextInfo:
-beginForDirectory:file:types:modelessDelegate:didEndSelector:contextInfo:
-runModalForDirectory:file:types:
-runModalForTypes:
NSOpenPanel will let the user choose files whose types conform to those identified by the passed-in UTIs. So, you can let the user select any image file by passing in a UTI like public.image. Be aware however that the set of types conforming to another can be extended by any application installed on the computer, so this might not be a good idea if your application actually has to open the files the user chooses with the open panel. Typically you'll pass in UTIs for more concrete types, like public.tiff, com.adobe.pdf, or com.apple.sketch2.
Support for UTIs in NSSavePanel
In Mac OS 10.5 NSSavePanel supports UTIs. The following methods now accept or return all valid UTIs as type strings, in addition to the file name extensions that were accepted and returned in Mac OS 10.4 (encoded HFS file types have never been valid values for these methods):
-setAllowedFileTypes:
-setRequiredFileType:
-allowedFileTypes:
-requiredFileType:
Support for UTIs in NSWorkspace
In Mac OS 10.5 NSWorkspace supports UTIs. -iconForFileType: now accepts all valid UTIs as type string, in addition to the file name extensions and encoded HFS file types that were accepted in Mac OS 10.4.
Several methods have been added to NSWorkspace:
- (NSString *)typeOfFile:(NSString *)absoluteFilePath error:(NSError **)outError;
Given an absolute file path, return the uniform type identifier (UTI) of the file, if one can be determined. Otherwise, return nil after setting *outError to an NSError that encapsulates the reason why the file's type could not be determined. If the file at the end of the path is a symbolic link the type of the symbolic link itself will be returned, not the type of the linked file. You can invoke this method to get the UTI of an existing file.
- (NSString *)localizedDescriptionForType:(NSString *)typeName;
Given a UTI, return a string that describes the document type and is fit to present to the user, or nil for failure. You can invoke this method to get the name of a type that must be shown to the user, in an alert about your application's inability to handle the type, for instance.
- (NSString *)preferredFilenameExtensionForType:(NSString *)typeName;
Given a UTI, return the best file name extension to use when creating a file of that type, or nil for failure. You can invoke this method when your application has only the base name of a file that's being written and it has to append a file name extension so that the file's type can be reliably identified later on.
- (BOOL)filenameExtension:(NSString *)filenameExtension isValidForType:(NSString *)typeName;
Given a file name extension and a UTI, return YES if the file name extension is a valid tag for the identified type, NO otherwise. You can invoke this method when your application needs to check if a file name extension can be used to reliably identify the type later on. For example, NSSavePanel uses this method to validate any extension that the user types in the panel's file name field.
- (BOOL)type:(NSString *)firstTypeName conformsToType:(NSString *)secondTypeName;
Given two UTIs, return YES if the first "conforms to" to the second in the uniform type identifier hierarchy, NO otherwise. This method will always return YES if the two strings are equal, so you can also use it with other kinds of type name, including those declared in CFBundleTypeName Info.plist entries in apps that don't take advantage of the support for UTIs that was added to Cocoa in Mac OS 10.5. You can invoke this method when your application must determine whether it can handle a file of a known type, returned by -typeOfFile:error: for instance. Use this method instead of merely comparing UTIs for equality.
Support for UTIs in NSPasteboard (Updated since WWDC 2007 Seed)
In Mac OS 10.5 NSPasteboard supports UTIs. Every NSPasteboard method that takes a type string or type string array argument now accepts UTIs as type strings, in addition to the sort of pasteboard type names that were accepted in Mac OS 10.4.
-types now returns an array that contains UTIs, as well as the pasteboard type names that would be returned in Mac OS 10.4.
When one of your application's pasteboard owners' -pasteboard:provideDataForType: methods is invoked it will still always be passed the same string that was specified in the promising invocation of -declareTypes:owner: or -addTypes:owner.
When -availableTypeFromArray: encounters a UTI in the type array provided to it, it will return that UTI if the exact UTI exists anywhere in the pasteboard's array of types. If no pasteboard type matches the UTI exactly, the first type on the pasteboard that conforms to the UTI will be returned.
Support for UTIs in NSView and NSWindow (Updated since WWDC 2007 Seed)
Likewise, in Mac OS 10.5 NSView and NSWindow support UTIs. NSView and NSWindow's -registerForDraggedTypes: methods now accept UTIs as type strings, in addition to the sort of pasteboard type names that were accepted in Mac OS 10.4. NSView's -dragPromisedFilesOfTypes:fromRect:source:slideBack:event: method now accepts UTIs as type strings, in addition to the sort of file name extensions that were accepted in Mac OS 10.4.
For UTIs registered as dragged types, UTI conformance is checked instead of equality to determine if a dragging destination should be given a chance to handle a drag. For example, a view with the UTI kUTTypeImage registered as a dragged type will have its dragging destination methods called to handle a drag in its bounds when the dragging pasteboard contains any type that conforms to kUTTypeImage.
Support for UTIs in Services
You can now specify declared UTIs instead of pasteboard types as the elements of the NSSendTypes or NSReturnTypes arrays in the Services declaration part of an application's Info.plist.
Support for UTIs in Miscellaneous AppKit Classes
In earlier versions of Mac OS X, these four classes:
NSImage
NSImageRep
NSSound
NSAttributedString (in the NSAttributedStringKitAdditions category)
have all had pairs of methods that return either arrays of file type strings (file name extensions and encoded HFS file types) or pasteboard type strings. In Mac OS 10.5, these methods are joined by single new methods that just return arrays of UTIs. Also, NSImageRep's methods that take file type or pasteboard type strings are joined by a new method that just takes UTIs.
It's important to keep in mind when working with UTIs that mere string equality checking is not the correct way to check if the type identified by one UTI "conforms" to the type identified by another. See the description of the -[NSWorkspace type:conformsToType:] in the "Support for UTIs in NSWorkspace" section.
The follow sections contain the details of this change for each class.
Support for UTIs in NSImage
In NSImage, these new methods:
+ (NSArray *)imageTypes;
+ (NSArray *)imageUnfilteredTypes;
join these methods, which might be deprecated in a future release of Mac OS X, but are not yet:
+ (NSArray *)imageFileTypes;
+ (NSArray *)imagePasteboardTypes;
+ (NSArray *)imageUnfilteredFileTypes;
+ (NSArray *)imageUnfilteredPasteboardTypes;
(The old methods are not yet deprecated because you might still have a reason to override them, because the -initWithContentsOfFile:, -initWithContentsOfURL:, -initByReferencingFile:, -initByReferencingURL:, -initWithPasteboard:, and +canInitWithPasteboard: methods have not yet been updated to use UTIs when deciding which subclass of NSImageRep should be instantiated. The same is true of -[NSBundle(NSBundleImageExtension) pathForImageResource:].)
Support for UTIs in NSImageRep
In NSImageRep, these new methods:
+ (Class)imageRepClassForType:(NSString *)type;
+ (NSArray *)imageTypes;
+ (NSArray *)imageUnfilteredTypes;
join these methods, which might be deprecated in a future release of Mac OS X, but are not yet:
+ (Class)imageRepClassForFileType:(NSString *)type;
+ (Class)imageRepClassForPasteboardType:(NSString *)type;
+ (NSArray *)imageFileTypes;
+ (NSArray *)imagePasteboardTypes;
+ (NSArray *)imageUnfilteredFileTypes;
+ (NSArray *)imageUnfilteredPasteboardTypes;
(The old methods are not yet deprecated because you might still have a reason to override them, because the +imageRepsWithContentsOfFile:, +imageRepWithContentsOfFile:, +imageRepsWithContentsOfURL:, +imageRepWithContentsOfURL:, +imageRepsWithPasteboard:, +imageRepWithPasteboard:, and +canInitWithPasteboard: methods have not yet been updated to use UTIs when deciding which subclass of NSImageRep should be instantiated, or whether a subclass can be instantiated, in the case of the last method.)
Support for UTIs in NSSound
In NSSound, this new method:
+ (NSArray*)soundUnfilteredTypes;
replaces these deprecated methods:
+ (NSArray *)soundUnfilteredFileTypes;
+ (NSArray *)soundUnfilteredPasteboardTypes;
Support for UTIs in AppKit's NSAttributedStringKitAdditions Category on NSAttributedString
In NSAttributedString(NSAttributedStringKitAdditions), these new methods:
+ (NSArray *)textTypes;
+ (NSArray *)textUnfilteredTypes;
replace these deprecated methods:
+ (NSArray *)textFileTypes;
+ (NSArray *)textPasteboardTypes;
+ (NSArray *)textUnfilteredFileTypes;
+ (NSArray *)textUnfilteredPasteboardTypes;
The -initWithURL:options:documentAttributes:error:, -initWithPath:documentAttributes:, and -initWithURL:documentAttributes: methods have all been updated to use UTIs when appropriate. So have NSMutableAttributedString(NSMutableAttributedStringKitAdditions)'s -readFromURL:options:documentAttributes:error: and -readFromURL:options:documentAttributes: methods.
Rewritten NSDocument Safe Saving, and a Bug Fixing for Saving Documents That Change From Plain Files to File Packages
-[NSDocument writeSafelyToURL:ofType:forSaveOperation:error:] has been rewritten to use CarbonCore's new FSPathReplaceObject() function. Some kinds of metadata, like extended attributes and access control lists, will now more often be properly preserved during document saving, especially of file packages. Also, safe document saving is now a little safer, particularly when the disk being written to is full. For example, your users will no longer see their documents get renamed with a "~" on the end, and left that way, when document saving fails because there is not enough space on disk to save a new document revision.
In Mac OS 10.4 and earlier there was a bug in which NSDocument would malfunction when a document that was a plain file on disk was overwritten with a file package of the same name during a save operation. (Some applications use the same file name extension for both the flat-file and the directory-based variants of what is conceptually, as far as the user is concerned, the same file format.) This bug has been fixed in Mac OS 10.5.
NSDocument Checking for Modified Files At Saving Time
In Mac OS 10.5 -[NSDocument saveDocumentWithDelegate:didSaveSelector:contextInfo:] now checks to see if the document's file has been modified since the document was opened or most recently saved or reverted, in addition to the checking for file moving, renaming, and trashing that it has done since Mac OS 10.1. When it senses file modification it presents an alert telling the user "This document’s file has been changed by another application since you opened or saved it," giving them the choice of saving or not saving. For backward binary compatibility this is only done in applications linked against Mac OS 10.5 or later.
When updating your application to link against Mac OS 10.5, keep in mind that it's usually more appropriate to invoke one of NSDocument's -save… methods in your application code than one of the -write… methods. The -write… methods are there primarily for you to override. -saveToURL:ofType:forSaveOperation:error:, which is the method that's meant to always be invoked during document saving, invokes -setFileModificationDate: with the file's new modification date after it's been written (for NSSaveOperation and NSSaveAsOperation only).
Likewise, it's usually more appropriate to invoke one of NSDocument's -revert… methods in your application code code than one of the -read… methods. The -read… methods are there primarily for you to override. -revertToContentsOfURL:ofType:error:, which is the method that's meant to always be invoked during rereading of an open document, invokes -setFileModificationDate: with the file's modification date after it's been read.
Bug Fix in -[NSDocument isDocumentEdited], and New Constant Used with -[NSDocument updateChangeCount:]
In previous versions of Mac OS X there has been a bug in which saving a document, undoing changes, and then making an equal number of new changes would cause the document to appear unmodified. If the user closed the document it would simply be closed, with no warning about unsaved changes, which would be lost. This happened in any application in which the document did not send [self updateChangeCount:NSChangeCleared] during document saving (which you're not supposed to have to do). This bug has been fixed in Mac OS 10.5, by virtue of NSDocument now drawing a distinction between doing and redoing of changes. It no longer invokes [self updateChangeCount:NSChangeDone] when it receives an NSUndoManagerDidRedoChangeNotification. Now it invokes [self updateChangeCount:NSChangeRedone] instead. (NSChangeDone is still used for NSUndoManagerWillCloseUndoGroupNotification.) NSChangeRedone is new, and declared in <AppKit/NSDocument.h>.
For backward binary compatibility NSDocument only uses NSChangeRedone instead of NSChangeDone in applications linked against Mac OS 10.5 or later, or if -updateChangeCount: is not overridden.
Bug Fix in NSDocument for Nested Undo Manager Groups
In previous versions of Mac OS X there was a bug in which NSDocument's undo support did not take into account nested undo manager groups. Each NSDocument would merely send itself a [self updateChangeCount:NSChangeDone] message whenever it received an NSUndoManagerWillCloseUndoGroupNotification from its undo manager. This would cause it to improperly count how many changes the user had made so that, for example, making one undoable change represented by multiple actions in nested undo groups, and then undoing that change, would still show the document as modified. This problem was particularly noticeable in document-based Core Data applications, because NSManagedObjectContext uses nested undo manager groups. This has been fixed in Mac OS 10.5. NSDocument now checks the undo manager's grouping level when it receives NSUndoManagerWillCloseUndoGroupNotification, and only invokes -updateChangeCount: if the nesting level is less than two.
Bug Fix in -[NSDocument writeToURL:ofType:error:]'s Use of NSFileWrapper
In Mac OS 10.4, -[NSDocument writeToURL:ofType:error:] passed NO as the last argument when invoking -[NSFileWrapper writeToFile:atomically:updateFilenames:]. In Mac OS 10.5 it now passes YES so that the file wrapper and any file wrappers it contains have their file names updated during saving. -[NSDocument writeToFile:ofType:], which was deprecated in Mac OS 10.4, has not been updated in a similar way.
Bug Fix in NSDocument's Use of -[NSDocument autosavingFileType]
In Mac OS 10.4, -[NSDocument autosaveDocumentWithDelegate:didAutosaveSelector:contextInfo:] would invoke [self autosavingFileType] the first time a document was autosaved, create an autosaved contents file URL using the implied file name extension, and then keep using that URL for all subsequent autosaves. If -autosavingFileType was overridden to return different values at different times, this could result in inconsistencies. For example, the new version of TextEdit in Leopard that uses autosaving could end up autosaving an RTFD file package with an ".rtf" file name extension. (Because it overrides -autosavingFileType to account for attachments being added to the document.) This was a bug, and has been fixed in Mac OS 10.5. NSDocument now consistently uses the result of invoking -autosavingFileType when determining where to autosave.
Advice for Overriders of -[NSDocument autosavingFileType]
Even with the bug fixed mentioned above, overriding -autosavingFileType can result in incorrect behavior during reopening of autosaved documents if you're not careful. -[NSDocument initForURL:withContentsOfURL:ofType:error:], which is invoked during reopening of autosaved documents after a crash, takes two URLs, but only the type name of the autosaved contents file. The default implementation invokes [self setFileType:] with that type name, but that is often not the right thing to do, if -autosavingFileType had returned something other than -fileType during document autosaving. If you override -autosavingFile, you probably have to override -initForURL:withContentsOfURL:ofType:error: too, and make the override invoke -setFileType: with the type of the actual document file, after invoking super. See TextEdit's Document class for an example of how to do this.
Advice for Overriders of NSDocument Reading and Writing Methods
If you subclass NSDocument and override any of these methods for reading:
-readFromData:ofType:error:
-readFromFileWrapper:ofType:error:
-readFromURL:ofType:error:
Or any of these for writing:
-dataOfType:error:
-fileWrapperOfType:error:
-writeToURL:ofType:error:
-writeToURL:ofType:forSaveOperation:originalContentsURL:error:
-fileAttributesToWriteToURL:ofType:forSaveOperation:originalContentsURL:error:
-writeSafelyToURL:ofType:forSaveOperation:error:
Or any of the methods that were deprecated in favor of them, in Mac OS 10.4:
-dataRepresentationOfType:
-fileAttributesToWriteToFile:ofType:saveOperation:
-fileWrapperRepresentationOfType:
-initWithContentsOfFile:ofType:
-initWithContentsOfURL:ofType:
-loadDataRepresentation:ofType:
-loadFileWrapperRepresentation:ofType:
-readFromFile:ofType:
-readFromURL:ofType:
-writeToFile:ofType:
-writeToFile:ofType:originalFile:saveOperation:
-writeToURL:ofType:
-writeWithBackupToFile:ofType:saveOperation:
Don't invoke -fileURL (or -fileName, the method that was deprecated in favor if it, in Mac OS 10.4), -fileType, or -fileModificationDate from within your overrides. During reading, which typically happens during object initialization, there is no guarantee that NSDocument properties like the file's location or type have been set yet. Your overridden method should be able to determine everything it needs to do the reading from the passed-in parameters. During writing, your document may be being asked to write its contents to a different location, or using a different file type. Again, your overridden method should be able to determine everything it needs to do the writing from the passed-in parameters.
If your override cannot determine all of the information it needs from the passed-in parameters, consider overriding another method. For example, if you see the need to invoke -fileURL from within an override of -readFromData:ofType:error:, perhaps you should instead override -readFromURL:ofType:error:. For another example, if you see the need to invoke -fileURL from within an override of -writeToURL:ofType:error:, perhaps you should instead override -writeToURL:ofType:forSaveOperation:originalContentsURL:error:.
Advice for Overriders of -[NSDocument displayName]
Some applications have subclasses of NSDocument that override -displayName to customize the the titles of windows associated with the document. That is usually not the right thing to do. Use a subclass of NSWindowController, and override -[NSWindowController windowTitleForDocumentDisplayName:] instead. If even deeper customization is required override -[NSWindowController synchronizeWindowTitleWithDocumentName]. A document's display name is used in several other places where the custom value that an application might want to use as a window title is typically not appropriate:
- In error alerts that may be presented during reverting, saving, or printing of the document.
- In alerts tha