9
9

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

[Objective-C] アイコン付きラベルのカスタムラベルを作る

Last updated at Posted at 2015-02-26

ちょっとしたアイコンの横にテキストが並ぶ、というのはよくあるUIパーツだと思います。
が、それをさくっと実装する方法がなく、アイコンとラベルを個別に用意して実装、というふうにしていました。

でもそれだと管理がめんどくさいし、AutoLayoutを使っても無駄に記述増えるしでめんどくさかったので、色々調べつつカスタムラベルを作ってみました。
コード量はそんなに多くないので全部載せちゃいます。

実行するとこんな感じ↓
スクリーンショット

IconLabel.h
@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

実装

IconLabel.m
#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されます)

override-drawTextInRect
/**
 *  テキストの描画
 */
- (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:が返すサイズに、さらにアイコン分のサイズを足して返しています。

override-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
やっていることは、指定されたアイコンをラベルの中央に整列させる、というもの。
このへんは実際に使いたいケースに合わせて調整するといいと思います。

override-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」は 本質的な という意味の英単語)

override-intrinsicContentSize
/**
 *  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;
}
9
9
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
9

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?