利用UIScrollView實(shí)現(xiàn)自定義的PickerView

? 偶然的在街上(虎撲)閑逛的時(shí)候板辽,然后上看到了一個(gè)長(zhǎng)得很好看的時(shí)間選擇器,然后想著我也能實(shí)現(xiàn)一個(gè)類似的,于是就有了這篇文章措嵌。
先上個(gè)效果吧:

復(fù)刻效果.gif

? 習(xí)慣上与柑,涉及到自定義UI的時(shí)候谤辜,我們先去看看系統(tǒng)所提供的UI庫(kù)到底滿不滿足自定義編寫的需要澎现,但十有八九都是不滿足的。這次做的是一個(gè)時(shí)間選擇器每辟,于是我去翻閱了UIDatePicker以及UIPickerView 剑辫,發(fā)現(xiàn)提供的借口不能滿足實(shí)現(xiàn)這個(gè)UI,所以順帶看了一下UIScrollView 的接口渠欺,想起前一陣子遇到的自定義UICollectionView妹蔽,嘗試使用Delegate和DataSource的方式來(lái)實(shí)現(xiàn)這個(gè)功能∧咏總的來(lái)說(shuō)這里模仿UITableView的實(shí)現(xiàn)大于要實(shí)現(xiàn)一個(gè)選擇器吧胳岂。

? 出于模仿UITableView,先定義一下DataSource所需要的內(nèi)容如下:

@protocol ECPickerViewDataSourse <NSObject>
///返回有多少個(gè)列內(nèi)容
-(NSInteger)numberOfItemsInSection:(ECPickerView *)view;
///對(duì)應(yīng)每一列的行數(shù)組
-(NSArray *)pickerView:(ECPickerView *)view withSection:(NSInteger)section;
///對(duì)應(yīng)每一行所顯示的UI內(nèi)容
-(UIView *)pickerView:(ECPickerView *)view withSection:(NSInteger)section indexPath:(NSInteger)index;
///返回實(shí)現(xiàn)行高度
-(CGFloat)pickerView:(ECPickerView *)view setHightForCell:(NSInteger)section indexPath:(NSInteger)index;
@end

接著定義一下Delegate的返回內(nèi)容:

@protocol ECPickerViewDelegate <NSObject>
///返回滑動(dòng)時(shí)的每一個(gè)行的內(nèi)容舔稀,對(duì)應(yīng)返回的是一組數(shù)組
-(void)cpickerViewMoveToItem:(NSArray *)selectArray;
///返回滑動(dòng)結(jié)束的時(shí)候每一個(gè)行的內(nèi)容乳丰,對(duì)應(yīng)返回的是一組數(shù)組
-(void)cpickerViewMoveToItemEndAnimation:(NSArray *)selectArray;
@end

那么我們這個(gè)UIView所包含的屬性應(yīng)該是這樣的:

@protocol ECPickerViewDataSourse,ECPickerViewDelegate;
@interface ECPickerView : UIView
///DataSourse
@property(nonatomic,unsafe_unretained)IBOutlet __nullable id<ECPickerViewDataSourse> datasourse;
///Delegate
@property(nonatomic,unsafe_unretained)IBOutlet __nullable id<ECPickerViewDelegate> delegate;
///列總數(shù)
@property (nonatomic, readonly) NSInteger numberOfSections;
///由于這里自定義的是一個(gè)時(shí)間選擇器,所以這里提供的是時(shí)間設(shè)置
-(void)scrollToDate:(NSDate *)date;
@end

那么接下來(lái)實(shí)現(xiàn)的首先是DataSource的處理咯内贮,因?yàn)楹罄m(xù)的初始化UI數(shù)據(jù)還是后來(lái)的刷新數(shù)據(jù)多是通過(guò)他來(lái)實(shí)現(xiàn)的产园,所以我們先重定義了DataSource的set方法和初始化UIView:

- (instancetype)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        [self initWithData];
        [self reloadData];
    }
    return self;
}

-(void)setDatasourse:(id<ECPickerViewDataSourse>)datasourse{
    if (_datasourse != datasourse) {
        _datasourse = datasourse;
        if (_datasourse) {
            [self reloadData];///刷新數(shù)據(jù)的顯示
            [self moveToNowTime];///移動(dòng)到指定默認(rèn)的時(shí)間戳位置
        }
    }
}

移動(dòng)到指定的時(shí)間戳位置選取的方法如下:

-(void)scrollToDate:(NSDate *)date{
    NSString *dateStr = [formatter stringFromDate:date];
    NSArray *newArr = [dateStr componentsSeparatedByString:@":"];
    for (int i = 0; i<newArr.count; i++) {
        int number = [newArr[i] intValue];
        UIScrollView *scrollview = scrViewArray[I];
        CGFloat hight = [_datasourse pickerView:self setHightForCell:i indexPath:0];
        [scrollview setContentOffset:CGPointMake(0, number*hight) animated:YES];
        [scrollview setBouncesZoom:NO];
    }
}

-(void)moveToNowTime{
    [self scrollToDate:[NSDate new]];
}

初始化用來(lái)記錄數(shù)據(jù)的數(shù)組

-(void)initWithData{
    scrViewArray = [NSMutableArray new];
    numberArray = [NSMutableArray new];
    formatter = [[NSDateFormatter alloc] init];
    formatter.dateFormat = @"HH:mm";
}

刷新數(shù)據(jù),初始化數(shù)據(jù)的代碼:

-(void)reloadData{
    _numberOfSections = [_datasourse numberOfItemsInSection:self];///通過(guò)反向代理的方式來(lái)獲取到外面DataSource所設(shè)置的值
    CGFloat width = self.frame.size.width/_numberOfSections;
    CGFloat height = self.frame.size.height;
    for (NSMutableArray *item in numberArray) {
        for (UIView *view in item) {
            [view removeFromSuperview];
        }
    }
    [numberArray removeAllObjects];
    
    for (UIScrollView *view in scrViewArray) {
        [view removeFromSuperview];
    }
    [scrViewArray removeAllObjects];
    for (int i = 0; i<self.numberOfSections; i++) {
        NSMutableArray *viewItemArr = [NSMutableArray new];
        UIScrollView *scrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(i*width, 0, width, height)];
        scrollView.tag = I;
        scrollView.delegate = self;
        scrollView.pagingEnabled = NO;//分頁(yè)滾動(dòng)
        [scrollView setBounces:YES];//到了邊緣以后不回彈
        scrollView.showsVerticalScrollIndicator = NO;
        scrollView.showsHorizontalScrollIndicator = NO;
        
        NSArray *itemArray = [_datasourse pickerView:self withSection:i];
        CGFloat countHight = 0;
        CGFloat itemHight1 = 0;
        for (int k = 0; k<itemArray.count; k++) {
            CGFloat itemHight = [_datasourse pickerView:self setHightForCell:i indexPath:k];
            itemHight1 = itemHight;
            countHight+=itemHight;
            UIView *view = [_datasourse pickerView:self withSection:i indexPath:k];
            view.frame = CGRectMake(0, k*itemHight+height/2-itemHight/2, width, itemHight);
            [viewItemArr addObject:view];
            [scrollView addSubview:view];
        }
        scrollView.contentSize = CGSizeMake(width, countHight+height-itemHight1);
        
        [self addSubview:scrollView];
        
        UIView *view1 = [[UIView alloc] initWithFrame:CGRectMake(i*width+width/2-(25/2), height/2-24, 25, 2)];
        view1.backgroundColor = [UIColor colorWithRed:128/255.0 green:91.0/255.0 blue:235.0/255.0 alpha:1.0];
        [self addSubview:view1];
        
        UIView *view2 = [[UIView alloc] initWithFrame:CGRectMake(i*width+width/2-(25/2), height/2+22, 25, 2)];
        view2.backgroundColor = [UIColor colorWithRed:128/255.0 green:91.0/255.0 blue:235.0/255.0 alpha:1.0];
        [self addSubview:view2];
        
        [numberArray addObject:viewItemArr];
        [scrViewArray addObject:scrollView];
    }
    CGFloat itemHight0 = [_datasourse pickerView:self setHightForCell:0 indexPath:0];
    UILabel *labCenter = [[UILabel alloc] initWithFrame:CGRectMake(self.frame.size.width/2-5, height/2-itemHight0/2-5, 10, itemHight0)];
    labCenter.font = [UIFont systemFontOfSize:40];
    labCenter.textColor = [UIColor blackColor];
    labCenter.text = @":";
    [self addSubview:labCenter];
    
    [self setNeedsDisplay];
}

為了讓整個(gè)PickerView在用戶操作的時(shí)候看起來(lái)更容易選擇和更像系統(tǒng)所自帶的UIDatePicker夜郁,所以這里需要利用好UIScrollView的代理什燕,主要是滑動(dòng)的起止以及慣性滑動(dòng)這些。


-(void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView{
    CGFloat hight = [_datasourse pickerView:self setHightForCell:scrollView.tag indexPath:0];
    if ((int)scrollView.contentOffset.y%(int)hight != 0) {
        int k = scrollView.contentOffset.y/hight;
        float t = scrollView.contentOffset.y/hight;
        NSString *f1 = [NSString stringWithFormat:@"%.4f",t];
        NSArray *tmpA = [f1 componentsSeparatedByString:@"."];
        NSString *f2 = tmpA[1];
        float f2f = [f2 floatValue]/10000;
        if (f2f >= 0.5) {
            k+=1;
        }
        [scrollView setContentOffset:CGPointMake(0, k*hight) animated:YES];
        scrollView.bouncesZoom = NO;
        [self labTextSet:scrollView];
    }
    [self shaker:scrollView withShake:YES];
    if ([_delegate respondsToSelector:@selector(cpickerViewMoveToItemEndAnimation:)]) {
        [_delegate cpickerViewMoveToItemEndAnimation:[self didSelectWithScroller]];
    }
    
}

-(void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView{
    
}

-(void)scrollViewDidChangeAdjustedContentInset:(UIScrollView *)scrollView{
    
}

-(void)scrollViewDidScroll:(UIScrollView *)scrollView{
    [self labTextSet:scrollView];
    if ([_delegate respondsToSelector:@selector(cpickerViewMoveToItem:)]) {
          [_delegate cpickerViewMoveToItem:[self didSelectWithScroller]];
      }
}
-(void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset{
    
    CGFloat hight = [_datasourse pickerView:self setHightForCell:scrollView.tag indexPath:0];
    if ((int)scrollView.contentOffset.y%(int)hight != 0) {
        int k = scrollView.contentOffset.y/hight;
        float t = scrollView.contentOffset.y/hight;
        NSString *f1 = [NSString stringWithFormat:@"%.4f",t];
        NSArray *tmpA = [f1 componentsSeparatedByString:@"."];
        NSString *f2 = tmpA[1];
        float f2f = [f2 floatValue]/10000;
        if (f2f >= 0.5) {
            k+=1;
        }
        [scrollView setContentOffset:CGPointMake(0, k*hight) animated:YES];
        scrollView.bouncesZoom = NO;
        
        [self labTextSet:scrollView];
    }
    
    [self shaker:scrollView withShake:YES];
    if ([_delegate respondsToSelector:@selector(cpickerViewMoveToItemEndAnimation:)]) {
        [_delegate cpickerViewMoveToItemEndAnimation:[self didSelectWithScroller]];
    }
    
}


-(void)labTextSet:(UIScrollView *)scrollView{
    NSArray *labArray = numberArray[scrollView.tag];
    CGFloat hight = [_datasourse pickerView:self setHightForCell:scrollView.tag indexPath:0];
    int k = scrollView.contentOffset.y/hight;
    float t = scrollView.contentOffset.y/hight;
    NSString *f1 = [NSString stringWithFormat:@"%.4f",t];
    NSArray *tmpA = [f1 componentsSeparatedByString:@"."];
    NSString *f2 = tmpA[1];
    float f2f = [f2 floatValue]/10000;
    if (f2f >= 0.5) {
        k+=1;
    }
    if (k>-1 && k<labArray.count) {
        for (int i = 0; i<labArray.count; i++) {
            if (i==k) {
                UILabel *lab = labArray[k];
                lab.font = [UIFont systemFontOfSize:40.0];
            }else{
                UILabel *lab = labArray[I];
                lab.font = [UIFont systemFontOfSize:25.0];
            }
        }
    }
}

-(void)shaker:(UIScrollView *)scrollView withShake:(BOOL)shake{
    CGFloat hight = [_datasourse pickerView:self setHightForCell:scrollView.tag indexPath:0];
    int k = scrollView.contentOffset.y/hight;
    if (oldIndex != k) {
        oldIndex = k;
        AudioServicesPlaySystemSoundWithCompletion(1157, nil);
        if (shake) {
            if (@available(iOS 10.0, *)) {
                UIImpactFeedbackGenerator *impactFeedBack = [[UIImpactFeedbackGenerator alloc] initWithStyle:UIImpactFeedbackStyleHeavy];
                [impactFeedBack prepare];
                [impactFeedBack impactOccurred];
            }
        }
        
    }
}



-(NSArray *)didSelectWithScroller{
    NSMutableArray *newItem = [NSMutableArray new];
    for (int i = 0;i<scrViewArray.count;i++){
        UIScrollView *scrollView = scrViewArray[I];
        NSArray *labArray = numberArray[scrollView.tag];
        CGFloat hight = [_datasourse pickerView:self setHightForCell:scrollView.tag indexPath:0];
        int k = scrollView.contentOffset.y/hight;
        if (k>-1 && k<labArray.count) {
            UILabel *lab = labArray[k];
            [newItem addObject:lab.text];
        }
    }
    return newItem;
}

為了讓他看起來(lái)更像是漸入漸出的效果竞端,于是我這里給他添加了遮罩屎即,通過(guò)這樣的方式來(lái)模仿UIDatePicker。

#pragma mark /// draw
-(void)drawRect:(CGRect)rect{
    CAGradientLayer *gradient = [CAGradientLayer layer];
    gradient.frame = self.bounds;
    gradient.colors = [NSArray arrayWithObjects:
                       (id)[UIColor colorWithRed:1 green:1 blue:1 alpha:1.0].CGColor,
                       (id)[UIColor colorWithRed:1 green:1 blue:1.0 alpha:0.0].CGColor,
                       (id)[UIColor colorWithRed:1 green:1 blue:1 alpha:1.0].CGColor, nil];
    [self.layer addSublayer:gradient];
}

@end

最后事富,調(diào)用起來(lái)也是參考著UITableView的實(shí)現(xiàn)技俐,更貼近系統(tǒng)的思想:

//
//  ViewController.m
//  PickerView
//
//  Created by JLee on 2020/7/14.
//  Copyright ? 2020 Eziochan. All rights reserved.
//

#import "ViewController.h"
#import "ECPickerView.h"


@interface ViewController ()<ECPickerViewDataSourse,ECPickerViewDelegate>{
    ECPickerView *pickerView;
    NSArray *array1;
    NSArray *array2;
}

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    
    NSMutableArray *ar = [NSMutableArray new];
     NSMutableArray *ar1 = [NSMutableArray new];
     for (int i = 0; i<60; i++) {
         NSString *str;
         if (i<10) {
             str = [NSString stringWithFormat:@"0%d",I];
         }else{
             str = [NSString stringWithFormat:@"%d",I];
         }
         [ar addObject:str];
         if (i<24) {
             [ar1 addObject:str];
         }
     }
     array1 = ar;
     array2 = ar1;
    
    
    pickerView = [[ECPickerView alloc] initWithFrame:CGRectMake(self.view.frame.size.width/4, 100, self.view.frame.size.width/2, 200)];
    pickerView.datasourse = self;
    pickerView.delegate = self;
    [self.view addSubview:pickerView];
}

-(NSInteger)numberOfItemsInSection:(ECPickerView *)view{
    return 2;
}
-(NSArray *)pickerView:(ECPickerView *)view withSection:(NSInteger)section{
    if (section == 0) {
        return array2;
    }else{
        return array1;
    }
}
-(UIView *)pickerView:(ECPickerView *)view withSection:(NSInteger)section indexPath:(NSInteger)index{
        NSString *str1;
      if (section == 0) {
          str1 = array2[index];
      }else{
          str1 = array1[index];
      }
      CGFloat width = view.frame.size.width/2;
      UILabel *lab = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, width, 40)];
      lab.text = str1;
      lab.font = [UIFont systemFontOfSize:25];
      lab.textColor = [UIColor blackColor];
      lab.textAlignment = NSTextAlignmentCenter;
      return lab;
}

-(CGFloat)pickerView:(ECPickerView *)view setHightForCell:(NSInteger)section indexPath:(NSInteger)index{
    return 40;
}


-(void)cpickerViewMoveToItem:(NSArray *)selectArray{
    NSLog(@"%s selectArray:%@ ",__func__,selectArray);
}
-(void)cpickerViewMoveToItemEndAnimation:(NSArray *)selectArray{
    NSLog(@"%s selectArray:%@ ",__func__,selectArray);
}

@end

寫在最后:

相對(duì)系統(tǒng)的UIPickerView,這里還差一個(gè)滾輪式淡出的效果统台,這里的遮罩效果只是個(gè)贗品雕擂,寫在這里也是想著能拋轉(zhuǎn)引玉,希望能引來(lái)其它同行的指導(dǎo)意見(jiàn)饺谬,謝謝捂刺。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市募寨,隨后出現(xiàn)的幾起案子族展,更是在濱河造成了極大的恐慌,老刑警劉巖拔鹰,帶你破解...
    沈念sama閱讀 221,198評(píng)論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件仪缸,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡列肢,警方通過(guò)查閱死者的電腦和手機(jī)恰画,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,334評(píng)論 3 398
  • 文/潘曉璐 我一進(jìn)店門宾茂,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人拴还,你說(shuō)我怎么就攤上這事跨晴。” “怎么了片林?”我有些...
    開(kāi)封第一講書人閱讀 167,643評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵端盆,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我费封,道長(zhǎng)焕妙,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 59,495評(píng)論 1 296
  • 正文 為了忘掉前任弓摘,我火速辦了婚禮焚鹊,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘韧献。我一直安慰自己末患,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,502評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布势决。 她就那樣靜靜地躺著阻塑,像睡著了一般蓝撇。 火紅的嫁衣襯著肌膚如雪果复。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 52,156評(píng)論 1 308
  • 那天渤昌,我揣著相機(jī)與錄音虽抄,去河邊找鬼。 笑死独柑,一個(gè)胖子當(dāng)著我的面吹牛迈窟,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播忌栅,決...
    沈念sama閱讀 40,743評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼车酣,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了索绪?” 一聲冷哼從身側(cè)響起湖员,我...
    開(kāi)封第一講書人閱讀 39,659評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎瑞驱,沒(méi)想到半個(gè)月后娘摔,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,200評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡唤反,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,282評(píng)論 3 340
  • 正文 我和宋清朗相戀三年凳寺,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了鸭津。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,424評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡肠缨,死狀恐怖逆趋,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情晒奕,我是刑警寧澤父泳,帶...
    沈念sama閱讀 36,107評(píng)論 5 349
  • 正文 年R本政府宣布,位于F島的核電站吴汪,受9級(jí)特大地震影響惠窄,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜漾橙,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,789評(píng)論 3 333
  • 文/蒙蒙 一杆融、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧霜运,春花似錦脾歇、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 32,264評(píng)論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至焦除,卻和暖如春激况,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背膘魄。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 33,390評(píng)論 1 271
  • 我被黑心中介騙來(lái)泰國(guó)打工乌逐, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人创葡。 一個(gè)月前我還...
    沈念sama閱讀 48,798評(píng)論 3 376
  • 正文 我出身青樓浙踢,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親灿渴。 傳聞我的和親對(duì)象是個(gè)殘疾皇子洛波,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,435評(píng)論 2 359