Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .swift-version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
3.0
4.2
4 changes: 3 additions & 1 deletion CalendarDateRangePickerViewController.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

Pod::Spec.new do |s|
s.name = 'CalendarDateRangePickerViewController'
s.version = '0.1.0'
s.version = '0.3.0'
s.summary = 'A calendar date range picker view controller in Swift for iOS.'

# This description is used to generate tags and improve search results.
Expand Down Expand Up @@ -39,4 +39,6 @@ This is a calendar date range picker view controller written in Swift for iOS. T
# s.public_header_files = 'Pod/Classes/**/*.h'
# s.frameworks = 'UIKit', 'MapKit'
# s.dependency 'AFNetworking', '~> 2.3'

s.swift_version = '4.2'
end
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,13 @@ import UIKit
class CalendarDateRangePickerCell: UICollectionViewCell {

private let defaultTextColor = UIColor.darkGray
private let highlightedColor = UIColor(white: 0.9, alpha: 1.0)
var highlightedColor = UIColor(white: 0.9, alpha: 1.0)
private let disabledColor = UIColor.lightGray

var font = UIFont(name: "HelveticaNeue", size: CalendarDateRangePickerViewController.defaultCellFontSize) {
didSet {
label?.font = font
}
}
var selectedColor: UIColor!

var date: Date?
Expand All @@ -36,7 +40,7 @@ class CalendarDateRangePickerCell: UICollectionViewCell {
func initLabel() {
label = UILabel(frame: frame)
label.center = CGPoint(x: frame.size.width / 2, y: frame.size.height / 2)
label.font = UIFont(name: "HelveticaNeue", size: 15.0)
label.font = font
label.textColor = UIColor.darkGray
label.textAlignment = NSTextAlignment.center
self.addSubview(label)
Expand Down Expand Up @@ -67,7 +71,7 @@ class CalendarDateRangePickerCell: UICollectionViewCell {
selectedView?.backgroundColor = selectedColor
selectedView?.layer.cornerRadius = height / 2
self.addSubview(selectedView!)
self.sendSubview(toBack: selectedView!)
self.sendSubviewToBack(selectedView!)

label.textColor = UIColor.white
}
Expand All @@ -79,7 +83,7 @@ class CalendarDateRangePickerCell: UICollectionViewCell {
halfBackgroundView = UIView(frame: CGRect(x: width / 2, y: 0, width: width / 2, height: height))
halfBackgroundView?.backgroundColor = highlightedColor
self.addSubview(halfBackgroundView!)
self.sendSubview(toBack: halfBackgroundView!)
self.sendSubviewToBack(halfBackgroundView!)

addRoundHighlightView()
}
Expand All @@ -91,7 +95,7 @@ class CalendarDateRangePickerCell: UICollectionViewCell {
halfBackgroundView = UIView(frame: CGRect(x: 0, y: 0, width: width / 2, height: height))
halfBackgroundView?.backgroundColor = highlightedColor
self.addSubview(halfBackgroundView!)
self.sendSubview(toBack: halfBackgroundView!)
self.sendSubviewToBack(halfBackgroundView!)

addRoundHighlightView()
}
Expand All @@ -103,7 +107,7 @@ class CalendarDateRangePickerCell: UICollectionViewCell {
roundHighlightView?.backgroundColor = highlightedColor
roundHighlightView?.layer.cornerRadius = height / 2
self.addSubview(roundHighlightView!)
self.sendSubview(toBack: roundHighlightView!)
self.sendSubviewToBack(roundHighlightView!)
}

func highlight() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ import UIKit
class CalendarDateRangePickerHeaderView: UICollectionReusableView {

var label: UILabel!
var font = UIFont(name: "HelveticaNeue-Light", size: CalendarDateRangePickerViewController.defaultHeaderFontSize) {
didSet {
label?.font = font
}
}

override init(frame: CGRect) {
super.init(frame: frame)
Expand All @@ -24,11 +29,14 @@ class CalendarDateRangePickerHeaderView: UICollectionReusableView {

func initLabel() {
label = UILabel(frame: frame)
label.center = CGPoint(x: frame.size.width / 2, y: frame.size.height / 2)
label.font = UIFont(name: "HelveticaNeue-Light", size: 17.0)
label.font = font
label.textColor = UIColor.darkGray
label.textAlignment = NSTextAlignment.center
self.addSubview(label)
}

override func layoutSubviews() {
super.layoutSubviews()
label.center = CGPoint(x: frame.size.width / 2, y: frame.size.height / 2)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,18 @@ import UIKit
public protocol CalendarDateRangePickerViewControllerDelegate {
func didCancelPickingDateRange()
func didPickDateRange(startDate: Date!, endDate: Date!)

// Optional
func didPickDate(date: Date)
func calendarViewDidLayoutSubviews()
func calendarViewWillLayoutSubviews()
}

public extension CalendarDateRangePickerViewControllerDelegate {
// Making these optional. For backwards compatiblity.
func didPickDate(date: Date) {}
func calendarViewDidLayoutSubviews() {}
func calendarViewWillLayoutSubviews() {}
}

public class CalendarDateRangePickerViewController: UICollectionViewController {
Expand All @@ -22,17 +34,41 @@ public class CalendarDateRangePickerViewController: UICollectionViewController {

let itemsPerRow = 7
let itemHeight: CGFloat = 40
let collectionViewInsets = UIEdgeInsets(top: 0, left: 25, bottom: 0, right: 25)

// 3/20/18; Changed from `let` to `var`, and made public, to allow Cocoapod user to change insets.
public var collectionViewInsets = UIEdgeInsets(top: 0, left: 25, bottom: 0, right: 25)

// Allow the user select "backwards" in the calendar? E.g., say I select March 16 first and then want to select March 14 for range of March 14 to March 16. Default is `false` because this was the original behavior of this Cocoapod.
public var allowBackwardSelection: Bool = false

// Only allow a single date (not a date range) to be selected. Default behavior is not to allow this.
public var onlySingleDateSelection: Bool = false

// If you set this to false, then you get a callback for date selection on every tap. `true` is the original behavior of this Cocoapod.
public var requireDone: Bool = true

fileprivate var calendar:Calendar = Calendar.current
public var timezone: TimeZone = TimeZone.current {
didSet {
calendar.timeZone = timezone
}
}

public var minimumDate: Date!
public var maximumDate: Date!

public var selectedStartDate: Date?
public var selectedEndDate: Date?

public var cellHighlightedColor = UIColor(white: 0.9, alpha: 1.0)
public static let defaultCellFontSize:CGFloat = 15.0
public static let defaultHeaderFontSize:CGFloat = 17.0
public var cellFont:UIFont = UIFont(name: "HelveticaNeue", size: CalendarDateRangePickerViewController.defaultCellFontSize)!
public var headerFont:UIFont = UIFont(name: "HelveticaNeue-Light", size: CalendarDateRangePickerViewController.defaultHeaderFontSize)!

public var selectedColor = UIColor(red: 66/255.0, green: 150/255.0, blue: 240/255.0, alpha: 1.0)
public var titleText = "Select Dates"

override public func viewDidLoad() {
super.viewDidLoad()

Expand All @@ -43,40 +79,55 @@ public class CalendarDateRangePickerViewController: UICollectionViewController {
collectionView?.backgroundColor = UIColor.white

collectionView?.register(CalendarDateRangePickerCell.self, forCellWithReuseIdentifier: cellReuseIdentifier)
collectionView?.register(CalendarDateRangePickerHeaderView.self, forSupplementaryViewOfKind: UICollectionElementKindSectionHeader, withReuseIdentifier: headerReuseIdentifier)
collectionView?.register(CalendarDateRangePickerHeaderView.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: headerReuseIdentifier)
collectionView?.contentInset = collectionViewInsets

if minimumDate == nil {
minimumDate = Date()
}
if maximumDate == nil {
maximumDate = Calendar.current.date(byAdding: .year, value: 3, to: minimumDate)
maximumDate = calendar.date(byAdding: .year, value: 3, to: minimumDate)
}

self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: "Cancel", style: .plain, target: self, action: #selector(CalendarDateRangePickerViewController.didTapCancel))
self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Done", style: .done, target: self, action: #selector(CalendarDateRangePickerViewController.didTapDone))
self.navigationItem.rightBarButtonItem?.isEnabled = selectedStartDate != nil && selectedEndDate != nil
}

func didTapCancel() {
override public func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
delegate.calendarViewDidLayoutSubviews()
}

override public func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
delegate.calendarViewWillLayoutSubviews()
}

@objc func didTapCancel() {
delegate.didCancelPickingDateRange()
}

func didTapDone() {
if selectedStartDate == nil || selectedEndDate == nil {
@objc func didTapDone() {
if selectedStartDate == nil {
return
}

if selectedEndDate == nil {
delegate.didPickDate(date: selectedStartDate!)
return
}

delegate.didPickDateRange(startDate: selectedStartDate!, endDate: selectedEndDate!)
}

}

extension CalendarDateRangePickerViewController {

// UICollectionViewDataSource

override public func numberOfSections(in collectionView: UICollectionView) -> Int {
let difference = Calendar.current.dateComponents([.month], from: minimumDate, to: maximumDate)
let difference = calendar.dateComponents([.month], from: minimumDate, to: maximumDate)
return difference.month! + 1
}

Expand All @@ -90,7 +141,9 @@ extension CalendarDateRangePickerViewController {

override public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellReuseIdentifier, for: indexPath) as! CalendarDateRangePickerCell
cell.highlightedColor = self.cellHighlightedColor
cell.selectedColor = self.selectedColor
cell.font = self.cellFont
cell.reset()
let blankItems = getWeekday(date: getFirstDateForSection(section: indexPath.section)) - 1
if indexPath.item < 7 {
Expand Down Expand Up @@ -132,9 +185,10 @@ extension CalendarDateRangePickerViewController {

override public func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
switch kind {
case UICollectionElementKindSectionHeader:
case UICollectionView.elementKindSectionHeader:
let headerView = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: headerReuseIdentifier, for: indexPath) as! CalendarDateRangePickerHeaderView
headerView.label.text = getMonthLabel(date: getFirstDateForSection(section: indexPath.section))
headerView.font = headerFont
return headerView
default:
fatalError("Unexpected element kind")
Expand All @@ -144,6 +198,11 @@ extension CalendarDateRangePickerViewController {
}

extension CalendarDateRangePickerViewController : UICollectionViewDelegateFlowLayout {
func didPickDateRangeIfNeeded() {
if !requireDone {
didTapDone()
}
}

override public func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let cell = collectionView.cellForItem(at: indexPath) as! CalendarDateRangePickerCell
Expand All @@ -153,20 +212,29 @@ extension CalendarDateRangePickerViewController : UICollectionViewDelegateFlowLa
if isBefore(dateA: cell.date!, dateB: minimumDate) {
return
}
if selectedStartDate == nil {
if selectedStartDate == nil || onlySingleDateSelection {
selectedStartDate = cell.date
} else if selectedEndDate == nil {
if isBefore(dateA: selectedStartDate!, dateB: cell.date!) {
selectedEndDate = cell.date
self.navigationItem.rightBarButtonItem?.isEnabled = true
} else {
// If a cell before the currently selected start date is selected then just set it as the new start date
selectedStartDate = cell.date
if allowBackwardSelection && selectedStartDate != cell.date {
selectedEndDate = selectedStartDate
selectedStartDate = cell.date
}
else {
// If a cell before the currently selected start date is selected then just set it as the new start date
selectedStartDate = cell.date
}
}
} else {
selectedStartDate = cell.date
selectedEndDate = nil
}

didPickDateRangeIfNeeded()

collectionView.reloadData()
}

Expand Down Expand Up @@ -198,54 +266,56 @@ extension CalendarDateRangePickerViewController {
// Helper functions

func getFirstDate() -> Date {
var components = Calendar.current.dateComponents([.month, .year], from: minimumDate)
var components = calendar.dateComponents([.month, .year], from: minimumDate)
components.day = 1
return Calendar.current.date(from: components)!
return calendar.date(from: components)!
}

func getFirstDateForSection(section: Int) -> Date {
return Calendar.current.date(byAdding: .month, value: section, to: getFirstDate())!
return calendar.date(byAdding: .month, value: section, to: getFirstDate())!
}

func getMonthLabel(date: Date) -> String {
let dateFormatter = DateFormatter()
dateFormatter.timeZone = timezone
dateFormatter.dateFormat = "MMMM yyyy"
return dateFormatter.string(from: date)
}

func getWeekdayLabel(weekday: Int) -> String {
var components = DateComponents()
components.calendar = Calendar.current
components.calendar = calendar
components.weekday = weekday
let date = Calendar.current.nextDate(after: Date(), matching: components, matchingPolicy: Calendar.MatchingPolicy.strict)
let date = calendar.nextDate(after: Date(), matching: components, matchingPolicy: Calendar.MatchingPolicy.strict)
if date == nil {
return "E"
}
let dateFormatter = DateFormatter()
dateFormatter.timeZone = timezone
dateFormatter.dateFormat = "EEEEE"
return dateFormatter.string(from: date!)
}

func getWeekday(date: Date) -> Int {
return Calendar.current.dateComponents([.weekday], from: date).weekday!
return calendar.dateComponents([.weekday], from: date).weekday!
}

func getNumberOfDaysInMonth(date: Date) -> Int {
return Calendar.current.range(of: .day, in: .month, for: date)!.count
return calendar.range(of: .day, in: .month, for: date)!.count
}

func getDate(dayOfMonth: Int, section: Int) -> Date {
var components = Calendar.current.dateComponents([.month, .year], from: getFirstDateForSection(section: section))
var components = calendar.dateComponents([.month, .year], from: getFirstDateForSection(section: section))
components.day = dayOfMonth
return Calendar.current.date(from: components)!
return calendar.date(from: components)!
}

func areSameDay(dateA: Date, dateB: Date) -> Bool {
return Calendar.current.compare(dateA, to: dateB, toGranularity: .day) == ComparisonResult.orderedSame
return calendar.compare(dateA, to: dateB, toGranularity: .day) == ComparisonResult.orderedSame
}

func isBefore(dateA: Date, dateB: Date) -> Bool {
return Calendar.current.compare(dateA, to: dateB, toGranularity: .day) == ComparisonResult.orderedAscending
return calendar.compare(dateA, to: dateB, toGranularity: .day) == ComparisonResult.orderedAscending
}

}
Loading