在写项目时,个人资料生日这一块,想写成QQ中使用的效果。然后就学了一下UIPickerView。
系统其实提供了一个日期选择器UIDatePicker,但是它的实现效果并不是我想要的
所以我自定义选择器来实现一个日期选择器。
设计思路
为了在点击时,背景为灰色,所以我打算将一个背景为灰色的图层加到主页面上,点击确定、取消和空白区域可以移除。再向此图层上加一个contentView。这个contentView上放取消按钮和确认按钮以及选择器。
代码:
BasePickerView.h
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface BasePickerView : UIView
@property (nonatomic, strong) UIView* contentView;
//选择器
@property (nonatomic, strong)UIPickerView *pickerView;
//取消按钮
@property (nonatomic, strong)UIButton *cancelButton;
//确定按钮
@property (nonatomic, strong)UIButton *confirmButton;
//选择器的高度
@property (nonatomic, assign)CGFloat pickerViewHeight;
//创建视图,初始化视图时初始数据
- (void)initPickView;
//确认按钮的点击事件
- (void)clickConfirmButton;
//pickerView的显示
- (void)show;
//移除pickerView
- (void)disMiss;
@end
NS_ASSUME_NONNULL_END
//BasePickerView.m
#import "BasePickerView.h"
#define ScreenWidth [UIScreen mainScreen].bounds.size.width
#define ScreenHeight [UIScreen mainScreen].bounds.size.height
@implementation BasePickerView
- (instancetype)init {
self = [super init];
if(self) {
_pickerViewHeight = 250;
//设置此图层大小为主屏幕大小
self.bounds = [UIScreen mainScreen].bounds;
//设置背景为灰色
self.backgroundColor = [UIColor colorWithRed:0 green:0 blue:0 alpha:0.6];
//定义手势,在点击空白区域时,移除此图层
UITapGestureRecognizer *tap=[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(disMiss)];
self.userInteractionEnabled = YES;
[self addGestureRecognizer:tap];
//将内容视图加入此图层以及将选择器、确定按钮、取消按钮加入内容视图
[self addSubview:self.contentView];
[self.contentView addSubview:self.pickerView];
[self.contentView addSubview:self.cancelButton];
[self.contentView addSubview:self.confirmButton];
//以便在子类中重写此方法,将pickerView所需的数据初始化
[self initPickView];
}
return self;
}
- (void)initPickView {
}
//初始化内容视图
- (UIView *)contentView
{
if (!_contentView) {
_contentView = [[UIView alloc]initWithFrame:CGRectMake(0, ScreenHeight, ScreenWidth, self.pickerViewHeight)];
[_contentView setBackgroundColor:[UIColor whiteColor]];
}
return _contentView;
}
//初始化选择器
- (UIPickerView *)pickerView
{
if (!_pickerView) {
_pickerView = [[UIPickerView alloc]initWithFrame:CGRectMake(0, 0, self.contentView.frame.size.width, self.contentView.frame.size.height)];
[_pickerView setBackgroundColor:[UIColor whiteColor]];
}
return _pickerView;
}
//初始化取消按钮
- (UIButton *)cancelButton {
if (!_cancelButton) {
_cancelButton = [[UIButton alloc]initWithFrame:CGRectMake(16, 0, 44, 44)];
[_cancelButton setTitle:@"取消" forState:UIControlStateNormal];
[_cancelButton setTitleColor:[UIColor grayColor] forState:UIControlStateNormal];
[_cancelButton.titleLabel setFont:[UIFont systemFontOfSize:16]];
[_cancelButton addTarget:self action:@selector(clickCancelButton) forControlEvents:UIControlEventTouchUpInside];
}
return _cancelButton;
}
//初始化确定按钮
- (UIButton *)confirmButton {
if (!_confirmButton) {
_confirmButton = [[UIButton alloc]initWithFrame:CGRectMake(self.contentView.frame.size.width - self.cancelButton.frame.size.width - self.cancelButton.frame.origin.x, self.cancelButton.frame.origin.y, self.cancelButton.frame.size.width, self.cancelButton.frame.size.height)];
[_confirmButton setTitle:@"确定" forState:UIControlStateNormal];
[_confirmButton setTitleColor:[UIColor blueColor] forState:UIControlStateNormal];
[_confirmButton.titleLabel setFont:[UIFont systemFontOfSize:16]];
[_confirmButton addTarget:self action:@selector(clickConfirmButton) forControlEvents:UIControlEventTouchUpInside];
}
return _confirmButton;
}
//等待继承他的子类重写此方法
- (void)clickConfirmButton {
[self disMiss];
}
- (void)clickCancelButton {
[self disMiss];
}
//获取keyWindow
- (UIWindow *)getKeyWindow {
if (@available(iOS 13.0, *)) {
for (UIWindowScene* windowScene in [UIApplication sharedApplication].connectedScenes) {
if (windowScene.activationState == UISceneActivationStateForegroundActive) {
for (UIWindow *window in windowScene.windows) {
if (window.isKeyWindow)
{
return window;
break;
}
}
}
}
} else {
return [UIApplication sharedApplication].keyWindow;
}
return nil;
}
//移除此图层
- (void)disMiss {
CGRect frameContent = self.contentView.frame;
// 相当于把contentView的y坐标设置为ScreenHeight,达到一个隐藏的目的
frameContent.origin.y += self.contentView.frame.size.height;
//设置动画效果
[UIView animateWithDuration:0.3 delay:0 options:UIViewAnimationOptionCurveEaseOut animations:^{
//将此图层不透明性设为0
[self.layer setOpacity:0];
// 再将内容视图隐藏
self.contentView.frame = frameContent;
} completion:^(BOOL finished) {
//将此图层从父视图中移除掉
[self removeFromSuperview];
}];
}
//推出此图层
- (void)show {
[[self getKeyWindow] addSubview:self];
[self setCenter:[self getKeyWindow].center];
[[self getKeyWindow] bringSubviewToFront:self];
CGRect frameContent = self.contentView.frame;
frameContent.origin.y -= self.contentView.frame.size.height;
[UIView animateWithDuration:0.3 delay:0 options:UIViewAnimationOptionCurveEaseOut animations:^{
[self.layer setOpacity:1.0];
self.contentView.frame = frameContent;
} completion:nil];
}
@end
新建一个继承于BasePickerView的类,在这个类里实现UIPickerView数据源和代理方法,以及重写一些方法。
#import "BasePickerView.h"
NS_ASSUME_NONNULL_BEGIN
@class BasePickerView;
@protocol DatePickerViewDelegate<NSObject> //定义一个代理。
//此方法目的是得到当前选择器的选择结果
- (void)pickerDateView:(BasePickerView *)pickerDateView selectYear:(NSInteger)year selectMonth:(NSInteger)month selectDay:(NSInteger)day;
@end
@interface DatePickerView : BasePickerView
<UIPickerViewDelegate,
UIPickerViewDataSource>
@property(nonatomic, weak)id <DatePickerViewDelegate>delegate ;
/** 选择的年 */
@property (nonatomic, assign)NSInteger selectYear;
/** 选择的月 */
@property (nonatomic, assign)NSInteger selectMonth;
/** 选择的日 */
@property (nonatomic, assign)NSInteger selectDay;
//现在的年月日
@property (nonatomic, assign)NSInteger currentYear;
@property (nonatomic, assign)NSInteger currentMonth;
@property (nonatomic, assign)NSInteger currentDay;
//默认年月日
@property (nonatomic, assign)NSInteger defaultYear;
@property (nonatomic, assign)NSInteger defaultMonth;
@property (nonatomic, assign)NSInteger defaultDay;
//显示的最低年
@property (nonatomic, assign)NSInteger minShowYear;
@property (nonatomic, assign)NSInteger yearSum;
-(void)setDefaultTSelectYear:(NSInteger)defaultSelectYear defaultSelectMonth:(NSInteger)defaultSelectMonth defaultSelectDay:(NSInteger)defaultSelectDay;
@end
NS_ASSUME_NONNULL_END
#import "DatePickerView.h"
@implementation DatePickerView
- (void)initPickView {
[super initPickView];
//设置数据
_minShowYear = 1940;//最小年份
NSCalendar *calendar = [[NSCalendar alloc]
initWithCalendarIdentifier:NSCalendarIdentifierGregorian];
// 获取当前日期
NSDate* date = [NSDate date];
// 指定获取指定年、月、日、时、分、秒的信息
unsigned unitFlags = NSCalendarUnitYear |
NSCalendarUnitMonth | NSCalendarUnitDay |
NSCalendarUnitHour | NSCalendarUnitMinute |
NSCalendarUnitSecond | NSCalendarUnitWeekday;
// 获取不同时间字段的信息
NSDateComponents* comp = [calendar components:unitFlags fromDate:date];
_yearSum = comp.year-_minShowYear+1;
_currentYear = comp.year;
_currentMonth = comp.month;
_currentDay = comp.day;
_selectYear = comp.year;
_selectMonth = comp.month;
_selectDay = comp.day;
_defaultYear = comp.year;
_defaultMonth = comp.month;
_defaultDay = comp.day;
[self.pickerView setDelegate:self];
[self.pickerView setDataSource:self];
}
- (void)setDefaultTSelectYear:(NSInteger)defaultSelectYear defaultSelectMonth:(NSInteger)defaultSelectMonth defaultSelectDay:(NSInteger)defaultSelectDay {
if (defaultSelectYear != 0) {
_defaultYear=defaultSelectYear;
}
if (defaultSelectMonth != 0) {
_defaultMonth = defaultSelectMonth;
}
if (defaultSelectDay != 0) {
_defaultDay=defaultSelectDay;
}
if (defaultSelectYear == -1) {
_defaultYear=_currentYear+1;
_defaultMonth=1;
_defaultDay=1;
}
[self.pickerView selectRow:(_defaultYear - _minShowYear) inComponent:0 animated:NO];
[self.pickerView selectRow:(_defaultMonth - 1) inComponent:1 animated:NO];
[self.pickerView reloadComponent:1];
[self.pickerView selectRow:(_defaultDay-1) inComponent:2 animated:NO];
[self.pickerView reloadComponent:2];
[self refreshPickViewData];
}
//返回列数
- (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView {
return 3;
}
- (CGFloat)pickerView:(UIPickerView *)pickerView rowHeightForComponent:(NSInteger)component {
//每一行的高度
return 40;
}
//返回行数
- (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component {
if (component == 0) {
return self.yearSum;
} else if(component == 1) {
NSInteger yearSelected = [pickerView selectedRowInComponent:0] + self.minShowYear;
return 12;
} else {
NSInteger yearSelected = [pickerView selectedRowInComponent:0] + self.minShowYear;
NSInteger monthSelected = [pickerView selectedRowInComponent:1] + 1;
return [self getDaysWithYear:yearSelected month:monthSelected];
}
}
- (NSInteger)getDaysWithYear:(NSInteger)year month:(NSInteger)month {
switch (month) {
case 1:
return 31;
break;
case 2:
//分为闰年和平年
if (year % 400 == 0 || (year % 100 != 0 && year % 4 == 0)) {
return 29;
}else{
return 28;
}
break;
case 3:
return 31;
break;
case 4:
return 30;
break;
case 5:
return 31;
break;
case 6:
return 30;
break;
case 7:
return 31;
break;
case 8:
return 31;
break;
case 9:
return 30;
break;
case 10:
return 31;
break;
case 11:
return 30;
break;
case 12:
return 31;
break;
default:
return 0;
break;
}
}
- (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component {
NSInteger selectYear;
NSInteger selectMonth;
switch (component) {
case 0:
[pickerView reloadComponent:1];
selectYear = row + _minShowYear;
[pickerView reloadComponent:2];
break;
case 1:
selectMonth = row + 1;
[pickerView reloadComponent:2];
default:
break;
}
[self refreshPickViewData];
}
//更新数据
- (void)refreshPickViewData {
self.selectYear = [self.pickerView selectedRowInComponent:0] + self.minShowYear;
self.selectMonth = [self.pickerView selectedRowInComponent:1] + 1;
self.selectDay = [self.pickerView selectedRowInComponent:2] + 1;
}
- (UIView *)pickerView:(UIPickerView *)pickerView viewForRow:(NSInteger)row forComponent:(NSInteger)component reusingView:(nullable UIView *)view {
NSString *text;
if (component == 0) {
text = [NSString stringWithFormat:@"%zd年", row + _minShowYear];
} else if (component == 1) {
text = [NSString stringWithFormat:@"%zd月", row + 1];
} else {
text = [NSString stringWithFormat:@"%zd日", row + 1];
}
UILabel *label = [[UILabel alloc]init];
label.textAlignment = 1;
label.font = [UIFont systemFontOfSize:16];
label.text = text;
return label;
}
- (void)clickConfirmButton {
if ([self.delegate respondsToSelector:@selector(pickerDateView:selectYear:selectMonth:selectDay:)]) {
[self.delegate pickerDateView:self selectYear:self.selectYear selectMonth:self.selectMonth selectDay:self.selectDay];
}
//为了保留dismiss操作
[super clickConfirmButton];
}
@end
//viewController.m
#import <UIKit/UIKit.h>
#import "DatePickerView.h"
@interface ViewController : UIViewController
<DatePickerViewDelegate>
@end
//viewController.h
#import "ViewController.h"
@interface ViewController () {
UIButton* dateButton;
}
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
dateButton = [UIButton buttonWithType:UIButtonTypeCustom];
dateButton.frame = CGRectMake(50, 100, 200, 50);
[dateButton setTitle:@"选择日期" forState:UIControlStateNormal];
[self.view addSubview:dateButton];
[dateButton setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
[dateButton addTarget:self action:@selector(pickViewSelect) forControlEvents:UIControlEventTouchUpInside];
}
- (void)pickViewSelect {
DatePickerView* datePickerView = [[DatePickerView alloc] init];
datePickerView.delegate = self;
[datePickerView setDefaultTSelectYear:2001 defaultSelectMonth:3 defaultSelectDay:23];
[datePickerView show];
}
- (void)pickerDateView:(BasePickerView *)pickerDateView selectYear:(NSInteger)year selectMonth:(NSInteger)month selectDay:(NSInteger)day {
NSString* string = [NSString stringWithFormat:@"%ld年%ld月%ld日", year, month, day];
[dateButton setTitle:string forState:UIControlStateNormal];
}
@end
效果: