Scroll View + Custom View

音楽ファイルの音声波形の表示を行うプログラムを作ろうと思っているのですが、Custom ViewとScroll Viewを使うことになるので注意点を洗い出すためにサンプルプログラムを作って見ました。
画面は以下の通りです。

Scroll View付きのCustom Viewを作るにはInterface BuilderのLibraryウィンドウの検索フィールドに"NSView"と打ち込んで表示されるオブジェクトの"Scroll View"をウィンドウにドラッグアンドドロップします。

●drawRect:メソッドで渡される矩形

一番最初につまずいたのはCustom ViewのdrawRect:メソッドで渡されるNSRectがスクロールした差分しか表していないこと。差分の範囲に画面全部を描こうとして以下のような崩れた画面になってしまいました。

対策としては以下のようにdrawRect:のパラメータdirtyRectをViewのboundsに書き換えて処理するようにしました。

- (void)drawRect:(NSRect)dirtyRect {
    // Drawing code here.
	//NSLog( @"x=%f, y=%f, width=%f, height=%f\n", dirtyRect.origin.x, dirtyRect.origin.y, dirtyRect.size.width, dirtyRect.size.height );
	dirtyRect = [self bounds];	// *** 注意 *** dirtyRectには差分のみの矩形が通知される。ビュー全体を再描画するようにした。
	
	NSEraseRect(dirtyRect);
	
	[[NSColor colorWithCalibratedRed:0.3647 green:0.7373 blue:0.5922 alpha:1.0] set];
	NSRectFill(dirtyRect);
	
	NSFont *oFont = [NSFont fontWithName:@"Helvetica" size:24.0];
	NSDictionary *dicAttr = [NSDictionary dictionaryWithObjects:[NSArray arrayWithObjects:oFont, [NSColor blueColor], nil]
														forKeys:[NSArray arrayWithObjects:NSFontAttributeName, NSForegroundColorAttributeName, nil]];
	NSAttributedString * oAttrStr = [[[NSAttributedString alloc] initWithString:@"The quick brown fox jumps over the lazy dog." attributes:dicAttr] autorelease];
	CGFloat fX = (dirtyRect.size.width - [oAttrStr size].width) / 2.0;
	CGFloat fY = dirtyRect.size.height / 2.0 - [oAttrStr size].height /2.0;
	[oAttrStr drawAtPoint:NSMakePoint( fX, fY )];
	
	[[NSColor blackColor] set];
	NSFrameRect(dirtyRect);
}

●現在の表示位置の取得

また、波形を表示する場合時間表示もしたいので、現在Custom Viewのどのあたりを表示しているか知る必要があります。
この情報を得るにはCustom ViewのsuperviewであるNSClipViewクラスのdocumentVisibleRectメソッドを呼び出します。

    NSRect rectVisible = [(NSClipView *)[outView superview] documentVisibleRect];

●Custom Viewクラス(UCMyView)

Custom Viewクラスのコードを以下に示します。

UCMyView.h:Custom Viewクラスインターフェースファイル
#import <Cocoa/Cocoa.h>


@interface UCMyView : NSView {
	NSSize	mViewSize;	// ドキュメントビューのサイズ
}

- (void)Extend;	// ドキュメントビューの拡張
- (void)Reduce;	// ドキュメントビューの縮小

@end

UCMyView.m:Custom Viewクラスインプリメントファイル
#import "UCMyView.h"


@implementation UCMyView

- (id)initWithFrame:(NSRect)frame {
    self = [super initWithFrame:frame];
    if (self) {
        // Initialization code here.
		mViewSize = frame.size;
    }
    return self;
}

- (void)drawRect:(NSRect)dirtyRect {
    // Drawing code here.
	//NSLog( @"x=%f, y=%f, width=%f, height=%f\n", dirtyRect.origin.x, dirtyRect.origin.y, dirtyRect.size.width, dirtyRect.size.height );
	dirtyRect = [self bounds];	// *** 注意 *** dirtyRectには差分のみの矩形が通知される。ビュー全体を再描画するようにした。
	
	NSEraseRect(dirtyRect);
	
	[[NSColor colorWithCalibratedRed:0.3647 green:0.7373 blue:0.5922 alpha:1.0] set];
	NSRectFill(dirtyRect);
	
	NSFont *oFont = [NSFont fontWithName:@"Helvetica" size:24.0];
	NSDictionary *dicAttr = [NSDictionary dictionaryWithObjects:[NSArray arrayWithObjects:oFont, [NSColor blueColor], nil]
														forKeys:[NSArray arrayWithObjects:NSFontAttributeName, NSForegroundColorAttributeName, nil]];
	NSAttributedString * oAttrStr = [[[NSAttributedString alloc] initWithString:@"The quick brown fox jumps over the lazy dog." attributes:dicAttr] autorelease];
	CGFloat fX = (dirtyRect.size.width - [oAttrStr size].width) / 2.0;
	CGFloat fY = dirtyRect.size.height / 2.0 - [oAttrStr size].height /2.0;
	[oAttrStr drawAtPoint:NSMakePoint( fX, fY )];
	
	[[NSColor blackColor] set];
	NSFrameRect(dirtyRect);
}

- (void)Extend
{
	mViewSize.width += 100;
	[self setFrameSize:mViewSize];
}

- (void)Reduce
{
	mViewSize.width -= 100;
	[self setFrameSize:mViewSize];
}

@end

●Application delegateクラス

アプリケーションの動きを制御するdelegateクラスのコードを以下に示します。

ScrollViewTestAppDelegate.h:App. Delegateクラスインターフェースファイル
#import <Cocoa/Cocoa.h>
#import "UCMyView.h"

@interface ScrollViewTestAppDelegate : NSObject <NSApplicationDelegate> {
    NSWindow *window;
	UCMyView *outView;
	NSButton *outBtnTimer;

@private
	NSPoint	mViewOrg;
	NSTimer *mTimer;
}

@property (assign) IBOutlet NSWindow *window;
@property (assign) IBOutlet UCMyView *outView;
@property (assign) IBOutlet NSButton *outBtnTimer;

- (IBAction)onBtnPlus:(id)sender;
- (IBAction)onBtnMinus:(id)sender;
- (IBAction)onBtnScroll:(id)sender;
- (IBAction)onBtnDebug:(id)sender;

- (void)onTimer:(NSTimer*)theTimer;

@end

ScrollViewTestAppDelegate.m:App. Delegateクラスインプリメントファイル
#import "ScrollViewTestAppDelegate.h"

@implementation ScrollViewTestAppDelegate

@synthesize window;
@synthesize outView;
@synthesize outBtnTimer;

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
	// Insert code here to initialize your application
	mViewOrg = NSMakePoint(0.0, 0.0);
	mTimer = nil;
}

// ビューの拡張
- (IBAction)onBtnPlus:(id)sender
{
	[outView Extend];
}

// ビューの縮小
- (IBAction)onBtnMinus:(id)sender
{
	[outView Reduce];
}

// プログラム内からのスクロール実験
- (IBAction)onBtnScroll:(id)sender
{
	if ( mTimer == nil ) {
		mTimer = [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:@selector(onTimer:) userInfo:nil repeats:YES];
		[outBtnTimer setTitle:@"Stop"];
	} else {
		[mTimer invalidate];
		mTimer = nil;
		[outBtnTimer setTitle:@"Timer"];
	}
}

- (IBAction)onBtnDebug:(id)sender
{
	NSLog( @"superview=%@\n", [outView superview] );
	NSRect rectVisible = [(NSClipView *)[outView superview] documentVisibleRect];
	NSLog( @"x=%f, y=%f, width=%f, height=%f\n", rectVisible.origin.x, rectVisible.origin.y, rectVisible.size.width, rectVisible.size.height );

	NSRect rectFrame = [outView frame];
	NSRect rectBounds = [outView bounds];
	NSLog( @"FRAME :x=%f, y=%f, width=%f, height=%f\n", rectFrame.origin.x, rectFrame.origin.y, rectFrame.size.width, rectFrame.size.height );
	NSLog( @"BOUNDS:x=%f, y=%f, width=%f, height=%f\n", rectBounds.origin.x, rectBounds.origin.y, rectBounds.size.width, rectBounds.size.height );
}

- (void)onTimer:(NSTimer*)theTimer
{
	NSRect rectVisible = [(NSClipView *)[outView superview] documentVisibleRect];
	NSRect rectBounds = [outView bounds];
	if ( rectVisible.origin.x + rectVisible.size.width >= rectBounds.size.width ) {
		[mTimer invalidate];
		mTimer = nil;
		[outBtnTimer setTitle:@"Timer"];
	} else {
		NSRect rectVisible = [(NSClipView *)[outView superview] documentVisibleRect];
		mViewOrg.x = rectVisible.origin.x + 10;
		[outView scrollPoint:mViewOrg];
	}
}

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

【作成・確認環境】
MacOS X v10.6.4
Xcode v3.2.3

一覧に戻る