Last active
September 13, 2022 19:52
-
-
Save nunogoncalves/66ca6e31d29c27d05b397e0a22ab99fb to your computer and use it in GitHub Desktop.
iOS DatePicker with just month and year (Still work in progress)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// DatePicker.swift | |
// CreditCard | |
// | |
// Created by Nuno Gonçalves on 13/11/16. | |
// Copyright © 2016 Nuno Gonçalves. All rights reserved. | |
// | |
import UIKit | |
class DatePicker : UIControl { | |
fileprivate let earliestPresentedDate = Date(timeIntervalSince1970: 0) | |
fileprivate lazy var earliestComponents: DateComponents = { | |
return self.calendar.dateComponents([.year, .month], from: self.earliestPresentedDate) | |
}() | |
fileprivate lazy var earliestYear: Int = { | |
return self.calendar.dateComponents([.year], from: self.earliestPresentedDate).year! | |
}() | |
fileprivate lazy var earliestMonth: Int = { | |
return self.calendar.dateComponents([.month], from: self.earliestPresentedDate).month! | |
}() | |
let shortMonthformatter = DateFormatter(format: "MM") | |
let longMonthFormatter = DateFormatter(format: "MMMM") | |
fileprivate let yearMonthFormatter = DateFormatter(format: "yyyy-MM") | |
fileprivate var calendar: Calendar! | |
fileprivate var picker: UIPickerView! | |
fileprivate let monthComponent = 0 | |
fileprivate let yearComponent = 1 | |
fileprivate lazy var totalYears: Int = { | |
return 10000 - self.earliestYear | |
}() | |
fileprivate lazy var totalMonths: Int = { | |
return self.totalYears * 12 | |
}() | |
fileprivate (set) var date = Date() | |
var minimumDate: Date? | |
var maximumDate: Date? | |
override init(frame: CGRect) { | |
super.init(frame: frame) | |
configure() | |
} | |
required init?(coder aDecoder: NSCoder) { | |
super.init(coder: aDecoder) | |
configure() | |
} | |
func configure() { | |
backgroundColor = .lightGray | |
calendar = Calendar(identifier: .gregorian) | |
picker = UIPickerView() | |
addSubview(picker) | |
picker.topAnchor.constraint(equalTo: topAnchor).isActive = true | |
picker.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true | |
picker.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true | |
picker.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true | |
picker.translatesAutoresizingMaskIntoConstraints = false | |
picker.dataSource = self | |
picker.delegate = self | |
set(date, animated: true) | |
} | |
func set(_ date: Date, animated: Bool) { | |
let components: DateComponents = calendar.dateComponents([.year, .month], from: date) | |
set(year: components.year!, animated: animated) | |
set(month: components.month!, with: components.year!, animated: animated) | |
} | |
} | |
extension DatePicker : UIPickerViewDataSource { | |
func numberOfComponents(in pickerView: UIPickerView) -> Int { | |
return 2 | |
} | |
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int { | |
if component == yearComponent { | |
return totalYears | |
} else { | |
return totalMonths | |
} | |
} | |
func pickerView(_ pickerView: UIPickerView, viewForRow row: Int, forComponent component: Int, reusing view: UIView?) -> UIView { | |
let dateLabel = UILabel() | |
dateLabel.font = UIFont.systemFont(ofSize: 22) | |
dateLabel.textAlignment = .center | |
let dateForRow = date(for: row, and: component) | |
if component == monthComponent { | |
print("view: ", row, row % 12) | |
print(dateForRow) | |
} | |
dateLabel.textColor = UIColor.black | |
// if !isInsideRange(dateForRow) { | |
// dateLabel.textColor = UIColor.darkGray | |
// } | |
if component == yearComponent { | |
dateLabel.text = "\(earliestYear + row)" | |
} else { | |
let month = row % 12 + 1 | |
let monthStr = longMonthFormatter.string(from: shortMonthformatter.date(from: "\(month)")!) | |
dateLabel.text = monthStr | |
} | |
return dateLabel | |
} | |
} | |
extension DatePicker : UIPickerViewDelegate { | |
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) { | |
let selectedDate: Date | |
if component == yearComponent { | |
let selectedMonth = month(for: pickerView.selectedRow(inComponent: monthComponent)) | |
selectedDate = yearMonthFormatter.date(from: "\(year(for: row))-\(selectedMonth)")! | |
} else { | |
let selectedYear = year(for: pickerView.selectedRow(inComponent: yearComponent)) | |
let selectedMonth = month(for: row) | |
selectedDate = yearMonthFormatter.date(from: "\(selectedYear)-\(selectedMonth)")! | |
} | |
if isBeforeMinimum(selectedDate) { | |
set(minimumDate!, animated: true) | |
return | |
} | |
if isAfterMaximum(selectedDate) { | |
set(maximumDate!, animated: true) | |
} | |
date = selectedDate | |
sendActions(for: UIControlEvents.valueChanged) | |
} | |
func isBeforeMinimum(_ date: Date) -> Bool { | |
guard let minimumDate = minimumDate else { return false } | |
return minimumDate.timeIntervalSinceNow > date.timeIntervalSinceNow | |
} | |
func isAfterMaximum(_ date: Date) -> Bool { | |
guard let maximumDate = maximumDate else { return false } | |
return maximumDate.timeIntervalSinceNow < date.timeIntervalSinceNow | |
} | |
func isInsideRange(_ date: Date) -> Bool { | |
return !isAfterMaximum(date) && !isBeforeMinimum(date) | |
} | |
func date(for row: Int, and component: Int) -> Date { | |
let _year: Int | |
let _month: Int | |
if component == monthComponent { | |
_year = year(for: row / 12) | |
_month = month(for: row) | |
} else { | |
_year = year(for: row) | |
_month = (row % 12) + 1 | |
} | |
return yearMonthFormatter.date(from: "\(_year)-\(_month)")! | |
} | |
func selectedYear() -> Int { | |
return year(for: picker.selectedRow(inComponent: yearComponent)) | |
} | |
func selectedMonth() -> Int { | |
return month(for: picker.selectedRow(inComponent: monthComponent)) | |
} | |
func set(year: Int, animated: Bool) { | |
let row = year - earliestYear | |
picker.selectRow(row, inComponent: yearComponent, animated: animated) | |
} | |
func set(month: Int, with year: Int, animated: Bool) { | |
picker.selectRow((((year - earliestYear) * 12) + month - 1), inComponent: monthComponent, animated: animated) | |
} | |
func year(for row: Int) -> Int { | |
return earliestYear + row | |
} | |
func month(for row: Int) -> Int { | |
return (row % 12) + 1 | |
} | |
} |
extension DateFormatter {
init(format: String) {
self.dateFormat = format
}
}
Thanks.
I was able to convert your DatePicker to Swift 5.
I still have a small problem of use in my code:
I use this portion which works well without your DatePicker:
DatePicker("Selection month",selection: $date,displayedComponents:[.date])
.labelsHidden()
.datePickerStyle(WheelDatePickerStyle())
How to use your code instead? like this ? :
JbqDatePicker(frame: CGRect(x: 20, y: 20, width: 300, height: 300))
I get the error:
Static method 'buildBlock' requires that 'JbqDatePicker' conform to 'View'
Thanks a lot
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
sorry,
I planted project to post my comment.
This is this code to support a MMAAAA DatePicker.
I can't convert lines 26, 27, 28 to swift 5.
Do you have an example of using this extension?
Thanks for the replies,
BACQ Js