— boreal-kiss.com

[iPhone] HTMLリンクのようなUIButtonのサブクラス

UIKitにはテキストにアンダーラインを付ける仕組みがない。そのため、HTMLのリンク表示のようなボタンを作るためにはアンダーラインを自前で描画してやらなければならない。ここで問題になるのが、アンダーラインの長さと描画位置が、アンダーラインを付加したい文字列の長さとフォントサイズに依存するということだ。以下のカスタムクラス(HTMLLinkButton)は、ボタンのタイトル文字列の長さとフォントサイズに比例してアンダーラインを可能な限り適切な位置に描画する。

使い方は簡単で、ボタンタイトルとリンク先URLを設定してやるだけ。例えば、UIViewControllerクラスに以下のように記述してやると上記画像のようなHTMLリンクボタンが表示される。リンクをクリックするとテキストがハイライトされ、指定のURLへナビゲートされる。

#import "HTMLLinkButton.h"
 
- (void)viewDidLoad {
    HTMLLinkButton *button1 = [[HTMLLinkButton alloc] 
		initWithTitle:@"Google" url:@"http://google.com/"];
	button1.frame = CGRectMake(50, 100, 100, 37);
	button1.font = [UIFont systemFontOfSize:24];
	[self.view addSubview:button1];
	[button1 release];
 
	HTMLLinkButton *button2 = [[HTMLLinkButton alloc] 
		initWithTitle:@"Yahoo" url:@"http://yahoo.com/"];
	button2.frame = CGRectMake(150, 100, 150, 37);
	button2.font = [UIFont systemFontOfSize:40];
	[self.view addSubview:button2];
	[button2 release];
}

先ほど、可能な限り適切な位置に描画する、と言ったのには訳がある。ボタンタイトルの文字列の扱いに以下のような仮定があるからである。

  • ボタン文字列の高さはUIButton.font.capHeight(現在使用されているフォントの大文字の高さ)に等しい
  • ボタン文字列の各文字の幅はUIButton.font.xHeight(現在使用されているフォントの小文字xの高さ。幅ではない)に等しい

これらの仮定を用いた処理はHTMLButtonLinkクラスのdrawRect:メソッドで行われている。改良の余地アリ。

(**追記2009/05/02: 文字列の長さ・高さの扱いは– (CGSize)sizeWithFont:(UIFont *)fontでスマートに解決した)

Source code

//  HTMLLinkButton.h
 
#import <UIKit/UIKit.h>
 
extern CGFloat const HTMLLinkButtonDefaultButtonWidth;
extern CGFloat const HTMLLinkButtonDefaultButtonHeight;
extern NSUInteger const HTMLLinkButtonDefaulltButtonNormalColor;
extern NSUInteger const HTMLLinkButtonDefaulltButtonHighlightedColor;
 
@interface HTMLLinkButton : UIButton {
	NSString *_buttonTitle;
	NSString *_url;
	UIColor *_normalColor;
	UIColor *_highlightedColor;
	UIColor *_currentColor;
}
@property (nonatomic, retain) NSString *buttonTitle;
@property (nonatomic, retain) NSString *url;
@property (nonatomic, retain) UIColor *normalColor;
@property (nonatomic, retain) UIColor *highlightedColor;
 
/**
 * The designated initializer.
 * @param title	Button title.
 * @param url URL string to link to.
 * @param normalColor Color of the button title in the normal state.
 * @param highlightedColor Color of the button title when highlighted.
 */
-(id)initWithTitle:(NSString *)title url:(NSString *)url 
	normalColor:(UIColor *)normalColor 
	highlightedColor:(UIColor *)highlightedColor;
 
/**
 * The same as the above initialize 
 * except for that the title color sets to default.
 * @param title	Button title.
 * @param url URL string to link to.
 */
-(id)initWithTitle:(NSString *)title url:(NSString *)url;
@end
 
//  HTMLLinkButton.m
 
#import "HTMLLinkButton.h"
#import "HTMLLinkButton-Private.h"
#import "UIColor-HexEncoding.h"
 
CGFloat const HTMLLinkButtonDefaultButtonWidth = 72.0;
CGFloat const HTMLLinkButtonDefaultButtonHeight = 37.0;
NSUInteger const HTMLLinkButtonDefaulltButtonNormalColor = 0x2255AA;
NSUInteger const HTMLLinkButtonDefaulltButtonHighlightedColor = 0xFF9900;
 
@implementation HTMLLinkButton
@synthesize buttonTitle = _buttonTitle;
@synthesize normalColor = _normalColor;
@synthesize highlightedColor = _highlightedColor;
@synthesize url = _url;
 
-(id)initWithTitle:(NSString *)title url:(NSString *)url 
			normalColor:(UIColor *)normalColor 
			highlightedColor:(UIColor *)highlightedColor{
	if (self = [super init]){
		self.buttonTitle = title;
		self.url = url;
		self.normalColor = normalColor;
		self.highlightedColor = highlightedColor;
		_currentColor = _normalColor;
		[self setup];
	}
	return self;
}
 
-(id)initWithTitle:(NSString *)title url:(NSString *)url{
	UIColor *normalColor = 
		[UIColor colorFromHex:HTMLLinkButtonDefaulltButtonNormalColor];
	UIColor *highlightedColor = 
		[UIColor colorFromHex:HTMLLinkButtonDefaulltButtonHighlightedColor];
	return [self initWithTitle:title 
			url:url normalColor:normalColor 
			highlightedColor:highlightedColor];
}
 
//Override
- (void)drawRect:(CGRect)rect {
	CGFloat w = rect.size.width;
	CGFloat h = rect.size.height;
	CGFloat capHeight = self.font.capHeight;
	CGFloat xHeight = self.font.xHeight;
	CGFloat underlineLength = [_buttonTitle length] * xHeight;
	CGFloat startX = (w - underlineLength) / 2.0;
	CGFloat startY = (h + capHeight) / 2.0;
	CGFloat endX = startX + underlineLength;
	CGFloat endY = startY;
 
	CGContextRef context = UIGraphicsGetCurrentContext();
	CGContextSetStrokeColorWithColor(context, _currentColor.CGColor);
	CGContextMoveToPoint(context, startX, startY);
	CGContextAddLineToPoint(context, endX, endY);
	CGContextStrokePath(context);
}
 
- (void)dealloc {
	[_buttonTitle release];
	[_url release];
	[_normalColor release];
	[_highlightedColor release];
	[_currentColor release];
    [super dealloc];
}
 
#pragma mark -
#pragma mark Setters
 
//Override
-(void)setFrame:(CGRect)frame{
	[super setFrame:frame];
	[self setNeedsDisplay];
}
 
//Override
-(void)setFont:(UIFont *)font{
	[super setFont:font];
	[self setNeedsDisplay];
}
 
//Override
-(void)setNormalColor:(UIColor *)color{
	if (_normalColor != color){
		[_normalColor release];
		_normalColor = [color retain];
		[self setNeedsDisplay];
	}
}
 
//Override
-(void)setHighlightedColor:(UIColor *)color{
	if (_highlightedColor != color){
		[_highlightedColor release];
		_highlightedColor = [color retain];
		[self setNeedsDisplay];
	}
}
@end
 
//  HTMLLinkButton-Private.h
 
#import <UIKit/UIKit.h>
#import "HTMLLinkButton.h"
 
@interface HTMLLinkButton (Private)
 
/**
 * @private
 */ 
-(void)setup;
-(void)changeTextColor;
-(void)touchUpInside;
@end
 
//  HTMLLinkButton-Private.m
 
#import "HTMLLinkButton-Private.h"
 
@implementation HTMLLinkButton (Private)
 
-(void)setup{
	[self setFrame:CGRectMake(0, 0, HTMLLinkButtonDefaultButtonWidth, 
		HTMLLinkButtonDefaultButtonHeight)];
	[self setTitle:_buttonTitle forState:UIControlStateNormal];
	[self setTitle:_buttonTitle forState:UIControlStateHighlighted];
	[self setTitleColor:_normalColor forState:UIControlStateNormal];
	[self setTitleColor:_highlightedColor forState:UIControlStateHighlighted];
	[self addTarget:self action:@selector(changeTextColor) 
		forControlEvents:UIControlEventTouchDown];
	[self addTarget:self action:@selector(changeTextColor) 
		forControlEvents:UIControlEventTouchDragExit];
	[self addTarget:self action:@selector(changeTextColor) 
		forControlEvents:UIControlEventTouchDragEnter];
	[self addTarget:self action:@selector(touchUpInside) 
		forControlEvents:UIControlEventTouchUpInside];
}
 
-(void)changeTextColor{
	if (_currentColor == _normalColor){
		_currentColor = _highlightedColor;
	}
	else{
		_currentColor = _normalColor;
	}
	[self setNeedsDisplay];
}
 
-(void)touchUpInside{
	[self changeTextColor];
	[[UIApplication sharedApplication] openURL:[NSURL URLWithString:_url]];
}
 
@end
 
// UIColor-HexEncoding.h
 
#import <UIKit/UIKit.h>
 
@interface UIColor (HexEncoding)
+(UIColor *)colorFromHex:(NSUInteger)color24;
@end
 
// UIColor-HexEncoding.m
 
#import "UIColor-HexEncoding.h"
 
@implementation UIColor (HexEncoding)
 
+(UIColor *)colorFromHex:(NSUInteger)color24{
	CGFloat r = (color24 >> 16) / 255.0f;
	CGFloat g = (color24 >> 8 & 0xFF) / 255.0f;
	CGFloat b = (color24 & 0xFF) / 255.0f;
	return [UIColor colorWithRed:r green:g blue:b alpha:1.0f];
}
 
@end
 
1 comment
  1. […] [iPhone] HTMLリンクのようなUIButtonのサブクラスでボタンタイトルの文字列の長さに応じてアンダーラインを引く方法を試行錯誤していたが、NSStringオブジェクトのCGSizeを返すメソッドであっさり解決した。 […]

Submit comment