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