// // QDatePicker.swift // O2Platform // 来自GitHub上的https://github.com/qyfeng009/QTimePicker // // Created by FancyLou on 2019/5/7. // Copyright © 2019 zoneland. All rights reserved. // import UIKit private let screenWidth = UIScreen.main.bounds.width private let screenHeight = UIScreen.main.bounds.height private let keyWindow = UIApplication.shared.keyWindow private let yearLH: CGFloat = 226.0 private let sureVH: CGFloat = 44.0 private let margin: CGFloat = 10.0 ///日期滚动选择器 可以选择 年月日时分、年月日、月日时分、选择 月日、选择 时分 class QDatePicker: UIView, UIGestureRecognizerDelegate, UIPickerViewDelegate, UIPickerViewDataSource { enum QPickerStyle { case datePicker // 时间选择器 case singlePicker // 单项选择器 } enum DatePickerStyle { // 此只在 datePicker 下生效 case YMDHM // 选择 年月日时分 case YMD // 选择 年月日 case MDHM // 选择 月日时分 case MD // 选择 月日 case HM // 选择 时分 } enum AminationStyle { case styleDefault // 默认弹出样式 case styleOptional // 可选弹出样式 } typealias DidSelectedDate = (_ date: String) -> Void ///< 定义确认回调 private var selectedBack: DidSelectedDate? var sureView: UIButton! var yearL: UILabel! open var pickerStyle: QPickerStyle = .datePicker ///< 选择样式 默认 datePicker open var datePickerStyle: DatePickerStyle = .MDHM ///< 日期选择样式 默认 MDHM,只在 datePicker 样式下 open var animationStyle: AminationStyle = .styleDefault { ///< 弹出动画样式 默认 styleDefault didSet { if animationStyle == .styleOptional { yearL.y = -yearLH yearL.transform = CGAffineTransform.identity sureView.y = screenHeight sureView.transform = CGAffineTransform.identity } } } open var themeColor: UIColor? = UIColor.hexInt(0x00B3C4) { ///< 主题颜色样式 didSet { sureView.backgroundColor = themeColor } } open var singlePickerDatas: [String] = [] { didSet { singleSelectedData = singlePickerDatas[0] } } // 单项选择器数据源 private var singleSelectedData: String = "" private lazy var pickerView: UIPickerView = { let pickerView = UIPickerView(frame: CGRect(x: 0, y: 0, width: yearL.width, height: yearL.height)) pickerView.backgroundColor = .clear pickerView.showsSelectionIndicator = true pickerView.delegate = self pickerView.dataSource = self return pickerView }() init(selectedDate: @escaping DidSelectedDate) { super.init(frame: UIScreen.main.bounds) selectedBack = selectedDate let tapGesture = UITapGestureRecognizer(target: self, action: #selector(close)) tapGesture.delegate = self addGestureRecognizer(tapGesture) backgroundColor = .clear yearL = UILabel(frame: CGRect(x: margin, y: screenHeight, width: screenWidth - margin * 2, height: yearLH)) yearL.transform = CGAffineTransform.identity yearL.isUserInteractionEnabled = true yearL.backgroundColor = .white yearL.textColor = UIColor.hexInt(0xE9ECF2) yearL.textAlignment = NSTextAlignment.center yearL.font = UIFont.systemFont(ofSize: 110) yearL.adjustsFontSizeToFitWidth = true addSubview(yearL) yearL.roundedCorners(cornerRadius: 10, rectCorner: UIRectCorner([.topLeft, .topRight])) sureView = UIButton(type: UIButton.ButtonType.system) sureView.frame = CGRect(x: margin, y: yearL.y + yearL.height, width: yearL.width, height: sureVH) sureView.transform = CGAffineTransform.identity sureView.setTitle("确认", for: UIControl.State.normal) sureView.setTitleColor(.white, for: UIControl.State.normal) sureView.backgroundColor = themeColor sureView.addTarget(self, action: #selector(sureDate(_:)), for: UIControl.Event.touchUpInside) addSubview(sureView) sureView.roundedCorners(cornerRadius: 10, rectCorner: UIRectCorner([.bottomLeft, .bottomRight])) if pickerStyle == .datePicker { self.initDefaultDate() } } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } /// 弹出 Picker datePicker类型 可以定义默认显示的时间 open func showDatePicker(defaultDate: Date = Date()) { // if self.pickerStyle != .datePicker { // self.show() // return // } keyWindow?.addSubview(self) keyWindow?.bringSubviewToFront(self) var duraton = 0.4 if animationStyle == .styleDefault { var y = -(margin + yearLH + sureVH) if screenHeight == 812 { y -= 34 duraton = 0.5 } UIView.animate(withDuration: duraton, delay: 0, usingSpringWithDamping: 0.6, initialSpringVelocity: 0, options: UIView.AnimationOptions([.curveEaseInOut, .beginFromCurrentState, .layoutSubviews]), animations: { self.backgroundColor = UIColor.black.withAlphaComponent(0.4) self.sureView.transform = CGAffineTransform(translationX: 0, y: y) }) UIView.animate(withDuration: duraton, delay: 0, usingSpringWithDamping: 0.4, initialSpringVelocity: 0, options: UIView.AnimationOptions([.curveEaseInOut, .beginFromCurrentState, .layoutSubviews]), animations: { self.yearL.transform = CGAffineTransform(translationX: 0, y: y) }, completion: { (finish: Bool) in self.yearL.addSubview(self.pickerView) if self.pickerStyle == .datePicker { self.resetDateShow(date: Date()) } }) } else { UIView.animate(withDuration: duraton, delay: 0, usingSpringWithDamping: 0.5, initialSpringVelocity: 0, options: UIView.AnimationOptions([.curveEaseInOut, .beginFromCurrentState, .layoutSubviews]), animations: { self.backgroundColor = UIColor.black.withAlphaComponent(0.4) self.sureView.transform = CGAffineTransform(translationX: 0, y: -sureVH-(screenHeight/2 - (yearLH + sureVH)/2)) }) UIView.animate(withDuration: duraton, delay: 0, usingSpringWithDamping: 0.5, initialSpringVelocity: 0, options: UIView.AnimationOptions([.curveEaseInOut, .beginFromCurrentState, .layoutSubviews]), animations: { self.yearL.transform = CGAffineTransform(translationX: 0, y: (yearLH + screenHeight/2-(yearLH + sureVH)/2)) }, completion: { (finish: Bool) in self.yearL.addSubview(self.pickerView) if self.pickerStyle == .datePicker { self.resetDateShow(date: Date()) } }) } } /// 弹出 Picker // open func show() { // keyWindow?.addSubview(self) // keyWindow?.bringSubviewToFront(self) // if animationStyle == .styleDefault { // var duraton = 0.8 // var y = -(margin + yearLH + sureVH) // if screenHeight == 812 { // y -= 34 // duraton = 1.0 // } // UIView.animate(withDuration: duraton, delay: 0, usingSpringWithDamping: 0.6, initialSpringVelocity: 0, options: UIView.AnimationOptions([.curveEaseInOut, .beginFromCurrentState, .layoutSubviews]), animations: { // self.backgroundColor = UIColor.black.withAlphaComponent(0.4) // self.sureView.transform = CGAffineTransform(translationX: 0, y: y) // }) // UIView.animate(withDuration: duraton, delay: 0, usingSpringWithDamping: 0.4, initialSpringVelocity: 0, options: UIView.AnimationOptions([.curveEaseInOut, .beginFromCurrentState, .layoutSubviews]), animations: { // self.yearL.transform = CGAffineTransform(translationX: 0, y: y) // }, completion: { (finish: Bool) in // self.yearL.addSubview(self.pickerView) // if self.pickerStyle == .datePicker { // self.resetDateShow(date: Date()) // } // }) // } else { // UIView.animate(withDuration: 0.8, delay: 0, usingSpringWithDamping: 0.5, initialSpringVelocity: 0, options: UIView.AnimationOptions([.curveEaseInOut, .beginFromCurrentState, .layoutSubviews]), animations: { // self.backgroundColor = UIColor.black.withAlphaComponent(0.4) // self.sureView.transform = CGAffineTransform(translationX: 0, y: -sureVH-(screenHeight/2 - (yearLH + sureVH)/2)) // }) // UIView.animate(withDuration: 0.8, delay: 0, usingSpringWithDamping: 0.5, initialSpringVelocity: 0, options: UIView.AnimationOptions([.curveEaseInOut, .beginFromCurrentState, .layoutSubviews]), animations: { // self.yearL.transform = CGAffineTransform(translationX: 0, y: (yearLH + screenHeight/2-(yearLH + sureVH)/2)) // }, completion: { (finish: Bool) in // self.yearL.addSubview(self.pickerView) // if self.pickerStyle == .datePicker { // self.resetDateShow(date: Date()) // } // }) // } // } @objc private func close() { if animationStyle == .styleDefault { let ani = CAKeyframeAnimation(keyPath: "transform.translation.y") let currentTy = yearL.transform.ty ani.duration = 0.3 ani.values = [currentTy, currentTy - 20, currentTy] ani.keyTimes = [0, 0.2, 0.1] ani.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut) yearL.layer.add(ani, forKey: "kViewShakerAnimationKey") var duraton = 0.21 var y = -(margin + yearLH + sureVH) + 20 if screenHeight == 812 { y -= 34 duraton = 0.27 } UIView.animate(withDuration: 0.3, delay: 0.3, usingSpringWithDamping: 0.3, initialSpringVelocity: 21, options: UIView.AnimationOptions([.curveEaseInOut, .beginFromCurrentState, .layoutSubviews]), animations: { self.yearL.transform = CGAffineTransform(translationX: 0, y: y) }) UIView.animate(withDuration: duraton, delay: 0.36, animations: { self.sureView.transform = CGAffineTransform.identity self.backgroundColor = .clear }) { (finish: Bool) in self.removeFromSuperview() } } else { UIView.animate(withDuration: 0.21, animations: { self.yearL.transform = CGAffineTransform.identity self.sureView.transform = CGAffineTransform.identity self.backgroundColor = .clear }, completion: { (finish: Bool) in self.removeFromSuperview() }) } } @objc private func sureDate(_ button: UIButton) { var string = "" if pickerStyle == .datePicker { switch datePickerStyle { case .YMDHM: string = intentDate.formatterDate(formatter: "yyyy-MM-dd HH:mm") case .YMD: string = intentDate.formatterDate(formatter: "yyyy-MM-dd") case .MDHM: string = intentDate.formatterDate(formatter: "yyyy-MM-dd HH:mm") case .MD: string = intentDate.formatterDate(formatter: "yyyy-MM-dd") case .HM: string = intentDate.formatterDate(formatter: "yyyy-MM-dd HH:mm") } } if pickerStyle == .singlePicker { string = singleSelectedData } selectedBack?(string) close() } func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool { if touch.view != self { return false } return true } // MARK: - 配置滚轴 func numberOfComponents(in pickerView: UIPickerView) -> Int { var num = 0 if pickerStyle == .datePicker { switch datePickerStyle { case .YMDHM: num = 5 case .YMD: num = 3 case .MDHM: num = 4 case .MD: num = 2 case .HM: num = 2 } } if pickerStyle == .singlePicker { num = 1 } return num } func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int { if pickerStyle == .datePicker { return getNumberOfRowsInComponent()[component] } else { return singlePickerDatas.count } } func pickerView(_ pickerView: UIPickerView, rowHeightForComponent component: Int) -> CGFloat { return 44 } func pickerView(_ pickerView: UIPickerView, viewForRow row: Int, forComponent component: Int, reusing view: UIView?) -> UIView { let label = UILabel() label.textAlignment = NSTextAlignment.center var title = "" if pickerStyle == .datePicker { switch datePickerStyle { case .YMDHM: addLabel(tags: ["年", "月", "日", "时", "分"]) if component == 0 { title = String(format: "%d", yearArray[row]) } if component == 1 { title = String(format: "%02d", monthArray[row]) } if component == 2 { title = String(format: "%02d", dayArray[row]) } if component == 3 { title = String(format: "%02d", hourArray[row]) } if component == 4 { title = String(format: "%02d", minuteArray[row]) } case .YMD: addLabel(tags: ["年", "月", "日"]) if component == 0 { title = String(format: "%d", yearArray[row]) } if component == 1 { title = String(format: "%02d", monthArray[row]) } if component == 2 { title = String(format: "%02d", dayArray[row]) } case .MDHM: addLabel(tags: ["月", "日", "时", "分"]) if component == 0 { title = String(format: "%02d", monthArray[row%12]) } if component == 1 { title = String(format: "%02d", dayArray[row]) } if component == 2 { title = String(format: "%02d", hourArray[row]) } if component == 3 { title = String(format: "%02d", minuteArray[row]) } case .MD: addLabel(tags: ["月", "日"]) if component == 0 { title = String(format: "%02d", monthArray[row%12]) } if component == 1 { title = String(format: "%02d", dayArray[row]) } case .HM: addLabel(tags: ["时", "分"]) if component == 0 { title = String(format: "%02d", hourArray[row]) } if component == 1 { title = String(format: "%02d", minuteArray[row]) } } } if pickerStyle == .singlePicker { title = singlePickerDatas[row] let view = UIView(frame: CGRect(x: 10, y: yearL.height/2 + 10, width: pickerView.width - 20, height: 0.5)) view.backgroundColor = themeColor yearL.addSubview(view) } label.text = title return label } func addLabel(tags: [String]) { for subView in yearL.subviews { if subView is UILabel { subView.removeFromSuperview() } } let cellW: CGFloat = pickerView.width/CGFloat(tags.count) for i in 0.. 0 && monthArray[monthIndex] < monthArray[supMonthIndex%12] { yearIndex += 1 } else if (supMonthIndex - row) < 12 && (supMonthIndex - row) > 0 && monthArray[monthIndex] > monthArray[supMonthIndex%12] { yearIndex -= 1 } else { let interval = (row - supMonthIndex)/12 yearIndex += interval } yearL.text = "\(yearArray[yearIndex])" supMonthIndex = row } // MARK: - 重置 Date 数据 func resetDateShow(date: Date) { yearIndex = date.year - minYear monthIndex = date.month - 1 dayIndex = date.day - 1 hourIndex = date.hour minuteIndex = date.minute supMonthIndex = (intentDate.year - minYear)*12 + (intentDate.month - 1) var indexArray = [Int]() if datePickerStyle == .YMDHM { indexArray = [yearIndex, monthIndex, dayIndex, hourIndex, minuteIndex] } if datePickerStyle == .YMD { indexArray = [yearIndex, monthIndex, dayIndex] } if datePickerStyle == .MDHM { indexArray = [monthIndex, dayIndex, hourIndex, minuteIndex] } if datePickerStyle == .MD { indexArray = [monthIndex, dayIndex] } if datePickerStyle == .HM { indexArray = [hourIndex, minuteIndex] } if datePickerStyle == .HM { yearL.text = "\(Date().year)" + " " + "\(Date().month)" + " " + "\(Date().day)" } else { yearL.text = "\(yearArray[yearIndex])" } for i in 0.. 0 && i <= 12 { monthArray.append(i) } if i < 24 { hourArray.append(i) } minuteArray.append(i) } for i in minYear..([.year, .month, .day, .hour, .minute]) let comps = caleddar.dateComponents(component, from: Date()) let year = comps.year if maxLimitDate == nil { maxLimitDate = String(format: "%ld-12-31 23:59", year! + 10).toDate(formatter: "yyyy-MM-dd HH:mm") } if minLimitDate == nil { minLimitDate = String(format: "%ld-01-01 00:00", year! - 10).toDate(formatter: "yyyy-MM-dd HH:mm") } } func getNumberOfRowsInComponent() -> [NSInteger] { dayArray = daysInTheDate(year: yearArray[yearIndex], month: monthArray[monthIndex]) let timeInterval = maxYear - minYear var rows = [NSInteger]() switch datePickerStyle { case .YMDHM: rows = [yearArray.count, monthArray.count, dayArray.count, hourArray.count, minuteArray.count] case .YMD: rows = [yearArray.count, monthArray.count, dayArray.count] case .MDHM: rows = [monthArray.count*timeInterval, dayArray.count, hourArray.count, minuteArray.count] case .MD: rows = [monthArray.count*timeInterval, dayArray.count] case .HM: rows = [hourArray.count, minuteArray.count] } return rows } func daysInTheDate(year: NSInteger, month: NSInteger) -> [Int] { let isLeapYear = year%4==0 ? (year%100==0 ? (year%400==0 ? true : false) : true) : false var days = 0 switch month { case 1, 3, 5, 7, 8, 10, 12: days = 31 case 4, 6, 9, 11: days = 30 case 2: if isLeapYear { days = 29 } else { days = 28 } default: break } var array = [Int]() for i in 1...days { array.append(i) } return array } }