LoginSignup
7
6

More than 5 years have passed since last update.

【自作】ドロップダウンリスト(UITextField継承)

Last updated at Posted at 2013-12-04

Objective-Cでドロップダウンリストが無いような感じなので自分で作ってみた。
折角なので、乗っけてみる
イメージ的には、HTMLのselectタグでsize属性に1指定した感じ

変更履歴

日付 内容
13.12.05 newメソッドに対応(対応してないのに使い方に書いてごめんなさい…)

構成

UITextFieldを継承して、内部にUITableViewをもつ

ソース

ANZDropDownField.h
#import <UIKit/UIKit.h>

@interface ANZDropDownField : UITextField <UITableViewDelegate, UITableViewDataSource>

@property (nonatomic) NSArray* dropList;            // 表示するドロップリスト
@property (nonatomic) CGFloat displayNumOfRows;     // 表示件数
@property (nonatomic) CGFloat heightOfListItem;     // リストアイテムの高さ
@property (nonatomic) UIColor* borderColorForList;  // リストの枠線
@property (nonatomic) UIFont* fontOfListItem;       // リストアイテムのフォント

@end

ANZDropDownField.m

#import "ANZDropDownField.h"

#import <QuartzCore/QuartzCore.h>

@interface ANZDropDownField() {
    CGFloat _borderWidth;
    UITapGestureRecognizer* _singleTapGesture;
}

@property (nonatomic) UITableView* tableView;
@end

@implementation ANZDropDownField

static NSString* _cellID = @"cell";

- (id)init
{
    if (self == [super init]) {
        [self prepare];
    }
    return self;
}

- (void)awakeFromNib
{
    [super awakeFromNib];

    [self prepare];
}

- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
{
    BOOL result = [_singleTapGesture isEqual:gestureRecognizer];
    if (! self.dropList || [self.dropList count] == 0) {
        return !result;
    } else {
        return result;
    }
}

- (void)setFrame:(CGRect)frame
{
    [super setFrame:frame];

    if (self.heightOfListItem > 0) {
        return;
    }
    self.heightOfListItem = frame.size.height;
}


#pragma mark - UITableViewDelegate, UITableViewDataSource
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    return [self.dropList count];
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    UITableViewCell* cell = [tableView dequeueReusableCellWithIdentifier:_cellID];
    if (! cell) {
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:_cellID];
        cell.contentView.backgroundColor = self.backgroundColor;
        cell.textLabel.textAlignment = self.textAlignment;
        cell.textLabel.font = self.font;
        cell.textLabel.textColor = self.textColor;
    }

    if (self.fontOfListItem) {
        cell.textLabel.font = self.fontOfListItem;
    }

    cell.textLabel.text = self.dropList[indexPath.item];

    return cell;
}

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    return self.heightOfListItem;
}

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    [self hideDropDownListWithUpdateText:self.dropList[indexPath.item]];
}

#pragma mark - methods
- (void)prepare
{
    _borderWidth = .5f;

    _tableView = [[UITableView alloc] initWithFrame:self.frame style:UITableViewStylePlain];
    _tableView.delegate = self;
    _tableView.dataSource = self;
    _tableView.layer.borderWidth = _borderWidth;

    _displayNumOfRows = 5;
    _heightOfListItem = self.frame.size.height;
    _borderColorForList = [UIColor lightGrayColor];

    // ドロップダウンリスト開閉用に登録
    _singleTapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleSingleTap:)];
    [self addGestureRecognizer:_singleTapGesture];


}

- (void)showDropDownList
{
    [self adjustTableView];     // 表示するドロップリストの高さ調整

    self.tableView.hidden = YES;

    [self.superview addSubview:self.tableView];
    [self.superview bringSubviewToFront:self];

    CGAffineTransform affine = CGAffineTransformMakeScale(1, 0);
    self.tableView.transform = affine;

    self.tableView.hidden = NO;
    [UIView animateWithDuration:.2f animations:^{
        self.tableView.transform = CGAffineTransformIdentity;
    } completion:^(BOOL finished) {

    }];
}

- (void)hideDropDownList
{
    [self hideDropDownListWithUpdateText:self.text];
}

- (void)hideDropDownListWithUpdateText:(NSString *)updateText;
{
    CGAffineTransform affine = CGAffineTransformScale(self.tableView.transform, 1, 0);

    [UIView animateWithDuration:.2f animations:^{
        self.tableView.transform = affine;
    } completion:^(BOOL finished) {
        self.text = updateText;
        self.tableView.hidden = YES;
        self.tableView.transform = CGAffineTransformIdentity;
        [self.tableView removeFromSuperview];
    }];
}

- (void)adjustTableView
{
    NSUInteger numOfRows = 0;
    if (self.displayNumOfRows > [self.dropList count]) {
        numOfRows = [self.dropList count];
    } else {
        numOfRows = self.displayNumOfRows;
    }

    CGFloat heightTableVeiw = self.heightOfListItem * numOfRows;
    CGPoint startPoint = CGPointZero;
    CGFloat selfBottomLine = self.frame.origin.y + self.frame.size.height;

    // 下にドロップ表示できるか判定 (マージン30.f)
    if ((self.superview.frame.size.height - (selfBottomLine + 30.f)) > heightTableVeiw) {
        self.tableView.layer.anchorPoint = CGPointMake(.5f, 0);
        startPoint = CGPointMake(self.frame.origin.x, selfBottomLine - 1.f);
    } else {
        self.tableView.layer.anchorPoint = CGPointMake(.5f, 1);
        startPoint = CGPointMake(self.frame.origin.x, (self.frame.origin.y - heightTableVeiw) + 1.f);
    }

    self.tableView.layer.cornerRadius = self.layer.cornerRadius;
    self.tableView.frame = CGRectMake(startPoint.x,
                                      startPoint.y,
                                      self.frame.size.width,
                                      heightTableVeiw);

    self.tableView.layer.borderColor = self.borderColorForList.CGColor;
    self.tableView.backgroundColor = self.backgroundColor;

}

- (void)handleSingleTap:(id)sender
{
    if (self.tableView.superview) {
        [self hideDropDownList];
    } else {
        [self showDropDownList];
    }
}

@end

使い方

Sample.m
ANZDropDownField* dropDown = [ANZDropDownField new];
dropDown.frame = CGRectMake(0, 0, 100, 30);
dropDown.dropList = @[@"item 1",@"item 2",@"item 3",@"item 4",@"item 5"];
[self.view addSubview: dropDown];

インターフェスビルダー上でUITextFieldを配置して、クラスをANZDropDownField指定してもOK
その場合はdropDown.dropListの設定だけ
設定しない場合は通常のTextFieldとなるだけ
他にもプロパティはあるけどANZDropDownField.hのコメントの通り

サンプルプロジェクトも置いておく
こちら

気になる所

概ね多分大丈夫なのだけれど…
タップした時の処理ですこしあやふやな感じ

- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
{
    BOOL result = [_singleTapGesture isEqual:gestureRecognizer];
    if (! self.dropList || [self.dropList count] == 0) {
        return !result;
    } else {
        return result;
    }
}

ここ
そもそも最初の考えでは、- (void)prepare;メソッドではUIGestureRecognizerを登録する気はなかった。
UITextFieldが内部で登録してるっぽい(UITextTapRecognizerというやつを)ので、それを使おうかと
ただ、インターフェスビルダー上で配置した場合は問題ないんだけど、
ソースでインスタンス作ってaddした時はなぜか- (BOOL)gestureRecognizerShouldBegin:2回連続呼ばれてしまって、想定してる通り挙動しなかった
なので、回避策として自前で登録してそっちでドロップダウンリストの開閉処理を任せる感じに

なにがあやふやなのかというと
インターフェスビルダー上で設置した場合とソースで設置した場合に- (BOOL)gestureRecognizerShouldBegin:が呼ばれる回数がなぜ違うのかっていうこと
結果違うんだから、善後策として今の感じでおさまってるけれども…
返ってくるUIGestureRecognizerから違いを見つけようとしたけれども、debugDescriptionで見る限り同じだったので、深追いはやめた…。

7
6
1

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
7
6