Skip to content

Commit

Permalink
Fix item selection for months which don't start on Sunday. (#12)
Browse files Browse the repository at this point in the history
* Fixed date selection

* Updated version and dependencies (#13)
  • Loading branch information
dtaylor1701 authored May 24, 2021
1 parent 5534e08 commit 8f51d05
Show file tree
Hide file tree
Showing 13 changed files with 96 additions and 156 deletions.
7 changes: 3 additions & 4 deletions Colander.podspec
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Pod::Spec.new do |s|
s.name = 'Colander'
s.version = '0.2.4'
s.version = '0.2.5'
s.summary = 'A highly customizable iOS calendar view'

s.description = <<-DESC
Expand All @@ -17,10 +17,9 @@ Pod::Spec.new do |s|
s.ios.deployment_target = '10.0'
s.swift_version = '5.0'


s.source_files = 'Colander/Classes/**/*'

s.dependency 'SnapKit', '~> 5.0.0'
s.dependency 'SwiftDate', '~> 6.0.3'
s.dependency 'SnapKit', '~> 5.0.1'
s.dependency 'SwiftDate', '~> 6.3.1'

end
6 changes: 3 additions & 3 deletions Colander/Classes/CalendarView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import SnapKit
import SwiftDate
import UIKit

public protocol CalendarViewDataSource: class {
public protocol CalendarViewDataSource: AnyObject {
var calendar: Calendar { get }
var startDate: Date { get }
var endDate: Date { get }
Expand All @@ -24,7 +24,7 @@ public extension CalendarViewDataSource {
}
}

public protocol CalendarViewDelegate: class {
public protocol CalendarViewDelegate: AnyObject {
func scrollViewDidScroll(_ scrollView: UIScrollView)
func calendar(_ calendar: CalendarView, shouldSelectCellAt date: Date) -> Bool
func calendar(_ calendar: CalendarView, didSelectCell cell: UICollectionViewCell, forDate date: Date)
Expand Down Expand Up @@ -302,7 +302,7 @@ extension CalendarView: UICollectionViewDataSource {
}

if let currentMonthInfo = viewModel?.monthInfos[indexPath.section], var headerView = headerView as? Dated {
headerView.date = currentMonthInfo.startDate
headerView.date = currentMonthInfo.startDate.date
}

return headerView
Expand Down
15 changes: 8 additions & 7 deletions Colander/Classes/CalendarViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ class CalendarViewModel {
}

static func makeMonthInfos(startDate: Date, endDate: Date, calendar: Calendar) throws -> [MonthInfo] {
let monthStartDate = startDate.beginningOfMonth
let monthStartDate = startDate.dateAtStartOf(.month)
let sections = (0..<(numberOfSectionsNeededFor(startDate: startDate, endDate: endDate)))
return try sections.map { try MonthInfo(forMonthContaining: monthStartDate + $0.months, with: calendar) }
}
Expand All @@ -53,7 +53,7 @@ class CalendarViewModel {
self.showLeadingWeeks = showLeadingWeeks
self.showTrailingWeeks = showTrailingWeeks
self.calendar = calendar
SwiftDate.defaultRegion = Region(calendar: calendar, zone: TimeZone.current, locale: Locale.current)
SwiftDate.defaultRegion = Region(calendar: calendar, zone: calendar.timeZone, locale: calendar.locale ?? Locale.current)
self.monthInfos = try CalendarViewModel.makeMonthInfos(startDate: startDate, endDate: endDate, calendar: calendar)
}

Expand All @@ -72,16 +72,17 @@ class CalendarViewModel {
}
let zeroIndexDate = firstDisplayDate(for: section, showLeadingWeeks: showLeadingWeeks)
// 1 hour is added to make this calculation correct for the beginning of Daylight Saving Time. I don't like it either.
let intervalDiff = (date + 1.hours) - zeroIndexDate
return IndexPath(item: intervalDiff.in(.day) ?? 0, section: section)
let dayCount = Date.enumerateDates(from: zeroIndexDate, to: date.dateAtStartOf(.day) + 1.hours,
increment: DateComponents(day: 1)).count - 1
return IndexPath(item: dayCount, section: section)
}

func firstDisplayDate(for section: Int, showLeadingWeeks: Bool) -> Date {
// returns the date that indexPath.item == 0 should map to,
// usually (but not always) before the start of the month if leading weeks are being shown
let monthInfo = monthInfos[section]
let isFirstMonth = section == 0
return (!showLeadingWeeks && isFirstMonth) ? startDate.beginningOfWeek : monthInfo.startDate.beginningOfWeek
return (!showLeadingWeeks && isFirstMonth) ? startDate.dateAtStartOf(.weekOfMonth).date : monthInfo.startDate.dateAtStartOf(.weekOfMonth).date
}

/**
Expand Down Expand Up @@ -115,8 +116,8 @@ class CalendarViewModel {
let isLastMonth = section == monthInfos.count - 1
if !showTrailingWeeks && isLastMonth {
// Determine whether the last day to display will change by trimming trailing weeks
let dayDifference = (monthInfo.endDate - endDate.endOfWeek).in(.day) ?? 0
lastDisplayIndex -= (dayDifference - 1)
let dayDifference = (monthInfo.endDate.date - endDate.dateAtEndOf(.weekOfMonth)).in(.day) ?? 0
lastDisplayIndex -= dayDifference
}

let requiredRows = ceil(Double(lastDisplayIndex + 1) / Double(daysPerWeek))
Expand Down
29 changes: 0 additions & 29 deletions Colander/Classes/DateExtensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,32 +3,3 @@ import SwiftDate
internal extension Calendar {
static let gregorian = Calendar(identifier: Calendar.Identifier.gregorian)
}

internal extension Date {
var beginningOfMonth: Date {
var calendar = Calendar(identifier: Calendar.Identifier.gregorian)
if let timeZone = TimeZone(secondsFromGMT: 0) {
calendar.timeZone = timeZone
}
var firstDayOfStartMonth = calendar.dateComponents( [.era, .year, .month], from: self)
firstDayOfStartMonth.day = 1
return calendar.date(from: firstDayOfStartMonth) ?? Date.nowAt(.startOfMonth)
}

var beginningOfWeek: Date {
var calendar = Calendar(identifier: Calendar.Identifier.gregorian)
if let timeZone = TimeZone(secondsFromGMT: 0) {
calendar.timeZone = timeZone
}
return calendar.date(from: calendar.dateComponents([.yearForWeekOfYear, .weekOfYear], from: self)) ?? Date.nowAt(.startOfWeek)
}

var endOfWeek: Date {
let numWeekdays = Calendar.gregorian.weekdaySymbols.count
return self + (numWeekdays - self.weekday).days
}

func isSameMonthAs(_ otherDate: Date) -> Bool {
return year == otherDate.year && month == otherDate.month
}
}
23 changes: 8 additions & 15 deletions Colander/Classes/MonthInfo.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,23 +10,16 @@ import Foundation
import SwiftDate

struct MonthInfo {
let startDate: Date
let endDate: Date
let startDate: DateInRegion
let endDate: DateInRegion
let firstDayWeekdayIndex: Int
let numberOfDaysInMonth: Int

init(forMonthContaining date: Date, with calendar: Calendar? = nil) throws {
var targetCalendar = calendar ?? Calendar(identifier: Calendar.Identifier.gregorian)
targetCalendar.timeZone = calendar?.timeZone ?? TimeZone(secondsFromGMT: 0)!

guard let numberOfDaysInMonth = targetCalendar.range(of: .day, in: .month, for: date)?.count else {
throw DateError.Generic("Could not determine number of days in month for \(date)")
}

let beginningOfMonth = date.beginningOfMonth
self.startDate = beginningOfMonth
self.endDate = beginningOfMonth + numberOfDaysInMonth.days
self.firstDayWeekdayIndex = targetCalendar.component(.weekday, from: startDate) - 1 // 1-indexed to 0-indexed
self.numberOfDaysInMonth = numberOfDaysInMonth
init(forMonthContaining date: Date, with calendar: Calendar) throws {
let workingDate = DateInRegion(date, region: Region(calendar: calendar, zone: calendar.timeZone, locale: calendar.locale ?? Locale.current))
self.startDate = workingDate.dateAtStartOf(.month)
self.endDate = workingDate.dateAtEndOf(.month)
self.firstDayWeekdayIndex = calendar.component(.weekday, from: startDate.date) - 1 // 1-indexed to 0-indexed
self.numberOfDaysInMonth = date.monthDays
}
}
10 changes: 5 additions & 5 deletions Example/Colander.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
/* End PBXContainerItemProxy section */

/* Begin PBXFileReference section */
1089385E523011FABC5B870B /* Colander.podspec */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = Colander.podspec; path = ../Colander.podspec; sourceTree = "<group>"; };
1089385E523011FABC5B870B /* Colander.podspec */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = Colander.podspec; path = ../Colander.podspec; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.ruby; };
1939F3CCD64571E5E090650F /* LICENSE */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = LICENSE; path = ../LICENSE; sourceTree = "<group>"; };
3E2AE24330433B58DE569374 /* Pods_Colander_Tests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Colander_Tests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
607FACD01AFB9204008FA782 /* Colander_Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Colander_Example.app; sourceTree = BUILT_PRODUCTS_DIR; };
Expand Down Expand Up @@ -340,7 +340,7 @@
files = (
);
inputPaths = (
"${SRCROOT}/Pods/Target Support Files/Pods-Colander_Example/Pods-Colander_Example-frameworks.sh",
"${PODS_ROOT}/Target Support Files/Pods-Colander_Example/Pods-Colander_Example-frameworks.sh",
"${BUILT_PRODUCTS_DIR}/Colander/Colander.framework",
"${BUILT_PRODUCTS_DIR}/SnapKit/SnapKit.framework",
"${BUILT_PRODUCTS_DIR}/SwiftDate/SwiftDate.framework",
Expand All @@ -353,7 +353,7 @@
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Colander_Example/Pods-Colander_Example-frameworks.sh\"\n";
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Colander_Example/Pods-Colander_Example-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
B3060291EC9B55999E1EBEBE /* [CP] Check Pods Manifest.lock */ = {
Expand All @@ -380,7 +380,7 @@
files = (
);
inputPaths = (
"${SRCROOT}/Pods/Target Support Files/Pods-Colander_Tests/Pods-Colander_Tests-frameworks.sh",
"${PODS_ROOT}/Target Support Files/Pods-Colander_Tests/Pods-Colander_Tests-frameworks.sh",
"${BUILT_PRODUCTS_DIR}/Nimble/Nimble.framework",
"${BUILT_PRODUCTS_DIR}/Quick/Quick.framework",
);
Expand All @@ -391,7 +391,7 @@
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Colander_Tests/Pods-Colander_Tests-frameworks.sh\"\n";
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Colander_Tests/Pods-Colander_Tests-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
/* End PBXShellScriptBuildPhase section */
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>
Original file line number Diff line number Diff line change
Expand Up @@ -25,37 +25,18 @@ class InteractiveDataSourceViewController: UIViewController, CalendarViewDataSou

let calendarView = CalendarView()

lazy var startDateTextField: UITextField = {
let textField = UITextField()
textField.inputAccessoryView = self.toolbar
textField.inputView = self.datePicker
textField.textColor = .blue
textField.delegate = self
return textField
}()

lazy var endDateTextField: UITextField = {
let textField = UITextField()
textField.inputAccessoryView = self.toolbar
textField.inputView = self.datePicker
textField.textColor = .blue
textField.delegate = self
return textField
}()

lazy var datePicker: UIDatePicker = {
lazy var startDatePicker: UIDatePicker = {
let datePicker = UIDatePicker()
datePicker.datePickerMode = .date
datePicker.addTarget(self, action: #selector(datePickerValueChanged), for: .valueChanged)
datePicker.addTarget(self, action: #selector(datePickerValueChanged(picker:)), for: .valueChanged)
return datePicker
}()

let toolbar: UIToolbar = {
let toolbar = UIToolbar()
let doneButton = UIBarButtonItem(title: "Done", style: .plain, target: self, action: #selector(doneButtonTapped))
toolbar.setItems([doneButton], animated: false)
toolbar.sizeToFit()
return toolbar
lazy var endDatePicker: UIDatePicker = {
let datePicker = UIDatePicker()
datePicker.datePickerMode = .date
datePicker.addTarget(self, action: #selector(datePickerValueChanged(picker:)), for: .valueChanged)
return datePicker
}()

override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
Expand Down Expand Up @@ -83,7 +64,12 @@ class InteractiveDataSourceViewController: UIViewController, CalendarViewDataSou

view.addSubview(configContainerView)
configContainerView.snp.makeConstraints { make in
make.left.bottom.right.equalToSuperview()
make.left.right.equalToSuperview()
if #available(iOS 11.0, *) {
make.bottom.equalTo(view.safeAreaLayoutGuide)
} else {
make.bottom.equalToSuperview()
}
}

calendarView.register(cellType: SpecializedDayCell.self)
Expand All @@ -95,7 +81,7 @@ class InteractiveDataSourceViewController: UIViewController, CalendarViewDataSou
}
calendarView.dataSource = self

updateTextFieldText()
updateDatePickers()
}

func createConfigView() -> UIView {
Expand All @@ -122,9 +108,9 @@ class InteractiveDataSourceViewController: UIViewController, CalendarViewDataSou
view.addSubview(leadingWeeksLabel)
view.addSubview(trailingWeeksSwitch)
view.addSubview(trailingWeeksLabel)
view.addSubview(startDateTextField)
view.addSubview(startDatePicker)
view.addSubview(toLabel)
view.addSubview(endDateTextField)
view.addSubview(endDatePicker)

leadingWeeksLabel.snp.makeConstraints { make in
make.left.equalToSuperview().inset(16)
Expand All @@ -147,42 +133,39 @@ class InteractiveDataSourceViewController: UIViewController, CalendarViewDataSou
make.top.equalToSuperview()
}

startDateTextField.snp.makeConstraints { make in
startDatePicker.snp.makeConstraints { make in
make.top.equalTo(leadingWeeksSwitch.snp.bottom).offset(20)
make.left.bottom.equalToSuperview().inset(16)
make.height.equalTo(40)
}

toLabel.snp.makeConstraints { make in
make.centerX.equalToSuperview()
make.centerY.equalTo(startDateTextField)
make.centerY.equalTo(startDatePicker)
}

endDateTextField.snp.makeConstraints { make in
endDatePicker.snp.makeConstraints { make in
make.right.bottom.equalToSuperview().inset(16)
make.height.equalTo(40)
}

return view
}

func updateTextFieldText() {
startDateTextField.text = dateFormatter.string(from: startDate)
endDateTextField.text = dateFormatter.string(from: endDate)
func updateDatePickers() {
startDatePicker.date = startDate
endDatePicker.date = endDate
}

@objc func datePickerValueChanged() {
print("date picker date is now \(datePicker.date)")
}

@objc func doneButtonTapped() {
if startDateTextField.isFirstResponder {
startDate = datePicker.date
startDateTextField.resignFirstResponder()
} else if endDateTextField.isFirstResponder {
endDate = datePicker.date
endDateTextField.resignFirstResponder()
@objc func datePickerValueChanged(picker: UIDatePicker) {
if picker == startDatePicker {
print("start date picker date is now \(startDatePicker.date)")
startDate = picker.date
} else if picker == endDatePicker {
print("end date picker date is now \(endDatePicker.date)")
endDate = picker.date
}
calendarView.reloadData()
updateTextFieldText()
}

@objc func leadingWeeksToggled(sender: UISwitch) {
Expand All @@ -195,12 +178,3 @@ class InteractiveDataSourceViewController: UIViewController, CalendarViewDataSou
calendarView.reloadData()
}
}

extension InteractiveDataSourceViewController: UITextFieldDelegate {
func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool {
if let date = dateFormatter.date(from: textField.text!) {
datePicker.date = date
}
return true
}
}
4 changes: 2 additions & 2 deletions Example/Podfile
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ target 'Colander_Example' do
target 'Colander_Tests' do
inherit! :search_paths

pod 'Quick', '~> 2.1.0'
pod 'Nimble', '~> 8.0.1'
pod 'Quick', '~> 4.0.0'
pod 'Nimble', '~> 9.2.0'
end
end
Loading

0 comments on commit 8f51d05

Please sign in to comment.