TableViewのCell内にスピンタイプの
NSProgressIndicatorを表示する

ソフトウェアアップデートや、.Macの同期時のようにNSTableViewのセル内にスピンタイプのNSProgressIndicatorを表示させたくていろいろネットで検索したのですが決定打が見つからず、仕方がないので自分で試行錯誤した結果なんとか表示できるようになりました。
今回作成したサンプルプログラムの画面は以下の通りです。

なお、NSProgressIndicatorを表示する行以外は(手抜きで ^_^;)NSButtonCellを表示するようにIBで設定しています。

セル内にNSProgressIndicatorを表示させる上でのポイントは以下の通りです。

NSCellのサブクラスを作成する
上記サブクラス内でNSProgressIndicatorのインスタンスを作成し、メンバ変数に保存しておく
セル内にNSProgressIndicatorを表示させるにはNSProgressIndicatorのインスタンスをNSTableViewのサブビューとして登録する必要がある(NSProgressIndicatorはNSViewのサブクラス)
逆に表示させたくない場合はサブビューリストから削除してやる必要がある
NSProgressIndicatorを表示させるカラム用にNSTableColumnのサブクラスを作成し、IB上で該当カラムのカスタムクラスとして設定する
NSTableColumnのサブクラスでは、NSProgressIndicatorを表示させたい行か否かでNSTableViewに通知するNSCellのサブクラスのオブジェクトを切り替える

1.NSCellのサブクラスの作成

以下の仕様のNSCellのサブクラスを作成します。

UCProgressIndicatorCell.h:ProgressIndicator表示用NSCellサブクラスインターフェースファイル
@interface UCProgressIndicatorCell : NSCell
{
@private
    NSTableColumn       *_poOwnerTblColumn;     // このCellを保持しているNSTableColumn
    NSProgressIndicator *_poProgInd;            // プログレスインジケーター
    BOOL                _bRegisteredAsSubView;  // TableViewのSubviewとして登録済みか否かのフラグ
}

//
// 初期化/後処理メソッド
//
// 指定イニシャライザ
- (id)initWithStyle:(NSProgressIndicatorStyle)nStyle
        controlSize:(NSControlSize)nCtrlSize
      indeterminate:(BOOL)bIndeterminate
           minValue:(double)dMinVal
           maxValue:(double)dMaxVal
       initialValue:(double)dInitVal
             column:(NSTableColumn *)poCol;

// デフォルトスピン形式インジケータCellの生成
- (id)initAsDefaultSpinProgressIndicatorWithColumn:(NSTableColumn *)poCol;

// 後処理
- (void)dealloc;

//
// オブジェクトの制御関連
//
// アニメーションの開始
- (void)startAnimation;

// アニメーションの停止
- (void)stopAnimation;

// TableViewのSubviewとして登録
- (void)attachToTableView:(NSView *)poTblView;

// TableViewのSubviewリストから削除
- (void)detachFromTableView;

// NSProgressIndicatorオブジェクトの取得
- (NSProgressIndicator *)progressIndicator;

//
// 描画関連
//
// Cellフレームと内容部の描画
- (void)drawWithFrame:(NSRect)cellFrame inView:(NSView *)controlView;

// Cellの内容部の描画
- (void)drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView *)controlView;

//
// 内部メソッド
//
// インジケータをCell内でセンタリングする
- (void)centeringIndicatorWithinCellFrame:(NSRect)rectCell;

@end

UCProgressIndicatorCell.m:ProgressIndicator表示用NSCellサブクラス実装ファイル
@implementation UCProgressIndicatorCell
//
// 初期化/後処理メソッド
//
// 指定イニシャライザ
- (id)initWithStyle:(NSProgressIndicatorStyle)nStyle
        controlSize:(NSControlSize)nCtrlSize
      indeterminate:(BOOL)bIndeterminate
           minValue:(double)dMinVal
           maxValue:(double)dMaxVal
       initialValue:(double)dInitVal
             column:(NSTableColumn *)poCol
{
    // 親クラスをダミーでテキストCellとして初期化
    self = [super initTextCell:@""];

    // メンバ変数の初期化と必要な情報のメンバ変数への保存
    _poOwnerTblColumn = [poCol retain];
    _poProgInd = nil;
    _bRegisteredAsSubView = NO;

    // NSProgressIndicatorのインスタンス生成
    NSRect rect = NSMakeRect( 0.0f, 0.0f, [_poOwnerTblColumn width], [[_poOwnerTblColumn tableView] rowHeight] );
    _poProgInd = [[NSProgressIndicator alloc] initWithFrame:rect];    // Rectは暫定

    // NSProgressIndicatorの各種属性を設定する
    [_poProgInd setStyle:nStyle];
    [_poProgInd setControlSize:nCtrlSize];
    [_poProgInd setIndeterminate:bIndeterminate];
    [_poProgInd setMinValue:dMinVal];
    [_poProgInd setMaxValue:dMaxVal];
    [_poProgInd setDoubleValue:dInitVal];
    [_poProgInd setDisplayedWhenStopped:YES];

    return self;
}


// デフォルトスピン形式インジケータCellの生成
- (id)initAsDefaultSpinProgressIndicatorWithColumn:(NSTableColumn *)poCol
{
    [self initWithStyle:NSProgressIndicatorSpinningStyle
            controlSize:NSSmallControlSize
          indeterminate:YES
               minValue:0.0
               maxValue:100.0
           initialValue:0.0
                 column:poCol];

    return self;
}

// 後処理
- (void)dealloc
{
    [_poProgInd stopAnimation:self];

    RELEASE_OBJECT( _poOwnerTblColumn );
    RELEASE_OBJECT( _poProgInd );

    [super dealloc];
}

//
// オブジェクトの制御関連
//
// アニメーションの開始
- (void)startAnimation
{
    [_poProgInd startAnimation:self];
}

// アニメーションの停止
- (void)stopAnimation
{
    [_poProgInd stopAnimation:self];
}

// TableViewのSubviewとして登録
- (void)attachToTableView:(NSView *)poTblView
{
    if ( _bRegisteredAsSubView == NO ) {
        [poTblView addSubview:_poProgInd];
        _bRegisteredAsSubView = YES;
    }
}

// TableViewのSubviewリストから削除
- (void)detachFromTableView
{
    if ( _bRegisteredAsSubView == YES ) {
        [_poProgInd removeFromSuperview];
        _bRegisteredAsSubView = NO;
    }
}

// NSProgressIndicatorオブジェクトの取得
- (NSProgressIndicator *)progressIndicator
{
    return _poProgInd;
}

//
// 描画関連
//
// Cellフレームと内容部の描画
- (void)drawWithFrame:(NSRect)cellFrame inView:(NSView *)controlView
{
    [super drawWithFrame:cellFrame inView:controlView];
}

// Cellの内容部の描画
- (void)drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView *)controlView
{
    // インジケータのセンタリング
    [self centeringIndicatorWithinCellFrame:cellFrame];
}

//
// 内部メソッド
//
// インジケータをCell内でセンタリングする
- (void)centeringIndicatorWithinCellFrame:(NSRect)rectCell
{
    // 指定されたNSControlSizeに合わせてインジケーターのViewサイズを調整する
    [_poProgInd setFrame:rectCell];
    [_poProgInd sizeToFit];

    // センタリング
    NSRect rectInd = [_poProgInd frame];
    float fOffsetX, fOffsetY;
    fOffsetX = (NSWidth( rectCell ) - NSWidth( rectInd )) / 2.0f;
    fOffsetY = (NSHeight( rectCell ) - NSHeight( rectInd )) / 2.0f;
    rectInd = NSOffsetRect( rectInd, fOffsetX, fOffsetY );
    [_poProgInd setFrame:rectInd];
}

@end

やたらパラメータの多い指定イニシャライザがありますが、これは将来的にプログレスバータイプのNSProgressIndicatorにも対応できるようにするためです。
キーとなる処理としてはattachToTableView:およびdetachFromTableViewメソッドで(NSTableColumnのサブクラス経由でアプリケーションのコントローラーから)NSTableViewのサブビューとしてNSProgressIndicatorのインスタンスを登録/削除してやること(あまり美しくないかもしれませんが...)と、セルの描画を行う以下のメソッドをオーバーライドしてやることです。

 // Cellフレームと内容部の描画
 - (void)drawWithFrame:(NSRect)cellFrame inView:(NSView *)controlView;

 // Cellの内容部の描画
 - (void)drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView *)controlView;

下の内容部の描画メソッドではsuperのメソッドは呼ばないでください。superのメソッドを呼んでしまうと、初期化時にダミーとしてNSTextFieldのCellとして初期化しているためにTextFieldの描画処理が実行されてしまいます。

2.NSTableColumnのサブクラスの作成

以下の仕様のNSTableColumnのサブクラスを作成します。

UCStatusTableColumn.h:ProgressIndicator表示用NSTableColumnサブクラスインターフェースファイル
@interface UCStatusTableColumn : NSTableColumn
{
@private
    UCProgressIndicatorCell    *_poProgIndCell;  // NSProgressIndicator表示用Cell
}

//
// 初期化/後処理メソッド
//
// nibからの起動完了通知
- (void)awakeFromNib;

// 後処理
- (void)dealloc;

//
// オーバーライドするメソッド
//
// 指定行のCellオブジェクトの取得
- (id)dataCellForRow:(int)row;

//
// インジケーターの制御
//
// インジケーターの表示
- (void)showProgressIndicator;

// インジケーターの消去
- (void)hideProgressIndicator;

// アニメーションの開始
- (void)startAnimation;

// アニメーションの停止
- (void)stopAnimation;

@end

UCStatusTableColumn.m:ProgressIndicator表示用NSTableColumnlサブクラス実装ファイル
@implementation UCStatusTableColumn

//
// 初期化/後処理メソッド
//
// nibからの起動完了通知
- (void)awakeFromNib
{
    // ProgressIndicatorCellのインスタンス生成
    _poProgIndCell = [[UCProgressIndicatorCell alloc] initAsDefaultSpinProgressIndicatorWithColumn:self];
}

// 後処理
- (void)dealloc
{
    [_poProgIndCell release];
    [super dealloc];
}

//
// オーバーライドするメソッド
//
// 指定行のCellオブジェクトの取得
- (id)dataCellForRow:(int)row
{
    NSNumber *pnumValue = [[[self tableView] dataSource] tableView:[self tableView] objectValueForTableColumn:self row:row];
    if ( [pnumValue intValue] == -1 ) {
        return _poProgIndCell;
    } else {
        return [super dataCellForRow:row];
    }
}

//
// インジケーターの制御
//
// インジケーターの表示
- (void)showProgressIndicator
{
    [_poProgIndCell attachToTableView:[self tableView]];
}

// インジケーターの消去
- (void)hideProgressIndicator
{
    [_poProgIndCell detachFromTableView];
}

// アニメーションの開始
- (void)startAnimation
{
    [_poProgIndCell startAnimation];
}

// アニメーションの停止
- (void)stopAnimation
{
    [_poProgIndCell stopAnimation];
}

@end

キーとなる処理はオーバーライドしたdataCellForRow:メソッドで、各セルを表示するたびに呼ばれ、ここで指定された行に応じて返すセルのオブジェクトを変えることで行毎に表示内容を変えることができます。このソースではNSTableViewのデータソースにこの行のデータ値の問い合わせを行い、処理中の行であればUCProgressIndicatorCellのインスタンスを返し、処理中の行でなければsuper経由で通常のCell(NSButtonCell)のインスタンスを返します。

あと、NSCellのサブクラスのところで課題となったNSProgressIndicatorのインスタンスをNSTableViewのサブビューとして登録/削除するために以下のメソッドを追加しています。

 // インジケーターの表示
 - (void)showProgressIndicator;

 // インジケーターの消去
 - (void)hideProgressIndicator;

またアニメーションの制御のために以下のメソッドを追加しています。

// アニメーションの開始
- (void)startAnimation;

// アニメーションの停止
- (void)stopAnimation;

3.UIの設計

IBで以下の画面を設定します。

注意するべき点は左端のカラムの設定で、NSButtonCellをドラッグすることと、カスタムクラスにUCStatusTableColumnを指定することです。

IBにUCStatusTableColumnクラスを認識させるには、Xcodeの画面からヘッダファイルを選択し、IBのドキュメントウィンドウにドラッグすることでできます。

アプリケーションのコントローラクラスについては説明を省きますのでソースを読んでください。

サンプルプログラムのダウンロード(60KB)

【作成・確認環境】
MacOS X v10.4.6
Xcode v2.3

一覧に戻る