Controllerと たわむれる8

前回のつづき


やっと到達しました。IKImageFlowView -- systemが持つCoverFlowです。

先ず最初に、IKImageFlowViewはsystemが持っていますが、ヘッダ、ドキュメントが提供されていません。いわゆる、隠しクラス、隠しAPIです。いきなり使えなくなる事が起こりえます。その辺のリスクを承知の上で使いましょう。


では、NSViewControllerのサブクラスの作成から。
名前はControllersFlowViewControllerとしました。

#import <Cocoa/Cocoa.h>

@class IKImageFlowView;
@interface ControllersFlowViewController : NSViewController
{
	IBOutlet IKImageFlowView *coverFlow;
}
- (id)init;
@end

後々の事を考えて、IKImageFlowViewをoutletで持ちました。
後は今までと同じ。


IBに移ります。
IKImageFlowViewは今の所CocoaBindingsに対応していないようなので、ArrayControllerは使えません*1。ので、今までのようにコピーして使うのではなく新規に作りました。

「Choose a Template」パネルから「Cocoa」「View」を選びます。

名前をFlowView.xibとして保存します。


File's OwnerのクラスをControllersFlowViewControllerに変更します。


既にある「Custom View」にさらに「Custom View」を配置します。
追加した「Custom View」のクラスを「IKImageFlowView」に変更します。

File's OwnerのviewおよびcoverFlowアウトレットをともにIKImageFlowViewに接続します。


FlowView.xibでの作業は以上です。
次にMainWindow.xibを編集します。

NSSegmentedControlのセグメント数を3に変更して、新たなセグメントのLabelをFlowにTagを3に設定します。
アイコンにNSFlowViewTemplateを使用しました。



IBでの作業は以上です。

ソースコードの編集に移ります。
ControllersWindowController.hの一部

enum {
	typeUnknown = 0,
	typeList = 1,
	typeIcon = 2,
	typeFlow = 3,
};

typeFlowを追加しました。


ControllerWindowController.mの一部

#import "ControllersFlowViewController.h"

...(snip)

- (void)switchViewByType:(NSInteger)type
{
	Class viewControllerClass = Nil;
	switch(type) {
		case typeList:
			viewControllerClass = [ControllersListViewController class];
			break;
		case typeIcon:
			viewControllerClass = [ControllersIconViewController class];
			break;
		case typeFlow:
			viewControllerClass = [ControllersFlowViewController class];
			break;
		default:
			NSLog(@"Unknown view type.");
			break;
	}
	if(!viewControllerClass) return;
	
	...(snip)
}

ControllersFlowViewController.hをインポートして、typeFlowに対応しました。


では、本題のControllersFlowViewControllerです。

#import "ControllersFlowViewController.h"
#import <Quartz/Quartz.h>

@interface NSObject(XpsfMIKImageFlowViewSupport)
- (void)setShowSplitter:(BOOL)flag;
- (void)setInlinePreviewEnabled:(BOOL)flag;
- (void)setSelectedIndex:(NSUInteger)index;
- (NSRect)selectedImageFrame;
@end

@implementation ControllersFlowViewController
- (id)init
{
	return [super initWithNibName:@"FlowView" bundle:nil];
}
- (void)awakeFromNib
{
	[coverFlow setInlinePreviewEnabled:YES];
	[(IKImageBrowserView *)coverFlow setDataSource:self];  // (*)
	[coverFlow setDelegate:self];
	[coverFlow reloadData];
}
- (void)setRepresentedObject:(id)representedObject
{
	id oldRep = self.representedObject;
	if([oldRep isEqual:representedObject]) return;
	
	[oldRep removeObserver:self forKeyPath:@"arrangedObjects"];
	[oldRep removeObserver:self forKeyPath:@"selectionIndex"];
	
	if(representedObject) {
		[representedObject addObserver:self forKeyPath:@"arrangedObjects" options:0 context:NULL];
		[representedObject addObserver:self forKeyPath:@"selectionIndex" options:0 context:NULL];
	}
	
	[super setRepresentedObject:representedObject];
	[coverFlow reloadData];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
	if([keyPath isEqualToString:@"arrangedObjects"]) {
		[coverFlow reloadData];
		return;
	}
	if([keyPath isEqualToString:@"selectionIndex"]) {
		[coverFlow setSelectedIndex:[self.representedObject selectionIndex]];
		return;
	}
	
	[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}

#pragma mark#### IKImageFlowView DataSource ####
- (NSUInteger)numberOfItemsInImageFlow:(id)imageFlowView
{
	return [[self.representedObject arrangedObjects] count];
}
- (id)imageFlow:(id)imageFlowView itemAtIndex:(NSUInteger)index
{
	return [[self.representedObject arrangedObjects] objectAtIndex:index];
}
#pragma mark#### IKImageFlowView Delegate
- (void)imageFlow:(id)imageFlowView didSelectItemAtIndex:(NSUInteger)index
{
	[self.representedObject setSelectionIndex:index];
}
@end

IKImageFlowViewは見えないクラスですので、カテゴリを用いてメソッドが見えているように見せかけてます。コンパイラを騙すためです。

IKImageFlowViewはNSTableViewやNSOutlineView,IKImageBrowserViewと良く似たクラスで、DataSourceで表示内容を設定します。
-awakeFromNibでそのDelegateとDataSourceを設定してます。
(*)のところで意味不明なキャストをしてますが、これはコンパイラを騙しているだけですので深く考えないでください。

-setRepresentedObject:と-observeValueForKeyPath:ofObject:change:context:で色々やってます。表示内容の変化や、選択アイテムの変化を同期するためにKVOを用います。
NSArrayContorllerのarrangedObjectsが変更されたら-[IKImageFlowView reloadData]を、selectionIndexが変更されたら選択アイテムの設定を行っています。


次に、DataSourceのメソッドです。
-numberOfItemsInImageFlow:は表示するアイテムの数を返します。
-imageFlow:itemAtIndex:はインデクスに対応するアイテムを返します。このとき返すアイテムはIKImageBrowserItem非形式プロトコルに準拠している必要があります。すでに、その実装はすましてますのでここでは行いません。


最後にIKImageFlowViewのDelegateメソッドです。
選択アイテムが変更されると、-imageFlow:didSelectItemAtIndex:が投げられますのでNSArrayControllerのselectionIndexを同期させています。
Delegateメソッドは他にもありますが、今回はこれだけ。


実行してみましょう。



次回は、何しましょう?
ここまでは一直線の強制シナリオだったので考える必要がなかったんだけど。。。
じゃあ、さらにNSViewController!で行きますか。


つづき

*1:contentをExposeしてますがガン無視されます