Figure 1

Figure 1

Figure 2

Figure 2

Token fields (Figures 1 and 2) – encapsulated by the NSTokenField class have been a familiar sight in Mac apps sense OS X 10.4. They represent a tidy means of categorizing data entry in a text field.  They are relatively straightforward for the developer to configure, with just two or three delegate methods to consider.

The delegate acts as a data source for the token field, supplying an array of strings that the field can tokenize.  But what if you have an array controller that is managing the underlying array?  The class CCFArrayControlledTokenField is one solution to this use case.  If you want to go straight for the source code, licensed under the MIT license, go for it.  Otherwise, stick around for the details.
Read more

01.16.2010

Recently, I needed to make a decision between Headerdoc and Doxygen for source code documentation.  On the one hand, Headerdoc has the tacit support of Apple, with built-in scripts in Xcode, special syntax highlighting for Headerdoc markup and the like.  But I really don’t like the output; and I couldn’t muster the energy to tinker with something that appears to be on its way out.  So, I’ve crafted some solutions for getting Doxygen to play nicely with XCode.

First, it really does work fairly well out of the box.  What follows are some tips to get it working as well (or better) than Headerdoc.

Syntax highlighting

If you want to apply the same syntax highlighting to Doxygen tags as for Headerdoc, you will need to edit two files:  Built-in languages.pblangspec and C.xlangspec.  If you have a standard install of the Developer Tools, find the Built-in languages.pblangspec file at
/Developer/Library/PrivateFrameworks/DevToolsCore.framework/Versions/A/Resources/Built-in languages.pblangspec

Open the file with your favorite text editor and do a search for:
// ANSI C

Scroll down to find the section DocCommentKeywords Just add whatever Doxygen tags you wish to that list.

Find the second file at:
/Developer/Library/PrivateFrameworks/XcodeEdit.framework/Versions/A/Resources/C.xclangspec

Again, open the file and search for:
// MARK: HeaderDoc

Scroll down and find the list of Words, adding your Doxygen tags to the list. Save the files; and the syntax highlighting should work just like for Headerdoc.

User scripts

Xcode comes with a number of user scripts that support Headerdoc out-of-the-box. It is a simple matter to adapt the scripts to apply Doxygen tags in a similar manner. These are just shell scripts; so you can use your favorite flavor of scripting language – Perl, Python, etc. By way of example, here’s a script that adapts the @class script from the Headerdoc scripts to the Doxygen format. It’s shamelessly lifted from the original scripts in Xcode; and all of the changes are at the end of the script.


#! /usr/bin/perl -w
#
# Inserts a template Doxygen comment for an
# Objective-C method.
# If the user selects a method declaration and
# chooses this command, the template includes
# the method name and the names of each parameter.
# If the user doesn't select a declaration before issuing
# this command, a default template is inserted.

use strict;

my $selection = <<'SELECTION';
%%%{PBXSelectedText}%%%
SELECTION
chomp $selection;
my $unmodifiedSelection = $selection; # used to retain linebreaks in output

$selection =~ s/\n/ /sg;     # put on one line, if necessary
$selection =~ s/\s+$//;      # remove any trailing spaces
$selection =~ s/\s{2,}/ /g;  # regularize remaining spaces

my $displayMethodName= '';
my $returnsAValue= 0;
my @params = ();

# is it a method declaration that we understand?
if (length($selection) && ($selection =~ /^[+-]/) && ($selection =~ /;$/)) {
    # determine if it returns a value
    $selection =~ m/[+-]\s+(\((.*?)\))?(.*);/;
    my $return = $2;
    my $fullMethodName = $3;
    if ((defined($return)) && ($return ne 'void')) {$returnsAValue=1;};

    if (defined($fullMethodName)) {
        # get rid of type info for args
        $fullMethodName =~ s/\(.*?\)//g;

        if ($fullMethodName =~ /:/) {
            # get keyword:arg pairs
            my @keyArgPairs = split(/\s+/, $fullMethodName);

            foreach my $pair (@keyArgPairs) {
                if ($pair =~ /:/) { # don't treat parameters with spaces as method names
                    my @parts = split(/:/, $pair);
                    while (@parts) {
                        $displayMethodName .= shift(@parts).":";
                        push (@params, shift @parts);
                    }
                } else {
                    if (length($pair)) { # but do add them to the parameter list
                        push (@params, $pair);
                    }
                }
            }
         } else {
             $displayMethodName = $fullMethodName;
         }
    }
}

print "/*!\n";
print "    \@method     $displayMethodName\n";
print "    \@brief      %%%{PBXSelection}%%%<#(brief description)#>%%%{PBXSelection}%%%\n\n";
print "                <#(comprehensive description)#>\n";

foreach my $param (@params) {
    print "    \@param      $param <#(description)#>\n" if (defined($param));
}

print "    \@return     <#(description)#>\n" if ($returnsAValue);
print "*/\n";
print $unmodifiedSelection;

exit 0;

Categories

Objective-C categories were a small sticking point in the transition to Doxygen. The category methods never seemed to show up anywhere in the docs. See this question on stackoverflow and my own answer. Essentially, you just can’t place a space between the name of the base class and the start of the category name. Thus, this will work:

@interface Foo(Bar);

but this will not:

@interface Foo (Bar);

And that’s about it for getting Doxygen to work well with Xcode.

With NSTableviews you sometimes want to create an exception to an ordered listing; for example, you may always want one particular row to stay at the top.  Here’s a category on NSString to use with an NSSortDescriptor to alphabetize a list with one specified exception always at the top:


- (NSComparisonResult)compareGroupNames:(id)other {
    if ([self isEqualToString:@"MYEXCEPTION"]) {
        return NSOrderedAscending;
    }
    if ([other isEqualToString:@"MYEXCEPTION"]) {
        return NSOrderedDescending;
    }
    return [self caseInsensitiveCompare:other];
}

Note that you need to handle situations where either side of the comparison holds the string of interest.

Image extensionsCore Image is an incredibly powerful image editing technology built-in to OS X 10.5 and beyond; but some care is required to use it interchangeably with NSImage. After a few hours playing Googling and playing around with it, I have working categories on NSImage and CIImage that allow you to do some cool things with NSImage. I owe a lot to these two posts: first, Dan Wood’s now ancient post on the subject, and this other post that uses the lockFocus and unlockFocus NSImage methods. What I’ve done is basically a mashup of the two.

First, the header file for the category on CIImage:  NSCoreImageExtensions (The CCF namespace prefix is my company Cocoa Factory.)  We’ll get to NSImageExtensions.h in a second…


#import <Cocoa/Cocoa.h>
#import <QuartzCore/QuartzCore.h>
#import "NSImageExtensions.h"

@interface CIImage (CCFCIImageExtensions)

- (NSImage *)toNSImageFromRect:(CGRect)r;
- (NSImage *)toNSImage;
- (CIImage *)dim;

@end

The dim method is actually there just to show how to use filters on CIImage.  Next the implementation for NSCoreImageExtensions:


@implementation CIImage (CCFCIImageExtensions)

- (NSImage *)toNSImageFromRect:(CGRect)r
{
    NSImage *image;

    NSRect outputImageRect = NSRectFromCGRect([self extent]);
    image = [[NSImage alloc] initWithSize:outputImageRect.size];
    [image lockFocus];
    [self drawAtPoint:NSZeroPoint fromRect:outputImageRect
            operation:NSCompositeCopy
             fraction:1.0];
    [image unlockFocus];
    return [image autorelease];
}

- (NSImage *)toNSImage
{
    return [self toNSImageFromRect:[self extent]];
}

- (CIImage *)dim
{
    CIFilter *myFilter;
    CIContext *context;

    context = [CIContext contextWithCGContext:[[NSGraphicsContext currentContext] graphicsPort] options: nil];

    NSDictionary *myFilterAttributes;
    myFilter = [CIFilter filterWithName:@"CIColorControls"];
    myFilterAttributes = [myFilter attributes];
    [myFilter setDefaults];
    [myFilter setValue:self forKey:@"inputImage"];
    [myFilter setValue:[NSNumber numberWithFloat:+0.25] forKey:@"inputBrightness"];
    [myFilter setValue:[NSNumber numberWithFloat:0.45] forKey:@"inputContrast"];

    CIFilter *myBlurFilter = [CIFilter filterWithName:@"CIGaussianBlur"];
    NSDictionary *myBlurAttributes;
    myBlurAttributes = [myBlurFilter attributes];
    [myBlurFilter setDefaults];
    [myBlurFilter setValue:[myFilter valueForKey:@"outputImage"] forKey:@"inputImage"];
    [myBlurFilter setValue:[NSNumber numberWithFloat:1.0] forKey:@"inputRadius"];

    return [myBlurFilter valueForKey:@"outputImage"];
}

@end

Then on the NSImage side, the interface and implementation blocks in NSImageExtensions:


#import <Cocoa/Cocoa.h>
#import <QuartzCore/QuartzCore.h>
#import "NSCoreImageExtensions.h"

@interface NSImage (DWImageExtension)

- (CIImage *)toCIImage;
- (NSImage *)dimImage;

@end

@implementation NSImage (DWImageExtension)

- (CIImage *)toCIImage
{

    NSBitmapImageRep *bitmapimagerep = [NSBitmapImageRep imageRepWithData:[self TIFFRepresentation]];
    CIImage *im = [[[CIImage alloc]
    initWithBitmapImageRep:bitmapimagerep] autorelease];
    return im;
}

- (NSImage *)dimImage
{
    CIImage *ciimage = [self toCIImage];
    return [[ciimage dim] toNSImage];
}

@end

And that’s it!  Here’s an example of how I use it:


NSImage *highVolumeImage = [NSImage imageNamed:@"volume_high.png"];
[highVolumeImageView setImage:[highVolumeImage dimImage]];
11.27.2009
Figure 1

Figure 1

This should really be filed under “Obvious stuff” – but it took a little while to find the visual effect I was looking for; so I thought I would pass it along.  You know the embossed text effect in iTunes and other apps (Figure 1)?

There’s a post here about how to achieve the effect the hard way; but it’s actually very easy to obtain by setting the background style on an NSTextField cell:

[[myTextField cell] setBackgroundStyle:NSBackgroundStyleRaised];
11.24.2009

I’m compiling a list of open source frameworks and source code that you can use in your applications.  Appreciate any additions to this list, which I’ll keep up-to-date:

Karelia Software has a number of categories on NSString and many others.
OmniFrameworks are the grand-daddy of open source Mac OS frameworks.
Matt Gemmell has graciously released some of his source code
Septicus Software has an SSCrypto framework for encryption/decryption
Uli Kusterer has some great source code.  I use his helper macros daily.

Others?

11.20.2009

For newcomers to the Cocoa/Mac OS environment, bindings represent one of those topics that takes a while to get one’s head around.  For this quick tutorial, we’ll create a Cocoa application that does nothing more than bind a list of the currently running applications to the content of an NSComboBox.  In future tutorials, we’ll take on more complex bindings examples.  One of the first things to know is that bindings rely on key-value coding (KVC) and key-value observing (KVO).  This means that if you have an interface object bound to an ivar, you need to use proper accessors to set that ivar; otherwise, you won’t see updates reflected in the interface objects to which the ivar is bound.  We’ll show an example in a moment.  Lastly by way of introduction, bindings haven’t come yet to iPhone.

We’ll assume you know how to create a project and get the basic stuff going in Xcode, OK?  So create a new Xcode project called ‘bindingTest’.  In the App Delegate header file, add an ivar called runningApps as follows:


#import <Cocoa/Cocoa.h>

@interface bindingTestAppDelegate : NSObject <NSApplicationDelegate> {
    NSWindow *window;
    NSArray *runningApps;
}

@property (nonatomic, retain) NSArray *runningApps;
@property (assign) IBOutlet NSWindow *window;

@end

Now, set up some code in the implementation file (.m) to assign runningApps:

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
     // Insert code here to initialize your application
     [self setRunningApps:[[NSWorkspace sharedWorkspace] launchedApplications]];
}

If you want to build and run your app now just to test that you haven’t made any errors, that would be fine. Next we’ll go to the Interface Builder and set up the actual bindings.  Although we can create the bindings in code, IB makes it easier.  Recognize that once your app gets more complex, debugging bindings kind be tricky because it’s harder to identify binding issues when flipping back and forth between Xcode and IB.

Figure 1

Figure 1

Open the MainWindow nib and add an NSComboBox to the window.  Then add an NSArrayController to the document.  First, we’ll set up the bindings for the array controller (Figure 1)  In this case, we are binding the runningApps array to the content array of the controller.

Next, we want to bind the NSArrayController, in turn, to the NSComboBox we just created.  So, we will inspect the bindings tab of the combo box and set up those bindings.  Because the runningApps array is actually an array of NSDictionary objects, we can’t specify just the array for the content.  Instead we need a string property.  Here, we’d be most interested in the application name.  It happens that NSApplicationName is one of the keys in the dictionary; so we’ll bind to that.  (Figure 2)

Figure 2

Figure 2

To recap, we set up our ivar runningApps as an NSArray to hold an array of NSDictionary objects returned by calling launchedApplcations: on NSWorkspace singleton instance.  That ivar is bound to an NSArrayController which is, in turn, bound to the NSComboBox.

If we have wired everything correctly, then we should see a combo box populated with the names of the apps currently running on your system.

A few minor issues that I’ve found coming to the platform.  Bindings depend on KVC/KVO; and therefore, you must use the accessors rather that accessing ivars directly.

Here are a few other resources on bindings that you might find interesting:

I need to return a list of currently running,windowed applications. The following:

NSArray *runningApps = [[NSWorkspace sharedWorkspace] launchedApplications];
for( NSDictionary *runApp in runningApps )
{
	NSLog(@"localizedName = %@, PID = %@, active = %d",
                    [runApp valueForKey:@"NSApplicationName"],
                    [runApp valueForKey:@"NSApplicationProcessIdentifier"]);
}

returns a compact list of applications as desired; but the NSWorkspace documentation advises post-10.6 applications use runningApplications instead. Calling this method on the NSWorkspace singleton returns an array of NSRunningApplication objects:

NSArray *apps = [[NSWorkspace sharedWorkspace] runningApplications];
for( NSRunningApplication *app in apps)
{
	NSLog(@"name = %@, PID = %@",[app valueForKey:@"localizedName"],
                                     [app valueForKey:@"processIdentifier"]);
}

The problem is that the lists aren’t the same; and I prefer the shorter list of windowed applications instead of including all of the daemons and helper apps in the latter.  I wonder if there’s a way of filtering the latter.

To be accurate, NSOperationQueue fails on the actual device when it has only a single NSOperation to perform. On the simulator, it works fine with n=1 operation.

Anyone else with this problem?

If you haven’t used the scripting bridge for interapplication communication, where the receiver app is not of your making, it can be a very powerful tool to extend your application’s functionality.  Because you don’t really have any control about that application’s behavior or scriptability, it is also very prone to breaking.

The scripting bridge is basically AppleScript without the weird scripty syntax of AppleScript.  It allows you to call methods on another application and access properties through Cocoa.  Here’s how to get started.  Let’s say you want to script “System Events.app”  First, you have to find the application.  Here it is with its buddies in /System/Library/CoreServices:

Locating the app

Locating the app


Read more

Next Page »