如何将侦听器添加到 UIControl 的子类
How to add a listener to a subclass of UIControl
为了自定义原生 iOS 日期选择器(UIDatePicker)的外观,我在 GitHub 上找到了一段代码。 (参考:https://github.com/xiaosao6/CCDatePicker)
现在我想为选择器添加一个事件侦听器。 selected 日期将更新为 UILabel。
我怎样才能像 UIDatePicker 那样做:
picker.addTarget(self, action: #selector(timeChanged(ccPicker:)), for: .valueChanged)
@objc func timeChanged(datePicker: UIDatePicker) {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = NumberUtils.format["time"]
label.text = dateFormatter.string(from: datePicker.date)
}
这是我的做法:
let picker = CCDatePicker.init(minDate: minDate, maxDate: maxDate)!
picker.frame = CGRect(x: 0, y: 0, width: pickerContainer.bounds.width, height: pickerContainer.bounds.height)
picker.isUserInteractionEnabled = true
picker.addTarget(self, action: #selector(timeChanged(ccPicker:)), for: .valueChanged)
pickerContainer.addSubview(picker)
@objc func timeChanged(ccPicker: CCDatePicker) {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = NumberUtils.format["date"]
label.text = ccPicker.date
}
但是,当我在 CCDatePicker 实例中 select 一个新行时,此函数 timeChanged(ccPicker: CCDatePicker)
从未被调用。
如何将 .valueChanged
的事件侦听器添加到我的自定义日期选择器?
//
// CCDatePicker.swift
// test
//
// Created by RenYuan on 5/11/19.
// Copyright © 2019 RenYuan. All rights reserved.
//
import UIKit
protocol CCDatePickerDelegate: class {
func didSelectDate(at picker: CCDatePicker)
}
protocol CCDatePickerDataSource: class {
func datepicker(_ picker: CCDatePicker, numberOfRowsInComponent component: Int) -> Int
func datepicker(_ picker: CCDatePicker, intValueForRow row: Int, forComponent component: Int) -> Int
}
class CCDatePicker: UIControl {
weak var delegate: CCDatePickerDelegate?
weak var dataSource: CCDatePickerDataSource?
let pickerDataSize = 120_000 // for loop loading
/// 单位字符
// var unitName: (year: String?, month: String?, day: String?) = ("年", "月", "日")
var unitName: (year: String?, month: String?, day: String?) = ("", "", "")
/// 标题字体
var titleFont = UIFont.systemFont(ofSize: 20)
/// 标题颜色
var titleColor = UIColor.white
/// 中心行高
var rowHeight: CGFloat = 45
/// 分割线颜色
var separatorColor = UIColor.black {
didSet{
setNeedsLayout()
layoutIfNeeded()
}
}
/// 当前选择的日期
var currentDate: Date {
let year = currentYearInt()
let month = currentMonthInt()
let day = currentDayInt()
let date = Date.cc_defaultFormatter.date(from: "\(month)-\(day)-\(year)")
return date!
}
fileprivate let componentCount = 3
fileprivate var manager: CCDateManager?
fileprivate lazy var pickerview: UIPickerView = {
let tmpv = UIPickerView.init()
tmpv.delegate = self
tmpv.dataSource = self
return tmpv
}()
required init?(frame: CGRect = .zero, minDate: Date, maxDate: Date) {
super.init(frame: frame)
if minDate.compare(maxDate) == .orderedDescending {
return nil
}
manager = CCDateManager.init(minDate: minDate, maxDate: maxDate)
manager?.delegate = self
self.dataSource = manager
pickerview.frame = frame
self.addSubview(pickerview)
self.isUserInteractionEnabled = true
self.isEnabled = true
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder) // 暂不支持xib
}
override func addTarget(_ target: Any?, action: Selector, for controlEvents: UIControl.Event) {
super.addTarget(target, action: action, for: controlEvents)
if controlEvents == .valueChanged {
print("123")
print(action.description)
}
}
}
//MARK: ------------------------ Public
extension CCDatePicker {
/// 设置日期,例如`"2007-8-20"`或`"2007-11-9"`
func setDate(_ dateString: String, animated: Bool = false) {
guard let date = Date.cc_defaultFormatter.date(from: dateString) else { return }
setDate(date, animated: animated)
}
func setDate(_ date: Date, animated: Bool = false) {
let rowInfo = manager?.setDate(date)
if let info = rowInfo {
pickerview.selectRow(info.mRow, inComponent: 0, animated: animated)
pickerview.selectRow(info.dRow, inComponent: 1, animated: animated)
pickerview.selectRow(info.yRow, inComponent: 2, animated: animated)
pickerview.reloadAllComponents()
}
}
}
//MARK: ------------------------ Private
extension CCDatePicker{
/// 分割线views
fileprivate var separatorLines: [UIView] {
return pickerview.subviews.filter {
[=14=].bounds.height < 1.0 && [=14=].bounds.width == pickerview.bounds.width
}
}
override func layoutSubviews() {
super.layoutSubviews()
pickerview.frame = self.bounds
separatorLines.forEach { [=14=].backgroundColor = separatorColor }
}
}
extension CCDatePicker: CCDateSelectionDelegate {
func currentMonthInt() -> Int {
let row = pickerview.selectedRow(inComponent: 0)
let attrStr = self.pickerView(pickerview, attributedTitleForRow: row, forComponent: 0)
let value: Int = attrStr?.string.getInt() ?? 1
return value
}
func currentDayInt() -> Int {
let row = pickerview.selectedRow(inComponent: 1)
let attrStr = self.pickerView(pickerview, attributedTitleForRow: row, forComponent: 1)
let value: Int = attrStr?.string.getInt() ?? 1
return value
}
func currentYearInt() -> Int {
let row = pickerview.selectedRow(inComponent: 2)
let attrStr = self.pickerView(pickerview, attributedTitleForRow: row, forComponent: 2)
let value: Int = attrStr?.string.getInt() ?? 1
return value
}
}
extension CCDatePicker: UIPickerViewDelegate{
func pickerView(_ pickerView: UIPickerView, widthForComponent component: Int) -> CGFloat {
switch component {
case 0: return pickerView.bounds.width * 0.5 * (1 - 0.45) // 根据字符宽度测得比例
case 1: return pickerView.bounds.width * 0.5 * (1 - 0.45)
case 2: return pickerView.bounds.width * 0.45
default: return 0
}
}
func pickerView(_ pickerView: UIPickerView, rowHeightForComponent component: Int) -> CGFloat {
return rowHeight
}
func pickerView(_ pickerView: UIPickerView, attributedTitleForRow row: Int, forComponent component: Int) -> NSAttributedString? {
var ostr = ""
let intValue = self.dataSource?.datepicker(self, intValueForRow: row, forComponent: component) ?? 1
switch component {
case 0: ostr = String(intValue) + (unitName.month ?? "")
case 1: ostr = String(intValue) + (unitName.day ?? "")
case 2: ostr = String(intValue) + (unitName.year ?? "")
default: break
}
let attStr = NSMutableAttributedString(string: ostr)
attStr.addAttributes([.foregroundColor: titleColor, .font: titleFont], range: NSMakeRange(0, ostr.count))
return attStr
}
func pickerView(_ pickerView: UIPickerView, viewForRow row: Int, forComponent component: Int, reusing view: UIView?) -> UIView {
let attrText = self.pickerView(pickerView, attributedTitleForRow: row, forComponent: component)
if let label = view as? UILabel {
label.attributedText = attrText
return label
}
let newlabel = UILabel.init()
newlabel.backgroundColor = UIColor.clear
newlabel.textAlignment = .center
newlabel.attributedText = attrText
return newlabel
}
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
switch component {
case 0: // month
let position = pickerDataSize / 2 + row % (manager?.months_available.count)!
pickerView.selectRow(position, inComponent: 0, animated: false)
// call the funtion to reload the days_available array accodrig to the diffrent moth
// so that the position will fall into the right day
if let dRow = manager?.onMonthRefreshed() {
pickerView.reloadComponent(1)
let day_position = dRow % (manager?.days_available.count)!
pickerview.selectRow(day_position, inComponent: 1, animated: false)
self.pickerView(pickerView, didSelectRow: day_position, inComponent: 1)
}
case 1: // day
self.delegate?.didSelectDate(at: self)
case 2: // year
if let mRow = manager?.onYearRefreshed(){
pickerView.reloadComponent(0)
pickerview.selectRow(mRow, inComponent: 0, animated: false)
self.pickerView(pickerView, didSelectRow: mRow, inComponent: 0)
}
default: break
}
}
}
extension CCDatePicker: UIPickerViewDataSource{
func numberOfComponents(in pickerView: UIPickerView) -> Int { return componentCount }
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
let rowCount = self.dataSource?.datepicker(self, numberOfRowsInComponent: component) ?? 0
return rowCount
}
}
extension String {
fileprivate func getInt() -> Int {
let scanner = Scanner(string: self)
scanner.scanUpToCharacters(from: CharacterSet.decimalDigits, into: nil)
var number: Int = 0
scanner.scanInt(&number)
return number
}
}
protocol CCDateSelectionDelegate: class {
func currentYearInt() -> Int
func currentMonthInt() -> Int
func currentDayInt() -> Int
}
/// 日期数据管理类
class CCDateManager {
fileprivate lazy var months_: [Int] = {
var arr = [Int]()
for i in 1...12 { arr.append(i) }
return arr
}()
fileprivate lazy var days_: [Int] = {
var arr = [Int]()
for i in 1...31 { arr.append(i) }
return arr
}()
/// 最小的日期
fileprivate let minDate: Date
/// 最大的日期
fileprivate let maxDate: Date
fileprivate var months_available :[Int]
fileprivate var days_available :[Int]
weak var delegate: CCDateSelectionDelegate?
init(minDate: Date, maxDate: Date) {
self.minDate = minDate
self.maxDate = maxDate
self.months_available = []
self.days_available = []
}
}
extension CCDateManager {
@discardableResult
func setDate(_ date: Date) -> (yRow: Int, mRow: Int, dRow: Int)? {
if date.compare(minDate) == .orderedAscending || date.compare(maxDate) == .orderedDescending {
NSLog("指定日期超过了可选范围")
return nil
}
let result = refreshCurrent(year: date.year, month: date.month, day: date.day)
return result
}
/// 更新`年`的选择,返回新的`月`index
func onYearRefreshed() -> Int {
let year = self.delegate?.currentYearInt() ?? 1
let month = self.delegate?.currentMonthInt() ?? 1
handleRefreshMonthsOf(year: year)
var mRow = months_available.index(of: month) ?? 0
if let monthLast = months_available.last, let monthFirst = months_available.first {
if month < monthFirst {
mRow = 0
} else if month > monthLast {
mRow = months_available.count - 1
}
}
return mRow
}
/// 更新`月`的选择,返回新的`日`index
func onMonthRefreshed() -> Int {
let year = self.delegate?.currentYearInt() ?? 1
let month = self.delegate?.currentMonthInt() ?? 1
let day = self.delegate?.currentDayInt() ?? 1
handleRefreshDaysOf(year: year, month: month)
var dRow = days_available.index(of: day) ?? 0
if let dayLast = days_available.last, let dayFirst = days_available.first {
if day < dayFirst {
dRow = 0
} else if day > dayLast {
dRow = days_available.count - 1
}
}
return dRow
}
}
extension CCDateManager {
fileprivate func refreshCurrent(year: Int, month: Int, day: Int) -> (yRow: Int, mRow: Int, dRow: Int) {
handleRefreshMonthsOf(year: year)
handleRefreshDaysOf(year: year, month: month)
var mRow = months_available.index(of: month) ?? 0
if let monthLast = months_available.last, let monthFirst = months_available.first {
if month < monthFirst {
mRow = 0
} else if month > monthLast {
mRow = months_available.count - 1
}
}
var dRow = days_available.index(of: day) ?? 0
if let dayLast = days_available.last, let dayFirst = days_available.first {
if day < dayFirst {
dRow = 0
} else if day > dayLast {
dRow = days_available.count - 1
}
}
let yRow = year - minDate.year
return (yRow, mRow, dRow)
}
/// 处理`月`范围
fileprivate func handleRefreshMonthsOf(year: Int) {
if (maxDate.year == minDate.year) {
months_available = months_.filter({ [=14=] >= minDate.month && [=14=] <= maxDate.month })
} else {
if year == minDate.year {
months_available = months_.filter({ [=14=] >= minDate.month })
} else if year == maxDate.year {
months_available = months_.filter({ [=14=] <= maxDate.month })
} else {
months_available = months_
}
}
}
/// 处理`日`范围
fileprivate func handleRefreshDaysOf(year: Int, month: Int) {
let fullDays = Date.fullDaysOf(year: year, month: month)
if (maxDate.year == minDate.year) {
if (maxDate.month == minDate.month){
days_available = days_.filter({ [=14=] >= minDate.day && [=14=] <= maxDate.day })
} else {
if (month == minDate.month) {
days_available = days_.filter({ [=14=] >= minDate.day && [=14=] <= fullDays })
} else if (month == maxDate.month) {
days_available = days_.filter({ [=14=] <= maxDate.day })
} else {
days_available = days_.filter({ [=14=] <= fullDays })
}
}
} else {
if year == minDate.year {
if month == minDate.month {
days_available = days_.filter({ [=14=] >= minDate.day && [=14=] <= fullDays })
} else {
days_available = days_.filter({ [=14=] <= fullDays })
}
} else if year == maxDate.year {
if month == maxDate.month {
days_available = days_.filter({ [=14=] <= maxDate.day })
} else {
days_available = days_.filter({ [=14=] <= fullDays })
}
} else {
days_available = days_.filter({ [=14=] <= fullDays })
}
}
}
}
extension CCDateManager {
fileprivate func numberOfRowsInComponent(_ component: Int) -> Int {
switch component {
case 0:
// return months_available.count
return 120_000 // for loop loading
case 1:
// return days_available.count
return 120_000 // for loop loading
case 2:
return (maxDate.year - minDate.year) + 1
default: return 0
}
}
fileprivate func intValueForRow(row: Int, forComponent component: Int) -> Int{
switch component {
case 0:
return months_available[row % months_available.count]
case 1:
return days_available[row % days_available.count ]
case 2:
return minDate.year + row
default: return 1
}
}
}
extension CCDateManager: CCDatePickerDataSource {
func datepicker(_ picker: CCDatePicker, numberOfRowsInComponent component: Int) -> Int {
return self.numberOfRowsInComponent(component)
}
func datepicker(_ picker: CCDatePicker, intValueForRow row: Int, forComponent component: Int) -> Int {
return self.intValueForRow(row: row, forComponent: component)
}
}
extension Date {
static var cc_defaultFormatter: DateFormatter {
return self.dateFormatterWith("MM-dd-yyyy")
}
/// 自定义时间格式的格式化器
fileprivate static func dateFormatterWith(_ formatString: String) -> DateFormatter {
let threadDic = Thread.current.threadDictionary
if let fmt = threadDic.object(forKey: formatString) as? DateFormatter {
return fmt
}
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = formatString
threadDic.setObject(dateFormatter, forKey: formatString as NSCopying)
return dateFormatter
}
/// 指定年月的天数
fileprivate static func fullDaysOf(year: Int, month: Int) -> Int {
if [1, 3, 5, 7, 8, 10, 12].contains(month) { return 31 }
if [4, 6, 9, 11].contains(month) { return 30 }
let isLeapYear = (year % 4 == 0 && year % 100 != 0) || year % 400 == 0
return isLeapYear ? 29 : 28 // 二月
}
fileprivate var year: Int {
return NSCalendar.current.component(.year, from: self)
}
fileprivate var month: Int {
return NSCalendar.current.component(.month, from: self)
}
fileprivate var day: Int {
return NSCalendar.current.component(.day, from: self)
}
}
您希望自定义控件在用户在内部选择器视图中选择一行时发出 valueChanged
事件。
添加行:
sendActions(for: .valueChanged)
您希望您的自定义控件在任何时候告诉所有已注册的目标值已更改。至少这将在您的 pickerView:didSelectRow
委托方法的实现中。
一些注意事项:
- 除调试目的外,您无需在自定义控件中重写
addTarget
。
- 奇怪的是,由于您的自定义控件提供了
CCDatePickerDelegate
委托协议,因此您想使用 addTarget
在值更改时收到通知。
为了自定义原生 iOS 日期选择器(UIDatePicker)的外观,我在 GitHub 上找到了一段代码。 (参考:https://github.com/xiaosao6/CCDatePicker)
现在我想为选择器添加一个事件侦听器。 selected 日期将更新为 UILabel。
我怎样才能像 UIDatePicker 那样做:
picker.addTarget(self, action: #selector(timeChanged(ccPicker:)), for: .valueChanged)
@objc func timeChanged(datePicker: UIDatePicker) {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = NumberUtils.format["time"]
label.text = dateFormatter.string(from: datePicker.date)
}
这是我的做法:
let picker = CCDatePicker.init(minDate: minDate, maxDate: maxDate)!
picker.frame = CGRect(x: 0, y: 0, width: pickerContainer.bounds.width, height: pickerContainer.bounds.height)
picker.isUserInteractionEnabled = true
picker.addTarget(self, action: #selector(timeChanged(ccPicker:)), for: .valueChanged)
pickerContainer.addSubview(picker)
@objc func timeChanged(ccPicker: CCDatePicker) {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = NumberUtils.format["date"]
label.text = ccPicker.date
}
但是,当我在 CCDatePicker 实例中 select 一个新行时,此函数 timeChanged(ccPicker: CCDatePicker)
从未被调用。
如何将 .valueChanged
的事件侦听器添加到我的自定义日期选择器?
//
// CCDatePicker.swift
// test
//
// Created by RenYuan on 5/11/19.
// Copyright © 2019 RenYuan. All rights reserved.
//
import UIKit
protocol CCDatePickerDelegate: class {
func didSelectDate(at picker: CCDatePicker)
}
protocol CCDatePickerDataSource: class {
func datepicker(_ picker: CCDatePicker, numberOfRowsInComponent component: Int) -> Int
func datepicker(_ picker: CCDatePicker, intValueForRow row: Int, forComponent component: Int) -> Int
}
class CCDatePicker: UIControl {
weak var delegate: CCDatePickerDelegate?
weak var dataSource: CCDatePickerDataSource?
let pickerDataSize = 120_000 // for loop loading
/// 单位字符
// var unitName: (year: String?, month: String?, day: String?) = ("年", "月", "日")
var unitName: (year: String?, month: String?, day: String?) = ("", "", "")
/// 标题字体
var titleFont = UIFont.systemFont(ofSize: 20)
/// 标题颜色
var titleColor = UIColor.white
/// 中心行高
var rowHeight: CGFloat = 45
/// 分割线颜色
var separatorColor = UIColor.black {
didSet{
setNeedsLayout()
layoutIfNeeded()
}
}
/// 当前选择的日期
var currentDate: Date {
let year = currentYearInt()
let month = currentMonthInt()
let day = currentDayInt()
let date = Date.cc_defaultFormatter.date(from: "\(month)-\(day)-\(year)")
return date!
}
fileprivate let componentCount = 3
fileprivate var manager: CCDateManager?
fileprivate lazy var pickerview: UIPickerView = {
let tmpv = UIPickerView.init()
tmpv.delegate = self
tmpv.dataSource = self
return tmpv
}()
required init?(frame: CGRect = .zero, minDate: Date, maxDate: Date) {
super.init(frame: frame)
if minDate.compare(maxDate) == .orderedDescending {
return nil
}
manager = CCDateManager.init(minDate: minDate, maxDate: maxDate)
manager?.delegate = self
self.dataSource = manager
pickerview.frame = frame
self.addSubview(pickerview)
self.isUserInteractionEnabled = true
self.isEnabled = true
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder) // 暂不支持xib
}
override func addTarget(_ target: Any?, action: Selector, for controlEvents: UIControl.Event) {
super.addTarget(target, action: action, for: controlEvents)
if controlEvents == .valueChanged {
print("123")
print(action.description)
}
}
}
//MARK: ------------------------ Public
extension CCDatePicker {
/// 设置日期,例如`"2007-8-20"`或`"2007-11-9"`
func setDate(_ dateString: String, animated: Bool = false) {
guard let date = Date.cc_defaultFormatter.date(from: dateString) else { return }
setDate(date, animated: animated)
}
func setDate(_ date: Date, animated: Bool = false) {
let rowInfo = manager?.setDate(date)
if let info = rowInfo {
pickerview.selectRow(info.mRow, inComponent: 0, animated: animated)
pickerview.selectRow(info.dRow, inComponent: 1, animated: animated)
pickerview.selectRow(info.yRow, inComponent: 2, animated: animated)
pickerview.reloadAllComponents()
}
}
}
//MARK: ------------------------ Private
extension CCDatePicker{
/// 分割线views
fileprivate var separatorLines: [UIView] {
return pickerview.subviews.filter {
[=14=].bounds.height < 1.0 && [=14=].bounds.width == pickerview.bounds.width
}
}
override func layoutSubviews() {
super.layoutSubviews()
pickerview.frame = self.bounds
separatorLines.forEach { [=14=].backgroundColor = separatorColor }
}
}
extension CCDatePicker: CCDateSelectionDelegate {
func currentMonthInt() -> Int {
let row = pickerview.selectedRow(inComponent: 0)
let attrStr = self.pickerView(pickerview, attributedTitleForRow: row, forComponent: 0)
let value: Int = attrStr?.string.getInt() ?? 1
return value
}
func currentDayInt() -> Int {
let row = pickerview.selectedRow(inComponent: 1)
let attrStr = self.pickerView(pickerview, attributedTitleForRow: row, forComponent: 1)
let value: Int = attrStr?.string.getInt() ?? 1
return value
}
func currentYearInt() -> Int {
let row = pickerview.selectedRow(inComponent: 2)
let attrStr = self.pickerView(pickerview, attributedTitleForRow: row, forComponent: 2)
let value: Int = attrStr?.string.getInt() ?? 1
return value
}
}
extension CCDatePicker: UIPickerViewDelegate{
func pickerView(_ pickerView: UIPickerView, widthForComponent component: Int) -> CGFloat {
switch component {
case 0: return pickerView.bounds.width * 0.5 * (1 - 0.45) // 根据字符宽度测得比例
case 1: return pickerView.bounds.width * 0.5 * (1 - 0.45)
case 2: return pickerView.bounds.width * 0.45
default: return 0
}
}
func pickerView(_ pickerView: UIPickerView, rowHeightForComponent component: Int) -> CGFloat {
return rowHeight
}
func pickerView(_ pickerView: UIPickerView, attributedTitleForRow row: Int, forComponent component: Int) -> NSAttributedString? {
var ostr = ""
let intValue = self.dataSource?.datepicker(self, intValueForRow: row, forComponent: component) ?? 1
switch component {
case 0: ostr = String(intValue) + (unitName.month ?? "")
case 1: ostr = String(intValue) + (unitName.day ?? "")
case 2: ostr = String(intValue) + (unitName.year ?? "")
default: break
}
let attStr = NSMutableAttributedString(string: ostr)
attStr.addAttributes([.foregroundColor: titleColor, .font: titleFont], range: NSMakeRange(0, ostr.count))
return attStr
}
func pickerView(_ pickerView: UIPickerView, viewForRow row: Int, forComponent component: Int, reusing view: UIView?) -> UIView {
let attrText = self.pickerView(pickerView, attributedTitleForRow: row, forComponent: component)
if let label = view as? UILabel {
label.attributedText = attrText
return label
}
let newlabel = UILabel.init()
newlabel.backgroundColor = UIColor.clear
newlabel.textAlignment = .center
newlabel.attributedText = attrText
return newlabel
}
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
switch component {
case 0: // month
let position = pickerDataSize / 2 + row % (manager?.months_available.count)!
pickerView.selectRow(position, inComponent: 0, animated: false)
// call the funtion to reload the days_available array accodrig to the diffrent moth
// so that the position will fall into the right day
if let dRow = manager?.onMonthRefreshed() {
pickerView.reloadComponent(1)
let day_position = dRow % (manager?.days_available.count)!
pickerview.selectRow(day_position, inComponent: 1, animated: false)
self.pickerView(pickerView, didSelectRow: day_position, inComponent: 1)
}
case 1: // day
self.delegate?.didSelectDate(at: self)
case 2: // year
if let mRow = manager?.onYearRefreshed(){
pickerView.reloadComponent(0)
pickerview.selectRow(mRow, inComponent: 0, animated: false)
self.pickerView(pickerView, didSelectRow: mRow, inComponent: 0)
}
default: break
}
}
}
extension CCDatePicker: UIPickerViewDataSource{
func numberOfComponents(in pickerView: UIPickerView) -> Int { return componentCount }
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
let rowCount = self.dataSource?.datepicker(self, numberOfRowsInComponent: component) ?? 0
return rowCount
}
}
extension String {
fileprivate func getInt() -> Int {
let scanner = Scanner(string: self)
scanner.scanUpToCharacters(from: CharacterSet.decimalDigits, into: nil)
var number: Int = 0
scanner.scanInt(&number)
return number
}
}
protocol CCDateSelectionDelegate: class {
func currentYearInt() -> Int
func currentMonthInt() -> Int
func currentDayInt() -> Int
}
/// 日期数据管理类
class CCDateManager {
fileprivate lazy var months_: [Int] = {
var arr = [Int]()
for i in 1...12 { arr.append(i) }
return arr
}()
fileprivate lazy var days_: [Int] = {
var arr = [Int]()
for i in 1...31 { arr.append(i) }
return arr
}()
/// 最小的日期
fileprivate let minDate: Date
/// 最大的日期
fileprivate let maxDate: Date
fileprivate var months_available :[Int]
fileprivate var days_available :[Int]
weak var delegate: CCDateSelectionDelegate?
init(minDate: Date, maxDate: Date) {
self.minDate = minDate
self.maxDate = maxDate
self.months_available = []
self.days_available = []
}
}
extension CCDateManager {
@discardableResult
func setDate(_ date: Date) -> (yRow: Int, mRow: Int, dRow: Int)? {
if date.compare(minDate) == .orderedAscending || date.compare(maxDate) == .orderedDescending {
NSLog("指定日期超过了可选范围")
return nil
}
let result = refreshCurrent(year: date.year, month: date.month, day: date.day)
return result
}
/// 更新`年`的选择,返回新的`月`index
func onYearRefreshed() -> Int {
let year = self.delegate?.currentYearInt() ?? 1
let month = self.delegate?.currentMonthInt() ?? 1
handleRefreshMonthsOf(year: year)
var mRow = months_available.index(of: month) ?? 0
if let monthLast = months_available.last, let monthFirst = months_available.first {
if month < monthFirst {
mRow = 0
} else if month > monthLast {
mRow = months_available.count - 1
}
}
return mRow
}
/// 更新`月`的选择,返回新的`日`index
func onMonthRefreshed() -> Int {
let year = self.delegate?.currentYearInt() ?? 1
let month = self.delegate?.currentMonthInt() ?? 1
let day = self.delegate?.currentDayInt() ?? 1
handleRefreshDaysOf(year: year, month: month)
var dRow = days_available.index(of: day) ?? 0
if let dayLast = days_available.last, let dayFirst = days_available.first {
if day < dayFirst {
dRow = 0
} else if day > dayLast {
dRow = days_available.count - 1
}
}
return dRow
}
}
extension CCDateManager {
fileprivate func refreshCurrent(year: Int, month: Int, day: Int) -> (yRow: Int, mRow: Int, dRow: Int) {
handleRefreshMonthsOf(year: year)
handleRefreshDaysOf(year: year, month: month)
var mRow = months_available.index(of: month) ?? 0
if let monthLast = months_available.last, let monthFirst = months_available.first {
if month < monthFirst {
mRow = 0
} else if month > monthLast {
mRow = months_available.count - 1
}
}
var dRow = days_available.index(of: day) ?? 0
if let dayLast = days_available.last, let dayFirst = days_available.first {
if day < dayFirst {
dRow = 0
} else if day > dayLast {
dRow = days_available.count - 1
}
}
let yRow = year - minDate.year
return (yRow, mRow, dRow)
}
/// 处理`月`范围
fileprivate func handleRefreshMonthsOf(year: Int) {
if (maxDate.year == minDate.year) {
months_available = months_.filter({ [=14=] >= minDate.month && [=14=] <= maxDate.month })
} else {
if year == minDate.year {
months_available = months_.filter({ [=14=] >= minDate.month })
} else if year == maxDate.year {
months_available = months_.filter({ [=14=] <= maxDate.month })
} else {
months_available = months_
}
}
}
/// 处理`日`范围
fileprivate func handleRefreshDaysOf(year: Int, month: Int) {
let fullDays = Date.fullDaysOf(year: year, month: month)
if (maxDate.year == minDate.year) {
if (maxDate.month == minDate.month){
days_available = days_.filter({ [=14=] >= minDate.day && [=14=] <= maxDate.day })
} else {
if (month == minDate.month) {
days_available = days_.filter({ [=14=] >= minDate.day && [=14=] <= fullDays })
} else if (month == maxDate.month) {
days_available = days_.filter({ [=14=] <= maxDate.day })
} else {
days_available = days_.filter({ [=14=] <= fullDays })
}
}
} else {
if year == minDate.year {
if month == minDate.month {
days_available = days_.filter({ [=14=] >= minDate.day && [=14=] <= fullDays })
} else {
days_available = days_.filter({ [=14=] <= fullDays })
}
} else if year == maxDate.year {
if month == maxDate.month {
days_available = days_.filter({ [=14=] <= maxDate.day })
} else {
days_available = days_.filter({ [=14=] <= fullDays })
}
} else {
days_available = days_.filter({ [=14=] <= fullDays })
}
}
}
}
extension CCDateManager {
fileprivate func numberOfRowsInComponent(_ component: Int) -> Int {
switch component {
case 0:
// return months_available.count
return 120_000 // for loop loading
case 1:
// return days_available.count
return 120_000 // for loop loading
case 2:
return (maxDate.year - minDate.year) + 1
default: return 0
}
}
fileprivate func intValueForRow(row: Int, forComponent component: Int) -> Int{
switch component {
case 0:
return months_available[row % months_available.count]
case 1:
return days_available[row % days_available.count ]
case 2:
return minDate.year + row
default: return 1
}
}
}
extension CCDateManager: CCDatePickerDataSource {
func datepicker(_ picker: CCDatePicker, numberOfRowsInComponent component: Int) -> Int {
return self.numberOfRowsInComponent(component)
}
func datepicker(_ picker: CCDatePicker, intValueForRow row: Int, forComponent component: Int) -> Int {
return self.intValueForRow(row: row, forComponent: component)
}
}
extension Date {
static var cc_defaultFormatter: DateFormatter {
return self.dateFormatterWith("MM-dd-yyyy")
}
/// 自定义时间格式的格式化器
fileprivate static func dateFormatterWith(_ formatString: String) -> DateFormatter {
let threadDic = Thread.current.threadDictionary
if let fmt = threadDic.object(forKey: formatString) as? DateFormatter {
return fmt
}
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = formatString
threadDic.setObject(dateFormatter, forKey: formatString as NSCopying)
return dateFormatter
}
/// 指定年月的天数
fileprivate static func fullDaysOf(year: Int, month: Int) -> Int {
if [1, 3, 5, 7, 8, 10, 12].contains(month) { return 31 }
if [4, 6, 9, 11].contains(month) { return 30 }
let isLeapYear = (year % 4 == 0 && year % 100 != 0) || year % 400 == 0
return isLeapYear ? 29 : 28 // 二月
}
fileprivate var year: Int {
return NSCalendar.current.component(.year, from: self)
}
fileprivate var month: Int {
return NSCalendar.current.component(.month, from: self)
}
fileprivate var day: Int {
return NSCalendar.current.component(.day, from: self)
}
}
您希望自定义控件在用户在内部选择器视图中选择一行时发出 valueChanged
事件。
添加行:
sendActions(for: .valueChanged)
您希望您的自定义控件在任何时候告诉所有已注册的目标值已更改。至少这将在您的 pickerView:didSelectRow
委托方法的实现中。
一些注意事项:
- 除调试目的外,您无需在自定义控件中重写
addTarget
。 - 奇怪的是,由于您的自定义控件提供了
CCDatePickerDelegate
委托协议,因此您想使用addTarget
在值更改时收到通知。