インスタンス変数とアクセサメソッドを追加する
OscillatorViewに選択されているかどうかを表すフラグと、そのアクセサメソッドを追加します。
//
// OscillatorView.h
// RepeatingMotifGenerator
//
// Copyright NovemberKou 2008. All rights reserved.
//
#import <Cocoa/Cocoa.h>
#import "BezierPathView.h"
@interface OscillatorView : BezierPathView
{
BOOL selected;
}
- (BOOL)selected;
- (void)setSelected:(BOOL)isSelected;
@end
追加されたフラグを初期化し、アクセサメソッドを追加します。
初期化は普通イニシャライザで行なうのですが、X軸のOscillatorViewが選択される様に初期化したいので、keyPathメソッドで初期化します。keyPathメソッドはawakeFromNibから呼ばれるので、これで必ず初期化されます。
選択状態が変わった時は画面の書き換えが必要になりますのでsetNeedsDisplay:メソッドで要変更マークをつけています。
//
// OscillatorView.m
// RepeatingMotifGenerator
//
// Copyright NovemberKou 2008. All rights reserved.
//
#import "OscillatorView.h"
#define AMPLITUDE_MAX 100
@implementation OscillatorView
#pragma mark -
#pragma mark Methods to be overrided
- (NSString *)keyPath
{
NSString *viewName;
viewName = [[self toolTip] substringWithRange:NSMakeRange(0,1)];
isXView = [viewName isEqualToString:@"x"];
selected = isXView;
return [NSString stringWithFormat:@"selection.%@.bezierPath",viewName];
}
- (float)pathHeight
{
return AMPLITUDE_MAX*2.0;
}
#pragma mark -
#pragma mark Accessor methods
- (BOOL)selected {return selected;}
- (void)setSelected:(BOOL)isSelected
{
if(selected != isSelected)
{
selected = isSelected;
[self setNeedsDisplay:YES];
}
}
選択枠を描画する
そこでdrawRect:メソッドを追加し、フラグに応じて選択枠を描いたり、消したりする処理を追加します。
まずスーパークラスのdrawRect:メソッドを呼んで、中身を描いてもらいます。その後、リサイズ中でなければ選択枠を描くか、もしくは消す様にします。リサイズ中に何もしないのは処理を軽くするためです。
savedFrameに現在のフレームを保存してから、一時的にフレームを3ピクセル拡げます。
フレームというのはスーパービューから見た位置とサイズの事です。これに対してバウンズは自分の座標系で見た位置とサイズの事です。描画する時は自分の座標系を使います。ちなみに自分の座標系なので原点はいつも(0, 0)です。
拡げた状態でバウンズを得て、それを1ピクセル縮めます。太さ2の線で選択枠を描くので、1ピクセル縮めておけば外側がぴったりになります。
selectedがYESなら黒、NOならウィンドウの背景色で選択枠を描きます。背景色で描くという事は、すなわち消すという事です。
最後にフレームを保存しておいたものに戻して終了です。
//
// OscillatorView.m
//
#pragma mark -
#pragma mark Event handler
- (void)drawRect:(NSRect)rect
{
NSRect bounds,frame,savedFrame;
NSBezierPath *borderPath;
[super drawRect:rect];
if(![self inLiveResize])
{
savedFrame = [self frame];
frame = NSInsetRect(savedFrame,-3,-3);
[self setFrame:frame];
bounds = [self bounds];
bounds = NSInsetRect(bounds,1,1);
borderPath = [NSBezierPath bezierPathWithRect:bounds];
[borderPath setLineWidth:2];
if(selected)
[[NSColor blackColor] set];
else
[[NSColor windowBackgroundColor] set];
[borderPath stroke];
[self setFrame:savedFrame];
}
}
領土を主張する
これだけでうまくいけばよかったのですが、やってみると描いた選択枠がすぐに消されてしまいます。一時的にフレームを拡げても、そこは自分の領土ではないのでクリーンアップされてしまうという事でしょうか?
そこでvisibleRectというメソッドを使って自分の領土を主張してみたところ、うまくいきました。これでせっかく描いた選択枠を消されなくなります。2という数値は試行錯誤で決めました。
//
// OscillatorView.m
//
- (NSRect)visibleRect
{
return NSInsetRect([self bounds],-2,-2);
}
ゴミが残る

これだけでうまくいけばよかったのですが、残念ながらウィンドウをリサイズすると選択枠を消しきれずにウィンドウにゴミが残ってしまいました。
先ほどは描いた選択枠が消されてしまい、今度は消されずに残ってしまうわけで、なかなかうまくいかないものです。
【失敗例】リサイズ中は選択枠を消す
drawRect:メソッドでリサイズ中は何もしない様にしていましたが、リサイズ中は選択状態に関わらず、選択枠を消す様にしてみました。下記のコードです。
結果は処理が重くなったせいか、もたつきが発生しました。かつ、まれにゴミが残ってしまいます。
//
// OscillatorView.m
//
- (void)drawRect:(NSRect)rect
{
NSRect bounds,frame,savedFrame;
NSBezierPath *borderPath;
[super drawRect:rect];
}
if(![self inLiveResize])
{
savedFrame = [self frame];
frame = NSInsetRect(savedFrame,-3,-3);
[self setFrame:frame];
bounds = [self bounds];
bounds = NSInsetRect(bounds,1,1);
borderPath = [NSBezierPath bezierPathWithRect:bounds];
[borderPath setLineWidth:2];
if(selected && ![self inLiveResize])
[[NSColor blackColor] set];
else
[[NSColor windowBackgroundColor] set];
[borderPath stroke];
[self setFrame:savedFrame];
}
【対策案1】スーパービューでリサイズスタート時に消す
消し残しをOscillatorViewで対策するのが難しそうなので、スーパービューに消してもらう事にしました。リサイズスタート時にフラグを立てて、フラグがたっていたらビュー全体を消去してフラグをクリアします。こうしておけば、普段は負荷がかかりません。
結果はウィンドウにゴミが残らなくなった点は成功です。リサイズ時のもたつきは発生したりしなかったりで再現性がなく、よくわからないのが正直なところです。
//
// EnclosureView.h
// RepeatingMotifGenerator
//
// Copyright NovemberKou 2008. All rights reserved.
//
#import <Cocoa/Cocoa.h>
@class OscillatorView;
@interface EnclosureView : NSView
{
IBOutlet NSView *xView, *yView, *lissajousView;
int whiteSpace,viewMargin;
BOOL dirty;
}
@end
//
// EnclosureView.m
// RepeatingMotifGenerator
//
// Copyright NovemberKou 2008. All rights reserved.
//
#import "EnclosureView.h"
@interface EnclosureView(Private)
- (void)frameDidChange:(NSNotification *)aNotification;
@end
@implementation EnclosureView
#pragma mark -
#pragma mark Initializer and Deallocator
- (id)initWithFrame:(NSRect)frame
{
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
self = [super initWithFrame:frame];
if(self)
{
whiteSpace = 4;
viewMargin = 8;
dirty = NO;
[nc addObserver:self
selector:@selector(frameDidChange:)
name:NSViewFrameDidChangeNotification
object:self];
}
return self;
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
[super dealloc];
}
- (void)awakeFromNib
{
[self frameDidChange:nil];
}
@end
@implementation EnclosureView(Private)
#pragma mark -
#pragma mark Event Handler
- (void)frameDidChange:(NSNotification *)aNotification
{
int width,height,viewSize,marginWidth,marginHeight;
NSSize sizeOfView;
NSPoint originViewX,originViewY,originViewLissajous;
width = ([self bounds].size.width - whiteSpace*2 - viewMargin)/2;
height = ([self bounds].size.height - whiteSpace*2 - viewMargin)/2;
viewSize = width < height ? width : height;
marginWidth = ([self bounds].size.width - viewSize*2 - viewMargin)/2;
marginHeight = ([self bounds].size.height - viewSize*2 - viewMargin)/2;
originViewY.x = marginWidth;
originViewY.y = marginHeight;
originViewX.x = originViewY.x + viewSize + viewMargin;
originViewX.y = originViewY.y + viewSize + viewMargin;
originViewLissajous.x = originViewX.x;
originViewLissajous.y = originViewY.y;
[xView setFrameOrigin:originViewX];
[yView setFrameOrigin:originViewY];
[lissajousView setFrameOrigin:originViewLissajous];
sizeOfView = NSMakeSize(viewSize,viewSize);
[xView setFrameSize:sizeOfView];
[yView setFrameSize:sizeOfView];
[lissajousView setFrameSize:sizeOfView];
}
- (void)viewWillStartLiveResize
{
dirty = YES;
}
- (void)drawRect:(NSRect)rect
{
if(dirty)
{
[[NSColor windowBackgroundColor] set];
[[NSBezierPath bezierPathWithRect:[self bounds]] fill];
dirty = NO;
}
}
@end
【対策案2】ダミーのdrawRect:メソッドを設ける
説明のためにウィンドウにゴミが残っているスクリーンショットを撮ろうとして、viewWillStartLiveResizeメソッドのdirty = YES;をコメントアウトしてみたのです。これでビュー全体を消去する動作はしなくなるので、ゴミが残る筈です。ところがいくらウィンドウをリサイズしてもゴミが残りません!
色々といじってみて、結局中身が空のdrawRect:メソッドが存在するだけで対策になる事がわかりました。しかも、これだともたつきが発生しないのです。理由は全くわかりませんし、何だかだまされているようですっきりしませんが、もたつきが発生しないメリットは大きいので、これを採用する事にします。
でも将来動作が変わってしまうような気がします。念のため環境を書いておくと、Xcode 3.1とMac OS X 10.5.4です。プロセッサはPowerPC G5 デュアル2.3GHzです。
- (void)viewWillStartLiveResize
{
dirty = YES;
}
- (void)drawRect:(NSRect)rect
{
if(dirty)
{
[[NSColor windowBackgroundColor] set];
[[NSBezierPath bezierPathWithRect:[self bounds]] fill];
dirty = NO;
}
}
- (void)drawRect:(NSRect)rect
{
}

ホーム
前へ