Custom Sheets don’t need their own xib files

9 08 2008

When you want to use custom sheets, Apple’s Documentation suggests that you create another xib file for the sheets(Using Custom Sheets).

But in fact, you don’t have to use a separate xib.

1. Create a controller class which handles both parent window and sheet window.

SheetTestController.h

@interface SheetTestController : NSObject {
	IBOutlet NSWindow *mainWindow;  //paent window
	IBOutlet NSWindow *sheetWindow; //sheet window
}

- (IBAction)showSheet:(id)sender; // open sheet on parent window
- (IBAction)closeSheet:(id)sender;// close sheet

@end

SheetController.m

@implementation SheetTestController

- (IBAction)showSheet:(id)sender
{
	[NSApp beginSheet: sheetWindow
	   modalForWindow: mainWindow
		modalDelegate: self
	   didEndSelector: @selector(didEndSheet:returnCode:contextInfo:)
		  contextInfo: nil];
}

- (IBAction)closeSheet:(id)sender
{
    [NSApp endSheet:sheetWindow];
}

- (void)didEndSheet:(NSWindow *)sheet returnCode:(int)returnCode contextInfo:(void *)contextInfo
{
    [sheet orderOut:self];
}

@end

2. Add “Show Sheet” button to parent window.

parentWindow

3. Add a sheet window, and put “close sheet” button on the window.

sheetWindow
make sure that “Visible At Launch” is unchecked.
sheetWindowAttributes

4. Add a SheetTestController object to the xib.

sheetControllerInstance

5. Connection settings of SheetTestController should be like this.

sheetControllerConnections

This is a little easier than “separate xib” way, but has disadvantages noted below.

  • increses memory usage because the sheet window always takes up some memory instead of instanciated only when necessary.
  • It’s difficult to use the sheet window from other xib files.




Fast Enumeration with NSEnumerator

2 04 2008

While reading The Objective-C 2.0 Programming Language, I found that Fast Enumeration can be used with NSEnumerator(The Objective-C 2.0 Programming Language: Fast Enumeration).

Suppose you have an array which you want to itarete over.

NSArray *tmpArray =
    [NSArray arrayWithObjects:@"first", @"second", @"third", nil];

Use the for statement against the reverseEnumerator obtained from the array. It loops through the array starting from the last element and proceed to the first.

NSEnumerator *enumerator = [tmpArray reverseObjectEnumerator];
for(NSString *s in enumerator) {
    NSLog(@"%@", s);
}




Draw on CGLayer

24 03 2008

I modified Create an on-memory CGImageRef to draw the small blue rectangle on a CGLayer, and combines the CGLayer to the background graphics context to generate the image. The result should remain same.

const int height = 250;
const int width = 400;

CGColorSpaceRef  imageColorSpace = CGColorSpaceCreateWithName( kCGColorSpaceGenericRGB );

CGContextRef context = CGBitmapContextCreate (NULL, width, height,
                                    8, width * 4,
                                    imageColorSpace, kCGImageAlphaPremultipliedLast);

CGContextSetRGBFillColor (context, 1.0, 0.8, 0.8, 1);
CGContextFillRect (context, CGRectMake (0, 0, width, height ));

// Addition starts here.
CGLayerRef          layer;
CGContextRef        layerContext;

layer = CGLayerCreateWithContext(context,CGSizeMake(width, height),NULL);
layerContext = CGLayerGetContext(layer);

CGContextSetRGBFillColor(layerContext, 0, 0, 1, 1);
CGContextFillRect (layerContext, CGRectMake (80, 80, 40, 30));

CGContextDrawLayerInRect(context, CGRectMake(0, 0, width, height), layer);
// Addition finished.

CGImageRef image = CGBitmapContextCreateImage(context);

[imageView setImage:image imageProperties:nil];

CGColorSpaceRelease( imageColorSpace );
CGLayerRelease(layer);
CGContextRelease(layerContext);
CGContextRelease(context);
CGImageRelease( image );




Create an on-memory CGImageRef

21 03 2008

To create an on-memory CGImageRef object and display it through IKImageView, follow these steps.

  1. Create a Bitmap Graphics Context with the CGBitmapContextCreate() function.
  2. Draw some graphics on the Bitmap Graphics Context.
  3. Create an CGImageRef object from the Bitmap Graphics Context with the CGBitmapContextCreateImage() function.This CGImageRef object contains the same image as the Bitmap Graphics Context drawn in step 2.
  4. Pass the CGImageRef to IKImageView.
const int height = 250;
const int width = 400;

CGColorSpaceRef  imageColorSpace =
        CGColorSpaceCreateWithName( kCGColorSpaceGenericRGB );

CGContextRef context = CGBitmapContextCreate (NULL, width, height,
         8, width * 4, imageColorSpace,
         kCGImageAlphaPremultipliedLast);

CGContextSetRGBFillColor (context, 1.0, 0.8, 0.8, 1);
CGContextFillRect (context, CGRectMake (0, 0, width, height ));

CGContextSetRGBFillColor(context, 0, 0, 1, 1);
CGContextFillRect (context, CGRectMake (80, 80, 40, 30));

CGImageRef image = CGBitmapContextCreateImage(context);

[imageView setImage:image imageProperties:nil];

CGColorSpaceRelease( imageColorSpace );
CGContextRelease(context);
CGImageRelease( image );




HTTP GET by CFNetwork

24 02 2008

I came across the same problem described in fraserspeirs: HTTP Nerdery, or, Why NSURLConnection Sucks For POST.I tried to send POST reqeust to a HTTP server with NSURLRequest and NSURLConnection, but nothing sent.

So I decided to use CFNetwork instead.Here is my first attempt. Because this code blocks until the server returns HTTP response, I’m modifying to non-blocking version which make use of “Run Loops”.

CFURLRef url = CFURLCreateWithString(NULL, CFSTR("http://www.google.co.jp/"), NULL);
CFHTTPMessageRef httpRequest = CFHTTPMessageCreateRequest(NULL, CFSTR("GET"), url, kCFHTTPVersion1_1);
CFReadStreamRef readStream = CFReadStreamCreateForHTTPRequest(NULL, httpRequest);
CFReadStreamOpen(readStream);

CFIndex bytes;
int bufSize = 1024;
UInt8 buf[bufSize];

do {
    bytes = CFReadStreamRead(readStream, buf, sizeof(buf));
    if( bytes > 0 ){
        NSString *responseString = [[NSString alloc] initWithBytes:buf length:bytes encoding:NSUTF8StringEncoding];
        NSLog(responseString);
    } else if( bytes < 0 ) {
        CFStreamError error = CFReadStreamGetError(readStream);
    }
} while( bytes > 0 );

CFHTTPMessageRef httpResponse = (CFHTTPMessageRef)CFReadStreamCopyProperty(readStream, kCFStreamPropertyHTTPResponseHeader);

if(httpResponse) {
    CFStringRef responseStatus = CFHTTPMessageCopyResponseStatusLine(httpResponse);
    NSLog((NSString *)responseStatus);
}




URL encoding

23 02 2008

I look for Cocoa methods those can perform “URL encode”, just like uri_escape in perl. NSString’s stringByAddingPercentEscapesUsingEncoding: works for some characters, but does not encode characters like ‘=’(equal) and ‘ ‘(white space).

However, CoreFoundation function CFURLCreateStringByAddingPercentEscapes does what I want.

CFStringRef param1_encoded = CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, (CFStringRef)param1, NULL, (CFStringRef)@";/?:@&=+$,", kCFStringEncodingUTF8);

It seems that this fact is rediscovered by many Cocoa developers…





Calculating MD5

19 02 2008

On Leopard, you can use CC_MD5 function to produce MD5 hashed data. Here is a code snippet which computes MD5 from NSString.

#import <CommonCrypto/CommonDigest.h>             // contains declaration of CC_MD5 

NSString *testString = @"Test";                   // String data that needs md5 checksum
const char *test_cstr = [testString UTF8String];  // Get data as C language string.
unsigned char md5_result[CC_MD5_DIGEST_LENGTH];   // storage for checksum result
CC_MD5(test_cstr, strlen(test_cstr), md5_result); // do calculation

If you want hexadecimal representation of the checksum as NSString, use stringWithFormat(taken from CocoaDev: MDFive).

NSString *hex_str = [NSString stringWithFormat: @"%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X",
md5_result[0], md5_result[1],
md5_result[2], md5_result[3],
md5_result[4], md5_result[5],
md5_result[6], md5_result[7],
md5_result[8], md5_result[9],
md5_result[10], md5_result[11],
md5_result[12], md5_result[13],
md5_result[14], md5_result[15]];

NSLog(hex_str);

In case you are not sure if the result is correct or not, compare hexidecimal string with other calculation method.
Try this.

perl -MDigest::MD5=md5_hex -le 'print md5_hex("Test")'




Base64 encoding in Cocoa

16 02 2008

While there are implementations written in Objective-C or making use of openssl(CocoaDev: BaseSixtyFour), on Leopard, you can also use Apache Portable Runtime functions.

1. Import header files nessesary to base64 encoding.

#import <apr-1/apr.h>
#import <apr-1/apr_base64.h>

2. Add linker flags.
Open “build settings” dialog page, add following flags to OTHER_LDFLAGS item(screenshot is taken in Japanese environment).

-laprutil-1 -lapr-1

linker_flag1

3. apr functions cannot recognize NSString nor CFStringRef, make wrapper around.

static NSString* encode_to_base64(NSString* source_str)
{
    char *source_cstr = (char*)[source_str UTF8String];

    int source_length = strlen(source_cstr);
    int result_length = apr_base64_encode_len(source_length);
    char *encoded_buf = malloc(result_length);
    apr_base64_encode(encoded_buf, source_cstr, source_length);

    NSString *resultString = [NSString stringWithCString:encoded_buf];

    free(encoded_buf);
    return resultString;
}

4. Use above routine in your program.

NSString *testString = @"Test";
NSString *base64String = encode_to_base64(testString);