Controllerと たわむれる7

前回のつづき


では、Viewを切り替える、です。


IBです!

MainWindow.xibを開きます。
普通に、NSSegmentedControlを使いました。


適当に、好きな所に配置してください。


セグメントの数を2個に変更。segment 0のLabelをListにtagを1に、segment 1のLabelをIconにtagを2にします。
LibraryのMediaのなかにNSIconViewTemplateとNSListViewTemplateがありますのでそれも使ってみました。




Bind関連。
NSSegmentedControlのSelected TagをFile's OwnerのviewTypeにBindします。

以上です。


では、ソースコードを変更していきましょう。変更するのはどれ?
それはViewの表示責任者たるWindowの管理者たるControllersWindowControllerです。

ControllersWindowController.h

#import <Cocoa/Cocoa.h>

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

@interface ControllersWindowController : NSWindowController
{
    NSArray *contents;
    IBOutlet NSView *placeholder;
    IBOutlet NSArrayController *controller;
    NSViewController *viewController;
    NSMutableDictionary *viewControllers;
    NSInteger viewType;
}

@property (retain) NSArray *contents;
@property (assign) NSViewController *viewController;
@property NSInteger viewType;
@end

無名の列挙があります。NSSegmentedControlのsegmentのtagに対応してます。


追加されたインスタンス変数変数が二つ。
NSMutableDictionaryのviewControllersとNSIntegerのviewType。viewTypeはプロパティでもあります。
viewControllersは生成したNSViewControllerを保持しておく容器です。何度も同じ物を生成する無駄をなくすためですね。
viewTypeは現在表示されているviewのタイプ。そのままですね。
そして、viewControllerは今回から役割が変わっています。今回から、viewControllerは現在表示しているビューのコントローラを単に示す物として使用します。ですので、retainからassignに変更されました。もちろん、retainのままでも問題ないです。僕の気分の問題です。


では、実装部。
ControllersWindowController.m

#import "ControllersWindowController.h"
#import "ControllersListViewController.h"
#import "ControllersIconViewController.h"

@implementation ControllersWindowController
@synthesize contents;
@synthesize viewController;

- (id)init
{
    self = [super initWithWindowNibName:@"MainWindow"];
    if(self) {
        viewControllers = [[NSMutableDictionary alloc] init];
    }
    return self;
}
- (void)dealloc
{
    self.contents = nil;
    [viewControllers release];
    [super dealloc];
}
- (void)awakeFromNib
{
    self.viewType = typeList;
}
- (void)switchViewByType:(NSInteger)type
{
    Class viewControllerClass = Nil;
    switch(type) {
        case typeList:
            viewControllerClass = [ControllersListViewController class];
            break;
        case typeIcon:
            viewControllerClass = [ControllersIconViewController class];
            break;
        default:
            NSLog(@"Unknown view type.");
            break;
    }
    if(!viewControllerClass) return;
    
    if(self.viewController) {    // (A)
        [self.viewController.view removeFromSuperview];
    }
    
    self.viewController = [viewControllers objectForKey:[NSNumber numberWithInteger:type]];
    if(!self.viewController) {
        self.viewController = [[[viewControllerClass alloc] init] autorelease];
        self.viewController.representedObject = controller;
        [viewControllers setObject:self.viewController forKey:[NSNumber numberWithInteger:type]];
    }
    
    NSView *view = self.viewController.view;
    view.frame = placeholder.frame;
    [view setFrameOrigin:NSZeroPoint];
    
    [placeholder addSubview:view];
}
- (NSInteger)viewType
{
    return viewType;
}
- (void)setViewType:(NSInteger)newType
{
    if(newType == viewType) return;
    viewType = newType;
    [self switchViewByType:viewType];
}
@end

初期化時にviewControllersを確保して、破棄される時に解放してます。当たり前ですね。


awakeFromNibはかなりダイエットされました。viewTypeをtypeListにする!って言ってるだけです。
これをやっておかないとウインドウが表示された時にビューがない状態になります。


ちょっと先に進んで、viewTypeのアクセッサです。
セッタはまず、現在値と同じかどうかの比較から始まります。同じであれば(無駄な処理をせずに)終了します。
違う値がセットされたらswitchViewByType:を自分に投げます。


では、本題のswitchViewByType:です。
ほとんど、前回までのawakeFromNibの内容と同じです。違う部分だけ説明します。

まず、typeに応じて生成するNSViewControllerのクラスを取得します。
取得出来なければ、何もせずに帰ります。

つぎに、現在のビューを取り除きます。(A)
viewContollerプロパティが現在表示中のビューを管理するNSViewControllerですから、こんな感じになります。あとで名前を変えたい所ですね。


次にtypeに合ったオブジェクトが既に生成されてviewControllersに納められているかを調べます。
まだ、生成されていない場合は生成します。
この、何気ない[[[viewControllerClass alloc] init] autorelease]。これをやりたいが故のinitのオーバーライドでした。やっと出てきた。initをオーバーライドせずに、ControllersWindowControllerがnibを指定していたら、よけいな手間がかかる所です。
生成したらviewControllersに登録しておきます。


あとは、今までと同じですね。


変更はこれだけです。どのビューを表示するかの管理はControllersWindowControllerの仕事ですから、他を変える必要はありません。


実行結果はこんな感じ。



気をつけてみてほしいのは、あるビューで選択アイテムを変更するともう一方でも選択アイテムが同期している事。
テーブルビューで並べ替えをすると、アイコンビューでも並べ替えが行われている事。
これらは、TableView.xibを作った時の余分なBind--Selection IndexesとSortDescriptorsのBindがあればこそです。CocoaBindingsを通じてそれらが同期されています。*1
ListViewControllerがもつNSArrayController <-> WindowControllerが持つNSArrayController<->IconViewControllerがもつNSArrayController
という関係です。



さて、いよいよ次回はIKImageFlowViewです。

つづきControllerと たわむれる8 - masakihの日記

*1:初回の切り替え時に同期が行われません。初回から同期させるためにはちょっと小細工が必要です。