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


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

## 変更履歴

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

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

## ソース

```objc: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

```

```objc: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

```

## 使い方
```objc: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`のコメントの通り

サンプルプロジェクトも置いておく
[こちら](https://github.com/anzfactory/CommonSample)

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

```objc:
- (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で見る限り同じだったので、深追いはやめた…。