読者です 読者をやめる 読者になる 読者になる

あおみかんのブログ

フリーランスのIT系エンジニア。ゲーム制作スタジオ4th cluster代表。

UIView の setFrame: をオーバーライドするときに気をつけるべきこと

iOSアプリ開発

UITableViewCellの横幅を調整する — ひよっこ
この辺の記事を読んでいて思ったこと。

こういうミスって、オブジェクト指向に真摯に向き合ってないと、ついついやらかしがちだよなーと思ったので、なるべく分かりやすくメモしてみる。

大前提として、元記事の通り、こんな風にsetFrameがオーバーライドされているとします。

// UITableViewCell のサブクラスにて。 元記事からコピペ
- (void)setFrame:(CGRect)frame
{
        frame.origin.x += self.inset;
        frame.size.width -= 2 * self.inset;
        [super setFrame:frame];
}

さて、ここでセルが選択されたら少しだけ右に寄って、解除されたら左に戻るようにしてみましょう。

// id<UITableViewDelegate> にて。 UITableViewControllerとかで。

static const CGFloat selectedCellOffset = 10;

- (NSIndexPath *)tableView:(UITableView *)tableView willSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    if ( ![[tableView indexPathForSelectedRow] isEqual:indexPath] ) {
        UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
        
        CGRect frame = cell.frame;
        frame.origin.x += selectedCellOffset;
        cell.frame = frame;
    }
    
    return indexPath;
}

- (void)tableView:(UITableView *)tableView didDeselectRowAtIndexPath:(NSIndexPath *)indexPath
{
    UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
    
    CGRect frame = cell.frame;
    frame.origin.x -= selectedCellOffset;
    cell.frame = frame;
}

実際にやってみなくても分かるようなことですが、
今回のようにframeで取得した値を利用してsetFrameが呼ばれるようなケースでは、先の例はバグを引き起こします。セルがどんどん小さくなっていく。 なんかまぬけで、実行してみるとちょっと笑える(?)

長く説明したけど、そもそも、ゲッタから受け取った値をセッタに代入できない時点で、何かおかしい訳ですよ。 Javaの入門書でも読んだら分かるようなことです。

ベターな解決策を提示してみます、こんな所でしょうか。

簡易版

// UITableViewCellのサブクラスに以下を書き加えましょう
- (void)setFrame:(CGRect)frame
{
        frame.origin.x += self.inset;
        frame.size.width -= 2 * self.inset;
        [super setFrame:frame];
}

ちょっと凝った版(あんまり変わらないような気も…)

@implementation AKNTableViewCell
{
    CGRect preservedFrame;
}

- (CGRect)frame
{
    return preservedFrame;
}

- (void)setFrame:(CGRect)frame
{
    self->preservedFrame = frame;
}

- (void)layoutSubviews
{
    CGRect insetFrame = [self insetFrame:self->preservedFrame];
    [super setFrame:insetFrame];
    [super layoutSubviews];
}

- (CGRect)insetFrame:(CGRect)frame
{
    frame.origin.x += inset;
    frame.origin.y += inset;
    frame.size.width -= 2 * inset;
    frame.size.height-= 2 * inset;
    
    return frame;
}

@end