Cocoa アプリで Cover Flow を使う方法を試してみた

すごくいまさらな気がするけれど, d:id:hetima:20080314:1205423499 を参考に,Cover Flowを表示させてみた。
さすがOpenGLでフレームレート高いなぁ。

IKImageFlowViewを表示するだけ

テーブルビューに表示させるような感じで,項目のNSArrayControllerがあるなら,
IKImageFlowViewのdelegateでは,NSArrayControllerのarrangedObjectとselectionをobserveして,
Cover Flow用のアイテム配列を作りなおしたり,IKImageFlowViewに-setSelectedIndex:したりすればいい。

IKImageFlowViewに渡すアイテムの実装

Cover Flow用のアイテムは,imageUID, imageRepresentationType, imageRepresentationを返せればいい。
イメージのロードは,imageRepresentationが呼ばれるまで遅延しておくこともできる。
Cover Flowで選択中の項目の下に表示される文字列は,imageTitleとimageSubtitleを返すことで設定できる。
ほとんど@propertyで書けばいいだけだから,あまりコーディングしなくていい。

IKImageFlowViewのリサイズ

IKImageFlowViewの-setShowSplitter:にYESを渡すと出てくるサイズ変更用のディバイダをクリックすると,
delegateの-imageFlow:startResizingWithEvent:が呼ばれる。
他にこのディバイダでのデリゲートメソッドは無いみたいだから,このメソッド内でリサイズを処理するということだろう。


iTunesみたいに,Cover Flowが一番上にある状態のNSSplitViewを使っているなら,次のようにする。
ただしflowViewとsplitViewは,それぞれIKImageFlowViewと,それを含むNSSplitView。

- (void)imageFlow:(id)fview startResizingWithEvent:(NSEvent *)theEvent
{
    NSPoint offset = [flowView convertPoint:[theEvent locationInWindow] fromView:nil];
    
    while (theEvent = [[flowView window] nextEventMatchingMask:(NSLeftMouseDraggedMask|NSLeftMouseUpMask)]){
        if (NSEventMaskFromType([theEvent type]) == NSLeftMouseUpMask) break;
        
        NSPoint p = [splitView convertPoint:[theEvent locationInWindow] fromView:nil];
        [splitView setPosition:p.y+offset.y ofDividerAtIndex:0];
    }
}

-nextEventMatchingMask:ってあまり使わないけれど,Appleのサンプルコードではたまに見かけるような気がする。
メソッドを終了させずに,その場でイベントループを回せるような感じ。

リサイズ周りの細かい調整

IKImageFlowViewの高さを小さくしすぎると,一度に表示する項目が多くて重くなるのと,座標軸が潰れて(?)おかしなことになる。
サブビューの大きさを制限できるNSSplitViewを使うか作るか,NSSplitViewのデリゲートで制限してやるのがよさそう。
高さの制限は128.0くらいがいいかな。

  • -splitView:constrainMinCoordinate:ofSubviewAt:では,128.0を返す。ディバイダで128.0より小くできなくなる。
  • -splitView:effectiveRect:forDrawnRect:ofDividerAtIndex:では,NSZeroRectを返す。もともとのNSSplitViewのディバイダを操作できなくなる。


これでも,windowのリサイズでの自動リサイズでは高さが小さくなりすぎることがあるので,

  • splitView:resizeSubviewsWithOldSize:で回避する。
- (void)splitView:(NSSplitView *)theSplitView resizeSubviewsWithOldSize:(NSSize)oldSize
{
    NSRect flowViewFrame = [flowView frame];
    [splitView adjustSubviews]; // デフォルトのリサイズ処理
    
    // splitView自体が128.0より潰れてしまうのを防ぐ
    if ([flowView frame].size.height < 128.0){
        [splitView setFrameSize:NSMakeSize([splitView frame].size.width, oldSize.height)];
    }
    
    // Cover Flowの高さを変更しない
    [splitView setPosition:flowViewFrame.size.height ofDividerAtIndex:0];
    return;
}