ちょっとしたアイコンの横にテキストが並ぶ、というのはよくあるUIパーツだと思います。
が、それをさくっと実装する方法がなく、アイコンとラベルを個別に用意して実装、というふうにしていました。
でもそれだと管理がめんどくさいし、AutoLayoutを使っても無駄に記述増えるしでめんどくさかったので、色々調べつつカスタムラベルを作ってみました。
コード量はそんなに多くないので全部載せちゃいます。
@import UIKit;
@interface IconLabel : UILabel
@property (nonatomic, strong) UIImage *icon;
@property (nonatomic, strong) UIImageView *iconView;
@property (nonatomic, assign) CGFloat iconPadding;
/**
* 生成メソッド
*
* @param icon アイコンイメージ
*
* @return インスタンス
*/
+ (instancetype)createWithIconImage:(UIImage *)icon;
@end
実装
# import "IconLabel.h"
@interface IconLabel ()
@end
@implementation IconLabel
@synthesize iconPadding = _iconPadding;
@synthesize icon = _icon;
/**
* 生成メソッド
*/
+ (instancetype)createWithIconImage:(UIImage *)icon
{
return [[self.class alloc] initWithIconImage:icon];
}
- (instancetype)initWithIconImage:(UIImage *)icon
{
self = [super init];
if (self) {
self.icon = icon;
self.iconPadding = 0;
[self setupViews];
}
return self;
}
/**
* ビューのセットアップ
*/
- (void)setupViews
{
self.iconView = [[UIImageView alloc] initWithImage:self.icon];
[self addSubview:self.iconView];
}
/////////////////////////////////////////////////////////////////////////////
# pragma mark - Override methods
/**
* レイアウトサブビュー
*/
- (void)layoutSubviews
{
CGRect frame = CGRectZero;
frame.size = self.iconView.bounds.size;
frame.origin.x = 0;
frame.origin.y = self.bounds.size.height / 2 - self.iconView.bounds.size.height / 2;
self.iconView.frame = frame;
[super layoutSubviews];
}
/**
* テキストの描画
*/
- (void)drawTextInRect:(CGRect)rect
{
CGFloat padding = self.icon.size.width + self.iconPadding;
rect.size.width += padding;
rect.origin.x += padding;
[super drawTextInRect:rect];
}
/**
* sizeThatFits
* @override
*
* @param size サイズ
*
* @return アイコンを加味したサイズ
*/
- (CGSize)sizeThatFits:(CGSize)size
{
CGSize returnSize = [super sizeThatFits:size];
returnSize.width += self.icon.size.width + self.iconPadding;
returnSize.height = MAX(self.icon.size.height, returnSize.height);
return returnSize;
}
/**
* Intrinsicサイズ
* (AutoLayout利用時に呼ばれる)
*
* @return アイコンを加味したサイズ
*/
- (CGSize)intrinsicContentSize
{
CGSize returnSize = [super intrinsicContentSize];
returnSize.width += self.icon.size.width + self.iconPadding;
returnSize.height = MAX(self.icon.size.height, returnSize.height);
return returnSize;
}
/////////////////////////////////////////////////////////////////////////////
# pragma mark - Dynamic properties
/**
* アイコン
*/
- (void)setIcon:(UIImage *)icon
{
_icon = icon;
self.iconView.image = icon;
[self setNeedsDisplay];
}
- (UIImage *)icon
{
return _icon;
}
/**
* アイコンのパディングを設定
*/
- (void)setIconPadding:(CGFloat)iconPadding
{
_iconPadding = iconPadding;
[self setNeedsDisplay];
}
- (CGFloat)iconPadding
{
return _iconPadding;
}
@end
overrideメソッド
[追記]
大事な点は以下の3つ 4つのオーバーライドしたメソッド。
(AutoLayoutを考慮するともうひとつオーバーライドするべきメソッドがありました)
drawTextInRect:
このメソッドは与えられたrect
(ラベルのbounds
)の中のどこにテキストを描くかを決めるもの。
なので、これを適切にずらしてあげれば、今回のサンプルのようにアイコンの分だけ位置をずらしてテキストを描画できます。
(bounds
からはみ出したら単純にclipされます)
/**
* テキストの描画
*/
- (void)drawTextInRect:(CGRect)rect
{
CGFloat padding = self.icon.size.width + self.iconPadding;
rect.size.width += padding;
rect.origin.x += padding;
[super drawTextInRect:rect];
}
sizeThatFits:
続いてオーバーライドする必要があるのがこれ。
sizeToFit
を実行する際などにも利用されます。(逆にsizeToFit
は Should not overrideとドキュメントにある)
このメソッドはUIView
で定義され、サブクラス側で適切にオーバーライドすることで、そのサブクラスのビューがFitするためにどんなサイズが必要か、を定義することができます。
(逆にこれをオーバーライドしないとまったく意味のないメソッドになる)
なので今回は、UILabel
が実装しているsizeThatFits:
が返すサイズに、さらにアイコン分のサイズを足して返しています。
/**
* sizeThatFits
* @override
*
* @param size サイズ
*
* @return アイコンを加味したサイズ
*/
- (CGSize)sizeThatFits:(CGSize)size
{
CGSize returnSize = [super sizeThatFits:size];
returnSize.width += self.icon.size.width + self.iconPadding;
returnSize.height = MAX(self.icon.size.height, returnSize.height);
return returnSize;
}
layoutSubviews
最後にオーバーライドしたのがlayoutSubviews
。
やっていることは、指定されたアイコンをラベルの中央に整列させる、というもの。
このへんは実際に使いたいケースに合わせて調整するといいと思います。
/**
* レイアウトサブビュー
*/
- (void)layoutSubviews
{
CGRect frame = CGRectZero;
frame.size = self.iconView.bounds.size;
frame.origin.x = 0;
frame.origin.y = self.bounds.size.height / 2 - self.iconView.bounds.size.height / 2;
self.iconView.frame = frame;
[super layoutSubviews];
}
intrinsicContentSize
AutoLayoutを使うと、sizeThatFits:
をオーバーライドしても値が利用されません。
その代わりに使われるのがこのメソッドです。
基本的にはsizeThatFits:
と似たものなので、アイコン分のサイズを足して返してあげれば大丈夫です。
(ちなみに「intrinsic」は 本質的な という意味の英単語)
/**
* Intrinsicサイズ
* (AutoLayout利用時に呼ばれる)
*
* @return アイコンを加味したサイズ
*/
- (CGSize)intrinsicContentSize
{
CGSize returnSize = [super intrinsicContentSize];
returnSize.width += self.icon.size.width + self.iconPadding;
returnSize.height = MAX(self.icon.size.height, returnSize.height);
return returnSize;
}