偶然的在街上(虎扑)闲逛的时候,然后上看到了一个长得很好看的时间选择器,然后想着我也能实现一个类似的,于是就有了这篇文章。
先上个效果吧:
习惯上,涉及到自定义UI的时候,我们先去看看系统所提供的UI库到底满不满足自定义编写的需要,但十有八九都是不满足的。这次做的是一个时间选择器,于是我去翻阅了UIDatePicker
以及UIPickerView
,发现提供的借口不能满足实现这个UI,所以顺带看了一下UIScrollView 的接口,想起前一阵子遇到的自定义UICollectionView,尝试使用Delegate和DataSource的方式来实现这个功能。总的来说这里模仿UITableView的实现大于要实现一个选择器吧。
出于模仿UITableView,先定义一下DataSource所需要的内容如下:
@protocol ECPickerViewDataSourse
///返回有多少个列内容
-(NSInteger)numberOfItemsInSection:(ECPickerView *)view;
///对应每一列的行数组
-(NSArray *)pickerView:(ECPickerView *)view withSection:(NSInteger)section;
///对应每一行所显示的UI内容
-(UIView *)pickerView:(ECPickerView *)view withSection:(NSInteger)section indexPath:(NSInteger)index;
///返回实现行高度
-(CGFloat)pickerView:(ECPickerView *)view setHightForCell:(NSInteger)section indexPath:(NSInteger)index;
@end
接着定义一下Delegate的返回内容:
@protocol ECPickerViewDelegate
///返回滑动时的每一个行的内容,对应返回的是一组数组
-(void)cpickerViewMoveToItem:(NSArray *)selectArray;
///返回滑动结束的时候每一个行的内容,对应返回的是一组数组
-(void)cpickerViewMoveToItemEndAnimation:(NSArray *)selectArray;
@end
那么我们这个UIView所包含的属性应该是这样的:
@protocol ECPickerViewDataSourse,ECPickerViewDelegate;
@interface ECPickerView : UIView
///DataSourse
@property(nonatomic,unsafe_unretained)IBOutlet __nullable id datasourse;
///Delegate
@property(nonatomic,unsafe_unretained)IBOutlet __nullable id delegate;
///列总数
@property (nonatomic, readonly) NSInteger numberOfSections;
///由于这里自定义的是一个时间选择器,所以这里提供的是时间设置
-(void)scrollToDate:(NSDate *)date;
@end
那么接下来实现的首先是DataSource的处理咯,因为后续的初始化UI数据还是后来的刷新数据多是通过他来实现的,所以我们先重定义了DataSource的set方法和初始化UIView:
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
[self initWithData];
[self reloadData];
}
return self;
}
-(void)setDatasourse:(id)datasourse{
if (_datasourse != datasourse) {
_datasourse = datasourse;
if (_datasourse) {
[self reloadData];///刷新数据的显示
[self moveToNowTime];///移动到指定默认的时间戳位置
}
}
}
移动到指定的时间戳位置选取的方法如下:
-(void)scrollToDate:(NSDate *)date{
NSString *dateStr = [formatter stringFromDate:date];
NSArray *newArr = [dateStr componentsSeparatedByString:@":"];
for (int i = 0; i
初始化用来记录数据的数组
-(void)initWithData{
scrViewArray = [NSMutableArray new];
numberArray = [NSMutableArray new];
formatter = [[NSDateFormatter alloc] init];
formatter.dateFormat = @"HH:mm";
}
刷新数据,初始化数据的代码:
-(void)reloadData{
_numberOfSections = [_datasourse numberOfItemsInSection:self];///通过反向代理的方式来获取到外面DataSource所设置的值
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
为了让整个PickerView在用户操作的时候看起来更容易选择和更像系统所自带的UIDatePicker,所以这里需要利用好UIScrollView的代理,主要是滑动的起止以及惯性滑动这些。
-(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-1 && k
为了让他看起来更像是渐入渐出的效果,于是我这里给他添加了遮罩,通过这样的方式来模仿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
最后,调用起来也是参考着UITableView的实现,更贴近系统的思想:
//
// ViewController.m
// PickerView
//
// Created by JLee on 2020/7/14.
// Copyright © 2020 Eziochan. All rights reserved.
//
#import "ViewController.h"
#import "ECPickerView.h"
@interface ViewController (){
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
写在最后:
相对系统的UIPickerView,这里还差一个滚轮式淡出的效果,这里的遮罩效果只是个赝品,写在这里也是想着能抛转引玉,希望能引来其它同行的指导意见,谢谢。