123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704 |
- //
- // DatePickerView.swift
- // DemoSwift
- //
- // Created by yaoxinpan on 2018/5/28.
- // Copyright © 2018年 yaoxp. All rights reserved.
- //
- import UIKit
- protocol NibLoadable {}
- extension NibLoadable {
- static func loadViewFromNib() -> Self {
- return Bundle.main.loadNibNamed("\(self)", owner: nil, options: nil)?.last as! Self
- }
- }
- enum DateStyle {
- case all // 年月日时分秒
- case yearMonthDay // 年月日
- case yearMonth // 年月
- case hourMinuteSecond // 时分秒
- case yearMonthDayHourMinute // 年月日时分
- }
- class DatePickerView: UIView, NibLoadable, UIGestureRecognizerDelegate, UIPickerViewDelegate, UIPickerViewDataSource {
- // MARK: - 公开属性
-
- /// 最小的时间,默认是1970/1/1 00:00:00
- var minLimitDate: Date = Date(timeIntervalSince1970: 0) {
- didSet {
-
- if maxLimitDate < minLimitDate {
-
- minLimitDate = oldValue
-
- } else {
- if minLimitDate > scrollToDate {
-
- scrollToDate = minLimitDate
-
- }
- }
- }
- }
-
- /// 最大的时间,默认是当前时间10年后
- var maxLimitDate: Date = Date(timeIntervalSinceNow: 60 * 60 * 24 * 365 * 10) {
- didSet {
- if maxLimitDate < minLimitDate {
-
- maxLimitDate = oldValue
-
- } else {
-
- if maxLimitDate < scrollToDate {
-
- scrollToDate = maxLimitDate
-
- }
- }
- }
- }
-
- /// 默认显示的当前时间
- var scrollToDate: Date = Date() {
- didSet {
-
- if maxLimitDate < scrollToDate {
-
- scrollToDate = maxLimitDate
-
- }
-
- if minLimitDate > scrollToDate {
-
- scrollToDate = minLimitDate
-
- }
- }
- }
-
- /// 确定按钮背景色,默认蓝色
- var sureButtonBackgroundColor: UIColor? {
- didSet {
- sureButton.backgroundColor = sureButtonBackgroundColor
- }
- }
-
- /// 确定按钮字体颜色,默认白色
- var sureButtonTitleColor: UIColor? {
- didSet {
- sureButton.setTitleColor(sureButtonTitleColor, for: .normal)
- }
- }
-
- /// 年 背景水印,设置无色可以隐藏
- var yearPlaceholderColor: UIColor? {
- didSet {
- yearLabel.textColor = yearPlaceholderColor
- }
- }
-
- // MARK: - 私有属性
-
- @IBOutlet private weak var bottomView: UIView!
- @IBOutlet private weak var sureButton: UIButton!
- @IBOutlet private weak var yearLabel: UILabel!
- @IBOutlet private weak var bottomViewBottom: NSLayoutConstraint!
- @IBOutlet private weak var pickerView: UIPickerView!
-
- private var componentsArray: Array<Dictionary> = [Dictionary<String, Array<Int>>]()
-
- /// 显示的时间样式
- private var style: DateStyle = .all
-
- private let yearKey = "年"
- private let monthKey = "月"
- private let dayKey = "日"
- private let hourKey = "时"
- private let minuteKey = "分"
- private let secondKey = "秒"
- private var completionHandler: (_ date: Date?) -> Void = {_ in }
-
- ///
- /// - Parameters:
- /// - style: 类型
- /// - scrollToDate: 滚动到的时间,如果不设刚是当前时间
- /// - Returns: 新的DatePickerView
- class func datePicker(style: DateStyle = .all, scrollToDate: Date = Date(), completionHandler: @escaping (_ date: Date?) -> Void) -> DatePickerView {
-
- let view: DatePickerView = DatePickerView.loadViewFromNib()
-
- view.style = style
-
- view.completionHandler = completionHandler
-
- view.scrollToDate = scrollToDate
-
- view.setupUI()
-
- view.setupData()
-
- return view
- }
-
- func show() {
-
- setupData()
-
- UIApplication.shared.keyWindow!.addSubview(self)
-
- self.frame = UIScreen.main.bounds
-
- UIView.animate(withDuration: 0.3, animations: {
-
- self.bottomViewBottom.constant = 0
-
- self.backgroundColor = UIColor.hexRGB(0x000000, 0.5)
-
- self.layoutIfNeeded()
- }, completion: { (finish) in
-
- self.scrollTo(self.scrollToDate)
-
- })
-
- }
-
- @objc func dismiss() {
- UIView.animate(withDuration: 0.3, animations: {
-
- self.bottomViewBottom.constant = self.bottomView.frame.size.height
-
- self.backgroundColor = UIColor.hexRGB(0x000000, 0.0)
-
- self.layoutIfNeeded()
-
- }, completion: { (finished) in
-
- self.removeFromSuperview()
-
- })
- }
-
- @IBAction private func onSureButtonAction(_ sender: Any) {
-
- completionHandler(currentDate())
-
- dismiss()
- }
- }
- extension DatePickerView {
- private func setupUI() {
- bottomViewBottom.constant = bottomView.frame.size.height
-
- backgroundColor = UIColor.hexRGB(0x000000, 0.0)
-
- let tap = UITapGestureRecognizer(target: self, action: #selector(dismiss))
- tap.delegate = self
- addGestureRecognizer(tap)
-
- pickerView.delegate = self
- pickerView.dataSource = self
-
- self.yearLabel.text = String(self.scrollToDate.year)
- }
-
- private func setupData() {
- componentsArray = [Dictionary]()
-
- switch style {
-
- case .all:
-
- initDataWithAllStyle()
-
- case .yearMonthDay:
-
- initDataWithYearMonthDayStyle()
-
- case .yearMonth:
-
- initDataWithYearMonthStyle()
-
- case .hourMinuteSecond:
-
- initDataWithHourMinuteSecondStyle()
-
- case .yearMonthDayHourMinute:
-
- initDataWithYearMonthDayHourMinuteStyle()
- }
- }
-
- private func currentDate() -> Date? {
- var result = [Int]()
-
- for index in 0..<pickerView.numberOfComponents {
- result.append(componentsArray[index].values.first![pickerView.selectedRow(inComponent: index)])
- }
-
- switch style {
-
- case .all:
-
- guard result.count == 6 else { return nil }
-
- let dateStr = String(result[0]) + "-" + String(result[1]) + "-" + String(result[2]) + " " +
- String(result[3]) + ":" + String(result[4]) + ":" + String(result[5])
-
- return Date.date(dateStr, formatter: "yyyy-MM-dd HH:mm:ss")
-
- case .yearMonthDay:
-
- guard result.count == 3 else { return nil }
-
- let dateStr = String(result[0]) + "-" + String(result[1]) + "-" + String(result[2])
-
- return Date.date(dateStr, formatter: "yyyy-MM-dd")
-
- case .yearMonth:
- guard result.count == 2 else { return nil }
-
- let dateStr = String(result[0]) + "-" + String(result[1])
-
- return Date.date(dateStr, formatter: "yyyy-MM")
- case .hourMinuteSecond:
-
- guard result.count == 3 else { return nil }
-
- let dateStr = String(result[0]) + ":" + String(result[1]) + ":" + String(result[2])
-
- return Date.date(dateStr, formatter: "HH:mm:ss")
-
- case .yearMonthDayHourMinute:
-
- guard result.count == 5 else { return nil }
-
- let dateStr = String(result[0]) + "-" + String(result[1]) + "-" + String(result[2]) + " " +
- String(result[3]) + ":" + String(result[4])
-
- return Date.date(dateStr, formatter: "yyyy-MM-dd HH:mm")
-
- }
-
- }
-
- }
- // MARK: - scroll to date
- extension DatePickerView {
- private func scrollTo(_ date: Date) {
-
- var scrollToDate = date
-
- if scrollToDate > maxLimitDate {
- scrollToDate = maxLimitDate
- }
-
- if scrollToDate < minLimitDate {
- scrollToDate = minLimitDate
- }
-
-
- switch style {
-
- case .all:
-
- allStyleScrollTo(scrollToDate)
-
- case .yearMonthDay:
-
- yearMonthDayStyleScrollTo(scrollToDate)
-
- case .yearMonth:
-
- yearMonthStyleScrollTo(scrollToDate)
-
- case .hourMinuteSecond:
-
- hourMinuteSecondStyleScrollTo(scrollToDate)
-
- case .yearMonthDayHourMinute:
-
- yearMonthDayHourMinuteStyleScrollTo(scrollToDate)
-
- }
- }
-
- private func allStyleScrollTo(_ date: Date) {
-
- guard componentsArray.count == 6 else {
- return
- }
-
- if let yearIndex = componentsArray[0][yearKey]?.firstIndex(of: date.year) {
- pickerView.selectRow(yearIndex, inComponent: 0, animated: true)
- }
-
- if let monthIndex = componentsArray[1][monthKey]?.firstIndex(of: date.month) {
- pickerView.selectRow(monthIndex, inComponent: 1, animated: true)
- }
-
- if let dayIndex = componentsArray[2][dayKey]?.firstIndex(of: date.day) {
- pickerView.selectRow(dayIndex, inComponent: 2, animated: true)
- }
-
- if let hourIndex = componentsArray[3][hourKey]?.firstIndex(of: date.hour) {
- pickerView.selectRow(hourIndex, inComponent: 3, animated: true)
- }
-
- if let minuteIndex = componentsArray[4][minuteKey]?.firstIndex(of: date.minute) {
- pickerView.selectRow(minuteIndex, inComponent: 4, animated: true)
- }
-
- if let secondIndex = componentsArray[5][secondKey]?.firstIndex(of: date.second) {
- pickerView.selectRow(secondIndex, inComponent: 5, animated: true)
- }
- }
-
- private func yearMonthDayStyleScrollTo(_ date: Date) {
-
- guard componentsArray.count == 3 else {
- return
- }
-
- if let yearIndex = componentsArray[0][yearKey]?.firstIndex(of: date.year) {
- pickerView.selectRow(yearIndex, inComponent: 0, animated: true)
- }
-
- if let monthIndex = componentsArray[1][monthKey]?.firstIndex(of: date.month) {
- pickerView.selectRow(monthIndex, inComponent: 1, animated: true)
- }
-
- if let dayIndex = componentsArray[2][dayKey]?.firstIndex(of: date.day) {
- pickerView.selectRow(dayIndex, inComponent: 2, animated: true)
- }
- }
-
- private func yearMonthStyleScrollTo(_ date: Date) {
-
- guard componentsArray.count == 2 else {
- return
- }
-
- if let yearIndex = componentsArray[0][yearKey]?.firstIndex(of: date.year) {
- pickerView.selectRow(yearIndex, inComponent: 0, animated: true)
- }
-
- if let monthIndex = componentsArray[1][monthKey]?.firstIndex(of: date.month) {
- pickerView.selectRow(monthIndex, inComponent: 1, animated: true)
- }
- }
-
- private func hourMinuteSecondStyleScrollTo(_ date: Date) {
-
- guard componentsArray.count == 3 else {
- return
- }
- if let hourIndex = componentsArray[0][hourKey]?.firstIndex(of: date.hour) {
- pickerView.selectRow(hourIndex, inComponent: 0, animated: true)
- }
-
- if let minuteIndex = componentsArray[1][minuteKey]?.firstIndex(of: date.minute) {
- pickerView.selectRow(minuteIndex, inComponent: 1, animated: true)
- }
-
- if let secondIndex = componentsArray[2][secondKey]?.firstIndex(of: date.second) {
- pickerView.selectRow(secondIndex, inComponent: 2, animated: true)
- }
- }
-
- private func yearMonthDayHourMinuteStyleScrollTo(_ date: Date) {
-
- guard componentsArray.count == 5 else {
- return
- }
-
- if let yearIndex = componentsArray[0][yearKey]?.firstIndex(of: date.year) {
- pickerView.selectRow(yearIndex, inComponent: 0, animated: true)
- }
-
- if let monthIndex = componentsArray[1][monthKey]?.firstIndex(of: date.month) {
- pickerView.selectRow(monthIndex, inComponent: 1, animated: true)
- }
-
- if let dayIndex = componentsArray[2][dayKey]?.firstIndex(of: date.day) {
- pickerView.selectRow(dayIndex, inComponent: 2, animated: true)
- }
-
- if let hourIndex = componentsArray[3][hourKey]?.firstIndex(of: date.hour) {
- pickerView.selectRow(hourIndex, inComponent: 3, animated: true)
- }
-
- if let minuteIndex = componentsArray[4][minuteKey]?.firstIndex(of: date.minute) {
- pickerView.selectRow(minuteIndex, inComponent: 4, animated: true)
- }
- }
- }
- // MARK: - init data
- extension DatePickerView {
- private func initDataWithAllStyle() {
- componentsArray.append([yearKey : Array(minLimitDate.year...maxLimitDate.year)])
-
- // if minLimitDate.haveSameYear(maxLimitDate) {
- // componentsArray.append([monthKey : Array(minLimitDate.month...maxLimitDate.month)])
- // } else {
- componentsArray.append([monthKey : Array(1...12)])
- // }
-
- // if minLimitDate.haveSameYearAndMonth(maxLimitDate) {
- // componentsArray.append([dayKey : Array(minLimitDate.day...maxLimitDate.day)])
- // } else {
- componentsArray.append([dayKey : Array(1...scrollToDate.numberOfDaysInMonth())])
- // }
-
- // if minLimitDate.haveSameYearMonthAndDay(maxLimitDate) {
- // componentsArray.append([hourKey : Array(minLimitDate.day...maxLimitDate.day)])
- // } else {
- componentsArray.append([hourKey : Array(0...23)])
- // }
-
- // if minLimitDate.haveSameYearMonthDayAndHour(maxLimitDate) {
- // componentsArray.append([minuteKey : Array(minLimitDate.minute...maxLimitDate.minute)])
- // } else {
- componentsArray.append([minuteKey : Array(0...59)])
- // }
-
- // if minLimitDate.haveSameYearMonthDayHourAndMinute(maxLimitDate) {
- // componentsArray.append([secondKey : Array(minLimitDate.minute...maxLimitDate.minute)])
- // } else {
- componentsArray.append([secondKey : Array(0...59)])
- // }
- }
-
- private func initDataWithYearMonthDayStyle() {
- componentsArray.append([yearKey : Array(minLimitDate.year...maxLimitDate.year)])
-
- // if minLimitDate.haveSameYear(maxLimitDate) {
- // componentsArray.append([monthKey : Array(minLimitDate.month...maxLimitDate.month)])
- // } else {
- componentsArray.append([monthKey : Array(1...12)])
- // }
-
- // if minLimitDate.haveSameYearAndMonth(maxLimitDate) {
- // componentsArray.append([dayKey : Array(minLimitDate.day...maxLimitDate.day)])
- // } else {
- componentsArray.append([dayKey : Array(1...scrollToDate.numberOfDaysInMonth())])
- // }
- }
-
- private func initDataWithYearMonthStyle() {
- componentsArray.append([yearKey : Array(minLimitDate.year...maxLimitDate.year)])
- componentsArray.append([monthKey : Array(1...12)])
- }
-
- private func initDataWithHourMinuteSecondStyle() {
-
- // if let date = Date.date("00:00:00", formatter: "HH:mm:ss") {
- // minLimitDate = date
- // }
- //
- // if let date = Date.date("23:59:59", formatter: "HH:mm:ss") {
- // maxLimitDate = date
- // }
-
- componentsArray.append([hourKey : Array(0...23)])
- // if minLimitDate.haveSameYearMonthDayAndHour(maxLimitDate) {
- // componentsArray.append([minuteKey : Array(minLimitDate.minute...maxLimitDate.minute)])
- // } else {
- componentsArray.append([minuteKey : Array(0...59)])
- // }
-
- // if minLimitDate.haveSameYearMonthDayHourAndMinute(maxLimitDate) {
- // componentsArray.append([secondKey : Array(minLimitDate.second...maxLimitDate.second)])
- // } else {
- componentsArray.append([secondKey : Array(0...59)])
- // }
- }
-
- private func initDataWithYearMonthDayHourMinuteStyle() {
- componentsArray.append([yearKey : Array(minLimitDate.year...maxLimitDate.year)])
-
- // if minLimitDate.haveSameYear(maxLimitDate) {
- // componentsArray.append([monthKey : Array(minLimitDate.month...maxLimitDate.month)])
- // } else {
- componentsArray.append([monthKey : Array(1...12)])
- // }
-
- // if minLimitDate.haveSameYearAndMonth(maxLimitDate) {
- // componentsArray.append([dayKey : Array(minLimitDate.day...maxLimitDate.day)])
- // } else {
- componentsArray.append([dayKey : Array(1...scrollToDate.numberOfDaysInMonth())])
- // }
-
- // if minLimitDate.haveSameYearMonthAndDay(maxLimitDate) {
- // componentsArray.append([hourKey : Array(minLimitDate.day...maxLimitDate.day)])
- // } else {
- componentsArray.append([hourKey : Array(0...23)])
- // }
-
- // if minLimitDate.haveSameYearMonthDayAndHour(maxLimitDate) {
- // componentsArray.append([minuteKey : Array(minLimitDate.minute...maxLimitDate.minute)])
- // } else {
- componentsArray.append([minuteKey : Array(0...59)])
- // }
- }
- }
- // MARK: - UIGestureRecognizerDelegate
- extension DatePickerView {
- func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
- guard let touchView = touch.view else { return false }
-
- if touchView.isDescendant(of: bottomView) {
- // 点击的view是否是bottomView或者bottomView的子视图
- return false
- }
-
- return true
- }
- }
- // MARK: - UIPickerViewDelegate, UIPickerViewDataSource
- extension DatePickerView {
- func numberOfComponents(in pickerView: UIPickerView) -> Int {
- return componentsArray.count
- }
-
- func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
- if let array = componentsArray[component].values.first {
- return array.count
- }
- return 0
- }
-
- func pickerView(_ pickerView: UIPickerView, rowHeightForComponent component: Int) -> CGFloat {
- return 40
- }
-
- func pickerView(_ pickerView: UIPickerView, viewForRow row: Int, forComponent component: Int, reusing view: UIView?) -> UIView {
-
- var title: String = " "
- let dic = componentsArray[component]
- if let key = dic.keys.first {
- let data = dic[key]
- title = String(data![row]) + key
- }
-
- if let label = view as? UILabel {
- label.text = title
- return label
- }
-
- let label = UILabel()
- label.textAlignment = .center
- label.font = UIFont.systemFont(ofSize: 17)
- label.text = title
- label.sizeToFit()
- return label
- }
-
- func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
- // 月分变化了,天数要跟着变化.年份变化了,如果是2月,天数也可能变化
-
- func scrollToValidTimeRange() {
- // 检查当前时间是否在最大和最小时间之前
- if let currentDate = currentDate() {
-
- if currentDate > maxLimitDate {
- // 超过最大值,则滚回最大值
- scrollTo(maxLimitDate)
-
- } else if currentDate < minLimitDate {
- // 小于最小值,刚滚回最小值
- scrollTo(minLimitDate)
- }
- }
- }
-
- guard let key = componentsArray[component].keys.first, key == yearKey || key == monthKey else {
- // 只是滚动年或者月时,才需要处理天数的变化
- scrollToValidTimeRange()
-
- return
- }
-
- let yearIndex = pickerView.selectedRow(inComponent: 0)
- if yearIndex < 0 {
- scrollToValidTimeRange()
-
- return
- }
-
- if key == yearKey {
- // 更新年份水印
- let year = componentsArray[0][yearKey]![yearIndex] // 年份
-
- yearLabel.text = String(year)
- }
-
- if key == yearKey && pickerView.selectedRow(inComponent: component + 1) != 1 {
- // 滚动年,月分不是2月时,不需要处理天数变化
- scrollToValidTimeRange()
-
- return
- }
-
-
- let monthIndex = pickerView.selectedRow(inComponent: 1)
- if monthIndex < 0 {
-
- scrollToValidTimeRange()
-
- return
- }
-
- let year = componentsArray[0][yearKey]![yearIndex] // 年份
- let month = componentsArray[1][monthKey]![monthIndex] // 月份
-
- guard let date = Date.date(String(year) + "-" + String(month), formatter: "yyyy-MM") else {
- scrollToValidTimeRange()
-
- return
-
- }
-
- if self.style == .yearMonth {
- return
- }
- let numberOfDays = date.numberOfDaysInMonth() // 当前月的天数
-
- if numberOfDays != 0 && numberOfDays == componentsArray[2][dayKey]!.count {
- // 天数没有变化,不需要任何操作
- scrollToValidTimeRange()
-
- return
- }
-
- let newDayDic = [dayKey : Array(1...numberOfDays)] // 新的天数数据
- componentsArray.replaceSubrange(2...2, with: [newDayDic])
-
- pickerView.reloadComponent(2)
-
- scrollToValidTimeRange()
- }
- }
|