目的
UIControlを継承しターゲットアクションメカニズムを持つクラスを作る方法を理解する。
主要クラス
UIControl,CALayer
使用テンプレートプロジェクト
Window-based Application
プロジェクトの名称
aquabutton
事前に体験しておくべきドリル
サンプル実装説明
このドリルではUIControlを継承し、描画に「CALayerのcontentsを理解する」で使ったCAGradientLayerを使う事でAquaライクなボタンを作成する。
まずはプロジェクトaquabuttonを作成し、UIControlを継承したAquaButtonクラスを作成する。
作成時はUIViewを継承クラスとして選び、AquaButton.hができた時点でUIViewをUIControlに書き換える。CAGradientLayerのインスタンスはハイライト状態、ディスエーブル状態などでグラデーションを変化させるので、internalButtonという名称でインスタンス変数として持つ。
プロパティtextLabelは「CALayerのcontentsを理解する」を参照。その他にボタンの色相を指定できるようにhueプロパティを用意した。
#import
@class CAGradientLayer;
@interface AquaButton : UIControl {
CAGradientLayer* internalButton;
}
@property (readonly) UILabel* textLabel;
@property (assign) float hue;
@end
@class CAGradientLayer;
@interface AquaButton : UIControl {
CAGradientLayer* internalButton;
}
@property (readonly) UILabel* textLabel;
@property (assign) float hue;
@end
今回、ボタンの状態ごとにグラデーションを作りなおせるよう、グラデーションカラー配列を作る部分をメソッドとして独立させた。内容自体は「CALayerのcontentsを理解する」と同じ。
- (NSArray*)gradientArrayWithHue:(float)hue_
saturation:(float)saturation
brightness:(float)brightness
{
CGColorRef halationTop = [UIColor colorWithHue:hue_
saturation:saturation * 0.2
brightness:brightness
alpha:1].CGColor;
CGColorRef halationBottom = [UIColor colorWithHue:hue_
saturation:saturation * 0.8
brightness:brightness * 0.8
alpha:1].CGColor;
CGColorRef normalTop = [UIColor colorWithHue:hue_
saturation:saturation
brightness:brightness * 0.8
alpha:1].CGColor;
CGColorRef normalBottom = [UIColor colorWithHue:hue_
saturation:saturation
brightness:brightness
alpha:1].CGColor;
NSMutableArray *colors = [NSArray arrayWithObjects:
(id)halationTop,
(id)halationBottom,
(id)normalTop,
(id)normalBottom, nil];
return colors;
}
saturation:(float)saturation
brightness:(float)brightness
{
CGColorRef halationTop = [UIColor colorWithHue:hue_
saturation:saturation * 0.2
brightness:brightness
alpha:1].CGColor;
CGColorRef halationBottom = [UIColor colorWithHue:hue_
saturation:saturation * 0.8
brightness:brightness * 0.8
alpha:1].CGColor;
CGColorRef normalTop = [UIColor colorWithHue:hue_
saturation:saturation
brightness:brightness * 0.8
alpha:1].CGColor;
CGColorRef normalBottom = [UIColor colorWithHue:hue_
saturation:saturation
brightness:brightness
alpha:1].CGColor;
NSMutableArray *colors = [NSArray arrayWithObjects:
(id)halationTop,
(id)halationBottom,
(id)normalTop,
(id)normalBottom, nil];
return colors;
}
initWithFrame:メソッドも、グラデーション色配列をgradientArrayWithHue:saturation:brightness:メソッドで作るようにした以外は特に違いはない。
@implementation AquaButton
@synthesize textLabel, hue;
・
・
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
internalButton = [CAGradientLayer layer];
internalButton.frame = self.bounds;
internalButton.borderWidth = 1;
internalButton.cornerRadius = self.bounds.size.height / 4.0;
NSMutableArray *locations = [NSArray arrayWithObjects:
[NSNumber numberWithFloat:0.0],
[NSNumber numberWithFloat:0.5],
[NSNumber numberWithFloat:0.5],
[NSNumber numberWithFloat:1.0],nil];
[internalButton setLocations:locations];
[internalButton setColors:[self gradientArrayWithHue:hue saturation:1 brightness:1]];
[self.layer addSublayer:internalButton];
textLabel = [[[UILabel alloc] initWithFrame:self.bounds] autorelease];
textLabel.font = [UIFont boldSystemFontOfSize:18];
textLabel.textColor = [UIColor whiteColor];
textLabel.textAlignment = UITextAlignmentCenter;
textLabel.shadowColor = [UIColor grayColor];
textLabel.backgroundColor = [UIColor clearColor];
[self addSubview:textLabel];
}
return self;
}
@synthesize textLabel, hue;
・
・
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
internalButton = [CAGradientLayer layer];
internalButton.frame = self.bounds;
internalButton.borderWidth = 1;
internalButton.cornerRadius = self.bounds.size.height / 4.0;
NSMutableArray *locations = [NSArray arrayWithObjects:
[NSNumber numberWithFloat:0.0],
[NSNumber numberWithFloat:0.5],
[NSNumber numberWithFloat:0.5],
[NSNumber numberWithFloat:1.0],nil];
[internalButton setLocations:locations];
[internalButton setColors:[self gradientArrayWithHue:hue saturation:1 brightness:1]];
[self.layer addSublayer:internalButton];
textLabel = [[[UILabel alloc] initWithFrame:self.bounds] autorelease];
textLabel.font = [UIFont boldSystemFontOfSize:18];
textLabel.textColor = [UIColor whiteColor];
textLabel.textAlignment = UITextAlignmentCenter;
textLabel.shadowColor = [UIColor grayColor];
textLabel.backgroundColor = [UIColor clearColor];
[self addSubview:textLabel];
}
return self;
}
あとはsetEnabled:、setHighlighted:メソッドをオーバーライドすればUIButtonと同じ動きをするAqua風ボタンが完成する。
- (void)setEnabled:(BOOL)enabled
{
[super setEnabled:enabled];
[internalButton setColors:[self gradientArrayWithHue:hue
saturation:enabled?1:0.3
brightness:1]];
internalButton.borderColor = enabled
?([UIColor blackColor].CGColor):([UIColor lightGrayColor].CGColor);
}
- (void)setHighlighted:(BOOL)highlighted
{
[super setHighlighted:highlighted];
[internalButton setColors:[self gradientArrayWithHue:hue
saturation:1
brightness:highlighted?0.7:1]];
}
{
[super setEnabled:enabled];
[internalButton setColors:[self gradientArrayWithHue:hue
saturation:enabled?1:0.3
brightness:1]];
internalButton.borderColor = enabled
?([UIColor blackColor].CGColor):([UIColor lightGrayColor].CGColor);
}
- (void)setHighlighted:(BOOL)highlighted
{
[super setHighlighted:highlighted];
[internalButton setColors:[self gradientArrayWithHue:hue
saturation:1
brightness:highlighted?0.7:1]];
}
あっけないが、ボタン程度ならこれで十分機能する。
aquabuttonAppDelegateで今回作成したAquaButton、UIButtonをそれぞれwindowに配置してUIControlEventTouchDown、UIControlEventTouchDragInside、…、UIControlEventTouchUpInsideなどのイベントに対しターゲットアクションを設定し比べてみる。

コンソールにはAquaButton、UIButtonどちらも同じように、指(カーソル)の動きに合わせてアクションメソッドが実行されていく。
EventTouchDown
UIControlEventTouchDragInside
UIControlEventTouchDragInside
UIControlEventTouchDragInside
UIControlEventTouchDragInside
UIControlEventTouchDragInside
UIControlEventTouchDragInside
UIControlEventTouchDragInside
UIControlEventTouchUpInside
UIControlEventTouchDragInside
UIControlEventTouchDragInside
UIControlEventTouchDragInside
UIControlEventTouchDragInside
UIControlEventTouchDragInside
UIControlEventTouchDragInside
UIControlEventTouchDragInside
UIControlEventTouchUpInside
以上、AquaButtonはボタンとしてはUIButton同等の働きができる事がわかる。

前回の応用としてUIViewを継承しCAGradientLayerを使ったBadgeViewもサンプルソースに含めておく。shadowOffset、shadowOpacityプロパティを使っている。
こちらは、いわゆるバッジ。

プロジェクト
検討
contentsにCGImageRefを設定する場合、retinaディスプレイに最適化したい場合、作成するCGContextRefを UIGraphicsBeginImageContext ではなくUIGraphicsBeginImageContextWithOptions を使って作成したり、CALayerのcontentsScaleプロパティを2.0にしたりを自分で調整しなければならない。
retinaディスプレイかどうかは[[UIScreen mainScreen].scaleで調べる。
iOS 4.0より前のiOSでも動かす予定なら、scaleプロパティを使う前に
[UIScreen instancesRespondToSelector:@selector(scale)]
といったチェックも必要。
iOS 4.0より前のiOSでも動かす予定なら、scaleプロパティを使う前に
[UIScreen instancesRespondToSelector:@selector(scale)]
といったチェックも必要。
CAGradientLayerはその点においても使う価値がある。