2012年2月25日土曜日

LionのIKImageBrowserのバグ

Lion(10.7.3で確認)ではIKImageBrowserのcellSizeを256*96のように横長のサイズに設定すると、右のスクリーンショットのように、上下のセルが重なって表示されてしまう問題があります。これはimageFrameの高さが実際のセルの高さより高く計算されてしまうのが原因のようですので、下記のようにNSImageBrowserCellのimageFrameをオーバーライドして、強制的にimageFrameの高さを実際のセルサイズ内に収めると問題を回避できます。



- (NSRect)imageFrame
{
NSRect imageFrame;
NSRect frame;
CGFloat maxHeight;
CGFloat ratio;
// Work around code for Mac OS X 10.7.x Lion's bug.
frame = [super frame];
imageFrame = [super imageFrame];
maxHeight = NSHeight(frame) - 20;
if(NSWidth(imageFrame) > maxHeight) {
ratio = maxHeight / NSWidth(imageFrame);
imageFrame.size = NSMakeSize(maxHeight, NSHeight(imageFrame) * ratio);
imageFrame.origin = NSMakePoint(NSMidX(frame) - NSWidth(imageFrame) / 2
NSMidY(frame) - NSHeight(imageFrame) / 2 + 10);
}
return imageFrame;
}


2012年2月20日月曜日

非同期メソッドを同期実行する方法

completionHandlerを引数に取るCocoaの非同期メソッドを同期実行したい場合、あまりスマートな方法ではないですが、下記の例のようにすると同期実行できます。completionHandlerはメソッドを呼び出したスレッドと同じスレッドで実行されますので、run loopをまわさないでNSLockなどで同期しようとするとデッドロックになることに注意。

__block BOOL finished = NO;

[[NSWorkspace sharedWorkspace] recycleURLs:trashURLs
 completionHandler:^(NSDictionary *newURLs, NSError *error) {
 finished = YES;
 }];

while(finished == NO) {
 [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
}

Cocoaアプリでコンピュータ名を取得する方法

CoreServices.frameworkの関数CSCopyMachineName()で取得できます。返される文字列はretainされているので、使い終わったらreleaseしましょう。

Cocoaでファイル名変更

ファイル名を変更するAPIとしてNSFileManagerにmoveItemAtPath:toPath:error:が用意されていますが、元のファイル名と大文字小文字が違うだけのファイル名には変更できないようです(Mac OS X 10.6.6で確認)。例えば、TEST.txtをtest.txtに変更する場合などです。これを回避するためには、下記のようにrenameシステムコールを使うと良さそうです。


manager = [NSFileManager defaultManager];
if([manager fileExistsAtPath:newPath] == NO || 
    [path caseInsensitiveCompare:newPath] == NSOrderedSame) {
  rename([path fileSystemRepresentation], [newPath fileSystemRepresentation]);
}


renameシステムコールは第2引数に指定したファイルが既に存在する場合はそれを消してしまうので、あらかじめファイル名変更後のファイルが存在するかどうかを確認して、ファイルが存在しないか、元のファイル名と新しいファイル名が大文字小文字が違うだけの場合のみ名前を変更するようにします。

ファイルサイズを取得する方法

Cocoaでファイルサイズを取得する方法はいくつかあります。
1つはNSFileManagerのattributesOfItemAtPath:error:で返されるNSDictionaryのNSFileSizeキーの値を取得する方法ですが、この方法ではファイルの論理サイズは取得できますが、物理サイズ(アロケートサイズ)は取得できません。
もう一つの方法はMac OS X 10.6から追加されたNSURLのgetResourceValue:forKey:error:を使う方法です。このメソッドで返される辞書のNSURLFileSizeKey、NSURLFileAllocatedSizeKeyの値を取得する事で論理サイズ、物理サイズが取得できます。ただ、この方法でもデータフォークのサイズしか取得できないようです。
リソースフォークも含めたサイズを取得するためにはCarbon File ManagerのFSGetCatalogInfoかFSIterateForksを使うしかないようです。CarbonのFile Managerを使うためにはパスを表すNSStringからFSRefを取得する必要がありますが、これは下記のようにすれば取得できます。

 FSRef   fileRef;
 OSStatus status;

 status = FSPathMakeRef((const UInt8*)[path fileSystemRepresentation], &fileRef, NULL);

IKImageViewのメモリリーク?

IKImageViewにsetImage:imageProperties:で画像をセットすると、IKImageViewのインスタンスを解放してもセットした画像が解放されない問題があるようです(Mac OS X 10.6.8で確認)。IKImageViewのインスタンスを解放する前に
 [imageView setImage:nil imageProperties:nil];

を実行すれば問題を回避できるようです。

IKImageBrowserCellのtitleFrameメソッドのクセ

IKImageBrowserViewに表示されるアイテムのタイトル領域を取得するために、IKImageBrowserCellクラスにtitleFrameメソッドが用意されていますが、スクロールビューの表示領域外にあるなどの理由で一度も描画されたことのないアイテムに対してこのメソッドを呼び出すと、正しいタイトル領域が返されないようです。